mirror of
https://github.com/CringeStudios/element-desktop.git
synced 2025-01-18 07:24:59 +01:00
Format all files with prettier
This commit is contained in:
parent
040344eeab
commit
0faac52dae
30
.eslintrc.js
30
.eslintrc.js
@ -1,8 +1,6 @@
|
||||
module.exports = {
|
||||
plugins: ["matrix-org"],
|
||||
extends: [
|
||||
"plugin:matrix-org/javascript",
|
||||
],
|
||||
extends: ["plugin:matrix-org/javascript"],
|
||||
parserOptions: {
|
||||
ecmaVersion: 2021,
|
||||
},
|
||||
@ -20,19 +18,19 @@ module.exports = {
|
||||
"prefer-promise-reject-errors": "off",
|
||||
"no-async-promise-executor": "off",
|
||||
},
|
||||
overrides: [{
|
||||
files: ["{src,scripts,hak}/**/*.{ts,tsx}"],
|
||||
extends: [
|
||||
"plugin:matrix-org/typescript",
|
||||
],
|
||||
rules: {
|
||||
// Things we do that break the ideal style
|
||||
"prefer-promise-reject-errors": "off",
|
||||
"quotes": "off",
|
||||
overrides: [
|
||||
{
|
||||
files: ["{src,scripts,hak}/**/*.{ts,tsx}"],
|
||||
extends: ["plugin:matrix-org/typescript"],
|
||||
rules: {
|
||||
// Things we do that break the ideal style
|
||||
"prefer-promise-reject-errors": "off",
|
||||
"quotes": "off",
|
||||
|
||||
"@typescript-eslint/no-explicit-any": "off",
|
||||
// We're okay with assertion errors when we ask for them
|
||||
"@typescript-eslint/no-non-null-assertion": "off",
|
||||
"@typescript-eslint/no-explicit-any": "off",
|
||||
// We're okay with assertion errors when we ask for them
|
||||
"@typescript-eslint/no-non-null-assertion": "off",
|
||||
},
|
||||
},
|
||||
}],
|
||||
],
|
||||
};
|
||||
|
6
.github/PULL_REQUEST_TEMPLATE.md
vendored
6
.github/PULL_REQUEST_TEMPLATE.md
vendored
@ -2,9 +2,9 @@
|
||||
|
||||
## Checklist
|
||||
|
||||
* [ ] Ensure your code works with manual testing
|
||||
* [ ] Linter and other CI checks pass
|
||||
* [ ] Sign-off given on the changes (see [CONTRIBUTING.md](https://github.com/vector-im/element-desktop/blob/develop/CONTRIBUTING.md))
|
||||
- [ ] Ensure your code works with manual testing
|
||||
- [ ] Linter and other CI checks pass
|
||||
- [ ] Sign-off given on the changes (see [CONTRIBUTING.md](https://github.com/vector-im/element-desktop/blob/develop/CONTRIBUTING.md))
|
||||
|
||||
<!--
|
||||
If you would like to specify text for the changelog entry other than your PR title, add the following:
|
||||
|
6
.github/renovate.json
vendored
6
.github/renovate.json
vendored
@ -1,6 +1,4 @@
|
||||
{
|
||||
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
|
||||
"extends": [
|
||||
"github>matrix-org/renovate-config-element-web"
|
||||
]
|
||||
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
|
||||
"extends": ["github>matrix-org/renovate-config-element-web"]
|
||||
}
|
||||
|
52
.github/workflows/backport.yml
vendored
52
.github/workflows/backport.yml
vendored
@ -1,30 +1,30 @@
|
||||
name: Backport
|
||||
on:
|
||||
pull_request_target:
|
||||
types:
|
||||
- closed
|
||||
- labeled
|
||||
branches:
|
||||
- develop
|
||||
pull_request_target:
|
||||
types:
|
||||
- closed
|
||||
- labeled
|
||||
branches:
|
||||
- develop
|
||||
|
||||
jobs:
|
||||
backport:
|
||||
name: Backport
|
||||
runs-on: ubuntu-latest
|
||||
# Only react to merged PRs for security reasons.
|
||||
# See https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows#pull_request_target.
|
||||
if: >
|
||||
github.event.pull_request.merged
|
||||
&& (
|
||||
github.event.action == 'closed'
|
||||
|| (
|
||||
github.event.action == 'labeled'
|
||||
&& contains(github.event.label.name, 'backport')
|
||||
)
|
||||
)
|
||||
steps:
|
||||
- uses: tibdex/backport@v2
|
||||
with:
|
||||
labels_template: "<%= JSON.stringify([...labels, 'X-Release-Blocker']) %>"
|
||||
# We can't use GITHUB_TOKEN here or CI won't run on the new PR
|
||||
github_token: ${{ secrets.ELEMENT_BOT_TOKEN }}
|
||||
backport:
|
||||
name: Backport
|
||||
runs-on: ubuntu-latest
|
||||
# Only react to merged PRs for security reasons.
|
||||
# See https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows#pull_request_target.
|
||||
if: >
|
||||
github.event.pull_request.merged
|
||||
&& (
|
||||
github.event.action == 'closed'
|
||||
|| (
|
||||
github.event.action == 'labeled'
|
||||
&& contains(github.event.label.name, 'backport')
|
||||
)
|
||||
)
|
||||
steps:
|
||||
- uses: tibdex/backport@v2
|
||||
with:
|
||||
labels_template: "<%= JSON.stringify([...labels, 'X-Release-Blocker']) %>"
|
||||
# We can't use GITHUB_TOKEN here or CI won't run on the new PR
|
||||
github_token: ${{ secrets.ELEMENT_BOT_TOKEN }}
|
||||
|
194
.github/workflows/build.yaml
vendored
194
.github/workflows/build.yaml
vendored
@ -1,107 +1,107 @@
|
||||
name: Build and Test
|
||||
on:
|
||||
pull_request: { }
|
||||
push:
|
||||
branches: [ develop, master ]
|
||||
pull_request: {}
|
||||
push:
|
||||
branches: [develop, master]
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
jobs:
|
||||
fetch:
|
||||
uses: ./.github/workflows/build_prepare.yaml
|
||||
with:
|
||||
config: ${{ github.event.pull_request.base.ref == 'develop' && 'element.io/nightly' || 'element.io/release' }}
|
||||
version: ${{ github.event.pull_request.base.ref == 'develop' && 'develop' || '' }}
|
||||
|
||||
windows:
|
||||
needs: fetch
|
||||
name: Windows
|
||||
uses: ./.github/workflows/build_windows.yaml
|
||||
strategy:
|
||||
matrix:
|
||||
arch: [ x64, x86 ]
|
||||
with:
|
||||
arch: ${{ matrix.arch }}
|
||||
|
||||
linux:
|
||||
needs: fetch
|
||||
name: Linux
|
||||
uses: ./.github/workflows/build_linux.yaml
|
||||
strategy:
|
||||
matrix:
|
||||
sqlcipher: [ system, static ]
|
||||
with:
|
||||
sqlcipher: ${{ matrix.sqlcipher }}
|
||||
|
||||
macos:
|
||||
needs: fetch
|
||||
name: macOS
|
||||
uses: ./.github/workflows/build_macos.yaml
|
||||
|
||||
test:
|
||||
needs:
|
||||
- macos
|
||||
- linux
|
||||
- windows
|
||||
strategy:
|
||||
matrix:
|
||||
include:
|
||||
# Disable macOS tests for now, they fail to run in CI, needs investigation.
|
||||
# - name: macOS Universal
|
||||
# os: macos
|
||||
# artifact: macos
|
||||
# executable: "./dist/mac-universal/Element.app/Contents/MacOS/Element"
|
||||
# prepare_cmd: "chmod +x ./dist/mac-universal/Element.app/Contents/MacOS/Element"
|
||||
- name: 'Linux (sqlcipher: system)'
|
||||
os: ubuntu
|
||||
artifact: linux-sqlcipher-system
|
||||
executable: "element-desktop"
|
||||
prepare_cmd: "sudo apt install ./dist/*.deb"
|
||||
- name: 'Linux (sqlcipher: static)'
|
||||
os: ubuntu
|
||||
artifact: linux-sqlcipher-static
|
||||
executable: "element-desktop"
|
||||
prepare_cmd: "sudo apt install ./dist/*.deb"
|
||||
- name: Windows (x86)
|
||||
os: windows
|
||||
artifact: win-x86
|
||||
executable: "./dist/win-ia32-unpacked/Element.exe"
|
||||
- name: Windows (x64)
|
||||
os: windows
|
||||
artifact: win-x64
|
||||
executable: "./dist/win-unpacked/Element.exe"
|
||||
name: Test ${{ matrix.name }}
|
||||
runs-on: ${{ matrix.os }}-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- uses: actions/setup-node@v3
|
||||
fetch:
|
||||
uses: ./.github/workflows/build_prepare.yaml
|
||||
with:
|
||||
cache: "yarn"
|
||||
config: ${{ github.event.pull_request.base.ref == 'develop' && 'element.io/nightly' || 'element.io/release' }}
|
||||
version: ${{ github.event.pull_request.base.ref == 'develop' && 'develop' || '' }}
|
||||
|
||||
- name: Install Deps
|
||||
run: "yarn install --pure-lockfile"
|
||||
|
||||
- uses: actions/download-artifact@v3
|
||||
windows:
|
||||
needs: fetch
|
||||
name: Windows
|
||||
uses: ./.github/workflows/build_windows.yaml
|
||||
strategy:
|
||||
matrix:
|
||||
arch: [x64, x86]
|
||||
with:
|
||||
name: ${{ matrix.artifact }}
|
||||
path: dist
|
||||
arch: ${{ matrix.arch }}
|
||||
|
||||
- name: Prepare for tests
|
||||
run: ${{ matrix.prepare_cmd }}
|
||||
if: matrix.prepare_cmd
|
||||
|
||||
- name: Run tests
|
||||
uses: GabrielBB/xvfb-action@v1
|
||||
timeout-minutes: 5
|
||||
linux:
|
||||
needs: fetch
|
||||
name: Linux
|
||||
uses: ./.github/workflows/build_linux.yaml
|
||||
strategy:
|
||||
matrix:
|
||||
sqlcipher: [system, static]
|
||||
with:
|
||||
run: "yarn test"
|
||||
env:
|
||||
ELEMENT_DESKTOP_EXECUTABLE: ${{ matrix.executable }}
|
||||
sqlcipher: ${{ matrix.sqlcipher }}
|
||||
|
||||
- name: Upload Artifacts
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: ${{ matrix.artifact }}
|
||||
path: test_artifacts
|
||||
retention-days: 1
|
||||
macos:
|
||||
needs: fetch
|
||||
name: macOS
|
||||
uses: ./.github/workflows/build_macos.yaml
|
||||
|
||||
test:
|
||||
needs:
|
||||
- macos
|
||||
- linux
|
||||
- windows
|
||||
strategy:
|
||||
matrix:
|
||||
include:
|
||||
# Disable macOS tests for now, they fail to run in CI, needs investigation.
|
||||
# - name: macOS Universal
|
||||
# os: macos
|
||||
# artifact: macos
|
||||
# executable: "./dist/mac-universal/Element.app/Contents/MacOS/Element"
|
||||
# prepare_cmd: "chmod +x ./dist/mac-universal/Element.app/Contents/MacOS/Element"
|
||||
- name: "Linux (sqlcipher: system)"
|
||||
os: ubuntu
|
||||
artifact: linux-sqlcipher-system
|
||||
executable: "element-desktop"
|
||||
prepare_cmd: "sudo apt install ./dist/*.deb"
|
||||
- name: "Linux (sqlcipher: static)"
|
||||
os: ubuntu
|
||||
artifact: linux-sqlcipher-static
|
||||
executable: "element-desktop"
|
||||
prepare_cmd: "sudo apt install ./dist/*.deb"
|
||||
- name: Windows (x86)
|
||||
os: windows
|
||||
artifact: win-x86
|
||||
executable: "./dist/win-ia32-unpacked/Element.exe"
|
||||
- name: Windows (x64)
|
||||
os: windows
|
||||
artifact: win-x64
|
||||
executable: "./dist/win-unpacked/Element.exe"
|
||||
name: Test ${{ matrix.name }}
|
||||
runs-on: ${{ matrix.os }}-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- uses: actions/setup-node@v3
|
||||
with:
|
||||
cache: "yarn"
|
||||
|
||||
- name: Install Deps
|
||||
run: "yarn install --pure-lockfile"
|
||||
|
||||
- uses: actions/download-artifact@v3
|
||||
with:
|
||||
name: ${{ matrix.artifact }}
|
||||
path: dist
|
||||
|
||||
- name: Prepare for tests
|
||||
run: ${{ matrix.prepare_cmd }}
|
||||
if: matrix.prepare_cmd
|
||||
|
||||
- name: Run tests
|
||||
uses: GabrielBB/xvfb-action@v1
|
||||
timeout-minutes: 5
|
||||
with:
|
||||
run: "yarn test"
|
||||
env:
|
||||
ELEMENT_DESKTOP_EXECUTABLE: ${{ matrix.executable }}
|
||||
|
||||
- name: Upload Artifacts
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: ${{ matrix.artifact }}
|
||||
path: test_artifacts
|
||||
retention-days: 1
|
||||
|
98
.github/workflows/build_linux.yaml
vendored
98
.github/workflows/build_linux.yaml
vendored
@ -2,63 +2,63 @@
|
||||
# Due to this extra care must be taken to only ever run all build_* scripts against the same branch to ensure
|
||||
# the correct cache scoping, and additional care must be taken to not run untrusted actions on the develop branch.
|
||||
on:
|
||||
workflow_call:
|
||||
inputs:
|
||||
sqlcipher:
|
||||
type: string
|
||||
required: true
|
||||
description: "How to link sqlcipher, one of 'system' | 'static'"
|
||||
workflow_call:
|
||||
inputs:
|
||||
sqlcipher:
|
||||
type: string
|
||||
required: true
|
||||
description: "How to link sqlcipher, one of 'system' | 'static'"
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- uses: actions/download-artifact@v3
|
||||
with:
|
||||
name: webapp
|
||||
- uses: actions/download-artifact@v3
|
||||
with:
|
||||
name: webapp
|
||||
|
||||
- name: Cache .hak
|
||||
id: cache
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
key: ${{ runner.os }}-${{ hashFiles('hakDependencies.json', 'electronVersion') }}
|
||||
path: |
|
||||
./.hak
|
||||
- name: Cache .hak
|
||||
id: cache
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
key: ${{ runner.os }}-${{ hashFiles('hakDependencies.json', 'electronVersion') }}
|
||||
path: |
|
||||
./.hak
|
||||
|
||||
- name: Install Rust
|
||||
if: steps.cache.outputs.cache-hit != 'true'
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: stable
|
||||
- name: Install Rust
|
||||
if: steps.cache.outputs.cache-hit != 'true'
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: stable
|
||||
|
||||
- name: Install libsqlcipher-dev
|
||||
if: steps.cache.outputs.cache-hit != 'true' && inputs.sqlcipher == 'system'
|
||||
run: sudo apt-get install -y libsqlcipher-dev
|
||||
- name: Install libsqlcipher-dev
|
||||
if: steps.cache.outputs.cache-hit != 'true' && inputs.sqlcipher == 'system'
|
||||
run: sudo apt-get install -y libsqlcipher-dev
|
||||
|
||||
- uses: actions/setup-node@v3
|
||||
with:
|
||||
cache: "yarn"
|
||||
- uses: actions/setup-node@v3
|
||||
with:
|
||||
cache: "yarn"
|
||||
|
||||
# Does not need branch matching as only analyses this layer
|
||||
- name: Install Deps
|
||||
run: "yarn install --pure-lockfile"
|
||||
# Does not need branch matching as only analyses this layer
|
||||
- name: Install Deps
|
||||
run: "yarn install --pure-lockfile"
|
||||
|
||||
- name: Build Natives
|
||||
if: steps.cache.outputs.cache-hit != 'true'
|
||||
run: "yarn build:native"
|
||||
env:
|
||||
SQLCIPHER_STATIC: ${{ inputs.sqlcipher == 'static' && '1' || '' }}
|
||||
- name: Build Natives
|
||||
if: steps.cache.outputs.cache-hit != 'true'
|
||||
run: "yarn build:native"
|
||||
env:
|
||||
SQLCIPHER_STATIC: ${{ inputs.sqlcipher == 'static' && '1' || '' }}
|
||||
|
||||
- name: Build App
|
||||
run: "yarn build --publish never -l"
|
||||
- name: Build App
|
||||
run: "yarn build --publish never -l"
|
||||
|
||||
- name: Install .deb
|
||||
run: "sudo apt install ./dist/*.deb"
|
||||
- name: Install .deb
|
||||
run: "sudo apt install ./dist/*.deb"
|
||||
|
||||
- name: Upload Artifacts
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: linux-sqlcipher-${{ inputs.sqlcipher }}
|
||||
path: dist
|
||||
retention-days: 1
|
||||
- name: Upload Artifacts
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: linux-sqlcipher-${{ inputs.sqlcipher }}
|
||||
path: dist
|
||||
retention-days: 1
|
||||
|
76
.github/workflows/build_macos.yaml
vendored
76
.github/workflows/build_macos.yaml
vendored
@ -2,50 +2,50 @@
|
||||
# Due to this extra care must be taken to only ever run all build_* scripts against the same branch to ensure
|
||||
# the correct cache scoping, and additional care must be taken to not run untrusted actions on the develop branch.
|
||||
on:
|
||||
workflow_call:
|
||||
workflow_call:
|
||||
jobs:
|
||||
build:
|
||||
runs-on: macos-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
build:
|
||||
runs-on: macos-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- uses: actions/download-artifact@v3
|
||||
with:
|
||||
name: webapp
|
||||
- uses: actions/download-artifact@v3
|
||||
with:
|
||||
name: webapp
|
||||
|
||||
- name: Cache .hak
|
||||
id: cache
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
key: ${{ runner.os }}-${{ hashFiles('hakDependencies.json', 'electronVersion') }}
|
||||
path: |
|
||||
./.hak
|
||||
- name: Cache .hak
|
||||
id: cache
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
key: ${{ runner.os }}-${{ hashFiles('hakDependencies.json', 'electronVersion') }}
|
||||
path: |
|
||||
./.hak
|
||||
|
||||
- name: Install Rust
|
||||
if: steps.cache.outputs.cache-hit != 'true'
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: stable
|
||||
target: aarch64-apple-darwin
|
||||
- name: Install Rust
|
||||
if: steps.cache.outputs.cache-hit != 'true'
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: stable
|
||||
target: aarch64-apple-darwin
|
||||
|
||||
- uses: actions/setup-node@v3
|
||||
with:
|
||||
cache: "yarn"
|
||||
- uses: actions/setup-node@v3
|
||||
with:
|
||||
cache: "yarn"
|
||||
|
||||
# Does not need branch matching as only analyses this layer
|
||||
- name: Install Deps
|
||||
run: "yarn install --pure-lockfile"
|
||||
# Does not need branch matching as only analyses this layer
|
||||
- name: Install Deps
|
||||
run: "yarn install --pure-lockfile"
|
||||
|
||||
- name: Build Natives
|
||||
if: steps.cache.outputs.cache-hit != 'true'
|
||||
run: "yarn build:native:universal"
|
||||
- name: Build Natives
|
||||
if: steps.cache.outputs.cache-hit != 'true'
|
||||
run: "yarn build:native:universal"
|
||||
|
||||
- name: Build App
|
||||
run: "yarn build:universal --publish never"
|
||||
- name: Build App
|
||||
run: "yarn build:universal --publish never"
|
||||
|
||||
- name: Upload Artifacts
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: macos
|
||||
path: dist
|
||||
retention-days: 1
|
||||
- name: Upload Artifacts
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: macos
|
||||
path: dist
|
||||
retention-days: 1
|
||||
|
72
.github/workflows/build_prepare.yaml
vendored
72
.github/workflows/build_prepare.yaml
vendored
@ -1,43 +1,43 @@
|
||||
on:
|
||||
workflow_call:
|
||||
inputs:
|
||||
config:
|
||||
type: string
|
||||
required: true
|
||||
description: "The config directory to use"
|
||||
version:
|
||||
type: string
|
||||
required: false
|
||||
description: "The version tag to fetch, or 'develop', will pick automatically if not passed"
|
||||
workflow_call:
|
||||
inputs:
|
||||
config:
|
||||
type: string
|
||||
required: true
|
||||
description: "The config directory to use"
|
||||
version:
|
||||
type: string
|
||||
required: false
|
||||
description: "The version tag to fetch, or 'develop', will pick automatically if not passed"
|
||||
jobs:
|
||||
prepare:
|
||||
name: Prepare
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
prepare:
|
||||
name: Prepare
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- uses: actions/setup-node@v3
|
||||
with:
|
||||
cache: "yarn"
|
||||
- uses: actions/setup-node@v3
|
||||
with:
|
||||
cache: "yarn"
|
||||
|
||||
- name: Install Deps
|
||||
run: "yarn install --pure-lockfile"
|
||||
- name: Install Deps
|
||||
run: "yarn install --pure-lockfile"
|
||||
|
||||
- name: Fetch Element Web
|
||||
run: yarn run fetch --noverify -d ${{ inputs.config }} ${{ inputs.version }}
|
||||
- name: Fetch Element Web
|
||||
run: yarn run fetch --noverify -d ${{ inputs.config }} ${{ inputs.version }}
|
||||
|
||||
# We split this out to save the build_* scripts having to do it to make use of `hashFiles` in the cache action
|
||||
- name: Generate cache hash files
|
||||
run: |
|
||||
yarn run --silent electron --version > electronVersion
|
||||
cat package.json | jq -c .hakDependencies > hakDependencies.json
|
||||
# We split this out to save the build_* scripts having to do it to make use of `hashFiles` in the cache action
|
||||
- name: Generate cache hash files
|
||||
run: |
|
||||
yarn run --silent electron --version > electronVersion
|
||||
cat package.json | jq -c .hakDependencies > hakDependencies.json
|
||||
|
||||
- uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: webapp
|
||||
retention-days: 1
|
||||
path: |
|
||||
webapp.asar
|
||||
package.json
|
||||
electronVersion
|
||||
hakDependencies.json
|
||||
- uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: webapp
|
||||
retention-days: 1
|
||||
path: |
|
||||
webapp.asar
|
||||
package.json
|
||||
electronVersion
|
||||
hakDependencies.json
|
||||
|
152
.github/workflows/build_windows.yaml
vendored
152
.github/workflows/build_windows.yaml
vendored
@ -2,92 +2,92 @@
|
||||
# Due to this extra care must be taken to only ever run all build_* scripts against the same branch to ensure
|
||||
# the correct cache scoping, and additional care must be taken to not run untrusted actions on the develop branch.
|
||||
on:
|
||||
workflow_call:
|
||||
inputs:
|
||||
arch:
|
||||
type: string
|
||||
required: true
|
||||
description: "The architecture to build for, one of 'x64' | 'x86'"
|
||||
workflow_call:
|
||||
inputs:
|
||||
arch:
|
||||
type: string
|
||||
required: true
|
||||
description: "The architecture to build for, one of 'x64' | 'x86'"
|
||||
jobs:
|
||||
build:
|
||||
runs-on: windows-latest
|
||||
steps:
|
||||
- uses: kanga333/variable-mapper@master
|
||||
id: config
|
||||
with:
|
||||
key: "${{ inputs.arch }}"
|
||||
export_to: output
|
||||
map: |
|
||||
{
|
||||
"x64": {
|
||||
"target": "x86_64-pc-windows-msvc"
|
||||
},
|
||||
"x86": {
|
||||
"target": "i686-pc-windows-msvc",
|
||||
"build-args": "--ia32"
|
||||
}
|
||||
}
|
||||
build:
|
||||
runs-on: windows-latest
|
||||
steps:
|
||||
- uses: kanga333/variable-mapper@master
|
||||
id: config
|
||||
with:
|
||||
key: "${{ inputs.arch }}"
|
||||
export_to: output
|
||||
map: |
|
||||
{
|
||||
"x64": {
|
||||
"target": "x86_64-pc-windows-msvc"
|
||||
},
|
||||
"x86": {
|
||||
"target": "i686-pc-windows-msvc",
|
||||
"build-args": "--ia32"
|
||||
}
|
||||
}
|
||||
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- uses: actions/download-artifact@v3
|
||||
with:
|
||||
name: webapp
|
||||
- uses: actions/download-artifact@v3
|
||||
with:
|
||||
name: webapp
|
||||
|
||||
- name: Cache .hak
|
||||
id: cache
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
key: ${{ runner.os }}-${{ hashFiles('hakDependencies.json', 'electronVersion') }}
|
||||
path: |
|
||||
./.hak
|
||||
- name: Cache .hak
|
||||
id: cache
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
key: ${{ runner.os }}-${{ hashFiles('hakDependencies.json', 'electronVersion') }}
|
||||
path: |
|
||||
./.hak
|
||||
|
||||
- name: Set up build tools
|
||||
uses: ilammy/msvc-dev-cmd@v1
|
||||
with:
|
||||
arch: ${{ inputs.arch }}
|
||||
- name: Set up build tools
|
||||
uses: ilammy/msvc-dev-cmd@v1
|
||||
with:
|
||||
arch: ${{ inputs.arch }}
|
||||
|
||||
# ActiveTCL package on choco is from 2015,
|
||||
# this one is newer but includes more than we need
|
||||
- name: Choco install tclsh
|
||||
shell: pwsh
|
||||
run: |
|
||||
choco install -y magicsplat-tcl-tk --no-progress
|
||||
echo "${HOME}/AppData/Local/Apps/Tcl86/bin" | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append
|
||||
# ActiveTCL package on choco is from 2015,
|
||||
# this one is newer but includes more than we need
|
||||
- name: Choco install tclsh
|
||||
shell: pwsh
|
||||
run: |
|
||||
choco install -y magicsplat-tcl-tk --no-progress
|
||||
echo "${HOME}/AppData/Local/Apps/Tcl86/bin" | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append
|
||||
|
||||
- name: Choco install NetWide Assembler
|
||||
shell: pwsh
|
||||
run: |
|
||||
choco install -y nasm --no-progress
|
||||
echo "C:/Program Files/NASM" | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append
|
||||
- name: Choco install NetWide Assembler
|
||||
shell: pwsh
|
||||
run: |
|
||||
choco install -y nasm --no-progress
|
||||
echo "C:/Program Files/NASM" | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append
|
||||
|
||||
- name: Install Rust
|
||||
if: steps.cache.outputs.cache-hit != 'true'
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: stable
|
||||
target: ${{ steps.config.outputs.target }}
|
||||
- name: Install Rust
|
||||
if: steps.cache.outputs.cache-hit != 'true'
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: stable
|
||||
target: ${{ steps.config.outputs.target }}
|
||||
|
||||
- uses: actions/setup-node@v3
|
||||
with:
|
||||
cache: "yarn"
|
||||
- uses: actions/setup-node@v3
|
||||
with:
|
||||
cache: "yarn"
|
||||
|
||||
# Does not need branch matching as only analyses this layer
|
||||
- name: Install Deps
|
||||
run: "yarn install --pure-lockfile"
|
||||
# Does not need branch matching as only analyses this layer
|
||||
- name: Install Deps
|
||||
run: "yarn install --pure-lockfile"
|
||||
|
||||
- name: Build Natives
|
||||
if: steps.cache.outputs.cache-hit != 'true'
|
||||
run: |
|
||||
refreshenv
|
||||
yarn build:native --target ${{ steps.config.outputs.target }}
|
||||
- name: Build Natives
|
||||
if: steps.cache.outputs.cache-hit != 'true'
|
||||
run: |
|
||||
refreshenv
|
||||
yarn build:native --target ${{ steps.config.outputs.target }}
|
||||
|
||||
- name: Build App
|
||||
run: "yarn build --publish never -w ${{ steps.config.outputs.build-args }}"
|
||||
- name: Build App
|
||||
run: "yarn build --publish never -w ${{ steps.config.outputs.build-args }}"
|
||||
|
||||
- name: Upload Artifacts
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: win-${{ inputs.arch }}
|
||||
path: dist
|
||||
retention-days: 1
|
||||
- name: Upload Artifacts
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: win-${{ inputs.arch }}
|
||||
path: dist
|
||||
retention-days: 1
|
||||
|
76
.github/workflows/packages_index.yaml
vendored
76
.github/workflows/packages_index.yaml
vendored
@ -1,43 +1,43 @@
|
||||
name: Generate packages.element.io directory indexes
|
||||
on:
|
||||
# Trigger a rebuild of all indexes if the template gets updated
|
||||
push:
|
||||
branches: [ develop ]
|
||||
paths:
|
||||
- 'packages.element.io/**'
|
||||
# Trigger a daily rebuild for nightlies
|
||||
schedule:
|
||||
- cron: '0 11 * * *'
|
||||
# Manual trigger for rebuilding for releases
|
||||
workflow_dispatch: { }
|
||||
# Trigger a rebuild of all indexes if the template gets updated
|
||||
push:
|
||||
branches: [develop]
|
||||
paths:
|
||||
- "packages.element.io/**"
|
||||
# Trigger a daily rebuild for nightlies
|
||||
schedule:
|
||||
- cron: "0 11 * * *"
|
||||
# Manual trigger for rebuilding for releases
|
||||
workflow_dispatch: {}
|
||||
jobs:
|
||||
deploy:
|
||||
name: "Deploy"
|
||||
runs-on: ubuntu-latest
|
||||
environment: develop
|
||||
env:
|
||||
R2_BUCKET: 'packages-element-io'
|
||||
R2_URL: ${{ secrets.CF_R2_S3_API }}
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- uses: actions/setup-node@v3
|
||||
with:
|
||||
cache: "yarn"
|
||||
|
||||
- name: Install Deps
|
||||
run: "yarn install --pure-lockfile"
|
||||
|
||||
- name: Copy static files
|
||||
if: github.event_name == 'push'
|
||||
run: aws s3 cp --recursive packages.element.io/ s3://$R2_BUCKET/ --endpoint-url $R2_URL --region auto
|
||||
deploy:
|
||||
name: "Deploy"
|
||||
runs-on: ubuntu-latest
|
||||
environment: develop
|
||||
env:
|
||||
AWS_ACCESS_KEY_ID: ${{ secrets.CF_R2_ACCESS_KEY_ID }}
|
||||
AWS_SECRET_ACCESS_KEY: ${{ secrets.CF_R2_TOKEN }}
|
||||
R2_BUCKET: "packages-element-io"
|
||||
R2_URL: ${{ secrets.CF_R2_S3_API }}
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- name: Generate directory indexes
|
||||
run: scripts/generate-packages-index.ts
|
||||
env:
|
||||
CF_R2_S3_API: ${{ secrets.CF_R2_S3_API }}
|
||||
CF_R2_ACCESS_KEY_ID: ${{ secrets.CF_R2_ACCESS_KEY_ID }}
|
||||
CF_R2_TOKEN: ${{ secrets.CF_R2_TOKEN }}
|
||||
- uses: actions/setup-node@v3
|
||||
with:
|
||||
cache: "yarn"
|
||||
|
||||
- name: Install Deps
|
||||
run: "yarn install --pure-lockfile"
|
||||
|
||||
- name: Copy static files
|
||||
if: github.event_name == 'push'
|
||||
run: aws s3 cp --recursive packages.element.io/ s3://$R2_BUCKET/ --endpoint-url $R2_URL --region auto
|
||||
env:
|
||||
AWS_ACCESS_KEY_ID: ${{ secrets.CF_R2_ACCESS_KEY_ID }}
|
||||
AWS_SECRET_ACCESS_KEY: ${{ secrets.CF_R2_TOKEN }}
|
||||
|
||||
- name: Generate directory indexes
|
||||
run: scripts/generate-packages-index.ts
|
||||
env:
|
||||
CF_R2_S3_API: ${{ secrets.CF_R2_S3_API }}
|
||||
CF_R2_ACCESS_KEY_ID: ${{ secrets.CF_R2_ACCESS_KEY_ID }}
|
||||
CF_R2_TOKEN: ${{ secrets.CF_R2_TOKEN }}
|
||||
|
16
.github/workflows/pull_request.yaml
vendored
16
.github/workflows/pull_request.yaml
vendored
@ -1,12 +1,12 @@
|
||||
name: Pull Request
|
||||
on:
|
||||
pull_request_target:
|
||||
types: [ opened, edited, labeled, unlabeled, synchronize ]
|
||||
pull_request_target:
|
||||
types: [opened, edited, labeled, unlabeled, synchronize]
|
||||
concurrency: ${{ github.workflow }}-${{ github.event.pull_request.head.ref }}
|
||||
jobs:
|
||||
action:
|
||||
uses: matrix-org/matrix-js-sdk/.github/workflows/pull_request.yaml@develop
|
||||
with:
|
||||
labels: "T-Defect,T-Enhancement,T-Task"
|
||||
secrets:
|
||||
ELEMENT_BOT_TOKEN: ${{ secrets.ELEMENT_BOT_TOKEN }}
|
||||
action:
|
||||
uses: matrix-org/matrix-js-sdk/.github/workflows/pull_request.yaml@develop
|
||||
with:
|
||||
labels: "T-Defect,T-Enhancement,T-Task"
|
||||
secrets:
|
||||
ELEMENT_BOT_TOKEN: ${{ secrets.ELEMENT_BOT_TOKEN }}
|
||||
|
64
.github/workflows/static_analysis.yaml
vendored
64
.github/workflows/static_analysis.yaml
vendored
@ -1,43 +1,43 @@
|
||||
name: Static Analysis
|
||||
on:
|
||||
pull_request: { }
|
||||
push:
|
||||
branches: [ develop, master ]
|
||||
pull_request: {}
|
||||
push:
|
||||
branches: [develop, master]
|
||||
jobs:
|
||||
ts_lint:
|
||||
name: "Typescript Syntax Check"
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
ts_lint:
|
||||
name: "Typescript Syntax Check"
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- uses: actions/setup-node@v3
|
||||
with:
|
||||
cache: 'yarn'
|
||||
- uses: actions/setup-node@v3
|
||||
with:
|
||||
cache: "yarn"
|
||||
|
||||
# Does not need branch matching as only analyses this layer
|
||||
- name: Install Deps
|
||||
run: "yarn install --pure-lockfile"
|
||||
# Does not need branch matching as only analyses this layer
|
||||
- name: Install Deps
|
||||
run: "yarn install --pure-lockfile"
|
||||
|
||||
- name: Typecheck
|
||||
run: "yarn run lint:types"
|
||||
- name: Typecheck
|
||||
run: "yarn run lint:types"
|
||||
|
||||
i18n_lint:
|
||||
name: "i18n Check"
|
||||
uses: matrix-org/matrix-react-sdk/.github/workflows/i18n_check.yml@develop
|
||||
i18n_lint:
|
||||
name: "i18n Check"
|
||||
uses: matrix-org/matrix-react-sdk/.github/workflows/i18n_check.yml@develop
|
||||
|
||||
js_lint:
|
||||
name: "ESLint"
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
js_lint:
|
||||
name: "ESLint"
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- uses: actions/setup-node@v3
|
||||
with:
|
||||
cache: 'yarn'
|
||||
- uses: actions/setup-node@v3
|
||||
with:
|
||||
cache: "yarn"
|
||||
|
||||
# Does not need branch matching as only analyses this layer
|
||||
- name: Install Deps
|
||||
run: "yarn install --pure-lockfile"
|
||||
# Does not need branch matching as only analyses this layer
|
||||
- name: Install Deps
|
||||
run: "yarn install --pure-lockfile"
|
||||
|
||||
- name: Run Linter
|
||||
run: "yarn run lint:js"
|
||||
- name: Run Linter
|
||||
run: "yarn run lint:js"
|
||||
|
10
.github/workflows/upgrade_dependencies.yml
vendored
10
.github/workflows/upgrade_dependencies.yml
vendored
@ -1,8 +1,8 @@
|
||||
name: Upgrade Dependencies
|
||||
on:
|
||||
workflow_dispatch: { }
|
||||
workflow_dispatch: {}
|
||||
jobs:
|
||||
upgrade:
|
||||
uses: matrix-org/matrix-js-sdk/.github/workflows/upgrade_dependencies.yml@develop
|
||||
secrets:
|
||||
ELEMENT_BOT_TOKEN: ${{ secrets.ELEMENT_BOT_TOKEN }}
|
||||
upgrade:
|
||||
uses: matrix-org/matrix-js-sdk/.github/workflows/upgrade_dependencies.yml@develop
|
||||
secrets:
|
||||
ELEMENT_BOT_TOKEN: ${{ secrets.ELEMENT_BOT_TOKEN }}
|
||||
|
19
.prettierignore
Normal file
19
.prettierignore
Normal file
@ -0,0 +1,19 @@
|
||||
/build/
|
||||
/dockerbuild/
|
||||
/lib/
|
||||
/node_modules/
|
||||
/packages.elememt.io/
|
||||
/webapp
|
||||
/src/i18n/strings
|
||||
/CHANGELOG.md
|
||||
/package-lock.json
|
||||
/yarn.lock
|
||||
|
||||
**/.idea
|
||||
.vscode
|
||||
.vscode/
|
||||
.tmp
|
||||
.env
|
||||
/coverage
|
||||
/.npmrc
|
||||
/*.log
|
1
.prettierrc.js
Normal file
1
.prettierrc.js
Normal file
@ -0,0 +1 @@
|
||||
module.exports = require("eslint-plugin-matrix-org/.prettierrc.js");
|
64
README.md
64
README.md
@ -5,21 +5,20 @@
|
||||
[![Vulnerabilities](https://sonarcloud.io/api/project_badges/measure?project=element-desktop&metric=vulnerabilities)](https://sonarcloud.io/summary/new_code?id=element-desktop)
|
||||
[![Bugs](https://sonarcloud.io/api/project_badges/measure?project=element-desktop&metric=bugs)](https://sonarcloud.io/summary/new_code?id=element-desktop)
|
||||
|
||||
Element Desktop
|
||||
===============
|
||||
# Element Desktop
|
||||
|
||||
Element Desktop is a Matrix client for desktop platforms with Element Web at its core.
|
||||
|
||||
First Steps
|
||||
===========
|
||||
# First Steps
|
||||
|
||||
Before you do anything else, fetch the dependencies:
|
||||
|
||||
```
|
||||
yarn install
|
||||
```
|
||||
|
||||
Fetching Element
|
||||
================
|
||||
# Fetching Element
|
||||
|
||||
Since this package is just the Electron wrapper for Element Web, it doesn't contain any of the Element Web code,
|
||||
so the first step is to get a working copy of Element Web. There are a few ways of doing this:
|
||||
|
||||
@ -31,6 +30,7 @@ yarn run fetch --noverify --cfgdir ""
|
||||
```
|
||||
|
||||
...or if you'd like to use GPG to verify the downloaded package:
|
||||
|
||||
```
|
||||
# Fetch the Element public key from the element.io web server over a secure connection and import
|
||||
# it into your local GPG keychain (you'll need GPG installed). You only need to to do this
|
||||
@ -41,6 +41,7 @@ yarn run fetch --cfgdir ""
|
||||
```
|
||||
|
||||
...or either of the above, but fetching a specific version of Element:
|
||||
|
||||
```
|
||||
# Fetch the prebuilt release Element package from the element-web GitHub releases page. The version
|
||||
# fetched will be the same as the local element-desktop package.
|
||||
@ -49,6 +50,7 @@ yarn run fetch --noverify --cfgdir "" v1.5.6
|
||||
|
||||
If you only want to run the app locally and don't need to build packages, you can
|
||||
provide the `webapp` directory directly:
|
||||
|
||||
```
|
||||
# Assuming you've checked out and built a copy of element-web in ../element-web
|
||||
ln -s ../element-web/webapp ./
|
||||
@ -56,29 +58,32 @@ ln -s ../element-web/webapp ./
|
||||
|
||||
[TODO: add support for fetching develop builds, arbitrary URLs and arbitrary paths]
|
||||
|
||||
Building
|
||||
========
|
||||
# Building
|
||||
|
||||
## Native Build
|
||||
|
||||
TODO: List native pre-requisites
|
||||
|
||||
Optionally, [build the native modules](https://github.com/vector-im/element-desktop/blob/develop/docs/native-node-modules.md),
|
||||
which include support for searching in encrypted rooms and secure storage. Skipping this step is fine, you just won't have those features.
|
||||
Optionally, [build the native modules](https://github.com/vector-im/element-desktop/blob/develop/docs/native-node-modules.md),
|
||||
which include support for searching in encrypted rooms and secure storage. Skipping this step is fine, you just won't have those features.
|
||||
|
||||
Then, run
|
||||
|
||||
```
|
||||
yarn run build
|
||||
```
|
||||
|
||||
This will do a couple of things:
|
||||
* Run the `setversion` script to set the local package version to match whatever
|
||||
version of Element you installed above.
|
||||
* Run electron-builder to build a package. The package built will match the operating system
|
||||
you're running the build process on.
|
||||
|
||||
- Run the `setversion` script to set the local package version to match whatever
|
||||
version of Element you installed above.
|
||||
- Run electron-builder to build a package. The package built will match the operating system
|
||||
you're running the build process on.
|
||||
|
||||
## Docker
|
||||
|
||||
Alternatively, you can also build using docker, which will always produce the linux package:
|
||||
|
||||
```
|
||||
# Run this once to make the docker image
|
||||
yarn run docker:setup
|
||||
@ -91,9 +96,10 @@ yarn run docker:build
|
||||
|
||||
After running, the packages should be in `dist/`.
|
||||
|
||||
Starting
|
||||
========
|
||||
# Starting
|
||||
|
||||
If you'd just like to run the electron app locally for development:
|
||||
|
||||
```
|
||||
# Install electron - we don't normally need electron itself as it's provided
|
||||
# by electron-builder when building packages
|
||||
@ -101,21 +107,22 @@ yarn add electron
|
||||
yarn start
|
||||
```
|
||||
|
||||
Config
|
||||
======
|
||||
# Config
|
||||
|
||||
If you'd like the packaged Element to have a configuration file, you can create a
|
||||
config directory and place `config.json` in there, then specify this directory
|
||||
with the `--cfgdir` option to `yarn run fetch`, eg:
|
||||
|
||||
```
|
||||
mkdir myconfig
|
||||
cp /path/to/my/config.json myconfig/
|
||||
yarn run fetch --cfgdir myconfig
|
||||
```
|
||||
|
||||
The config dir for the official Element app is in `element.io`. If you use this,
|
||||
your app will auto-update itself using builds from element.io.
|
||||
|
||||
Profiles
|
||||
========
|
||||
# Profiles
|
||||
|
||||
To run multiple instances of the desktop app for different accounts, you can
|
||||
launch the executable with the `--profile` argument followed by a unique
|
||||
@ -125,20 +132,18 @@ not interfere with the default one.
|
||||
Alternatively, a custom location for the profile data can be specified using the
|
||||
`--profile-dir` flag followed by the desired path.
|
||||
|
||||
User-specified config.json
|
||||
==========================
|
||||
# User-specified config.json
|
||||
|
||||
+ `%APPDATA%\$NAME\config.json` on Windows
|
||||
+ `$XDG_CONFIG_HOME/$NAME/config.json` or `~/.config/$NAME/config.json` on Linux
|
||||
+ `~/Library/Application Support/$NAME/config.json` on macOS
|
||||
- `%APPDATA%\$NAME\config.json` on Windows
|
||||
- `$XDG_CONFIG_HOME/$NAME/config.json` or `~/.config/$NAME/config.json` on Linux
|
||||
- `~/Library/Application Support/$NAME/config.json` on macOS
|
||||
|
||||
In the paths above, `$NAME` is typically `Element`, unless you use `--profile
|
||||
$PROFILE` in which case it becomes `Element-$PROFILE`, or it is using one of
|
||||
the above created by a pre-1.7 install, in which case it will be `Riot` or
|
||||
`Riot-$PROFILE`.
|
||||
|
||||
Translations
|
||||
==========================
|
||||
# Translations
|
||||
|
||||
To add a new translation, head to the [translating doc](https://github.com/vector-im/element-web/blob/develop/docs/translating.md).
|
||||
|
||||
@ -146,9 +151,8 @@ For a developer guide, see the [translating dev doc](https://github.com/vector-i
|
||||
|
||||
[<img src="https://translate.element.io/widgets/element-desktop/-/multi-auto.svg" alt="translationsstatus" width="340">](https://translate.element.io/engage/element-desktop/?utm_source=widget)
|
||||
|
||||
Report bugs & give feedback
|
||||
==========================
|
||||
# Report bugs & give feedback
|
||||
|
||||
If you run into any bugs or have feedback you'd like to share, please let us know on GitHub.
|
||||
|
||||
To help avoid duplicate issues, please [view existing issues](https://github.com/vector-im/element-web/issues?q=is%3Aopen+is%3Aissue+sort%3Areactions-%2B1-desc) first (and add a +1) or [create a new issue](https://github.com/vector-im/element-web/issues/new/choose) if you can't find it. Please note that this issue tracker is associated with the [element-web](https://github.com/vector-im/element-web) repo, but is also applied to the code in this repo as well.
|
||||
To help avoid duplicate issues, please [view existing issues](https://github.com/vector-im/element-web/issues?q=is%3Aopen+is%3Aissue+sort%3Areactions-%2B1-desc) first (and add a +1) or [create a new issue](https://github.com/vector-im/element-web/issues/new/choose) if you can't find it. Please note that this issue tracker is associated with the [element-web](https://github.com/vector-im/element-web) repo, but is also applied to the code in this repo as well.
|
||||
|
@ -1,6 +1,3 @@
|
||||
module.exports = {
|
||||
presets: [
|
||||
['@babel/preset-env', { targets: { node: 'current' } }],
|
||||
'@babel/preset-typescript',
|
||||
],
|
||||
presets: [["@babel/preset-env", { targets: { node: "current" } }], "@babel/preset-typescript"],
|
||||
};
|
||||
|
@ -17,13 +17,14 @@ when releasing.
|
||||
|
||||
Install the pre-requisites for your system:
|
||||
|
||||
* [Windows pre-requisites](https://github.com/vector-im/element-desktop/blob/develop/docs/windows-requirements.md)
|
||||
* Linux: TODO
|
||||
* OS X: TODO
|
||||
- [Windows pre-requisites](https://github.com/vector-im/element-desktop/blob/develop/docs/windows-requirements.md)
|
||||
- Linux: TODO
|
||||
- OS X: TODO
|
||||
|
||||
Then optionally, [add seshat and dependencies to support search in E2E rooms](#adding-seshat-for-search-in-e2e-encrypted-rooms).
|
||||
|
||||
Then, to build for an architecture selected automatically based on your system (recommended), run:
|
||||
|
||||
```
|
||||
yarn run build:native
|
||||
```
|
||||
@ -70,7 +71,7 @@ as usual using:
|
||||
|
||||
On Windows & macOS we always statically link libsqlcipher for it is not generally available.
|
||||
On Linux by default we will use a system package, on debian & ubuntu this is `libsqlcipher0`,
|
||||
but this is problematic for some other packages.
|
||||
but this is problematic for some other packages.
|
||||
By including `SQLCIPHER_STATIC=1` in the build environment, the build scripts will statically link sqlcipher,
|
||||
note that this will want a `libcrypto1.1` shared library available in the system.
|
||||
|
||||
@ -82,15 +83,19 @@ and https://github.com/vector-im/element-web/issues/20926.
|
||||
### macOS
|
||||
|
||||
On macOS, you can build universal native modules too:
|
||||
|
||||
```
|
||||
yarn run build:native:universal
|
||||
```
|
||||
|
||||
...or you can build for a specific architecture:
|
||||
|
||||
```
|
||||
yarn run build:native --target x86_64-apple-darwin
|
||||
```
|
||||
|
||||
or
|
||||
|
||||
```
|
||||
yarn run build:native --target aarch64-apple-darwin
|
||||
```
|
||||
@ -105,10 +110,13 @@ yarn run build:universal
|
||||
### Windows
|
||||
|
||||
If you're on Windows, you can choose to build specifically for 32 or 64 bit:
|
||||
|
||||
```
|
||||
yarn run build:32
|
||||
```
|
||||
|
||||
or
|
||||
|
||||
```
|
||||
yarn run build:64
|
||||
```
|
||||
@ -144,6 +152,6 @@ The current set of native modules are stored in `.hak/hakModules`,
|
||||
so you can use this to check what architecture is currently in place, eg:
|
||||
|
||||
```
|
||||
$ lipo -info .hak/hakModules/keytar/build/Release/keytar.node
|
||||
Architectures in the fat file: .hak/hakModules/keytar/build/Release/keytar.node are: x86_64 arm64
|
||||
$ lipo -info .hak/hakModules/keytar/build/Release/keytar.node
|
||||
Architectures in the fat file: .hak/hakModules/keytar/build/Release/keytar.node are: x86_64 arm64
|
||||
```
|
||||
|
@ -13,4 +13,3 @@ and can be served by any compatible Squirrel server, such as https://github.com/
|
||||
|
||||
On macOS the update mechanism used is [Squirrel.Mac](https://github.com/Squirrel/Squirrel.Mac)
|
||||
using the newer JSON format as documented [here](https://github.com/Squirrel/Squirrel.Mac#update-file-json-format).
|
||||
|
||||
|
@ -4,23 +4,24 @@
|
||||
|
||||
If you want to build native modules, make sure that the following tools are installed on your system.
|
||||
|
||||
- [Git for Windows](https://git-scm.com/download/win)
|
||||
- [Node 14](https://nodejs.org)
|
||||
- [Python 3](https://www.python.org/downloads/) (if you type 'python' into command prompt it will offer to install it from the windows store)
|
||||
- [Strawberry Perl](https://strawberryperl.com/)
|
||||
- [Rustup](https://rustup.rs/)
|
||||
- [NASM](https://www.nasm.us/)
|
||||
- [Build Tools for Visual Studio 2019](https://visualstudio.microsoft.com/downloads/#build-tools-for-visual-studio-2019) with the following configuration:
|
||||
- On the Workloads tab:
|
||||
- Desktop & Mobile -> C++ build tools
|
||||
- On the Individual components tab:
|
||||
- MSVC VS 2019 C++ build tools
|
||||
- Windows 10 SDK (latest version available)
|
||||
- C++ CMake tools for Windows
|
||||
- [Git for Windows](https://git-scm.com/download/win)
|
||||
- [Node 14](https://nodejs.org)
|
||||
- [Python 3](https://www.python.org/downloads/) (if you type 'python' into command prompt it will offer to install it from the windows store)
|
||||
- [Strawberry Perl](https://strawberryperl.com/)
|
||||
- [Rustup](https://rustup.rs/)
|
||||
- [NASM](https://www.nasm.us/)
|
||||
- [Build Tools for Visual Studio 2019](https://visualstudio.microsoft.com/downloads/#build-tools-for-visual-studio-2019) with the following configuration:
|
||||
- On the Workloads tab:
|
||||
- Desktop & Mobile -> C++ build tools
|
||||
- On the Individual components tab:
|
||||
- MSVC VS 2019 C++ build tools
|
||||
- Windows 10 SDK (latest version available)
|
||||
- C++ CMake tools for Windows
|
||||
|
||||
Once installed make sure all those utilities are accessible in your `PATH`.
|
||||
|
||||
If you want to be able to build x86 targets from an x64 host install the right toolchain:
|
||||
|
||||
```cmd
|
||||
rustup toolchain install stable-i686-pc-windows-msvc
|
||||
rustup target add i686-pc-windows-msvc
|
||||
|
@ -16,11 +16,7 @@
|
||||
"uisi_autorageshake_app": "element-auto-uisi",
|
||||
"showLabsSettings": true,
|
||||
"roomDirectory": {
|
||||
"servers": [
|
||||
"matrix.org",
|
||||
"gitter.im",
|
||||
"libera.chat"
|
||||
]
|
||||
"servers": ["matrix.org", "gitter.im", "libera.chat"]
|
||||
},
|
||||
"enable_presence_by_hs_url": {
|
||||
"https://matrix.org": false,
|
||||
|
@ -15,11 +15,7 @@
|
||||
"bug_report_endpoint_url": "https://element.io/bugreports/submit",
|
||||
"uisi_autorageshake_app": "element-auto-uisi",
|
||||
"roomDirectory": {
|
||||
"servers": [
|
||||
"matrix.org",
|
||||
"gitter.im",
|
||||
"libera.chat"
|
||||
]
|
||||
"servers": ["matrix.org", "gitter.im", "libera.chat"]
|
||||
},
|
||||
"showLabsSettings": false,
|
||||
"enable_presence_by_hs_url": {
|
||||
|
@ -14,11 +14,11 @@ See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
import path from 'path';
|
||||
import childProcess from 'child_process';
|
||||
import path from "path";
|
||||
import childProcess from "child_process";
|
||||
|
||||
import HakEnv from '../../scripts/hak/hakEnv';
|
||||
import { DependencyInfo } from '../../scripts/hak/dep';
|
||||
import HakEnv from "../../scripts/hak/hakEnv";
|
||||
import { DependencyInfo } from "../../scripts/hak/dep";
|
||||
|
||||
export default async function buildKeytar(hakEnv: HakEnv, moduleInfo: DependencyInfo): Promise<void> {
|
||||
const env = hakEnv.makeGypEnv();
|
||||
@ -26,15 +26,15 @@ export default async function buildKeytar(hakEnv: HakEnv, moduleInfo: Dependency
|
||||
console.log("Running yarn with env", env);
|
||||
await new Promise<void>((resolve, reject) => {
|
||||
const proc = childProcess.spawn(
|
||||
path.join(moduleInfo.nodeModuleBinDir, 'node-gyp' + (hakEnv.isWin() ? '.cmd' : '')),
|
||||
['rebuild'],
|
||||
path.join(moduleInfo.nodeModuleBinDir, "node-gyp" + (hakEnv.isWin() ? ".cmd" : "")),
|
||||
["rebuild"],
|
||||
{
|
||||
cwd: moduleInfo.moduleBuildDir,
|
||||
env,
|
||||
stdio: 'inherit',
|
||||
stdio: "inherit",
|
||||
},
|
||||
);
|
||||
proc.on('exit', (code) => {
|
||||
proc.on("exit", (code) => {
|
||||
code ? reject(code) : resolve();
|
||||
});
|
||||
});
|
||||
|
@ -14,20 +14,20 @@ See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
import childProcess from 'child_process';
|
||||
import childProcess from "child_process";
|
||||
|
||||
import HakEnv from '../../scripts/hak/hakEnv';
|
||||
import { DependencyInfo } from '../../scripts/hak/dep';
|
||||
import HakEnv from "../../scripts/hak/hakEnv";
|
||||
import { DependencyInfo } from "../../scripts/hak/dep";
|
||||
|
||||
export default async function(hakEnv: HakEnv, moduleInfo: DependencyInfo): Promise<void> {
|
||||
const tools = [['python', '--version']]; // node-gyp uses python for reasons beyond comprehension
|
||||
export default async function (hakEnv: HakEnv, moduleInfo: DependencyInfo): Promise<void> {
|
||||
const tools = [["python", "--version"]]; // node-gyp uses python for reasons beyond comprehension
|
||||
|
||||
for (const tool of tools) {
|
||||
await new Promise<void>((resolve, reject) => {
|
||||
const proc = childProcess.spawn(tool[0], tool.slice(1), {
|
||||
stdio: ['ignore'],
|
||||
stdio: ["ignore"],
|
||||
});
|
||||
proc.on('exit', (code) => {
|
||||
proc.on("exit", (code) => {
|
||||
if (code !== 0) {
|
||||
reject("Can't find " + tool);
|
||||
} else {
|
||||
|
@ -14,15 +14,15 @@ See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
import path from 'path';
|
||||
import childProcess from 'child_process';
|
||||
import mkdirp from 'mkdirp';
|
||||
import fsExtra from 'fs-extra';
|
||||
import path from "path";
|
||||
import childProcess from "child_process";
|
||||
import mkdirp from "mkdirp";
|
||||
import fsExtra from "fs-extra";
|
||||
|
||||
import HakEnv from '../../scripts/hak/hakEnv';
|
||||
import { DependencyInfo } from '../../scripts/hak/dep';
|
||||
import HakEnv from "../../scripts/hak/hakEnv";
|
||||
import { DependencyInfo } from "../../scripts/hak/dep";
|
||||
|
||||
export default async function(hakEnv: HakEnv, moduleInfo: DependencyInfo): Promise<void> {
|
||||
export default async function (hakEnv: HakEnv, moduleInfo: DependencyInfo): Promise<void> {
|
||||
if (hakEnv.isWin()) {
|
||||
await buildOpenSslWin(hakEnv, moduleInfo);
|
||||
await buildSqlCipherWin(hakEnv, moduleInfo);
|
||||
@ -36,99 +36,91 @@ async function buildOpenSslWin(hakEnv: HakEnv, moduleInfo: DependencyInfo): Prom
|
||||
const version = moduleInfo.cfg.dependencies.openssl;
|
||||
const openSslDir = path.join(moduleInfo.moduleTargetDotHakDir, `openssl-${version}`);
|
||||
|
||||
const openSslArch = hakEnv.getTargetArch() === 'x64' ? 'VC-WIN64A' : 'VC-WIN32';
|
||||
const openSslArch = hakEnv.getTargetArch() === "x64" ? "VC-WIN64A" : "VC-WIN32";
|
||||
|
||||
console.log("Building openssl in " + openSslDir);
|
||||
await new Promise<void>((resolve, reject) => {
|
||||
const proc = childProcess.spawn(
|
||||
'perl',
|
||||
"perl",
|
||||
[
|
||||
'Configure',
|
||||
'--prefix=' + moduleInfo.depPrefix,
|
||||
"Configure",
|
||||
"--prefix=" + moduleInfo.depPrefix,
|
||||
// sqlcipher only uses about a tiny part of openssl. We link statically
|
||||
// so will only pull in the symbols we use, but we may as well turn off
|
||||
// as much as possible to save on build time.
|
||||
'no-afalgeng',
|
||||
'no-capieng',
|
||||
'no-cms',
|
||||
'no-ct',
|
||||
'no-deprecated',
|
||||
'no-dgram',
|
||||
'no-dso',
|
||||
'no-ec',
|
||||
'no-ec2m',
|
||||
'no-gost',
|
||||
'no-nextprotoneg',
|
||||
'no-ocsp',
|
||||
'no-sock',
|
||||
'no-srp',
|
||||
'no-srtp',
|
||||
'no-tests',
|
||||
'no-ssl',
|
||||
'no-tls',
|
||||
'no-dtls',
|
||||
'no-shared',
|
||||
'no-aria',
|
||||
'no-camellia',
|
||||
'no-cast',
|
||||
'no-chacha',
|
||||
'no-cmac',
|
||||
'no-des',
|
||||
'no-dh',
|
||||
'no-dsa',
|
||||
'no-ecdh',
|
||||
'no-ecdsa',
|
||||
'no-idea',
|
||||
'no-md4',
|
||||
'no-mdc2',
|
||||
'no-ocb',
|
||||
'no-poly1305',
|
||||
'no-rc2',
|
||||
'no-rc4',
|
||||
'no-rmd160',
|
||||
'no-scrypt',
|
||||
'no-seed',
|
||||
'no-siphash',
|
||||
'no-sm2',
|
||||
'no-sm3',
|
||||
'no-sm4',
|
||||
'no-whirlpool',
|
||||
"no-afalgeng",
|
||||
"no-capieng",
|
||||
"no-cms",
|
||||
"no-ct",
|
||||
"no-deprecated",
|
||||
"no-dgram",
|
||||
"no-dso",
|
||||
"no-ec",
|
||||
"no-ec2m",
|
||||
"no-gost",
|
||||
"no-nextprotoneg",
|
||||
"no-ocsp",
|
||||
"no-sock",
|
||||
"no-srp",
|
||||
"no-srtp",
|
||||
"no-tests",
|
||||
"no-ssl",
|
||||
"no-tls",
|
||||
"no-dtls",
|
||||
"no-shared",
|
||||
"no-aria",
|
||||
"no-camellia",
|
||||
"no-cast",
|
||||
"no-chacha",
|
||||
"no-cmac",
|
||||
"no-des",
|
||||
"no-dh",
|
||||
"no-dsa",
|
||||
"no-ecdh",
|
||||
"no-ecdsa",
|
||||
"no-idea",
|
||||
"no-md4",
|
||||
"no-mdc2",
|
||||
"no-ocb",
|
||||
"no-poly1305",
|
||||
"no-rc2",
|
||||
"no-rc4",
|
||||
"no-rmd160",
|
||||
"no-scrypt",
|
||||
"no-seed",
|
||||
"no-siphash",
|
||||
"no-sm2",
|
||||
"no-sm3",
|
||||
"no-sm4",
|
||||
"no-whirlpool",
|
||||
openSslArch,
|
||||
],
|
||||
{
|
||||
cwd: openSslDir,
|
||||
stdio: 'inherit',
|
||||
stdio: "inherit",
|
||||
},
|
||||
);
|
||||
proc.on('exit', (code) => {
|
||||
proc.on("exit", (code) => {
|
||||
code ? reject(code) : resolve();
|
||||
});
|
||||
});
|
||||
|
||||
await new Promise<void>((resolve, reject) => {
|
||||
const proc = childProcess.spawn(
|
||||
'nmake',
|
||||
['build_libs'],
|
||||
{
|
||||
cwd: openSslDir,
|
||||
stdio: 'inherit',
|
||||
},
|
||||
);
|
||||
proc.on('exit', (code) => {
|
||||
const proc = childProcess.spawn("nmake", ["build_libs"], {
|
||||
cwd: openSslDir,
|
||||
stdio: "inherit",
|
||||
});
|
||||
proc.on("exit", (code) => {
|
||||
code ? reject(code) : resolve();
|
||||
});
|
||||
});
|
||||
|
||||
await new Promise<void>((resolve, reject) => {
|
||||
const proc = childProcess.spawn(
|
||||
'nmake',
|
||||
['install_dev'],
|
||||
{
|
||||
cwd: openSslDir,
|
||||
stdio: 'inherit',
|
||||
},
|
||||
);
|
||||
proc.on('exit', (code) => {
|
||||
const proc = childProcess.spawn("nmake", ["install_dev"], {
|
||||
cwd: openSslDir,
|
||||
stdio: "inherit",
|
||||
});
|
||||
proc.on("exit", (code) => {
|
||||
code ? reject(code) : resolve();
|
||||
});
|
||||
});
|
||||
@ -137,38 +129,28 @@ async function buildOpenSslWin(hakEnv: HakEnv, moduleInfo: DependencyInfo): Prom
|
||||
async function buildSqlCipherWin(hakEnv: HakEnv, moduleInfo: DependencyInfo): Promise<void> {
|
||||
const version = moduleInfo.cfg.dependencies.sqlcipher;
|
||||
const sqlCipherDir = path.join(moduleInfo.moduleTargetDotHakDir, `sqlcipher-${version}`);
|
||||
const buildDir = path.join(sqlCipherDir, 'bld');
|
||||
const buildDir = path.join(sqlCipherDir, "bld");
|
||||
|
||||
await mkdirp(buildDir);
|
||||
|
||||
await new Promise<void>((resolve, reject) => {
|
||||
const proc = childProcess.spawn(
|
||||
'nmake',
|
||||
['/f', path.join('..', 'Makefile.msc'), 'libsqlite3.lib', 'TOP=..'],
|
||||
{
|
||||
cwd: buildDir,
|
||||
stdio: 'inherit',
|
||||
env: Object.assign({}, process.env, {
|
||||
CCOPTS: "-DSQLITE_HAS_CODEC -I" + path.join(moduleInfo.depPrefix, 'include'),
|
||||
LTLIBPATHS: "/LIBPATH:" + path.join(moduleInfo.depPrefix, 'lib'),
|
||||
LTLIBS: "libcrypto.lib",
|
||||
}),
|
||||
},
|
||||
);
|
||||
proc.on('exit', (code) => {
|
||||
const proc = childProcess.spawn("nmake", ["/f", path.join("..", "Makefile.msc"), "libsqlite3.lib", "TOP=.."], {
|
||||
cwd: buildDir,
|
||||
stdio: "inherit",
|
||||
env: Object.assign({}, process.env, {
|
||||
CCOPTS: "-DSQLITE_HAS_CODEC -I" + path.join(moduleInfo.depPrefix, "include"),
|
||||
LTLIBPATHS: "/LIBPATH:" + path.join(moduleInfo.depPrefix, "lib"),
|
||||
LTLIBS: "libcrypto.lib",
|
||||
}),
|
||||
});
|
||||
proc.on("exit", (code) => {
|
||||
code ? reject(code) : resolve();
|
||||
});
|
||||
});
|
||||
|
||||
await fsExtra.copy(
|
||||
path.join(buildDir, 'libsqlite3.lib'),
|
||||
path.join(moduleInfo.depPrefix, 'lib', 'sqlcipher.lib'),
|
||||
);
|
||||
await fsExtra.copy(path.join(buildDir, "libsqlite3.lib"), path.join(moduleInfo.depPrefix, "lib", "sqlcipher.lib"));
|
||||
|
||||
await fsExtra.copy(
|
||||
path.join(buildDir, 'sqlite3.h'),
|
||||
path.join(moduleInfo.depPrefix, 'include', 'sqlcipher.h'),
|
||||
);
|
||||
await fsExtra.copy(path.join(buildDir, "sqlite3.h"), path.join(moduleInfo.depPrefix, "include", "sqlcipher.h"));
|
||||
}
|
||||
|
||||
async function buildSqlCipherUnix(hakEnv: HakEnv, moduleInfo: DependencyInfo): Promise<void> {
|
||||
@ -176,21 +158,21 @@ async function buildSqlCipherUnix(hakEnv: HakEnv, moduleInfo: DependencyInfo): P
|
||||
const sqlCipherDir = path.join(moduleInfo.moduleTargetDotHakDir, `sqlcipher-${version}`);
|
||||
|
||||
const args = [
|
||||
'--prefix=' + moduleInfo.depPrefix + '',
|
||||
'--enable-tempstore=yes',
|
||||
'--enable-shared=no',
|
||||
'--enable-tcl=no',
|
||||
"--prefix=" + moduleInfo.depPrefix + "",
|
||||
"--enable-tempstore=yes",
|
||||
"--enable-shared=no",
|
||||
"--enable-tcl=no",
|
||||
];
|
||||
|
||||
if (hakEnv.isMac()) {
|
||||
args.push('--with-crypto-lib=commoncrypto');
|
||||
args.push("--with-crypto-lib=commoncrypto");
|
||||
}
|
||||
|
||||
if (hakEnv.wantsStaticSqlCipherUnix()) {
|
||||
args.push('--enable-tcl=no');
|
||||
args.push("--enable-tcl=no");
|
||||
|
||||
if (hakEnv.isLinux()) {
|
||||
args.push('--with-pic=yes');
|
||||
args.push("--with-pic=yes");
|
||||
}
|
||||
}
|
||||
|
||||
@ -201,9 +183,7 @@ async function buildSqlCipherUnix(hakEnv: HakEnv, moduleInfo: DependencyInfo): P
|
||||
args.push(`--host=${hakEnv.getTargetId()}`);
|
||||
}
|
||||
|
||||
const cflags = [
|
||||
'-DSQLITE_HAS_CODEC',
|
||||
];
|
||||
const cflags = ["-DSQLITE_HAS_CODEC"];
|
||||
|
||||
if (!hakEnv.isHost()) {
|
||||
// `clang` uses more logical option naming.
|
||||
@ -211,58 +191,46 @@ async function buildSqlCipherUnix(hakEnv: HakEnv, moduleInfo: DependencyInfo): P
|
||||
}
|
||||
|
||||
if (cflags.length) {
|
||||
args.push(`CFLAGS=${cflags.join(' ')}`);
|
||||
args.push(`CFLAGS=${cflags.join(" ")}`);
|
||||
}
|
||||
|
||||
const ldflags: string[] = [];
|
||||
|
||||
if (hakEnv.isMac()) {
|
||||
ldflags.push('-framework Security');
|
||||
ldflags.push('-framework Foundation');
|
||||
ldflags.push("-framework Security");
|
||||
ldflags.push("-framework Foundation");
|
||||
}
|
||||
|
||||
if (ldflags.length) {
|
||||
args.push(`LDFLAGS=${ldflags.join(' ')}`);
|
||||
args.push(`LDFLAGS=${ldflags.join(" ")}`);
|
||||
}
|
||||
|
||||
await new Promise<void>((resolve, reject) => {
|
||||
const proc = childProcess.spawn(
|
||||
path.join(sqlCipherDir, 'configure'),
|
||||
args,
|
||||
{
|
||||
cwd: sqlCipherDir,
|
||||
stdio: 'inherit',
|
||||
},
|
||||
);
|
||||
proc.on('exit', (code) => {
|
||||
const proc = childProcess.spawn(path.join(sqlCipherDir, "configure"), args, {
|
||||
cwd: sqlCipherDir,
|
||||
stdio: "inherit",
|
||||
});
|
||||
proc.on("exit", (code) => {
|
||||
code ? reject(code) : resolve();
|
||||
});
|
||||
});
|
||||
|
||||
await new Promise<void>((resolve, reject) => {
|
||||
const proc = childProcess.spawn(
|
||||
'make',
|
||||
[],
|
||||
{
|
||||
cwd: sqlCipherDir,
|
||||
stdio: 'inherit',
|
||||
},
|
||||
);
|
||||
proc.on('exit', (code) => {
|
||||
const proc = childProcess.spawn("make", [], {
|
||||
cwd: sqlCipherDir,
|
||||
stdio: "inherit",
|
||||
});
|
||||
proc.on("exit", (code) => {
|
||||
code ? reject(code) : resolve();
|
||||
});
|
||||
});
|
||||
|
||||
await new Promise<void>((resolve, reject) => {
|
||||
const proc = childProcess.spawn(
|
||||
'make',
|
||||
['install'],
|
||||
{
|
||||
cwd: sqlCipherDir,
|
||||
stdio: 'inherit',
|
||||
},
|
||||
);
|
||||
proc.on('exit', (code) => {
|
||||
const proc = childProcess.spawn("make", ["install"], {
|
||||
cwd: sqlCipherDir,
|
||||
stdio: "inherit",
|
||||
});
|
||||
proc.on("exit", (code) => {
|
||||
code ? reject(code) : resolve();
|
||||
});
|
||||
});
|
||||
@ -277,8 +245,8 @@ async function buildMatrixSeshat(hakEnv: HakEnv, moduleInfo: DependencyInfo): Pr
|
||||
if (!hakEnv.isLinux() || hakEnv.wantsStaticSqlCipherUnix()) {
|
||||
Object.assign(env, {
|
||||
SQLCIPHER_STATIC: 1,
|
||||
SQLCIPHER_LIB_DIR: path.join(moduleInfo.depPrefix, 'lib'),
|
||||
SQLCIPHER_INCLUDE_DIR: path.join(moduleInfo.depPrefix, 'include'),
|
||||
SQLCIPHER_LIB_DIR: path.join(moduleInfo.depPrefix, "lib"),
|
||||
SQLCIPHER_INCLUDE_DIR: path.join(moduleInfo.depPrefix, "include"),
|
||||
});
|
||||
}
|
||||
|
||||
@ -298,11 +266,11 @@ async function buildMatrixSeshat(hakEnv: HakEnv, moduleInfo: DependencyInfo): Pr
|
||||
// --exclude-libs ALL
|
||||
// Prevent symbols from being exported by any archive libraries.
|
||||
// Reduces output filesize and prevents being dynamically linked against.
|
||||
env.RUSTFLAGS = '-Clink-arg=-Wl,-Bsymbolic -Clink-arg=-Wl,--exclude-libs,ALL';
|
||||
env.RUSTFLAGS = "-Clink-arg=-Wl,-Bsymbolic -Clink-arg=-Wl,--exclude-libs,ALL";
|
||||
}
|
||||
|
||||
if (hakEnv.isWin()) {
|
||||
env.RUSTFLAGS = '-Ctarget-feature=+crt-static -Clink-args=libcrypto.lib';
|
||||
env.RUSTFLAGS = "-Ctarget-feature=+crt-static -Clink-args=libcrypto.lib";
|
||||
// Note that in general, you can specify targets in Rust without having to have
|
||||
// the matching toolchain, however for this, cargo gets confused when building
|
||||
// the build scripts since they run on the host, but vcvarsall.bat sets the c
|
||||
@ -318,15 +286,15 @@ async function buildMatrixSeshat(hakEnv: HakEnv, moduleInfo: DependencyInfo): Pr
|
||||
console.log("Running neon with env", env);
|
||||
await new Promise<void>((resolve, reject) => {
|
||||
const proc = childProcess.spawn(
|
||||
path.join(moduleInfo.nodeModuleBinDir, 'neon' + (hakEnv.isWin() ? '.cmd' : '')),
|
||||
['build', '--release'],
|
||||
path.join(moduleInfo.nodeModuleBinDir, "neon" + (hakEnv.isWin() ? ".cmd" : "")),
|
||||
["build", "--release"],
|
||||
{
|
||||
cwd: moduleInfo.moduleBuildDir,
|
||||
env,
|
||||
stdio: 'inherit',
|
||||
stdio: "inherit",
|
||||
},
|
||||
);
|
||||
proc.on('exit', (code) => {
|
||||
proc.on("exit", (code) => {
|
||||
code ? reject(code) : resolve();
|
||||
});
|
||||
});
|
||||
|
@ -14,20 +14,20 @@ See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
import childProcess from 'child_process';
|
||||
import fsProm from 'fs/promises';
|
||||
import childProcess from "child_process";
|
||||
import fsProm from "fs/promises";
|
||||
|
||||
import HakEnv from '../../scripts/hak/hakEnv';
|
||||
import { DependencyInfo } from '../../scripts/hak/dep';
|
||||
import HakEnv from "../../scripts/hak/hakEnv";
|
||||
import { DependencyInfo } from "../../scripts/hak/dep";
|
||||
|
||||
export default async function(hakEnv: HakEnv, moduleInfo: DependencyInfo): Promise<void> {
|
||||
export default async function (hakEnv: HakEnv, moduleInfo: DependencyInfo): Promise<void> {
|
||||
if (hakEnv.wantsStaticSqlCipher()) {
|
||||
// of course tcl doesn't have a --version
|
||||
await new Promise<void>((resolve, reject) => {
|
||||
const proc = childProcess.spawn('tclsh', [], {
|
||||
stdio: ['pipe', 'ignore', 'ignore'],
|
||||
const proc = childProcess.spawn("tclsh", [], {
|
||||
stdio: ["pipe", "ignore", "ignore"],
|
||||
});
|
||||
proc.on('exit', (code) => {
|
||||
proc.on("exit", (code) => {
|
||||
if (code !== 0) {
|
||||
reject("Can't find tclsh - have you installed TCL?");
|
||||
} else {
|
||||
@ -39,24 +39,24 @@ export default async function(hakEnv: HakEnv, moduleInfo: DependencyInfo): Promi
|
||||
}
|
||||
|
||||
const tools = [
|
||||
['rustc', '--version'],
|
||||
['python', '--version'], // node-gyp uses python for reasons beyond comprehension
|
||||
["rustc", "--version"],
|
||||
["python", "--version"], // node-gyp uses python for reasons beyond comprehension
|
||||
];
|
||||
if (hakEnv.isWin()) {
|
||||
tools.push(['perl', '--version']); // for openssl configure
|
||||
tools.push(['nasm', '-v']); // for openssl building
|
||||
tools.push(['patch', '--version']); // to patch sqlcipher Makefile.msc
|
||||
tools.push(['nmake', '/?']);
|
||||
tools.push(["perl", "--version"]); // for openssl configure
|
||||
tools.push(["nasm", "-v"]); // for openssl building
|
||||
tools.push(["patch", "--version"]); // to patch sqlcipher Makefile.msc
|
||||
tools.push(["nmake", "/?"]);
|
||||
} else {
|
||||
tools.push(['make', '--version']);
|
||||
tools.push(["make", "--version"]);
|
||||
}
|
||||
|
||||
for (const tool of tools) {
|
||||
await new Promise<void>((resolve, reject) => {
|
||||
const proc = childProcess.spawn(tool[0], tool.slice(1), {
|
||||
stdio: ['ignore'],
|
||||
stdio: ["ignore"],
|
||||
});
|
||||
proc.on('exit', (code) => {
|
||||
proc.on("exit", (code) => {
|
||||
if (code !== 0) {
|
||||
reject("Can't find " + tool);
|
||||
} else {
|
||||
@ -68,19 +68,24 @@ export default async function(hakEnv: HakEnv, moduleInfo: DependencyInfo): Promi
|
||||
|
||||
// Ensure Rust target exists (nb. we avoid depending on rustup)
|
||||
await new Promise((resolve, reject) => {
|
||||
const rustc = childProcess.execFile('rustc', [
|
||||
'--target', hakEnv.getTargetId(), '-o', 'tmp', '-',
|
||||
], (err, out) => {
|
||||
if (err) {
|
||||
reject(
|
||||
"rustc can't build for target " + hakEnv.getTargetId() +
|
||||
": ensure target is installed via `rustup target add " + hakEnv.getTargetId() + "` " +
|
||||
"or your package manager if not using `rustup`",
|
||||
);
|
||||
}
|
||||
fsProm.unlink('tmp').then(resolve);
|
||||
});
|
||||
rustc.stdin!.write('fn main() {}');
|
||||
const rustc = childProcess.execFile(
|
||||
"rustc",
|
||||
["--target", hakEnv.getTargetId(), "-o", "tmp", "-"],
|
||||
(err, out) => {
|
||||
if (err) {
|
||||
reject(
|
||||
"rustc can't build for target " +
|
||||
hakEnv.getTargetId() +
|
||||
": ensure target is installed via `rustup target add " +
|
||||
hakEnv.getTargetId() +
|
||||
"` " +
|
||||
"or your package manager if not using `rustup`",
|
||||
);
|
||||
}
|
||||
fsProm.unlink("tmp").then(resolve);
|
||||
},
|
||||
);
|
||||
rustc.stdin!.write("fn main() {}");
|
||||
rustc.stdin!.end();
|
||||
});
|
||||
}
|
||||
|
@ -14,16 +14,16 @@ See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
import path from 'path';
|
||||
import childProcess from 'child_process';
|
||||
import fs from 'fs';
|
||||
import fsProm from 'fs/promises';
|
||||
import tar from 'tar';
|
||||
import fetch from 'node-fetch';
|
||||
import path from "path";
|
||||
import childProcess from "child_process";
|
||||
import fs from "fs";
|
||||
import fsProm from "fs/promises";
|
||||
import tar from "tar";
|
||||
import fetch from "node-fetch";
|
||||
import { promises as stream } from "stream";
|
||||
|
||||
import HakEnv from '../../scripts/hak/hakEnv';
|
||||
import { DependencyInfo } from '../../scripts/hak/dep';
|
||||
import HakEnv from "../../scripts/hak/hakEnv";
|
||||
import { DependencyInfo } from "../../scripts/hak/dep";
|
||||
|
||||
async function download(url: string, filename: string): Promise<void> {
|
||||
const resp = await fetch(url);
|
||||
@ -32,7 +32,7 @@ async function download(url: string, filename: string): Promise<void> {
|
||||
await stream.pipeline(resp.body, fs.createWriteStream(filename));
|
||||
}
|
||||
|
||||
export default async function(hakEnv: HakEnv, moduleInfo: DependencyInfo): Promise<void> {
|
||||
export default async function (hakEnv: HakEnv, moduleInfo: DependencyInfo): Promise<void> {
|
||||
if (hakEnv.wantsStaticSqlCipher()) {
|
||||
await getSqlCipher(hakEnv, moduleInfo);
|
||||
}
|
||||
@ -83,15 +83,11 @@ async function getSqlCipher(hakEnv: HakEnv, moduleInfo: DependencyInfo): Promise
|
||||
await new Promise<void>((resolve, reject) => {
|
||||
const readStream = fs.createReadStream(patchFile);
|
||||
|
||||
const proc = childProcess.spawn(
|
||||
'patch',
|
||||
['-p1'],
|
||||
{
|
||||
cwd: sqlCipherDir,
|
||||
stdio: ['pipe', 'inherit', 'inherit'],
|
||||
},
|
||||
);
|
||||
proc.on('exit', (code) => {
|
||||
const proc = childProcess.spawn("patch", ["-p1"], {
|
||||
cwd: sqlCipherDir,
|
||||
stdio: ["pipe", "inherit", "inherit"],
|
||||
});
|
||||
proc.on("exit", (code) => {
|
||||
code ? reject(code) : resolve();
|
||||
});
|
||||
readStream.pipe(proc.stdin);
|
||||
|
@ -1,19 +1,14 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"moduleResolution": "node",
|
||||
"esModuleInterop": true,
|
||||
"target": "es2016",
|
||||
"sourceMap": false,
|
||||
"strict": true,
|
||||
"lib": [
|
||||
"es2019",
|
||||
]
|
||||
},
|
||||
"include": [
|
||||
"../scripts/@types/*.d.ts",
|
||||
"./**/*.ts"
|
||||
],
|
||||
"ts-node": {
|
||||
"transpileOnly": true
|
||||
}
|
||||
"compilerOptions": {
|
||||
"moduleResolution": "node",
|
||||
"esModuleInterop": true,
|
||||
"target": "es2016",
|
||||
"sourceMap": false,
|
||||
"strict": true,
|
||||
"lib": ["es2019"]
|
||||
},
|
||||
"include": ["../scripts/@types/*.d.ts", "./**/*.ts"],
|
||||
"ts-node": {
|
||||
"transpileOnly": true
|
||||
}
|
||||
}
|
||||
|
@ -79,8 +79,9 @@
|
||||
"electron-devtools-installer": "^3.1.1",
|
||||
"eslint": "^8.26.0",
|
||||
"eslint-config-google": "^0.14.0",
|
||||
"eslint-config-prettier": "^8.5.0",
|
||||
"eslint-plugin-import": "^2.25.4",
|
||||
"eslint-plugin-matrix-org": "^0.8.0",
|
||||
"eslint-plugin-matrix-org": "^0.9.0",
|
||||
"eslint-plugin-unicorn": "^45.0.0",
|
||||
"expect-playwright": "^0.8.0",
|
||||
"find-npm-prefix": "^1.0.2",
|
||||
|
@ -1,154 +1,139 @@
|
||||
@font-face {
|
||||
font-family: 'Inter';
|
||||
font-style: normal;
|
||||
font-weight: 100;
|
||||
font-display: swap;
|
||||
src: url("Inter-Thin.woff2?v=3.19") format("woff2"),
|
||||
url("Inter-Thin.woff?v=3.19") format("woff");
|
||||
font-family: "Inter";
|
||||
font-style: normal;
|
||||
font-weight: 100;
|
||||
font-display: swap;
|
||||
src: url("Inter-Thin.woff2?v=3.19") format("woff2"), url("Inter-Thin.woff?v=3.19") format("woff");
|
||||
}
|
||||
@font-face {
|
||||
font-family: 'Inter';
|
||||
font-style: italic;
|
||||
font-weight: 100;
|
||||
font-display: swap;
|
||||
src: url("Inter-ThinItalic.woff2?v=3.19") format("woff2"),
|
||||
url("Inter-ThinItalic.woff?v=3.19") format("woff");
|
||||
font-family: "Inter";
|
||||
font-style: italic;
|
||||
font-weight: 100;
|
||||
font-display: swap;
|
||||
src: url("Inter-ThinItalic.woff2?v=3.19") format("woff2"), url("Inter-ThinItalic.woff?v=3.19") format("woff");
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'Inter';
|
||||
font-style: normal;
|
||||
font-weight: 200;
|
||||
font-display: swap;
|
||||
src: url("Inter-ExtraLight.woff2?v=3.19") format("woff2"),
|
||||
url("Inter-ExtraLight.woff?v=3.19") format("woff");
|
||||
font-family: "Inter";
|
||||
font-style: normal;
|
||||
font-weight: 200;
|
||||
font-display: swap;
|
||||
src: url("Inter-ExtraLight.woff2?v=3.19") format("woff2"), url("Inter-ExtraLight.woff?v=3.19") format("woff");
|
||||
}
|
||||
@font-face {
|
||||
font-family: 'Inter';
|
||||
font-style: italic;
|
||||
font-weight: 200;
|
||||
font-display: swap;
|
||||
src: url("Inter-ExtraLightItalic.woff2?v=3.19") format("woff2"),
|
||||
url("Inter-ExtraLightItalic.woff?v=3.19") format("woff");
|
||||
font-family: "Inter";
|
||||
font-style: italic;
|
||||
font-weight: 200;
|
||||
font-display: swap;
|
||||
src: url("Inter-ExtraLightItalic.woff2?v=3.19") format("woff2"),
|
||||
url("Inter-ExtraLightItalic.woff?v=3.19") format("woff");
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'Inter';
|
||||
font-style: normal;
|
||||
font-weight: 300;
|
||||
font-display: swap;
|
||||
src: url("Inter-Light.woff2?v=3.19") format("woff2"),
|
||||
url("Inter-Light.woff?v=3.19") format("woff");
|
||||
font-family: "Inter";
|
||||
font-style: normal;
|
||||
font-weight: 300;
|
||||
font-display: swap;
|
||||
src: url("Inter-Light.woff2?v=3.19") format("woff2"), url("Inter-Light.woff?v=3.19") format("woff");
|
||||
}
|
||||
@font-face {
|
||||
font-family: 'Inter';
|
||||
font-style: italic;
|
||||
font-weight: 300;
|
||||
font-display: swap;
|
||||
src: url("Inter-LightItalic.woff2?v=3.19") format("woff2"),
|
||||
url("Inter-LightItalic.woff?v=3.19") format("woff");
|
||||
font-family: "Inter";
|
||||
font-style: italic;
|
||||
font-weight: 300;
|
||||
font-display: swap;
|
||||
src: url("Inter-LightItalic.woff2?v=3.19") format("woff2"), url("Inter-LightItalic.woff?v=3.19") format("woff");
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'Inter';
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
font-display: swap;
|
||||
src: url("Inter-Regular.woff2?v=3.19") format("woff2"),
|
||||
url("Inter-Regular.woff?v=3.19") format("woff");
|
||||
font-family: "Inter";
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
font-display: swap;
|
||||
src: url("Inter-Regular.woff2?v=3.19") format("woff2"), url("Inter-Regular.woff?v=3.19") format("woff");
|
||||
}
|
||||
@font-face {
|
||||
font-family: 'Inter';
|
||||
font-style: italic;
|
||||
font-weight: 400;
|
||||
font-display: swap;
|
||||
src: url("Inter-Italic.woff2?v=3.19") format("woff2"),
|
||||
url("Inter-Italic.woff?v=3.19") format("woff");
|
||||
font-family: "Inter";
|
||||
font-style: italic;
|
||||
font-weight: 400;
|
||||
font-display: swap;
|
||||
src: url("Inter-Italic.woff2?v=3.19") format("woff2"), url("Inter-Italic.woff?v=3.19") format("woff");
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'Inter';
|
||||
font-style: normal;
|
||||
font-weight: 500;
|
||||
font-display: swap;
|
||||
src: url("Inter-Medium.woff2?v=3.19") format("woff2"),
|
||||
url("Inter-Medium.woff?v=3.19") format("woff");
|
||||
font-family: "Inter";
|
||||
font-style: normal;
|
||||
font-weight: 500;
|
||||
font-display: swap;
|
||||
src: url("Inter-Medium.woff2?v=3.19") format("woff2"), url("Inter-Medium.woff?v=3.19") format("woff");
|
||||
}
|
||||
@font-face {
|
||||
font-family: 'Inter';
|
||||
font-style: italic;
|
||||
font-weight: 500;
|
||||
font-display: swap;
|
||||
src: url("Inter-MediumItalic.woff2?v=3.19") format("woff2"),
|
||||
url("Inter-MediumItalic.woff?v=3.19") format("woff");
|
||||
font-family: "Inter";
|
||||
font-style: italic;
|
||||
font-weight: 500;
|
||||
font-display: swap;
|
||||
src: url("Inter-MediumItalic.woff2?v=3.19") format("woff2"), url("Inter-MediumItalic.woff?v=3.19") format("woff");
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'Inter';
|
||||
font-style: normal;
|
||||
font-weight: 600;
|
||||
font-display: swap;
|
||||
src: url("Inter-SemiBold.woff2?v=3.19") format("woff2"),
|
||||
url("Inter-SemiBold.woff?v=3.19") format("woff");
|
||||
font-family: "Inter";
|
||||
font-style: normal;
|
||||
font-weight: 600;
|
||||
font-display: swap;
|
||||
src: url("Inter-SemiBold.woff2?v=3.19") format("woff2"), url("Inter-SemiBold.woff?v=3.19") format("woff");
|
||||
}
|
||||
@font-face {
|
||||
font-family: 'Inter';
|
||||
font-style: italic;
|
||||
font-weight: 600;
|
||||
font-display: swap;
|
||||
src: url("Inter-SemiBoldItalic.woff2?v=3.19") format("woff2"),
|
||||
url("Inter-SemiBoldItalic.woff?v=3.19") format("woff");
|
||||
font-family: "Inter";
|
||||
font-style: italic;
|
||||
font-weight: 600;
|
||||
font-display: swap;
|
||||
src: url("Inter-SemiBoldItalic.woff2?v=3.19") format("woff2"),
|
||||
url("Inter-SemiBoldItalic.woff?v=3.19") format("woff");
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'Inter';
|
||||
font-style: normal;
|
||||
font-weight: 700;
|
||||
font-display: swap;
|
||||
src: url("Inter-Bold.woff2?v=3.19") format("woff2"),
|
||||
url("Inter-Bold.woff?v=3.19") format("woff");
|
||||
font-family: "Inter";
|
||||
font-style: normal;
|
||||
font-weight: 700;
|
||||
font-display: swap;
|
||||
src: url("Inter-Bold.woff2?v=3.19") format("woff2"), url("Inter-Bold.woff?v=3.19") format("woff");
|
||||
}
|
||||
@font-face {
|
||||
font-family: 'Inter';
|
||||
font-style: italic;
|
||||
font-weight: 700;
|
||||
font-display: swap;
|
||||
src: url("Inter-BoldItalic.woff2?v=3.19") format("woff2"),
|
||||
url("Inter-BoldItalic.woff?v=3.19") format("woff");
|
||||
font-family: "Inter";
|
||||
font-style: italic;
|
||||
font-weight: 700;
|
||||
font-display: swap;
|
||||
src: url("Inter-BoldItalic.woff2?v=3.19") format("woff2"), url("Inter-BoldItalic.woff?v=3.19") format("woff");
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'Inter';
|
||||
font-style: normal;
|
||||
font-weight: 800;
|
||||
font-display: swap;
|
||||
src: url("Inter-ExtraBold.woff2?v=3.19") format("woff2"),
|
||||
url("Inter-ExtraBold.woff?v=3.19") format("woff");
|
||||
font-family: "Inter";
|
||||
font-style: normal;
|
||||
font-weight: 800;
|
||||
font-display: swap;
|
||||
src: url("Inter-ExtraBold.woff2?v=3.19") format("woff2"), url("Inter-ExtraBold.woff?v=3.19") format("woff");
|
||||
}
|
||||
@font-face {
|
||||
font-family: 'Inter';
|
||||
font-style: italic;
|
||||
font-weight: 800;
|
||||
font-display: swap;
|
||||
src: url("Inter-ExtraBoldItalic.woff2?v=3.19") format("woff2"),
|
||||
url("Inter-ExtraBoldItalic.woff?v=3.19") format("woff");
|
||||
font-family: "Inter";
|
||||
font-style: italic;
|
||||
font-weight: 800;
|
||||
font-display: swap;
|
||||
src: url("Inter-ExtraBoldItalic.woff2?v=3.19") format("woff2"),
|
||||
url("Inter-ExtraBoldItalic.woff?v=3.19") format("woff");
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'Inter';
|
||||
font-style: normal;
|
||||
font-weight: 900;
|
||||
font-display: swap;
|
||||
src: url("Inter-Black.woff2?v=3.19") format("woff2"),
|
||||
url("Inter-Black.woff?v=3.19") format("woff");
|
||||
font-family: "Inter";
|
||||
font-style: normal;
|
||||
font-weight: 900;
|
||||
font-display: swap;
|
||||
src: url("Inter-Black.woff2?v=3.19") format("woff2"), url("Inter-Black.woff?v=3.19") format("woff");
|
||||
}
|
||||
@font-face {
|
||||
font-family: 'Inter';
|
||||
font-style: italic;
|
||||
font-weight: 900;
|
||||
font-display: swap;
|
||||
src: url("Inter-BlackItalic.woff2?v=3.19") format("woff2"),
|
||||
url("Inter-BlackItalic.woff?v=3.19") format("woff");
|
||||
font-family: "Inter";
|
||||
font-style: italic;
|
||||
font-weight: 900;
|
||||
font-display: swap;
|
||||
src: url("Inter-BlackItalic.woff2?v=3.19") format("woff2"), url("Inter-BlackItalic.woff?v=3.19") format("woff");
|
||||
}
|
||||
|
||||
/* -------------------------------------------------------
|
||||
@ -161,21 +146,20 @@ Usage:
|
||||
}
|
||||
*/
|
||||
@font-face {
|
||||
font-family: 'Inter var';
|
||||
font-weight: 100 900;
|
||||
font-display: swap;
|
||||
font-style: normal;
|
||||
src: url("Inter-roman.var.woff2?v=3.19") format("woff2");
|
||||
font-family: "Inter var";
|
||||
font-weight: 100 900;
|
||||
font-display: swap;
|
||||
font-style: normal;
|
||||
src: url("Inter-roman.var.woff2?v=3.19") format("woff2");
|
||||
}
|
||||
@font-face {
|
||||
font-family: 'Inter var';
|
||||
font-weight: 100 900;
|
||||
font-display: swap;
|
||||
font-style: italic;
|
||||
src: url("Inter-italic.var.woff2?v=3.19") format("woff2");
|
||||
font-family: "Inter var";
|
||||
font-weight: 100 900;
|
||||
font-display: swap;
|
||||
font-style: italic;
|
||||
src: url("Inter-italic.var.woff2?v=3.19") format("woff2");
|
||||
}
|
||||
|
||||
|
||||
/* --------------------------------------------------------------------------
|
||||
[EXPERIMENTAL] Multi-axis, single variable font.
|
||||
|
||||
@ -190,9 +174,9 @@ explicitly, e.g.
|
||||
|
||||
*/
|
||||
@font-face {
|
||||
font-family: 'Inter var experimental';
|
||||
font-weight: 100 900;
|
||||
font-display: swap;
|
||||
font-style: oblique 0deg 10deg;
|
||||
src: url("Inter.var.woff2?v=3.19") format("woff2");
|
||||
font-family: "Inter var experimental";
|
||||
font-weight: 100 900;
|
||||
font-display: swap;
|
||||
font-style: oblique 0deg 10deg;
|
||||
src: url("Inter.var.woff2?v=3.19") format("woff2");
|
||||
}
|
||||
|
@ -5,13 +5,16 @@
|
||||
* hosted on GitHub, https://GitHub.com/Naereen/Nginx-Fancyindex-Theme
|
||||
*/
|
||||
|
||||
@import url('./fonts/inter.css');
|
||||
@import url("./fonts/inter.css");
|
||||
|
||||
* { font-family: 'Inter', sans-serif; }
|
||||
@supports (font-variation-settings: normal) {
|
||||
* { font-family: 'Inter var', sans-serif; }
|
||||
* {
|
||||
font-family: "Inter", sans-serif;
|
||||
}
|
||||
@supports (font-variation-settings: normal) {
|
||||
* {
|
||||
font-family: "Inter var", sans-serif;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
* {
|
||||
margin: 0;
|
||||
@ -22,14 +25,14 @@
|
||||
}
|
||||
|
||||
html {
|
||||
color: #17191C;
|
||||
color: #17191c;
|
||||
font-weight: 400;
|
||||
font-size: 1em;
|
||||
line-height: 1.6em;
|
||||
}
|
||||
|
||||
body {
|
||||
background-color: #F4F6FA;
|
||||
background-color: #f4f6fa;
|
||||
margin: 0 auto;
|
||||
padding: 100px 20px 20px;
|
||||
max-width: 800px;
|
||||
@ -52,9 +55,11 @@ a {
|
||||
text-decoration: underline;
|
||||
}
|
||||
a:hover {
|
||||
color: #0DBD8B;
|
||||
color: #0dbd8b;
|
||||
}
|
||||
a.clear, a.clear:link, a.clear:visited {
|
||||
a.clear,
|
||||
a.clear:link,
|
||||
a.clear:visited {
|
||||
color: #666;
|
||||
padding: 2px 0;
|
||||
font-weight: 400;
|
||||
@ -74,7 +79,7 @@ a.clear, a.clear:link, a.clear:visited {
|
||||
input {
|
||||
vertical-align: middle;
|
||||
*overflow: visible;
|
||||
font-family: 'Open Sans', sans-serif;
|
||||
font-family: "Open Sans", sans-serif;
|
||||
font-weight: 300;
|
||||
display: inline-block;
|
||||
height: 20px;
|
||||
@ -89,20 +94,20 @@ input {
|
||||
width: 196px;
|
||||
background-color: #fff;
|
||||
border: 1px solid #ccc;
|
||||
-webkit-box-shadow: inset 0 1px 1px rgba(0,0,0,0.075);
|
||||
-moz-box-shadow: inset 0 1px 1px rgba(0,0,0,0.075);
|
||||
box-shadow: inset 0 1px 1px rgba(0,0,0,0.075);
|
||||
-webkit-transition: border linear .2s,box-shadow linear .2s;
|
||||
-moz-transition: border linear .2s,box-shadow linear .2s;
|
||||
-o-transition: border linear .2s,box-shadow linear .2s;
|
||||
transition: border linear .2s,box-shadow linear .2s;
|
||||
-webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
|
||||
-moz-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
|
||||
box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
|
||||
-webkit-transition: border linear 0.2s, box-shadow linear 0.2s;
|
||||
-moz-transition: border linear 0.2s, box-shadow linear 0.2s;
|
||||
-o-transition: border linear 0.2s, box-shadow linear 0.2s;
|
||||
transition: border linear 0.2s, box-shadow linear 0.2s;
|
||||
}
|
||||
input:focus {
|
||||
outline: 0;
|
||||
border-color: rgba(0,0,0,0.8);
|
||||
-webkit-box-shadow: inset 0 1px 1px rgba(0,0,0,0.075),0 0 8px rgba(0,0,0,0.6);
|
||||
-moz-box-shadow: inset 0 1px 1px rgba(0,0,0,0.075),0 0 8px rgba(0,0,0,0.6);
|
||||
box-shadow: inset 0 1px 1px rgba(0,0,0,0.075),0 0 8px rgba(0,0,0,0.6);
|
||||
border-color: rgba(0, 0, 0, 0.8);
|
||||
-webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 8px rgba(0, 0, 0, 0.6);
|
||||
-moz-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 8px rgba(0, 0, 0, 0.6);
|
||||
box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 8px rgba(0, 0, 0, 0.6);
|
||||
}
|
||||
input::-moz-focus-inner {
|
||||
padding: 0;
|
||||
@ -128,13 +133,12 @@ tr td:first-of-type {
|
||||
padding-right: 10px;
|
||||
}
|
||||
tr.parent a {
|
||||
color: #9099A3;
|
||||
color: #9099a3;
|
||||
}
|
||||
|
||||
th {
|
||||
|
||||
text-align: left;
|
||||
font-size: .75em;
|
||||
font-size: 0.75em;
|
||||
padding-right: 20px;
|
||||
}
|
||||
th + th {
|
||||
@ -157,7 +161,8 @@ td {
|
||||
-o-transition: background 300ms ease-in;
|
||||
transition: background 300ms ease-in;
|
||||
}
|
||||
td:last-child,th:last-child {
|
||||
td:last-child,
|
||||
th:last-child {
|
||||
text-align: right;
|
||||
padding-right: 0;
|
||||
}
|
||||
@ -187,11 +192,11 @@ td a {
|
||||
padding: 0;
|
||||
list-style: none;
|
||||
overflow: hidden;
|
||||
background-color: #FFF;
|
||||
background-color: #fff;
|
||||
}
|
||||
|
||||
.nav li a {
|
||||
color: #17191C;
|
||||
color: #17191c;
|
||||
display: block;
|
||||
padding: 20px 20px;
|
||||
text-decoration: none;
|
||||
@ -199,7 +204,7 @@ td a {
|
||||
|
||||
.nav li a:hover,
|
||||
.nav .menu-btn:hover {
|
||||
color: #0DBD8B;
|
||||
color: #0dbd8b;
|
||||
}
|
||||
|
||||
.nav .logo {
|
||||
@ -216,7 +221,7 @@ td a {
|
||||
.nav .menu {
|
||||
clear: both;
|
||||
max-height: 0;
|
||||
transition: max-height .2s ease-out;
|
||||
transition: max-height 0.2s ease-out;
|
||||
}
|
||||
|
||||
/* menu icon */
|
||||
@ -237,18 +242,18 @@ td a {
|
||||
margin-top: 6px;
|
||||
margin-bottom: 4px;
|
||||
position: relative;
|
||||
transition: background .2s ease-out;
|
||||
transition: background 0.2s ease-out;
|
||||
width: 18px;
|
||||
}
|
||||
|
||||
.nav .menu-icon .navicon:before,
|
||||
.nav .menu-icon .navicon:after {
|
||||
background: #17191C;
|
||||
content: '';
|
||||
background: #17191c;
|
||||
content: "";
|
||||
display: block;
|
||||
height: 100%;
|
||||
position: absolute;
|
||||
transition: all .2s ease-out;
|
||||
transition: all 0.2s ease-out;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
@ -303,13 +308,13 @@ td a {
|
||||
margin-left: 20px;
|
||||
padding: 12px 20px;
|
||||
border-radius: 100px;
|
||||
background-color: #0DBD8B;
|
||||
color:#FFF;
|
||||
background-color: #0dbd8b;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.nav .primary:hover {
|
||||
background-color: #099970;
|
||||
color:#FFF;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.nav .menu {
|
||||
@ -324,10 +329,10 @@ td a {
|
||||
|
||||
footer {
|
||||
margin-top: 40px;
|
||||
font-size:0.8em;
|
||||
text-align:center;
|
||||
font-size: 0.8em;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
footer a {
|
||||
color:#03b381;
|
||||
color: #03b381;
|
||||
}
|
||||
|
@ -20,16 +20,16 @@ function errCheck(err?: Error): void {
|
||||
}
|
||||
|
||||
const I18N_BASE_PATH = "src/i18n/strings/";
|
||||
const INCLUDE_LANGS = fs.readdirSync(I18N_BASE_PATH).filter(fn => fn.endsWith(".json"));
|
||||
const INCLUDE_LANGS = fs.readdirSync(I18N_BASE_PATH).filter((fn) => fn.endsWith(".json"));
|
||||
|
||||
// Ensure lib, lib/i18n and lib/i18n/strings all exist
|
||||
fs.mkdirSync('lib/i18n/strings', { recursive: true });
|
||||
fs.mkdirSync("lib/i18n/strings", { recursive: true });
|
||||
|
||||
type Translations = Record<string, Record<string, string> | string>;
|
||||
|
||||
function genLangFile(file: string, dest: string): void {
|
||||
const inTrs: Record<string, string> = {};
|
||||
[file].forEach(function(f) {
|
||||
[file].forEach(function (f) {
|
||||
if (fs.existsSync(f)) {
|
||||
try {
|
||||
Object.assign(inTrs, JSON.parse(fs.readFileSync(f).toString()));
|
||||
@ -68,7 +68,7 @@ function weblateToCounterpart(inTrs: Record<string, string>): Translations {
|
||||
const outTrs: Translations = {};
|
||||
|
||||
for (const key of Object.keys(inTrs)) {
|
||||
const keyParts = key.split('|', 2);
|
||||
const keyParts = key.split("|", 2);
|
||||
if (keyParts.length === 2) {
|
||||
let obj = outTrs[keyParts[0]];
|
||||
if (obj === undefined) {
|
||||
@ -77,7 +77,7 @@ function weblateToCounterpart(inTrs: Record<string, string>): Translations {
|
||||
// This is a transitional edge case if a string went from singular to pluralised and both still remain
|
||||
// in the translation json file. Use the singular translation as `other` and merge pluralisation atop.
|
||||
obj = outTrs[keyParts[0]] = {
|
||||
"other": inTrs[key],
|
||||
other: inTrs[key],
|
||||
};
|
||||
console.warn("Found entry in i18n file in both singular and pluralised form", keyParts[0]);
|
||||
}
|
||||
@ -108,10 +108,7 @@ function watchLanguage(file: string, dest: string): void {
|
||||
}, 500);
|
||||
};
|
||||
|
||||
chokidar.watch(file)
|
||||
.on('add', makeLang)
|
||||
.on('change', makeLang)
|
||||
.on('error', errCheck);
|
||||
chokidar.watch(file).on("add", makeLang).on("change", makeLang).on("error", errCheck);
|
||||
}
|
||||
|
||||
// language resources
|
||||
@ -121,5 +118,5 @@ INCLUDE_LANGS.forEach((file): void => {
|
||||
}, {});
|
||||
|
||||
if (watch) {
|
||||
INCLUDE_LANGS.forEach(file => watchLanguage(I18N_BASE_PATH + file, I18N_DEST));
|
||||
INCLUDE_LANGS.forEach((file) => watchLanguage(I18N_BASE_PATH + file, I18N_DEST));
|
||||
}
|
||||
|
@ -1,7 +1,7 @@
|
||||
const fsProm = require('fs').promises;
|
||||
const path = require('path');
|
||||
const fsProm = require("fs").promises;
|
||||
const path = require("path");
|
||||
|
||||
exports.default = async function(context) {
|
||||
exports.default = async function (context) {
|
||||
const { electronPlatformName, appOutDir } = context;
|
||||
|
||||
// Squirrel windows will try to relaunch the app using an executable of the same name as
|
||||
@ -9,7 +9,7 @@ exports.default = async function(context) {
|
||||
// We add a fake Riot.exe that it can run which runs the real one.
|
||||
// This also gets signed automatically, presumably because electron-build just looks for all
|
||||
// exe files and signs them all...
|
||||
if (electronPlatformName === 'win32') {
|
||||
await fsProm.copyFile('build/rebrand_stub/rebrand_stub.exe', path.join(appOutDir, "Riot.exe"));
|
||||
if (electronPlatformName === "win32") {
|
||||
await fsProm.copyFile("build/rebrand_stub/rebrand_stub.exe", path.join(appOutDir, "Riot.exe"));
|
||||
}
|
||||
};
|
||||
|
@ -1,11 +1,11 @@
|
||||
const { notarize } = require('@electron/notarize');
|
||||
const { notarize } = require("@electron/notarize");
|
||||
|
||||
let warned = false;
|
||||
exports.default = async function(context) {
|
||||
exports.default = async function (context) {
|
||||
const { electronPlatformName, appOutDir } = context;
|
||||
const appId = context.packager.info.appInfo.id;
|
||||
|
||||
if (electronPlatformName === 'darwin') {
|
||||
if (electronPlatformName === "darwin") {
|
||||
const appName = context.packager.appInfo.productFilename;
|
||||
|
||||
const keychainProfile = process.env.NOTARIZE_KEYCHAIN_PROFILE;
|
||||
|
@ -1,4 +1,4 @@
|
||||
const { execFile } = require('child_process');
|
||||
const { execFile } = require("child_process");
|
||||
|
||||
// Loosely based on computeSignToolArgs from app-builder-lib/src/codeSign/windowsCodeSign.ts
|
||||
function computeSignToolArgs(options, keyContainer) {
|
||||
@ -8,15 +8,15 @@ function computeSignToolArgs(options, keyContainer) {
|
||||
const timestampingServiceUrl = options.options.timeStampServer || "http://timestamp.digicert.com";
|
||||
args.push(
|
||||
options.isNest || options.hash === "sha256" ? "/tr" : "/t",
|
||||
options.isNest || options.hash === "sha256" ? (
|
||||
options.options.rfc3161TimeStampServer || "http://timestamp.comodoca.com/rfc3161"
|
||||
) : timestampingServiceUrl,
|
||||
options.isNest || options.hash === "sha256"
|
||||
? options.options.rfc3161TimeStampServer || "http://timestamp.comodoca.com/rfc3161"
|
||||
: timestampingServiceUrl,
|
||||
);
|
||||
}
|
||||
|
||||
args.push('/kc', keyContainer);
|
||||
args.push("/kc", keyContainer);
|
||||
// To use the hardware token (this should probably be less hardcoded)
|
||||
args.push('/csp', 'eToken Base Cryptographic Provider');
|
||||
args.push("/csp", "eToken Base Cryptographic Provider");
|
||||
// The certificate file. Somehow this appears to be the only way to specify
|
||||
// the cert that works. If you specify the subject name or hash, it will
|
||||
// say it can't associate the private key to the certificate.
|
||||
@ -24,7 +24,7 @@ function computeSignToolArgs(options, keyContainer) {
|
||||
// so we don't have to hard-code this here
|
||||
// fwiw https://stackoverflow.com/questions/17927895/automate-extended-validation-ev-code-signing
|
||||
// is about the most useful resource on automating code signing...
|
||||
args.push('/f', 'element.io\\New_Vector_Ltd.pem');
|
||||
args.push("/f", "element.io\\New_Vector_Ltd.pem");
|
||||
|
||||
if (options.hash !== "sha1") {
|
||||
args.push("/fd", options.hash);
|
||||
@ -35,7 +35,7 @@ function computeSignToolArgs(options, keyContainer) {
|
||||
|
||||
// msi does not support dual-signing
|
||||
if (options.isNest) {
|
||||
args.push("/as");
|
||||
args.push("/as");
|
||||
}
|
||||
|
||||
// https://github.com/electron-userland/electron-builder/issues/2875#issuecomment-387233610
|
||||
@ -47,15 +47,15 @@ function computeSignToolArgs(options, keyContainer) {
|
||||
}
|
||||
|
||||
let warned = false;
|
||||
exports.default = async function(options) {
|
||||
exports.default = async function (options) {
|
||||
const keyContainer = process.env.SIGNING_KEY_CONTAINER;
|
||||
if (keyContainer === undefined) {
|
||||
if (!warned) {
|
||||
console.warn(
|
||||
"!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!\n" +
|
||||
"! Skipping Windows signing. !\n" +
|
||||
"! SIGNING_KEY_CONTAINER not defined. !\n" +
|
||||
"!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!",
|
||||
"! Skipping Windows signing. !\n" +
|
||||
"! SIGNING_KEY_CONTAINER not defined. !\n" +
|
||||
"!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!",
|
||||
);
|
||||
warned = true;
|
||||
}
|
||||
@ -63,9 +63,9 @@ exports.default = async function(options) {
|
||||
}
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
const args = ['sign'].concat(computeSignToolArgs(options, keyContainer));
|
||||
const args = ["sign"].concat(computeSignToolArgs(options, keyContainer));
|
||||
|
||||
execFile('signtool', args, {}, (error, stdout) => {
|
||||
execFile("signtool", args, {}, (error, stdout) => {
|
||||
if (error) {
|
||||
console.error("signtool failed with code " + error);
|
||||
reject("signtool failed with code " + error);
|
||||
|
@ -14,7 +14,7 @@ import { setPackageVersion } from "./set-version";
|
||||
const PUB_KEY_URL = "https://packages.riot.im/element-release-key.asc";
|
||||
const PACKAGE_URL_PREFIX = "https://github.com/vector-im/element-web/releases/download/";
|
||||
const DEVELOP_TGZ_URL = "https://develop.element.io/develop.tar.gz";
|
||||
const ASAR_PATH = 'webapp.asar';
|
||||
const ASAR_PATH = "webapp.asar";
|
||||
|
||||
async function downloadToFile(url: string, filename: string): Promise<void> {
|
||||
console.log("Downloading " + url + "...");
|
||||
@ -35,7 +35,7 @@ async function downloadToFile(url: string, filename: string): Promise<void> {
|
||||
|
||||
async function verifyFile(filename: string): Promise<void> {
|
||||
return new Promise<void>((resolve, reject) => {
|
||||
childProcess.execFile('gpg', ['--verify', filename + '.asc', filename], (error) => {
|
||||
childProcess.execFile("gpg", ["--verify", filename + ".asc", filename], (error) => {
|
||||
if (error) {
|
||||
reject(error);
|
||||
} else {
|
||||
@ -48,8 +48,8 @@ async function verifyFile(filename: string): Promise<void> {
|
||||
async function main(): Promise<number | undefined> {
|
||||
let verify = true;
|
||||
let importkey = false;
|
||||
let pkgDir = 'packages';
|
||||
let deployDir = 'deploys';
|
||||
let pkgDir = "packages";
|
||||
let deployDir = "deploys";
|
||||
let cfgDir: string | undefined;
|
||||
let targetVersion: string | undefined;
|
||||
let filename: string | undefined;
|
||||
@ -58,22 +58,22 @@ async function main(): Promise<number | undefined> {
|
||||
|
||||
while (process.argv.length > 2) {
|
||||
switch (process.argv[2]) {
|
||||
case '--noverify':
|
||||
case "--noverify":
|
||||
verify = false;
|
||||
break;
|
||||
case '--importkey':
|
||||
case "--importkey":
|
||||
importkey = true;
|
||||
break;
|
||||
case '--packages':
|
||||
case "--packages":
|
||||
process.argv.shift();
|
||||
pkgDir = process.argv[2];
|
||||
break;
|
||||
case '--deploys':
|
||||
case "--deploys":
|
||||
process.argv.shift();
|
||||
deployDir = process.argv[2];
|
||||
break;
|
||||
case '--cfgdir':
|
||||
case '-d':
|
||||
case "--cfgdir":
|
||||
case "-d":
|
||||
process.argv.shift();
|
||||
cfgDir = process.argv[2];
|
||||
break;
|
||||
@ -84,13 +84,13 @@ async function main(): Promise<number | undefined> {
|
||||
}
|
||||
|
||||
if (targetVersion === undefined) {
|
||||
targetVersion = 'v' + riotDesktopPackageJson.version;
|
||||
} else if (targetVersion !== 'develop') {
|
||||
targetVersion = "v" + riotDesktopPackageJson.version;
|
||||
} else if (targetVersion !== "develop") {
|
||||
setVersion = true; // version was specified
|
||||
}
|
||||
|
||||
if (targetVersion === 'develop') {
|
||||
filename = 'develop.tar.gz';
|
||||
if (targetVersion === "develop") {
|
||||
filename = "develop.tar.gz";
|
||||
url = DEVELOP_TGZ_URL;
|
||||
verify = false; // develop builds aren't signed
|
||||
} else if (targetVersion.includes("://")) {
|
||||
@ -99,11 +99,11 @@ async function main(): Promise<number | undefined> {
|
||||
verify = false; // manually verified
|
||||
} else {
|
||||
filename = `element-${targetVersion}.tar.gz`;
|
||||
url = PACKAGE_URL_PREFIX + targetVersion + '/' + filename;
|
||||
url = PACKAGE_URL_PREFIX + targetVersion + "/" + filename;
|
||||
}
|
||||
|
||||
const haveGpg = await new Promise<boolean>((resolve) => {
|
||||
childProcess.execFile('gpg', ['--version'], (error) => {
|
||||
childProcess.execFile("gpg", ["--version"], (error) => {
|
||||
resolve(!error);
|
||||
});
|
||||
});
|
||||
@ -115,7 +115,7 @@ async function main(): Promise<number | undefined> {
|
||||
}
|
||||
|
||||
await new Promise<boolean>((resolve) => {
|
||||
const gpgProc = childProcess.execFile('gpg', ['--import'], (error) => {
|
||||
const gpgProc = childProcess.execFile("gpg", ["--import"], (error) => {
|
||||
if (error) {
|
||||
console.log("Failed to import key", error);
|
||||
} else {
|
||||
@ -123,7 +123,7 @@ async function main(): Promise<number | undefined> {
|
||||
}
|
||||
resolve(!error);
|
||||
});
|
||||
fetch(PUB_KEY_URL).then(resp => {
|
||||
fetch(PUB_KEY_URL).then((resp) => {
|
||||
stream.pipeline(resp.body, gpgProc.stdin!);
|
||||
});
|
||||
});
|
||||
@ -143,13 +143,12 @@ async function main(): Promise<number | undefined> {
|
||||
}
|
||||
|
||||
let haveDeploy = false;
|
||||
let expectedDeployDir = path.join(deployDir, path.basename(filename).replace(/\.tar\.gz/, ''));
|
||||
let expectedDeployDir = path.join(deployDir, path.basename(filename).replace(/\.tar\.gz/, ""));
|
||||
try {
|
||||
await fs.opendir(expectedDeployDir);
|
||||
console.log(expectedDeployDir + "already exists");
|
||||
haveDeploy = true;
|
||||
} catch (e) {
|
||||
}
|
||||
} catch (e) {}
|
||||
|
||||
if (!haveDeploy) {
|
||||
const outPath = path.join(pkgDir, filename);
|
||||
@ -167,11 +166,11 @@ async function main(): Promise<number | undefined> {
|
||||
|
||||
if (verify) {
|
||||
try {
|
||||
await fs.stat(outPath+'.asc');
|
||||
await fs.stat(outPath + ".asc");
|
||||
console.log("Already have " + filename + ".asc: not redownloading");
|
||||
} catch (e) {
|
||||
try {
|
||||
await downloadToFile(url + '.asc', outPath + '.asc');
|
||||
await downloadToFile(url + ".asc", outPath + ".asc");
|
||||
} catch (e) {
|
||||
console.log("Failed to download " + url, e);
|
||||
return 1;
|
||||
@ -192,7 +191,7 @@ async function main(): Promise<number | undefined> {
|
||||
await tar.x({
|
||||
file: outPath,
|
||||
cwd: deployDir,
|
||||
onentry: entry => {
|
||||
onentry: (entry) => {
|
||||
// Find the appropriate extraction path, only needed for `develop` where the dir name is unknown
|
||||
if (entry.type === "Directory" && !path.join(deployDir, entry.path).startsWith(expectedDeployDir)) {
|
||||
expectedDeployDir = path.join(deployDir, entry.path);
|
||||
@ -205,13 +204,12 @@ async function main(): Promise<number | undefined> {
|
||||
await fs.stat(ASAR_PATH);
|
||||
console.log(ASAR_PATH + " already present: removing");
|
||||
await fs.unlink(ASAR_PATH);
|
||||
} catch (e) {
|
||||
}
|
||||
} catch (e) {}
|
||||
|
||||
if (cfgDir.length) {
|
||||
const configJsonSource = path.join(cfgDir, 'config.json');
|
||||
const configJsonDest = path.join(expectedDeployDir, 'config.json');
|
||||
console.log(configJsonSource + ' -> ' + configJsonDest);
|
||||
const configJsonSource = path.join(cfgDir, "config.json");
|
||||
const configJsonDest = path.join(expectedDeployDir, "config.json");
|
||||
console.log(configJsonSource + " -> " + configJsonDest);
|
||||
await fs.copyFile(configJsonSource, configJsonDest);
|
||||
} else {
|
||||
console.log("Skipping config file");
|
||||
@ -229,9 +227,11 @@ async function main(): Promise<number | undefined> {
|
||||
console.log("Done!");
|
||||
}
|
||||
|
||||
main().then((ret) => {
|
||||
process.exit(ret);
|
||||
}).catch(e => {
|
||||
console.error(e);
|
||||
process.exit(1);
|
||||
});
|
||||
main()
|
||||
.then((ret) => {
|
||||
process.exit(ret);
|
||||
})
|
||||
.catch((e) => {
|
||||
console.error(e);
|
||||
process.exit(1);
|
||||
});
|
||||
|
@ -84,21 +84,21 @@ function humanFileSize(bytes: number, si = false, dp = 1): string {
|
||||
const thresh = si ? 1000 : 1024;
|
||||
|
||||
if (Math.abs(bytes) < thresh) {
|
||||
return bytes + ' B';
|
||||
return bytes + " B";
|
||||
}
|
||||
|
||||
const units = si
|
||||
? ['kB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB']
|
||||
: ['KiB', 'MiB', 'GiB', 'TiB', 'PiB', 'EiB', 'ZiB', 'YiB'];
|
||||
? ["kB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"]
|
||||
: ["KiB", "MiB", "GiB", "TiB", "PiB", "EiB", "ZiB", "YiB"];
|
||||
let u = -1;
|
||||
const r = 10**dp;
|
||||
const r = 10 ** dp;
|
||||
|
||||
do {
|
||||
bytes /= thresh;
|
||||
++u;
|
||||
} while (Math.round(Math.abs(bytes) * r) / r >= thresh && u < units.length - 1);
|
||||
|
||||
return bytes.toFixed(dp) + ' ' + units[u];
|
||||
return bytes.toFixed(dp) + " " + units[u];
|
||||
}
|
||||
|
||||
const dateTimeOptions: Intl.DateTimeFormatOptions = {
|
||||
@ -122,7 +122,8 @@ function indexLayout(prefix: string, files: _Object[], dirs: string[]): string {
|
||||
}
|
||||
|
||||
for (const file of files) {
|
||||
if (!file.Key ||
|
||||
if (
|
||||
!file.Key ||
|
||||
HIDDEN_FILES.includes(`/${file.Key}`) ||
|
||||
HIDDEN_FILES.includes(file.Key.slice(file.Key.lastIndexOf("/") + 1))
|
||||
) {
|
||||
@ -143,11 +144,15 @@ function indexLayout(prefix: string, files: _Object[], dirs: string[]): string {
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
${rows.map(([link, name, size, date]) => `<tr>
|
||||
${rows
|
||||
.map(
|
||||
([link, name, size, date]) => `<tr>
|
||||
<td class="link"><a href="${link}">${name}</a></td>
|
||||
<td class="size">${size ? humanFileSize(size) : "-"}</td>
|
||||
<td class="date">${date?.toLocaleString("en-GB", dateTimeOptions) ?? "-"}</td>
|
||||
</tr>`).join("")}
|
||||
</tr>`,
|
||||
)
|
||||
.join("")}
|
||||
</tbody>
|
||||
</table>
|
||||
`);
|
||||
@ -166,17 +171,20 @@ async function generateIndex(Prefix: string): Promise<{
|
||||
|
||||
const listResponse = await client.send(command);
|
||||
const files = listResponse.Contents ?? [];
|
||||
const dirs = listResponse.CommonPrefixes
|
||||
?.map(p => p.Prefix?.slice(Prefix.length).split("/", 2)[0])
|
||||
.filter(Boolean) as string[] ?? [];
|
||||
const dirs =
|
||||
(listResponse.CommonPrefixes?.map((p) => p.Prefix?.slice(Prefix.length).split("/", 2)[0]).filter(
|
||||
Boolean,
|
||||
) as string[]) ?? [];
|
||||
const Body = indexLayout(Prefix, files, dirs);
|
||||
|
||||
await client.send(new PutObjectCommand({
|
||||
Body,
|
||||
Bucket,
|
||||
ContentType: "text/html",
|
||||
Key: Prefix + "index.html",
|
||||
}));
|
||||
await client.send(
|
||||
new PutObjectCommand({
|
||||
Body,
|
||||
Bucket,
|
||||
ContentType: "text/html",
|
||||
Key: Prefix + "index.html",
|
||||
}),
|
||||
);
|
||||
|
||||
return { files, dirs };
|
||||
}
|
||||
|
@ -1,40 +1,40 @@
|
||||
hak
|
||||
===
|
||||
# hak
|
||||
|
||||
This tool builds native dependencies for element-desktop. Here follows some very minimal
|
||||
documentation for it.
|
||||
|
||||
Goals:
|
||||
* Must build compiled native node modules in a shippable state
|
||||
(ie. only dynamically linked against libraries that will be on the
|
||||
target system, all unnecessary files removed).
|
||||
* Must be able to build any native module, no matter what build system
|
||||
it uses (electron-rebuild is supposed to do this job but only works
|
||||
for modules that use gyp).
|
||||
|
||||
- Must build compiled native node modules in a shippable state
|
||||
(ie. only dynamically linked against libraries that will be on the
|
||||
target system, all unnecessary files removed).
|
||||
- Must be able to build any native module, no matter what build system
|
||||
it uses (electron-rebuild is supposed to do this job but only works
|
||||
for modules that use gyp).
|
||||
|
||||
It's also loosely designed to be a general tool and agnostic to what it's
|
||||
actually building. It's used here to build modules for the electron app
|
||||
but should work equally well for building modules for normal node.
|
||||
|
||||
Running
|
||||
=======
|
||||
# Running
|
||||
|
||||
Hak is invoked with a command and a dependency, eg. `yarn run hak fetch matrix-seshat`.
|
||||
If no dependencies are given, hak runs the command on all dependencies.
|
||||
|
||||
Files
|
||||
=====
|
||||
# Files
|
||||
|
||||
There are a lot of files involved:
|
||||
|
||||
* scripts/hak/... - The tool itself
|
||||
* hak/[dependency] - Files provided by the app that tell hak how to build each of its native dependencies.
|
||||
Contains a hak.json file and also some script files, each of which must be referenced in hak.json.
|
||||
* .hak/ - Files generated by hak in the course of doing its job. Includes the dependency module itself and
|
||||
any of the native dependency's native dependencies.
|
||||
* .hak/[dependency]/build - An extracted copy of the dependency's node module used to build it.
|
||||
* .hak/[dependency]/out - Another extracted copy of the dependency, this one contains only what will be shipped.
|
||||
- scripts/hak/... - The tool itself
|
||||
- hak/[dependency] - Files provided by the app that tell hak how to build each of its native dependencies.
|
||||
Contains a hak.json file and also some script files, each of which must be referenced in hak.json.
|
||||
- .hak/ - Files generated by hak in the course of doing its job. Includes the dependency module itself and
|
||||
any of the native dependency's native dependencies.
|
||||
- .hak/[dependency]/build - An extracted copy of the dependency's node module used to build it.
|
||||
- .hak/[dependency]/out - Another extracted copy of the dependency, this one contains only what will be shipped.
|
||||
|
||||
# Workings
|
||||
|
||||
Workings
|
||||
========
|
||||
Hak works around native node modules that try to fetch or build their native component in
|
||||
the npm 'install' phase - modules that do this will typically end up with native components
|
||||
targeted to the build platform and the node that npm/yarn is using, which is no good for an
|
||||
@ -49,33 +49,34 @@ This also means that the dependencies cannot be listed in `dependencies` or
|
||||
try to fetch their native parts. Instead, they are listed in `hakDependencies` which
|
||||
hak reads to install them for you.
|
||||
|
||||
Hak will *not* install dependencies for the copy of the module it links into your
|
||||
Hak will _not_ install dependencies for the copy of the module it links into your
|
||||
project, so if your native module has javascript dependencies that are actually needed at
|
||||
runtime (and not just to fetch / build the native parts), it won't work.
|
||||
|
||||
Hak will generate a `.yarnrc` in the project directory to set the link directory to its
|
||||
own in the .hak directory (unless one already exists, in which case this is your problem).
|
||||
|
||||
Lifecycle
|
||||
=========
|
||||
Hak is divided into lifecycle stages, in order:
|
||||
* fetch - Download and extract the source of the dependency
|
||||
* link - Link the copy of the dependency into your node_modules directory
|
||||
* fetchDeps - Fetch & extract any native dependencies required to build the module.
|
||||
* build - The Good Stuff. Configure and build any native dependencies, then the module itself.
|
||||
* copy - Copy the built artifact from the module build directory to the module output directory.
|
||||
# Lifecycle
|
||||
|
||||
Hak is divided into lifecycle stages, in order:
|
||||
|
||||
- fetch - Download and extract the source of the dependency
|
||||
- link - Link the copy of the dependency into your node_modules directory
|
||||
- fetchDeps - Fetch & extract any native dependencies required to build the module.
|
||||
- build - The Good Stuff. Configure and build any native dependencies, then the module itself.
|
||||
- copy - Copy the built artifact from the module build directory to the module output directory.
|
||||
|
||||
# hak.json
|
||||
|
||||
hak.json
|
||||
========
|
||||
The scripts section contains scripts used for lifecycle stages that need them (fetch, fetchDeps, build).
|
||||
It also contains 'prune' and 'copy' which are globs of files to delete from the output module directory
|
||||
and copy over from the module build directory to the output module directory, respectively.
|
||||
|
||||
Shortcomings
|
||||
============
|
||||
# Shortcomings
|
||||
|
||||
Hak doesn't know about dependencies between lifecycle stages, ie. it doesn't know that you need to
|
||||
'fetch' and 'fetchDeps' before you can 'build', etc. You get to run each individually, and remember
|
||||
the right order.
|
||||
|
||||
There is also a *lot* of duplication in the command execution: we should abstract away
|
||||
There is also a _lot_ of duplication in the command execution: we should abstract away
|
||||
some of the boilerplate required to run commands & so forth.
|
||||
|
@ -14,11 +14,11 @@ See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
import path from 'path';
|
||||
import rimraf from 'rimraf';
|
||||
import path from "path";
|
||||
import rimraf from "rimraf";
|
||||
|
||||
import { DependencyInfo } from './dep';
|
||||
import HakEnv from './hakEnv';
|
||||
import { DependencyInfo } from "./dep";
|
||||
import HakEnv from "./hakEnv";
|
||||
|
||||
export default async function clean(hakEnv: HakEnv, moduleInfo: DependencyInfo): Promise<void> {
|
||||
await new Promise<void>((resolve, reject) => {
|
||||
@ -32,7 +32,7 @@ export default async function clean(hakEnv: HakEnv, moduleInfo: DependencyInfo):
|
||||
});
|
||||
|
||||
await new Promise<void>((resolve, reject) => {
|
||||
rimraf(path.join(hakEnv.dotHakDir, 'links', moduleInfo.name), (err?: Error | null) => {
|
||||
rimraf(path.join(hakEnv.dotHakDir, "links", moduleInfo.name), (err?: Error | null) => {
|
||||
if (err) {
|
||||
reject(err);
|
||||
} else {
|
||||
@ -42,7 +42,7 @@ export default async function clean(hakEnv: HakEnv, moduleInfo: DependencyInfo):
|
||||
});
|
||||
|
||||
await new Promise<void>((resolve, reject) => {
|
||||
rimraf(path.join(hakEnv.projectRoot, 'node_modules', moduleInfo.name), (err?: Error | null) => {
|
||||
rimraf(path.join(hakEnv.projectRoot, "node_modules", moduleInfo.name), (err?: Error | null) => {
|
||||
if (err) {
|
||||
reject(err);
|
||||
} else {
|
||||
|
@ -14,15 +14,15 @@ See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
import path from 'path';
|
||||
import fsProm from 'fs/promises';
|
||||
import childProcess from 'child_process';
|
||||
import rimraf from 'rimraf';
|
||||
import glob from 'glob';
|
||||
import mkdirp from 'mkdirp';
|
||||
import path from "path";
|
||||
import fsProm from "fs/promises";
|
||||
import childProcess from "child_process";
|
||||
import rimraf from "rimraf";
|
||||
import glob from "glob";
|
||||
import mkdirp from "mkdirp";
|
||||
|
||||
import HakEnv from './hakEnv';
|
||||
import { DependencyInfo } from './dep';
|
||||
import HakEnv from "./hakEnv";
|
||||
import { DependencyInfo } from "./dep";
|
||||
|
||||
export default async function copy(hakEnv: HakEnv, moduleInfo: DependencyInfo): Promise<void> {
|
||||
if (moduleInfo.cfg.prune) {
|
||||
@ -34,7 +34,7 @@ export default async function copy(hakEnv: HakEnv, moduleInfo: DependencyInfo):
|
||||
await mkdirp(moduleInfo.moduleOutDir);
|
||||
process.chdir(moduleInfo.moduleOutDir);
|
||||
await new Promise<void>((resolve, reject) => {
|
||||
rimraf(moduleInfo.cfg.prune, {}, err => {
|
||||
rimraf(moduleInfo.cfg.prune, {}, (err) => {
|
||||
err ? reject(err) : resolve();
|
||||
});
|
||||
});
|
||||
@ -48,46 +48,44 @@ export default async function copy(hakEnv: HakEnv, moduleInfo: DependencyInfo):
|
||||
// is the same as moduleBuildDirs[0], so we're just listing the contents
|
||||
// of the first one.
|
||||
const files = await new Promise<string[]>((resolve, reject) => {
|
||||
glob(moduleInfo.cfg.copy, {
|
||||
nosort: true,
|
||||
silent: true,
|
||||
cwd: moduleInfo.moduleBuildDir,
|
||||
}, (err, files) => {
|
||||
err ? reject(err) : resolve(files);
|
||||
});
|
||||
glob(
|
||||
moduleInfo.cfg.copy,
|
||||
{
|
||||
nosort: true,
|
||||
silent: true,
|
||||
cwd: moduleInfo.moduleBuildDir,
|
||||
},
|
||||
(err, files) => {
|
||||
err ? reject(err) : resolve(files);
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
if (moduleInfo.moduleBuildDirs.length > 1) {
|
||||
if (!hakEnv.isMac()) {
|
||||
console.error(
|
||||
"You asked me to copy multiple targets but I've only been taught " +
|
||||
"how to do that on macOS.",
|
||||
"You asked me to copy multiple targets but I've only been taught " + "how to do that on macOS.",
|
||||
);
|
||||
throw new Error("Can't copy multiple targets on this platform");
|
||||
}
|
||||
|
||||
for (const f of files) {
|
||||
const components = moduleInfo.moduleBuildDirs.map(dir => path.join(dir, f));
|
||||
const components = moduleInfo.moduleBuildDirs.map((dir) => path.join(dir, f));
|
||||
const dst = path.join(moduleInfo.moduleOutDir, f);
|
||||
|
||||
await mkdirp(path.dirname(dst));
|
||||
await new Promise<void>((resolve, reject) => {
|
||||
childProcess.execFile('lipo',
|
||||
['-create', '-output', dst, ...components], (err) => {
|
||||
if (err) {
|
||||
reject(err);
|
||||
} else {
|
||||
resolve();
|
||||
}
|
||||
},
|
||||
);
|
||||
childProcess.execFile("lipo", ["-create", "-output", dst, ...components], (err) => {
|
||||
if (err) {
|
||||
reject(err);
|
||||
} else {
|
||||
resolve();
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
} else {
|
||||
console.log(
|
||||
"Copying files from " +
|
||||
moduleInfo.moduleBuildDir + " to " + moduleInfo.moduleOutDir,
|
||||
);
|
||||
console.log("Copying files from " + moduleInfo.moduleBuildDir + " to " + moduleInfo.moduleOutDir);
|
||||
for (const f of files) {
|
||||
console.log("\t" + f);
|
||||
const src = path.join(moduleInfo.moduleBuildDir, f);
|
||||
|
@ -28,5 +28,5 @@ export interface DependencyInfo {
|
||||
moduleOutDir: string;
|
||||
nodeModuleBinDir: string;
|
||||
depPrefix: string;
|
||||
scripts: Record<string, (hakEnv: HakEnv, moduleInfo: DependencyInfo) => Promise<void> >;
|
||||
scripts: Record<string, (hakEnv: HakEnv, moduleInfo: DependencyInfo) => Promise<void>>;
|
||||
}
|
||||
|
@ -14,12 +14,12 @@ See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
import fsProm from 'fs/promises';
|
||||
import childProcess from 'child_process';
|
||||
import pacote from 'pacote';
|
||||
import fsProm from "fs/promises";
|
||||
import childProcess from "child_process";
|
||||
import pacote from "pacote";
|
||||
|
||||
import HakEnv from './hakEnv';
|
||||
import { DependencyInfo } from './dep';
|
||||
import HakEnv from "./hakEnv";
|
||||
import { DependencyInfo } from "./dep";
|
||||
|
||||
export default async function fetch(hakEnv: HakEnv, moduleInfo: DependencyInfo): Promise<void> {
|
||||
let haveModuleBuildDir;
|
||||
@ -41,15 +41,11 @@ export default async function fetch(hakEnv: HakEnv, moduleInfo: DependencyInfo):
|
||||
|
||||
console.log("Running yarn install in " + moduleInfo.moduleBuildDir);
|
||||
await new Promise<void>((resolve, reject) => {
|
||||
const proc = childProcess.spawn(
|
||||
hakEnv.isWin() ? 'yarn.cmd' : 'yarn',
|
||||
['install', '--ignore-scripts'],
|
||||
{
|
||||
stdio: 'inherit',
|
||||
cwd: moduleInfo.moduleBuildDir,
|
||||
},
|
||||
);
|
||||
proc.on('exit', code => {
|
||||
const proc = childProcess.spawn(hakEnv.isWin() ? "yarn.cmd" : "yarn", ["install", "--ignore-scripts"], {
|
||||
stdio: "inherit",
|
||||
cwd: moduleInfo.moduleBuildDir,
|
||||
});
|
||||
proc.on("exit", (code) => {
|
||||
code ? reject(code) : resolve();
|
||||
});
|
||||
});
|
||||
|
@ -14,10 +14,10 @@ See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
import mkdirp from 'mkdirp';
|
||||
import mkdirp from "mkdirp";
|
||||
|
||||
import { DependencyInfo } from './dep';
|
||||
import HakEnv from './hakEnv';
|
||||
import { DependencyInfo } from "./dep";
|
||||
import HakEnv from "./hakEnv";
|
||||
|
||||
export default async function fetchDeps(hakEnv: HakEnv, moduleInfo: DependencyInfo): Promise<void> {
|
||||
await mkdirp(moduleInfo.moduleDotHakDir);
|
||||
|
@ -14,16 +14,16 @@ See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
import path from 'path';
|
||||
import os from 'os';
|
||||
import path from "path";
|
||||
import os from "os";
|
||||
import nodePreGypVersioning from "node-pre-gyp/lib/util/versioning";
|
||||
import { getElectronVersion } from "app-builder-lib/out/electron/electronVersion";
|
||||
|
||||
import { Arch, Target, TARGETS, getHost, isHostId, TargetId } from './target';
|
||||
import { Arch, Target, TARGETS, getHost, isHostId, TargetId } from "./target";
|
||||
|
||||
async function getRuntime(projectRoot: string): Promise<string> {
|
||||
const electronVersion = await getElectronVersion(projectRoot);
|
||||
return electronVersion ? 'electron' : 'node-webkit';
|
||||
return electronVersion ? "electron" : "node-webkit";
|
||||
}
|
||||
|
||||
async function getRuntimeVersion(projectRoot: string): Promise<string> {
|
||||
@ -48,7 +48,7 @@ export default class HakEnv {
|
||||
throw new Error(`Unknown target ${targetId}!`);
|
||||
}
|
||||
this.target = target;
|
||||
this.dotHakDir = path.join(this.projectRoot, '.hak');
|
||||
this.dotHakDir = path.join(this.projectRoot, ".hak");
|
||||
}
|
||||
|
||||
public async init(): Promise<void> {
|
||||
@ -62,7 +62,7 @@ export default class HakEnv {
|
||||
|
||||
// {node_abi}-{platform}-{arch}
|
||||
public getNodeTriple(): string {
|
||||
return this.getRuntimeAbi() + '-' + this.target.platform + '-' + this.target.arch;
|
||||
return this.getRuntimeAbi() + "-" + this.target.platform + "-" + this.target.arch;
|
||||
}
|
||||
|
||||
public getTargetId(): TargetId {
|
||||
@ -70,15 +70,15 @@ export default class HakEnv {
|
||||
}
|
||||
|
||||
public isWin(): boolean {
|
||||
return this.target.platform === 'win32';
|
||||
return this.target.platform === "win32";
|
||||
}
|
||||
|
||||
public isMac(): boolean {
|
||||
return this.target.platform === 'darwin';
|
||||
return this.target.platform === "darwin";
|
||||
}
|
||||
|
||||
public isLinux(): boolean {
|
||||
return this.target.platform === 'linux';
|
||||
return this.target.platform === "linux";
|
||||
}
|
||||
|
||||
public getTargetArch(): Arch {
|
||||
@ -93,7 +93,7 @@ export default class HakEnv {
|
||||
return Object.assign({}, process.env, {
|
||||
npm_config_arch: this.target.arch,
|
||||
npm_config_target_arch: this.target.arch,
|
||||
npm_config_disturl: 'https://electronjs.org/headers',
|
||||
npm_config_disturl: "https://electronjs.org/headers",
|
||||
npm_config_runtime: this.runtime,
|
||||
npm_config_target: this.runtimeVersion,
|
||||
npm_config_build_from_source: true,
|
||||
@ -102,7 +102,7 @@ export default class HakEnv {
|
||||
}
|
||||
|
||||
public wantsStaticSqlCipherUnix(): boolean {
|
||||
return this.isMac() || process.env.SQLCIPHER_STATIC == '1';
|
||||
return this.isMac() || process.env.SQLCIPHER_STATIC == "1";
|
||||
}
|
||||
|
||||
public wantsStaticSqlCipher(): boolean {
|
||||
|
@ -14,42 +14,27 @@ See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
import path from 'path';
|
||||
import findNpmPrefix from 'find-npm-prefix';
|
||||
import path from "path";
|
||||
import findNpmPrefix from "find-npm-prefix";
|
||||
|
||||
import HakEnv from './hakEnv';
|
||||
import { TargetId } from './target';
|
||||
import { DependencyInfo } from './dep';
|
||||
import HakEnv from "./hakEnv";
|
||||
import { TargetId } from "./target";
|
||||
import { DependencyInfo } from "./dep";
|
||||
|
||||
const GENERALCOMMANDS = [
|
||||
'target',
|
||||
];
|
||||
const GENERALCOMMANDS = ["target"];
|
||||
|
||||
// These can only be run on specific modules
|
||||
const MODULECOMMANDS = [
|
||||
'check',
|
||||
'fetch',
|
||||
'link',
|
||||
'fetchDeps',
|
||||
'build',
|
||||
'copy',
|
||||
'clean',
|
||||
];
|
||||
const MODULECOMMANDS = ["check", "fetch", "link", "fetchDeps", "build", "copy", "clean"];
|
||||
|
||||
// Shortcuts for multiple commands at once (useful for building universal binaries
|
||||
// because you can run the fetch/fetchDeps/build for each arch and then copy/link once)
|
||||
const METACOMMANDS: Record<string, string[]> = {
|
||||
'fetchandbuild': ['check', 'fetch', 'fetchDeps', 'build'],
|
||||
'copyandlink': ['copy', 'link'],
|
||||
fetchandbuild: ["check", "fetch", "fetchDeps", "build"],
|
||||
copyandlink: ["copy", "link"],
|
||||
};
|
||||
|
||||
// Scripts valid in a hak.json 'scripts' section
|
||||
const HAKSCRIPTS = [
|
||||
'check',
|
||||
'fetch',
|
||||
'fetchDeps',
|
||||
'build',
|
||||
];
|
||||
const HAKSCRIPTS = ["check", "fetch", "fetchDeps", "build"];
|
||||
|
||||
async function main(): Promise<void> {
|
||||
const prefix = await findNpmPrefix(process.cwd());
|
||||
@ -65,11 +50,12 @@ async function main(): Promise<void> {
|
||||
// Apply `--target <target>` option if specified
|
||||
// Can be specified multiple times for the copy command to bundle
|
||||
// multiple archs into a single universal output module)
|
||||
while (true) { // eslint-disable-line no-constant-condition
|
||||
const targetIndex = process.argv.indexOf('--target');
|
||||
for (;;) {
|
||||
// eslint-disable-line no-constant-condition
|
||||
const targetIndex = process.argv.indexOf("--target");
|
||||
if (targetIndex === -1) break;
|
||||
|
||||
if ((targetIndex + 1) >= process.argv.length) {
|
||||
if (targetIndex + 1 >= process.argv.length) {
|
||||
console.error("--target option specified without a target");
|
||||
process.exit(1);
|
||||
}
|
||||
@ -77,7 +63,7 @@ async function main(): Promise<void> {
|
||||
targetIds.push(process.argv.splice(targetIndex, 2)[1] as TargetId);
|
||||
}
|
||||
|
||||
const hakEnvs = targetIds.map(tid => new HakEnv(prefix, tid));
|
||||
const hakEnvs = targetIds.map((tid) => new HakEnv(prefix, tid));
|
||||
if (hakEnvs.length == 0) hakEnvs.push(new HakEnv(prefix, null));
|
||||
for (const h of hakEnvs) {
|
||||
await h.init();
|
||||
@ -89,7 +75,7 @@ async function main(): Promise<void> {
|
||||
const hakDepsCfg = packageJson.hakDependencies || {};
|
||||
|
||||
for (const dep of Object.keys(hakDepsCfg)) {
|
||||
const hakJsonPath = path.join(prefix, 'hak', dep, 'hak.json');
|
||||
const hakJsonPath = path.join(prefix, "hak", dep, "hak.json");
|
||||
let hakJson: Record<string, any>;
|
||||
try {
|
||||
hakJson = await require(hakJsonPath);
|
||||
@ -102,20 +88,20 @@ async function main(): Promise<void> {
|
||||
name: dep,
|
||||
version: hakDepsCfg[dep],
|
||||
cfg: hakJson,
|
||||
moduleHakDir: path.join(prefix, 'hak', dep),
|
||||
moduleHakDir: path.join(prefix, "hak", dep),
|
||||
moduleDotHakDir: path.join(hakEnv.dotHakDir, dep),
|
||||
moduleTargetDotHakDir: path.join(hakEnv.dotHakDir, dep, hakEnv.getTargetId()),
|
||||
moduleBuildDir: path.join(hakEnv.dotHakDir, dep, hakEnv.getTargetId(), 'build'),
|
||||
moduleBuildDirs: hakEnvs.map(h => path.join(h.dotHakDir, dep, h.getTargetId(), 'build')),
|
||||
moduleOutDir: path.join(hakEnv.dotHakDir, 'hakModules', dep),
|
||||
nodeModuleBinDir: path.join(hakEnv.dotHakDir, dep, hakEnv.getTargetId(), 'build', 'node_modules', '.bin'),
|
||||
depPrefix: path.join(hakEnv.dotHakDir, dep, hakEnv.getTargetId(), 'opt'),
|
||||
moduleBuildDir: path.join(hakEnv.dotHakDir, dep, hakEnv.getTargetId(), "build"),
|
||||
moduleBuildDirs: hakEnvs.map((h) => path.join(h.dotHakDir, dep, h.getTargetId(), "build")),
|
||||
moduleOutDir: path.join(hakEnv.dotHakDir, "hakModules", dep),
|
||||
nodeModuleBinDir: path.join(hakEnv.dotHakDir, dep, hakEnv.getTargetId(), "build", "node_modules", ".bin"),
|
||||
depPrefix: path.join(hakEnv.dotHakDir, dep, hakEnv.getTargetId(), "opt"),
|
||||
scripts: {},
|
||||
};
|
||||
|
||||
for (const s of HAKSCRIPTS) {
|
||||
if (hakJson.scripts && hakJson.scripts[s]) {
|
||||
const scriptModule = await import(path.join(prefix, 'hak', dep, hakJson.scripts[s]));
|
||||
const scriptModule = await import(path.join(prefix, "hak", dep, hakJson.scripts[s]));
|
||||
if (scriptModule.__esModule) {
|
||||
deps[dep].scripts[s] = scriptModule.default;
|
||||
} else {
|
||||
@ -127,14 +113,14 @@ async function main(): Promise<void> {
|
||||
|
||||
let cmds: string[];
|
||||
if (process.argv.length < 3) {
|
||||
cmds = ['check', 'fetch', 'fetchDeps', 'build', 'copy', 'link'];
|
||||
cmds = ["check", "fetch", "fetchDeps", "build", "copy", "link"];
|
||||
} else if (METACOMMANDS[process.argv[2]]) {
|
||||
cmds = METACOMMANDS[process.argv[2]];
|
||||
} else {
|
||||
cmds = [process.argv[2]];
|
||||
}
|
||||
|
||||
if (hakEnvs.length > 1 && cmds.some(c => !['copy', 'link'].includes(c))) {
|
||||
if (hakEnvs.length > 1 && cmds.some((c) => !["copy", "link"].includes(c))) {
|
||||
// We allow link here too for convenience because it's completely arch independent
|
||||
console.error("Multiple targets only supported with the copy command");
|
||||
return;
|
||||
@ -145,7 +131,7 @@ async function main(): Promise<void> {
|
||||
|
||||
for (const cmd of cmds) {
|
||||
if (GENERALCOMMANDS.includes(cmd)) {
|
||||
if (cmd === 'target') {
|
||||
if (cmd === "target") {
|
||||
console.log(hakEnv.getNodeTriple());
|
||||
}
|
||||
return;
|
||||
@ -160,15 +146,12 @@ async function main(): Promise<void> {
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const cmdFunc = (await import('./' + cmd)).default;
|
||||
const cmdFunc = (await import("./" + cmd)).default;
|
||||
|
||||
for (const mod of modules) {
|
||||
const depInfo = deps[mod];
|
||||
if (depInfo === undefined) {
|
||||
console.log(
|
||||
"Module " + mod + " not found - is it in hakDependencies " +
|
||||
"in your package.json?",
|
||||
);
|
||||
console.log("Module " + mod + " not found - is it in hakDependencies " + "in your package.json?");
|
||||
process.exit(1);
|
||||
}
|
||||
console.log("hak " + cmd + ": " + mod);
|
||||
@ -177,7 +160,7 @@ async function main(): Promise<void> {
|
||||
}
|
||||
}
|
||||
|
||||
main().catch(err => {
|
||||
main().catch((err) => {
|
||||
console.error(err);
|
||||
process.exit(1);
|
||||
});
|
||||
|
@ -14,16 +14,16 @@ See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
import path from 'path';
|
||||
import os from 'os';
|
||||
import fsProm from 'fs/promises';
|
||||
import childProcess from 'child_process';
|
||||
import path from "path";
|
||||
import os from "os";
|
||||
import fsProm from "fs/promises";
|
||||
import childProcess from "child_process";
|
||||
|
||||
import HakEnv from './hakEnv';
|
||||
import { DependencyInfo } from './dep';
|
||||
import HakEnv from "./hakEnv";
|
||||
import { DependencyInfo } from "./dep";
|
||||
|
||||
export default async function link(hakEnv: HakEnv, moduleInfo: DependencyInfo): Promise<void> {
|
||||
const yarnrc = path.join(hakEnv.projectRoot, '.yarnrc');
|
||||
const yarnrc = path.join(hakEnv.projectRoot, ".yarnrc");
|
||||
// this is fairly terrible but it's reasonably clunky to either parse a yarnrc
|
||||
// properly or get yarn to do it, so this will probably suffice for now.
|
||||
// We just check to see if there is a local .yarnrc at all, and assume that
|
||||
@ -43,28 +43,28 @@ export default async function link(hakEnv: HakEnv, moduleInfo: DependencyInfo):
|
||||
// (ie. Windows absolute paths) but strings in quotes get parsed as
|
||||
// JSON so need to be valid JSON encoded strings (ie. have the
|
||||
// backslashes escaped). JSON.stringify will add quotes and escape.
|
||||
'--link-folder ' + JSON.stringify(path.join(hakEnv.dotHakDir, 'links')) + os.EOL,
|
||||
"--link-folder " + JSON.stringify(path.join(hakEnv.dotHakDir, "links")) + os.EOL,
|
||||
);
|
||||
}
|
||||
|
||||
const yarnCmd = 'yarn' + (hakEnv.isWin() ? '.cmd' : '');
|
||||
const yarnCmd = "yarn" + (hakEnv.isWin() ? ".cmd" : "");
|
||||
|
||||
await new Promise<void>((resolve, reject) => {
|
||||
const proc = childProcess.spawn(yarnCmd, ['link'], {
|
||||
const proc = childProcess.spawn(yarnCmd, ["link"], {
|
||||
cwd: moduleInfo.moduleOutDir,
|
||||
stdio: 'inherit',
|
||||
stdio: "inherit",
|
||||
});
|
||||
proc.on('exit', code => {
|
||||
proc.on("exit", (code) => {
|
||||
code ? reject(code) : resolve();
|
||||
});
|
||||
});
|
||||
|
||||
await new Promise<void>((resolve, reject) => {
|
||||
const proc = childProcess.spawn(yarnCmd, ['link', moduleInfo.name], {
|
||||
const proc = childProcess.spawn(yarnCmd, ["link", moduleInfo.name], {
|
||||
cwd: hakEnv.projectRoot,
|
||||
stdio: 'inherit',
|
||||
stdio: "inherit",
|
||||
});
|
||||
proc.on('exit', code => {
|
||||
proc.on("exit", (code) => {
|
||||
code ? reject(code) : resolve();
|
||||
});
|
||||
});
|
||||
|
@ -20,29 +20,29 @@ import { GLIBC, MUSL, family as processLibC } from "detect-libc";
|
||||
// details in a single string.
|
||||
// See https://doc.rust-lang.org/rustc/platform-support.html.
|
||||
export type TargetId =
|
||||
'aarch64-apple-darwin' |
|
||||
'x86_64-apple-darwin' |
|
||||
'universal-apple-darwin' |
|
||||
'i686-pc-windows-msvc' |
|
||||
'x86_64-pc-windows-msvc' |
|
||||
'i686-unknown-linux-musl' |
|
||||
'i686-unknown-linux-gnu' |
|
||||
'x86_64-unknown-linux-musl' |
|
||||
'x86_64-unknown-linux-gnu' |
|
||||
'aarch64-unknown-linux-musl' |
|
||||
'aarch64-unknown-linux-gnu' |
|
||||
'powerpc64le-unknown-linux-musl' |
|
||||
'powerpc64le-unknown-linux-gnu';
|
||||
| "aarch64-apple-darwin"
|
||||
| "x86_64-apple-darwin"
|
||||
| "universal-apple-darwin"
|
||||
| "i686-pc-windows-msvc"
|
||||
| "x86_64-pc-windows-msvc"
|
||||
| "i686-unknown-linux-musl"
|
||||
| "i686-unknown-linux-gnu"
|
||||
| "x86_64-unknown-linux-musl"
|
||||
| "x86_64-unknown-linux-gnu"
|
||||
| "aarch64-unknown-linux-musl"
|
||||
| "aarch64-unknown-linux-gnu"
|
||||
| "powerpc64le-unknown-linux-musl"
|
||||
| "powerpc64le-unknown-linux-gnu";
|
||||
|
||||
// Values are expected to match those used in `process.platform`.
|
||||
export type Platform = 'darwin' | 'linux' | 'win32';
|
||||
export type Platform = "darwin" | "linux" | "win32";
|
||||
|
||||
// Values are expected to match those used in `process.arch`.
|
||||
export type Arch = 'arm64' | 'ia32' | 'x64' | 'ppc64' | 'universal';
|
||||
export type Arch = "arm64" | "ia32" | "x64" | "ppc64" | "universal";
|
||||
|
||||
// Values are expected to match those used by Visual Studio's `vcvarsall.bat`.
|
||||
// See https://docs.microsoft.com/cpp/build/building-on-the-command-line?view=msvc-160#vcvarsall-syntax
|
||||
export type VcVarsArch = 'amd64' | 'arm64' | 'x86';
|
||||
export type VcVarsArch = "amd64" | "arm64" | "x86";
|
||||
|
||||
export type Target = {
|
||||
id: TargetId;
|
||||
@ -51,140 +51,135 @@ export type Target = {
|
||||
};
|
||||
|
||||
export type WindowsTarget = Target & {
|
||||
platform: 'win32';
|
||||
platform: "win32";
|
||||
vcVarsArch: VcVarsArch;
|
||||
};
|
||||
|
||||
export type LinuxTarget = Target & {
|
||||
platform: 'linux';
|
||||
platform: "linux";
|
||||
libC: typeof processLibC;
|
||||
};
|
||||
|
||||
export type UniversalTarget = Target & {
|
||||
arch: 'universal';
|
||||
arch: "universal";
|
||||
subtargets: Target[];
|
||||
};
|
||||
|
||||
const aarch64AppleDarwin: Target = {
|
||||
id: 'aarch64-apple-darwin',
|
||||
platform: 'darwin',
|
||||
arch: 'arm64',
|
||||
id: "aarch64-apple-darwin",
|
||||
platform: "darwin",
|
||||
arch: "arm64",
|
||||
};
|
||||
|
||||
const x8664AppleDarwin: Target = {
|
||||
id: 'x86_64-apple-darwin',
|
||||
platform: 'darwin',
|
||||
arch: 'x64',
|
||||
id: "x86_64-apple-darwin",
|
||||
platform: "darwin",
|
||||
arch: "x64",
|
||||
};
|
||||
|
||||
const universalAppleDarwin: UniversalTarget = {
|
||||
id: 'universal-apple-darwin',
|
||||
platform: 'darwin',
|
||||
arch: 'universal',
|
||||
subtargets: [
|
||||
aarch64AppleDarwin,
|
||||
x8664AppleDarwin,
|
||||
],
|
||||
id: "universal-apple-darwin",
|
||||
platform: "darwin",
|
||||
arch: "universal",
|
||||
subtargets: [aarch64AppleDarwin, x8664AppleDarwin],
|
||||
};
|
||||
|
||||
const i686PcWindowsMsvc: WindowsTarget = {
|
||||
id: 'i686-pc-windows-msvc',
|
||||
platform: 'win32',
|
||||
arch: 'ia32',
|
||||
vcVarsArch: 'x86',
|
||||
id: "i686-pc-windows-msvc",
|
||||
platform: "win32",
|
||||
arch: "ia32",
|
||||
vcVarsArch: "x86",
|
||||
};
|
||||
|
||||
const x8664PcWindowsMsvc: WindowsTarget = {
|
||||
id: 'x86_64-pc-windows-msvc',
|
||||
platform: 'win32',
|
||||
arch: 'x64',
|
||||
vcVarsArch: 'amd64',
|
||||
id: "x86_64-pc-windows-msvc",
|
||||
platform: "win32",
|
||||
arch: "x64",
|
||||
vcVarsArch: "amd64",
|
||||
};
|
||||
|
||||
const x8664UnknownLinuxGnu: LinuxTarget = {
|
||||
id: 'x86_64-unknown-linux-gnu',
|
||||
platform: 'linux',
|
||||
arch: 'x64',
|
||||
id: "x86_64-unknown-linux-gnu",
|
||||
platform: "linux",
|
||||
arch: "x64",
|
||||
libC: GLIBC,
|
||||
};
|
||||
|
||||
const x8664UnknownLinuxMusl: LinuxTarget = {
|
||||
id: 'x86_64-unknown-linux-musl',
|
||||
platform: 'linux',
|
||||
arch: 'x64',
|
||||
id: "x86_64-unknown-linux-musl",
|
||||
platform: "linux",
|
||||
arch: "x64",
|
||||
libC: MUSL,
|
||||
};
|
||||
|
||||
const i686UnknownLinuxGnu: LinuxTarget = {
|
||||
id: 'i686-unknown-linux-gnu',
|
||||
platform: 'linux',
|
||||
arch: 'ia32',
|
||||
id: "i686-unknown-linux-gnu",
|
||||
platform: "linux",
|
||||
arch: "ia32",
|
||||
libC: GLIBC,
|
||||
};
|
||||
|
||||
const i686UnknownLinuxMusl: LinuxTarget = {
|
||||
id: 'i686-unknown-linux-musl',
|
||||
platform: 'linux',
|
||||
arch: 'ia32',
|
||||
id: "i686-unknown-linux-musl",
|
||||
platform: "linux",
|
||||
arch: "ia32",
|
||||
libC: MUSL,
|
||||
};
|
||||
|
||||
const aarch64UnknownLinuxGnu: LinuxTarget = {
|
||||
id: 'aarch64-unknown-linux-gnu',
|
||||
platform: 'linux',
|
||||
arch: 'arm64',
|
||||
id: "aarch64-unknown-linux-gnu",
|
||||
platform: "linux",
|
||||
arch: "arm64",
|
||||
libC: GLIBC,
|
||||
};
|
||||
|
||||
const aarch64UnknownLinuxMusl: LinuxTarget = {
|
||||
id: 'aarch64-unknown-linux-musl',
|
||||
platform: 'linux',
|
||||
arch: 'arm64',
|
||||
id: "aarch64-unknown-linux-musl",
|
||||
platform: "linux",
|
||||
arch: "arm64",
|
||||
libC: MUSL,
|
||||
};
|
||||
|
||||
const powerpc64leUnknownLinuxGnu: LinuxTarget = {
|
||||
id: 'powerpc64le-unknown-linux-gnu',
|
||||
platform: 'linux',
|
||||
arch: 'ppc64',
|
||||
id: "powerpc64le-unknown-linux-gnu",
|
||||
platform: "linux",
|
||||
arch: "ppc64",
|
||||
libC: GLIBC,
|
||||
};
|
||||
|
||||
const powerpc64leUnknownLinuxMusl: LinuxTarget = {
|
||||
id: 'powerpc64le-unknown-linux-musl',
|
||||
platform: 'linux',
|
||||
arch: 'ppc64',
|
||||
id: "powerpc64le-unknown-linux-musl",
|
||||
platform: "linux",
|
||||
arch: "ppc64",
|
||||
libC: MUSL,
|
||||
};
|
||||
|
||||
export const TARGETS: Record<TargetId, Target> = {
|
||||
// macOS
|
||||
'aarch64-apple-darwin': aarch64AppleDarwin,
|
||||
'x86_64-apple-darwin': x8664AppleDarwin,
|
||||
'universal-apple-darwin': universalAppleDarwin,
|
||||
"aarch64-apple-darwin": aarch64AppleDarwin,
|
||||
"x86_64-apple-darwin": x8664AppleDarwin,
|
||||
"universal-apple-darwin": universalAppleDarwin,
|
||||
// Windows
|
||||
'i686-pc-windows-msvc': i686PcWindowsMsvc,
|
||||
'x86_64-pc-windows-msvc': x8664PcWindowsMsvc,
|
||||
"i686-pc-windows-msvc": i686PcWindowsMsvc,
|
||||
"x86_64-pc-windows-msvc": x8664PcWindowsMsvc,
|
||||
// Linux
|
||||
'i686-unknown-linux-musl': i686UnknownLinuxMusl,
|
||||
'i686-unknown-linux-gnu': i686UnknownLinuxGnu,
|
||||
'x86_64-unknown-linux-musl': x8664UnknownLinuxMusl,
|
||||
'x86_64-unknown-linux-gnu': x8664UnknownLinuxGnu,
|
||||
'aarch64-unknown-linux-musl': aarch64UnknownLinuxMusl,
|
||||
'aarch64-unknown-linux-gnu': aarch64UnknownLinuxGnu,
|
||||
'powerpc64le-unknown-linux-musl': powerpc64leUnknownLinuxMusl,
|
||||
'powerpc64le-unknown-linux-gnu': powerpc64leUnknownLinuxGnu,
|
||||
"i686-unknown-linux-musl": i686UnknownLinuxMusl,
|
||||
"i686-unknown-linux-gnu": i686UnknownLinuxGnu,
|
||||
"x86_64-unknown-linux-musl": x8664UnknownLinuxMusl,
|
||||
"x86_64-unknown-linux-gnu": x8664UnknownLinuxGnu,
|
||||
"aarch64-unknown-linux-musl": aarch64UnknownLinuxMusl,
|
||||
"aarch64-unknown-linux-gnu": aarch64UnknownLinuxGnu,
|
||||
"powerpc64le-unknown-linux-musl": powerpc64leUnknownLinuxMusl,
|
||||
"powerpc64le-unknown-linux-gnu": powerpc64leUnknownLinuxGnu,
|
||||
};
|
||||
|
||||
export function getHost(): Target | undefined {
|
||||
return Object.values(TARGETS).find(target => (
|
||||
target.platform === process.platform &&
|
||||
target.arch === process.arch &&
|
||||
(
|
||||
process.platform !== 'linux' ||
|
||||
(target as LinuxTarget).libC === processLibC
|
||||
)
|
||||
));
|
||||
return Object.values(TARGETS).find(
|
||||
(target) =>
|
||||
target.platform === process.platform &&
|
||||
target.arch === process.arch &&
|
||||
(process.platform !== "linux" || (target as LinuxTarget).libC === processLibC),
|
||||
);
|
||||
}
|
||||
|
||||
export function isHostId(id: TargetId): boolean {
|
||||
|
@ -11,31 +11,35 @@ import * as childProcess from "child_process";
|
||||
|
||||
export async function versionFromAsar(): Promise<string> {
|
||||
try {
|
||||
await fs.stat('webapp.asar');
|
||||
await fs.stat("webapp.asar");
|
||||
} catch (e) {
|
||||
throw new Error("No 'webapp.asar' found. Run 'yarn run fetch'");
|
||||
}
|
||||
|
||||
return asar.extractFile('webapp.asar', 'version').toString().trim();
|
||||
return asar.extractFile("webapp.asar", "version").toString().trim();
|
||||
}
|
||||
|
||||
export async function setPackageVersion(ver: string): Promise<void> {
|
||||
// set version in package.json: electron-builder will use this to populate
|
||||
// all the various version fields
|
||||
await new Promise<void>((resolve, reject) => {
|
||||
childProcess.execFile(process.platform === 'win32' ? 'yarn.cmd' : 'yarn', [
|
||||
'version',
|
||||
'-s',
|
||||
'--no-git-tag-version', // This also means "don't commit to git" as it turns out
|
||||
'--new-version',
|
||||
ver,
|
||||
], (err) => {
|
||||
if (err) {
|
||||
reject(err);
|
||||
} else {
|
||||
resolve();
|
||||
}
|
||||
});
|
||||
childProcess.execFile(
|
||||
process.platform === "win32" ? "yarn.cmd" : "yarn",
|
||||
[
|
||||
"version",
|
||||
"-s",
|
||||
"--no-git-tag-version", // This also means "don't commit to git" as it turns out
|
||||
"--new-version",
|
||||
ver,
|
||||
],
|
||||
(err) => {
|
||||
if (err) {
|
||||
reject(err);
|
||||
} else {
|
||||
resolve();
|
||||
}
|
||||
},
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
@ -49,10 +53,12 @@ async function main(args: string[]): Promise<number> {
|
||||
}
|
||||
|
||||
if (require.main === module) {
|
||||
main(process.argv.slice(2)).then((ret) => {
|
||||
process.exit(ret);
|
||||
}).catch(e => {
|
||||
console.error(e);
|
||||
process.exit(1);
|
||||
});
|
||||
main(process.argv.slice(2))
|
||||
.then((ret) => {
|
||||
process.exit(ret);
|
||||
})
|
||||
.catch((e) => {
|
||||
console.error(e);
|
||||
process.exit(1);
|
||||
});
|
||||
}
|
||||
|
@ -1,21 +1,16 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"resolveJsonModule": true,
|
||||
"moduleResolution": "node",
|
||||
"esModuleInterop": true,
|
||||
"target": "es2017",
|
||||
"module": "commonjs",
|
||||
"sourceMap": false,
|
||||
"strict": true,
|
||||
"lib": [
|
||||
"es2019",
|
||||
"dom"
|
||||
]
|
||||
},
|
||||
"include": [
|
||||
"./**/*.ts"
|
||||
],
|
||||
"ts-node": {
|
||||
"transpileOnly": true
|
||||
}
|
||||
"compilerOptions": {
|
||||
"resolveJsonModule": true,
|
||||
"moduleResolution": "node",
|
||||
"esModuleInterop": true,
|
||||
"target": "es2017",
|
||||
"module": "commonjs",
|
||||
"sourceMap": false,
|
||||
"strict": true,
|
||||
"lib": ["es2019", "dom"]
|
||||
},
|
||||
"include": ["./**/*.ts"],
|
||||
"ts-node": {
|
||||
"transpileOnly": true
|
||||
}
|
||||
}
|
||||
|
2
src/@types/keytar.d.ts
vendored
2
src/@types/keytar.d.ts
vendored
@ -50,5 +50,5 @@ declare module "keytar" {
|
||||
*
|
||||
* @returns A promise for the array of found credentials.
|
||||
*/
|
||||
export function findCredentials(service: string): Promise<Array<{ account: string, password: string}>>;
|
||||
export function findCredentials(service: string): Promise<Array<{ account: string; password: string }>>;
|
||||
}
|
||||
|
@ -19,18 +19,11 @@ limitations under the License.
|
||||
|
||||
// Squirrel on windows starts the app with various flags as hooks to tell us when we've been installed/uninstalled etc.
|
||||
import "./squirrelhooks";
|
||||
import {
|
||||
app,
|
||||
BrowserWindow,
|
||||
Menu,
|
||||
autoUpdater,
|
||||
protocol,
|
||||
dialog,
|
||||
} from "electron";
|
||||
import { app, BrowserWindow, Menu, autoUpdater, protocol, dialog } from "electron";
|
||||
import AutoLaunch from "auto-launch";
|
||||
import path from "path";
|
||||
import windowStateKeeper from 'electron-window-state';
|
||||
import Store from 'electron-store';
|
||||
import windowStateKeeper from "electron-window-state";
|
||||
import Store from "electron-store";
|
||||
import fs, { promises as afs } from "fs";
|
||||
import { URL } from "url";
|
||||
import minimist from "minimist";
|
||||
@ -40,11 +33,11 @@ import "./keytar";
|
||||
import "./seshat";
|
||||
import "./settings";
|
||||
import * as tray from "./tray";
|
||||
import { buildMenuTemplate } from './vectormenu';
|
||||
import webContentsHandler from './webcontents-handler';
|
||||
import * as updater from './updater';
|
||||
import { getProfileFromDeeplink, protocolInit } from './protocol';
|
||||
import { _t, AppLocalization } from './language-helper';
|
||||
import { buildMenuTemplate } from "./vectormenu";
|
||||
import webContentsHandler from "./webcontents-handler";
|
||||
import * as updater from "./updater";
|
||||
import { getProfileFromDeeplink, protocolInit } from "./protocol";
|
||||
import { _t, AppLocalization } from "./language-helper";
|
||||
import Input = Electron.Input;
|
||||
|
||||
const argv = minimist(process.argv, {
|
||||
@ -65,8 +58,7 @@ if (argv["help"]) {
|
||||
console.log(" --no-update: Disable automatic updating.");
|
||||
console.log(" --hidden: Start the application hidden in the system tray.");
|
||||
console.log(" --help: Displays this help message.");
|
||||
console.log("And more such as --proxy, see:" +
|
||||
"https://electronjs.org/docs/api/command-line-switches");
|
||||
console.log("And more such as --proxy, see:" + "https://electronjs.org/docs/api/command-line-switches");
|
||||
app.exit();
|
||||
}
|
||||
|
||||
@ -74,7 +66,7 @@ if (argv["help"]) {
|
||||
// as soon as the app path is set, so pick a random path in it that must exist if it's a
|
||||
// real user data directory.
|
||||
function isRealUserDataDir(d: string): boolean {
|
||||
return fs.existsSync(path.join(d, 'IndexedDB'));
|
||||
return fs.existsSync(path.join(d, "IndexedDB"));
|
||||
}
|
||||
|
||||
// check if we are passed a profile in the SSO callback url
|
||||
@ -83,22 +75,22 @@ let userDataPath: string;
|
||||
const userDataPathInProtocol = getProfileFromDeeplink(argv["_"]);
|
||||
if (userDataPathInProtocol) {
|
||||
userDataPath = userDataPathInProtocol;
|
||||
} else if (argv['profile-dir']) {
|
||||
userDataPath = argv['profile-dir'];
|
||||
} else if (argv["profile-dir"]) {
|
||||
userDataPath = argv["profile-dir"];
|
||||
} else {
|
||||
let newUserDataPath = app.getPath('userData');
|
||||
if (argv['profile']) {
|
||||
newUserDataPath += '-' + argv['profile'];
|
||||
let newUserDataPath = app.getPath("userData");
|
||||
if (argv["profile"]) {
|
||||
newUserDataPath += "-" + argv["profile"];
|
||||
}
|
||||
const newUserDataPathExists = isRealUserDataDir(newUserDataPath);
|
||||
let oldUserDataPath = path.join(app.getPath('appData'), app.getName().replace('Element', 'Riot'));
|
||||
if (argv['profile']) {
|
||||
oldUserDataPath += '-' + argv['profile'];
|
||||
let oldUserDataPath = path.join(app.getPath("appData"), app.getName().replace("Element", "Riot"));
|
||||
if (argv["profile"]) {
|
||||
oldUserDataPath += "-" + argv["profile"];
|
||||
}
|
||||
|
||||
const oldUserDataPathExists = isRealUserDataDir(oldUserDataPath);
|
||||
console.log(newUserDataPath + " exists: " + (newUserDataPathExists ? 'yes' : 'no'));
|
||||
console.log(oldUserDataPath + " exists: " + (oldUserDataPathExists ? 'yes' : 'no'));
|
||||
console.log(newUserDataPath + " exists: " + (newUserDataPathExists ? "yes" : "no"));
|
||||
console.log(oldUserDataPath + " exists: " + (oldUserDataPathExists ? "yes" : "no"));
|
||||
if (!newUserDataPathExists && oldUserDataPathExists) {
|
||||
console.log("Using legacy user data path: " + oldUserDataPath);
|
||||
userDataPath = oldUserDataPath;
|
||||
@ -106,54 +98,53 @@ if (userDataPathInProtocol) {
|
||||
userDataPath = newUserDataPath;
|
||||
}
|
||||
}
|
||||
app.setPath('userData', userDataPath);
|
||||
app.setPath("userData", userDataPath);
|
||||
|
||||
async function tryPaths(name: string, root: string, rawPaths: string[]): Promise<string> {
|
||||
// Make everything relative to root
|
||||
const paths = rawPaths.map(p => path.join(root, p));
|
||||
const paths = rawPaths.map((p) => path.join(root, p));
|
||||
|
||||
for (const p of paths) {
|
||||
try {
|
||||
await afs.stat(p);
|
||||
return p + '/';
|
||||
} catch (e) {
|
||||
}
|
||||
return p + "/";
|
||||
} catch (e) {}
|
||||
}
|
||||
console.log(`Couldn't find ${name} files in any of: `);
|
||||
for (const p of paths) {
|
||||
console.log("\t"+path.resolve(p));
|
||||
console.log("\t" + path.resolve(p));
|
||||
}
|
||||
throw new Error(`Failed to find ${name} files`);
|
||||
}
|
||||
|
||||
const homeserverProps = ['default_is_url', 'default_hs_url', 'default_server_name', 'default_server_config'] as const;
|
||||
const homeserverProps = ["default_is_url", "default_hs_url", "default_server_name", "default_server_config"] as const;
|
||||
|
||||
// Find the webapp resources and set up things that require them
|
||||
async function setupGlobals(): Promise<void> {
|
||||
// find the webapp asar.
|
||||
asarPath = await tryPaths("webapp", __dirname, [
|
||||
// If run from the source checkout, this will be in the directory above
|
||||
'../webapp.asar',
|
||||
"../webapp.asar",
|
||||
// but if run from a packaged application, electron-main.js will be in
|
||||
// a different asar file so it will be two levels above
|
||||
'../../webapp.asar',
|
||||
"../../webapp.asar",
|
||||
// also try without the 'asar' suffix to allow symlinking in a directory
|
||||
'../webapp',
|
||||
"../webapp",
|
||||
// from a packaged application
|
||||
'../../webapp',
|
||||
"../../webapp",
|
||||
]);
|
||||
|
||||
// we assume the resources path is in the same place as the asar
|
||||
resPath = await tryPaths("res", path.dirname(asarPath), [
|
||||
// If run from the source checkout
|
||||
'res',
|
||||
"res",
|
||||
// if run from packaged application
|
||||
'',
|
||||
"",
|
||||
]);
|
||||
|
||||
try {
|
||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||
global.vectorConfig = require(asarPath + 'config.json');
|
||||
global.vectorConfig = require(asarPath + "config.json");
|
||||
} catch (e) {
|
||||
// it would be nice to check the error code here and bail if the config
|
||||
// is unparsable, but we get MODULE_NOT_FOUND in the case of a missing
|
||||
@ -165,15 +156,15 @@ async function setupGlobals(): Promise<void> {
|
||||
try {
|
||||
// Load local config and use it to override values from the one baked with the build
|
||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||
const localConfig = require(path.join(app.getPath('userData'), 'config.json'));
|
||||
const localConfig = require(path.join(app.getPath("userData"), "config.json"));
|
||||
|
||||
// If the local config has a homeserver defined, don't use the homeserver from the build
|
||||
// config. This is to avoid a problem where Riot thinks there are multiple homeservers
|
||||
// defined, and panics as a result.
|
||||
if (Object.keys(localConfig).find(k => homeserverProps.includes(<any>k))) {
|
||||
if (Object.keys(localConfig).find((k) => homeserverProps.includes(<any>k))) {
|
||||
// Rip out all the homeserver options from the vector config
|
||||
global.vectorConfig = Object.keys(global.vectorConfig)
|
||||
.filter(k => !homeserverProps.includes(<any>k))
|
||||
.filter((k) => !homeserverProps.includes(<any>k))
|
||||
.reduce((obj, key) => {
|
||||
obj[key] = global.vectorConfig[key];
|
||||
return obj;
|
||||
@ -185,9 +176,10 @@ async function setupGlobals(): Promise<void> {
|
||||
if (e instanceof SyntaxError) {
|
||||
dialog.showMessageBox({
|
||||
type: "error",
|
||||
title: `Your ${global.vectorConfig.brand || 'Element'} is misconfigured`,
|
||||
message: `Your custom ${global.vectorConfig.brand || 'Element'} configuration contains invalid JSON. ` +
|
||||
`Please correct the problem and reopen ${global.vectorConfig.brand || 'Element'}.`,
|
||||
title: `Your ${global.vectorConfig.brand || "Element"} is misconfigured`,
|
||||
message:
|
||||
`Your custom ${global.vectorConfig.brand || "Element"} configuration contains invalid JSON. ` +
|
||||
`Please correct the problem and reopen ${global.vectorConfig.brand || "Element"}.`,
|
||||
detail: e.message || "",
|
||||
});
|
||||
}
|
||||
@ -197,16 +189,16 @@ async function setupGlobals(): Promise<void> {
|
||||
|
||||
// The tray icon
|
||||
// It's important to call `path.join` so we don't end up with the packaged asar in the final path.
|
||||
const iconFile = `element.${process.platform === 'win32' ? 'ico' : 'png'}`;
|
||||
const iconFile = `element.${process.platform === "win32" ? "ico" : "png"}`;
|
||||
iconPath = path.join(resPath, "img", iconFile);
|
||||
global.trayConfig = {
|
||||
icon_path: iconPath,
|
||||
brand: global.vectorConfig.brand || 'Element',
|
||||
brand: global.vectorConfig.brand || "Element",
|
||||
};
|
||||
|
||||
// launcher
|
||||
global.launcher = new AutoLaunch({
|
||||
name: global.vectorConfig.brand || 'Element',
|
||||
name: global.vectorConfig.brand || "Element",
|
||||
isHidden: true,
|
||||
mac: {
|
||||
useLaunchAgent: true,
|
||||
@ -217,9 +209,9 @@ async function setupGlobals(): Promise<void> {
|
||||
async function moveAutoLauncher(): Promise<void> {
|
||||
// Look for an auto-launcher under 'Riot' and if we find one, port it's
|
||||
// enabled/disabled-ness over to the new 'Element' launcher
|
||||
if (!global.vectorConfig.brand || global.vectorConfig.brand === 'Element') {
|
||||
if (!global.vectorConfig.brand || global.vectorConfig.brand === "Element") {
|
||||
const oldLauncher = new AutoLaunch({
|
||||
name: 'Riot',
|
||||
name: "Riot",
|
||||
isHidden: true,
|
||||
mac: {
|
||||
useLaunchAgent: true,
|
||||
@ -238,26 +230,30 @@ global.store = new Store({ name: "electron-config" });
|
||||
global.appQuitting = false;
|
||||
|
||||
const exitShortcuts: Array<(input: Input, platform: string) => boolean> = [
|
||||
(input, platform): boolean => platform !== 'darwin' && input.alt && input.key.toUpperCase() === 'F4',
|
||||
(input, platform): boolean => platform !== 'darwin' && input.control && input.key.toUpperCase() === 'Q',
|
||||
(input, platform): boolean => platform === 'darwin' && input.meta && input.key.toUpperCase() === 'Q',
|
||||
(input, platform): boolean => platform !== "darwin" && input.alt && input.key.toUpperCase() === "F4",
|
||||
(input, platform): boolean => platform !== "darwin" && input.control && input.key.toUpperCase() === "Q",
|
||||
(input, platform): boolean => platform === "darwin" && input.meta && input.key.toUpperCase() === "Q",
|
||||
];
|
||||
|
||||
const warnBeforeExit = (event: Event, input: Input): void => {
|
||||
const shouldWarnBeforeExit = global.store.get('warnBeforeExit', true);
|
||||
const shouldWarnBeforeExit = global.store.get("warnBeforeExit", true);
|
||||
const exitShortcutPressed =
|
||||
input.type === 'keyDown' && exitShortcuts.some(shortcutFn => shortcutFn(input, process.platform));
|
||||
input.type === "keyDown" && exitShortcuts.some((shortcutFn) => shortcutFn(input, process.platform));
|
||||
|
||||
if (shouldWarnBeforeExit && exitShortcutPressed && global.mainWindow) {
|
||||
const shouldCancelCloseRequest = dialog.showMessageBoxSync(global.mainWindow, {
|
||||
type: "question",
|
||||
buttons: [_t("Cancel"), _t("Close %(brand)s", {
|
||||
brand: global.vectorConfig.brand || 'Element',
|
||||
})],
|
||||
message: _t("Are you sure you want to quit?"),
|
||||
defaultId: 1,
|
||||
cancelId: 0,
|
||||
}) === 0;
|
||||
const shouldCancelCloseRequest =
|
||||
dialog.showMessageBoxSync(global.mainWindow, {
|
||||
type: "question",
|
||||
buttons: [
|
||||
_t("Cancel"),
|
||||
_t("Close %(brand)s", {
|
||||
brand: global.vectorConfig.brand || "Element",
|
||||
}),
|
||||
],
|
||||
message: _t("Are you sure you want to quit?"),
|
||||
defaultId: 1,
|
||||
cancelId: 0,
|
||||
}) === 0;
|
||||
|
||||
if (shouldCancelCloseRequest) {
|
||||
event.preventDefault();
|
||||
@ -271,18 +267,18 @@ const warnBeforeExit = (event: Event, input: Input): void => {
|
||||
// no other way to catch this error).
|
||||
// Assuming we generally run from the console when developing,
|
||||
// this is far preferable.
|
||||
process.on('uncaughtException', function(error: Error): void {
|
||||
console.log('Unhandled exception', error);
|
||||
process.on("uncaughtException", function (error: Error): void {
|
||||
console.log("Unhandled exception", error);
|
||||
});
|
||||
|
||||
app.commandLine.appendSwitch('--enable-usermedia-screen-capturing');
|
||||
if (!app.commandLine.hasSwitch('enable-features')) {
|
||||
app.commandLine.appendSwitch('enable-features', 'WebRTCPipeWireCapturer');
|
||||
app.commandLine.appendSwitch("--enable-usermedia-screen-capturing");
|
||||
if (!app.commandLine.hasSwitch("enable-features")) {
|
||||
app.commandLine.appendSwitch("enable-features", "WebRTCPipeWireCapturer");
|
||||
}
|
||||
|
||||
const gotLock = app.requestSingleInstanceLock();
|
||||
if (!gotLock) {
|
||||
console.log('Other instance detected: exiting');
|
||||
console.log("Other instance detected: exiting");
|
||||
app.exit();
|
||||
}
|
||||
|
||||
@ -294,14 +290,16 @@ protocolInit();
|
||||
// work.
|
||||
// Also mark it as secure (ie. accessing resources from this
|
||||
// protocol and HTTPS won't trigger mixed content warnings).
|
||||
protocol.registerSchemesAsPrivileged([{
|
||||
scheme: 'vector',
|
||||
privileges: {
|
||||
standard: true,
|
||||
secure: true,
|
||||
supportFetchAPI: true,
|
||||
protocol.registerSchemesAsPrivileged([
|
||||
{
|
||||
scheme: "vector",
|
||||
privileges: {
|
||||
standard: true,
|
||||
secure: true,
|
||||
supportFetchAPI: true,
|
||||
},
|
||||
},
|
||||
}]);
|
||||
]);
|
||||
|
||||
// Turn the sandbox on for *all* windows we might generate. Doing this means we don't
|
||||
// have to specify a `sandbox: true` to each BrowserWindow.
|
||||
@ -315,15 +313,15 @@ protocol.registerSchemesAsPrivileged([{
|
||||
app.enableSandbox();
|
||||
|
||||
// We disable media controls here. We do this because calls use audio and video elements and they sometimes capture the media keys. See https://github.com/vector-im/element-web/issues/15704
|
||||
app.commandLine.appendSwitch('disable-features', 'HardwareMediaKeyHandling,MediaSessionService');
|
||||
app.commandLine.appendSwitch("disable-features", "HardwareMediaKeyHandling,MediaSessionService");
|
||||
|
||||
// Disable hardware acceleration if the setting has been set.
|
||||
if (global.store.get('disableHardwareAcceleration', false) === true) {
|
||||
if (global.store.get("disableHardwareAcceleration", false) === true) {
|
||||
console.log("Disabling hardware acceleration.");
|
||||
app.disableHardwareAcceleration();
|
||||
}
|
||||
|
||||
app.on('ready', async () => {
|
||||
app.on("ready", async () => {
|
||||
try {
|
||||
await setupGlobals();
|
||||
await moveAutoLauncher();
|
||||
@ -337,51 +335,51 @@ app.on('ready', async () => {
|
||||
return;
|
||||
}
|
||||
|
||||
if (argv['devtools']) {
|
||||
if (argv["devtools"]) {
|
||||
try {
|
||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||
const { default: installExt, REACT_DEVELOPER_TOOLS, REACT_PERF } = require('electron-devtools-installer');
|
||||
const { default: installExt, REACT_DEVELOPER_TOOLS, REACT_PERF } = require("electron-devtools-installer");
|
||||
installExt(REACT_DEVELOPER_TOOLS)
|
||||
.then((name: string) => console.log(`Added Extension: ${name}`))
|
||||
.catch((err: unknown) => console.log('An error occurred: ', err));
|
||||
.catch((err: unknown) => console.log("An error occurred: ", err));
|
||||
installExt(REACT_PERF)
|
||||
.then((name: string) => console.log(`Added Extension: ${name}`))
|
||||
.catch((err: unknown) => console.log('An error occurred: ', err));
|
||||
.catch((err: unknown) => console.log("An error occurred: ", err));
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
}
|
||||
}
|
||||
|
||||
protocol.registerFileProtocol('vector', (request, callback) => {
|
||||
if (request.method !== 'GET') {
|
||||
protocol.registerFileProtocol("vector", (request, callback) => {
|
||||
if (request.method !== "GET") {
|
||||
callback({ error: -322 }); // METHOD_NOT_SUPPORTED from chromium/src/net/base/net_error_list.h
|
||||
return null;
|
||||
}
|
||||
|
||||
const parsedUrl = new URL(request.url);
|
||||
if (parsedUrl.protocol !== 'vector:') {
|
||||
if (parsedUrl.protocol !== "vector:") {
|
||||
callback({ error: -302 }); // UNKNOWN_URL_SCHEME
|
||||
return;
|
||||
}
|
||||
if (parsedUrl.host !== 'vector') {
|
||||
if (parsedUrl.host !== "vector") {
|
||||
callback({ error: -105 }); // NAME_NOT_RESOLVED
|
||||
return;
|
||||
}
|
||||
|
||||
const target = parsedUrl.pathname.split('/');
|
||||
const target = parsedUrl.pathname.split("/");
|
||||
|
||||
// path starts with a '/'
|
||||
if (target[0] !== '') {
|
||||
if (target[0] !== "") {
|
||||
callback({ error: -6 }); // FILE_NOT_FOUND
|
||||
return;
|
||||
}
|
||||
|
||||
if (target[target.length - 1] == '') {
|
||||
target[target.length - 1] = 'index.html';
|
||||
if (target[target.length - 1] == "") {
|
||||
target[target.length - 1] = "index.html";
|
||||
}
|
||||
|
||||
let baseDir: string;
|
||||
if (target[1] === 'webapp') {
|
||||
if (target[1] === "webapp") {
|
||||
baseDir = asarPath;
|
||||
} else {
|
||||
callback({ error: -6 }); // FILE_NOT_FOUND
|
||||
@ -393,7 +391,7 @@ app.on('ready', async () => {
|
||||
baseDir = path.normalize(baseDir);
|
||||
|
||||
const relTarget = path.normalize(path.join(...target.slice(2)));
|
||||
if (relTarget.startsWith('..')) {
|
||||
if (relTarget.startsWith("..")) {
|
||||
callback({ error: -6 }); // FILE_NOT_FOUND
|
||||
return;
|
||||
}
|
||||
@ -404,13 +402,13 @@ app.on('ready', async () => {
|
||||
});
|
||||
});
|
||||
|
||||
if (argv['no-update']) {
|
||||
if (argv["no-update"]) {
|
||||
console.log('Auto update disabled via command line flag "--no-update"');
|
||||
} else if (global.vectorConfig['update_base_url']) {
|
||||
console.log(`Starting auto update with base URL: ${global.vectorConfig['update_base_url']}`);
|
||||
updater.start(global.vectorConfig['update_base_url']);
|
||||
} else if (global.vectorConfig["update_base_url"]) {
|
||||
console.log(`Starting auto update with base URL: ${global.vectorConfig["update_base_url"]}`);
|
||||
updater.start(global.vectorConfig["update_base_url"]);
|
||||
} else {
|
||||
console.log('No update_base_url is defined: auto update is disabled');
|
||||
console.log("No update_base_url is defined: auto update is disabled");
|
||||
}
|
||||
|
||||
// Load the previous window state with fallback to defaults
|
||||
@ -422,11 +420,11 @@ app.on('ready', async () => {
|
||||
const preloadScript = path.normalize(`${__dirname}/preload.js`);
|
||||
global.mainWindow = new BrowserWindow({
|
||||
// https://www.electronjs.org/docs/faq#the-font-looks-blurry-what-is-this-and-what-can-i-do
|
||||
backgroundColor: '#fff',
|
||||
backgroundColor: "#fff",
|
||||
|
||||
icon: iconPath,
|
||||
show: false,
|
||||
autoHideMenuBar: global.store.get('autoHideMenuBar', true),
|
||||
autoHideMenuBar: global.store.get("autoHideMenuBar", true),
|
||||
|
||||
x: mainWindowState.x,
|
||||
y: mainWindowState.y,
|
||||
@ -440,20 +438,20 @@ app.on('ready', async () => {
|
||||
webgl: true,
|
||||
},
|
||||
});
|
||||
global.mainWindow.loadURL('vector://vector/webapp/');
|
||||
global.mainWindow.loadURL("vector://vector/webapp/");
|
||||
|
||||
// Handle spellchecker
|
||||
// For some reason spellCheckerEnabled isn't persisted, so we have to use the store here
|
||||
global.mainWindow.webContents.session.setSpellCheckerEnabled(global.store.get("spellCheckerEnabled", true));
|
||||
|
||||
// Create trayIcon icon
|
||||
if (global.store.get('minimizeToTray', true)) tray.create(global.trayConfig);
|
||||
if (global.store.get("minimizeToTray", true)) tray.create(global.trayConfig);
|
||||
|
||||
global.mainWindow.once('ready-to-show', () => {
|
||||
global.mainWindow.once("ready-to-show", () => {
|
||||
if (!global.mainWindow) return;
|
||||
mainWindowState.manage(global.mainWindow);
|
||||
|
||||
if (!argv['hidden']) {
|
||||
if (!argv["hidden"]) {
|
||||
global.mainWindow.show();
|
||||
} else {
|
||||
// hide here explicitly because window manage above sometimes shows it
|
||||
@ -461,21 +459,21 @@ app.on('ready', async () => {
|
||||
}
|
||||
});
|
||||
|
||||
global.mainWindow.webContents.on('before-input-event', warnBeforeExit);
|
||||
global.mainWindow.webContents.on("before-input-event", warnBeforeExit);
|
||||
|
||||
global.mainWindow.on('closed', () => {
|
||||
global.mainWindow.on("closed", () => {
|
||||
global.mainWindow = null;
|
||||
});
|
||||
global.mainWindow.on('close', async (e) => {
|
||||
global.mainWindow.on("close", async (e) => {
|
||||
// If we are not quitting and have a tray icon then minimize to tray
|
||||
if (!global.appQuitting && (tray.hasTray() || process.platform === 'darwin')) {
|
||||
if (!global.appQuitting && (tray.hasTray() || process.platform === "darwin")) {
|
||||
// On Mac, closing the window just hides it
|
||||
// (this is generally how single-window Mac apps
|
||||
// behave, eg. Mail.app)
|
||||
e.preventDefault();
|
||||
|
||||
if (global.mainWindow?.isFullScreen()) {
|
||||
global.mainWindow.once('leave-full-screen', () => global.mainWindow?.hide());
|
||||
global.mainWindow.once("leave-full-screen", () => global.mainWindow?.hide());
|
||||
|
||||
global.mainWindow.setFullScreen(false);
|
||||
} else {
|
||||
@ -486,12 +484,12 @@ app.on('ready', async () => {
|
||||
}
|
||||
});
|
||||
|
||||
if (process.platform === 'win32') {
|
||||
if (process.platform === "win32") {
|
||||
// Handle forward/backward mouse buttons in Windows
|
||||
global.mainWindow.on('app-command', (e, cmd) => {
|
||||
if (cmd === 'browser-backward' && global.mainWindow?.webContents.canGoBack()) {
|
||||
global.mainWindow.on("app-command", (e, cmd) => {
|
||||
if (cmd === "browser-backward" && global.mainWindow?.webContents.canGoBack()) {
|
||||
global.mainWindow.webContents.goBack();
|
||||
} else if (cmd === 'browser-forward' && global.mainWindow?.webContents.canGoForward()) {
|
||||
} else if (cmd === "browser-forward" && global.mainWindow?.webContents.canGoForward()) {
|
||||
global.mainWindow.webContents.goForward();
|
||||
}
|
||||
});
|
||||
@ -501,32 +499,29 @@ app.on('ready', async () => {
|
||||
|
||||
global.appLocalization = new AppLocalization({
|
||||
store: global.store,
|
||||
components: [
|
||||
(): void => tray.initApplicationMenu(),
|
||||
(): void => Menu.setApplicationMenu(buildMenuTemplate()),
|
||||
],
|
||||
components: [(): void => tray.initApplicationMenu(), (): void => Menu.setApplicationMenu(buildMenuTemplate())],
|
||||
});
|
||||
});
|
||||
|
||||
app.on('window-all-closed', () => {
|
||||
app.on("window-all-closed", () => {
|
||||
app.quit();
|
||||
});
|
||||
|
||||
app.on('activate', () => {
|
||||
app.on("activate", () => {
|
||||
global.mainWindow?.show();
|
||||
});
|
||||
|
||||
function beforeQuit(): void {
|
||||
global.appQuitting = true;
|
||||
global.mainWindow?.webContents.send('before-quit');
|
||||
global.mainWindow?.webContents.send("before-quit");
|
||||
}
|
||||
|
||||
app.on('before-quit', beforeQuit);
|
||||
autoUpdater.on('before-quit-for-update', beforeQuit);
|
||||
app.on("before-quit", beforeQuit);
|
||||
autoUpdater.on("before-quit-for-update", beforeQuit);
|
||||
|
||||
app.on('second-instance', (ev, commandLine, workingDirectory) => {
|
||||
app.on("second-instance", (ev, commandLine, workingDirectory) => {
|
||||
// If other instance launched with --hidden then skip showing window
|
||||
if (commandLine.includes('--hidden')) return;
|
||||
if (commandLine.includes("--hidden")) return;
|
||||
|
||||
// Someone tried to run a second instance, we should focus our window.
|
||||
if (global.mainWindow) {
|
||||
@ -540,4 +535,4 @@ app.on('second-instance', (ev, commandLine, workingDirectory) => {
|
||||
// installer uses for the shortcut icon.
|
||||
// This makes notifications work on windows 8.1 (and is
|
||||
// a noop on other platforms).
|
||||
app.setAppUserModelId('com.squirrel.element-desktop.Element');
|
||||
app.setAppUserModelId("com.squirrel.element-desktop.Element");
|
||||
|
67
src/ipc.ts
67
src/ipc.ts
@ -22,8 +22,8 @@ import { randomArray } from "./utils";
|
||||
import { Settings } from "./settings";
|
||||
import { keytar } from "./keytar";
|
||||
|
||||
ipcMain.on('setBadgeCount', function(_ev: IpcMainEvent, count: number): void {
|
||||
if (process.platform !== 'win32') {
|
||||
ipcMain.on("setBadgeCount", function (_ev: IpcMainEvent, count: number): void {
|
||||
if (process.platform !== "win32") {
|
||||
// only set badgeCount on Mac/Linux, the docs say that only those platforms support it but turns out Electron
|
||||
// has some Windows support too, and in some Windows environments this leads to two badges rendering atop
|
||||
// each other. See https://github.com/vector-im/element-web/issues/16942
|
||||
@ -35,10 +35,10 @@ ipcMain.on('setBadgeCount', function(_ev: IpcMainEvent, count: number): void {
|
||||
});
|
||||
|
||||
let focusHandlerAttached = false;
|
||||
ipcMain.on('loudNotification', function(): void {
|
||||
if (process.platform === 'win32' && global.mainWindow && !global.mainWindow.isFocused() && !focusHandlerAttached) {
|
||||
ipcMain.on("loudNotification", function (): void {
|
||||
if (process.platform === "win32" && global.mainWindow && !global.mainWindow.isFocused() && !focusHandlerAttached) {
|
||||
global.mainWindow.flashFrame(true);
|
||||
global.mainWindow.once('focus', () => {
|
||||
global.mainWindow.once("focus", () => {
|
||||
global.mainWindow?.flashFrame(false);
|
||||
focusHandlerAttached = false;
|
||||
});
|
||||
@ -47,17 +47,17 @@ ipcMain.on('loudNotification', function(): void {
|
||||
});
|
||||
|
||||
let powerSaveBlockerId: number | null = null;
|
||||
ipcMain.on('app_onAction', function(_ev: IpcMainEvent, payload) {
|
||||
ipcMain.on("app_onAction", function (_ev: IpcMainEvent, payload) {
|
||||
switch (payload.action) {
|
||||
case 'call_state': {
|
||||
case "call_state": {
|
||||
if (powerSaveBlockerId !== null && powerSaveBlocker.isStarted(powerSaveBlockerId)) {
|
||||
if (payload.state === 'ended') {
|
||||
if (payload.state === "ended") {
|
||||
powerSaveBlocker.stop(powerSaveBlockerId);
|
||||
powerSaveBlockerId = null;
|
||||
}
|
||||
} else {
|
||||
if (powerSaveBlockerId === null && payload.state === 'connected') {
|
||||
powerSaveBlockerId = powerSaveBlocker.start('prevent-display-sleep');
|
||||
if (powerSaveBlockerId === null && payload.state === "connected") {
|
||||
powerSaveBlockerId = powerSaveBlocker.start("prevent-display-sleep");
|
||||
}
|
||||
}
|
||||
break;
|
||||
@ -65,35 +65,35 @@ ipcMain.on('app_onAction', function(_ev: IpcMainEvent, payload) {
|
||||
}
|
||||
});
|
||||
|
||||
ipcMain.on('ipcCall', async function(_ev: IpcMainEvent, payload) {
|
||||
ipcMain.on("ipcCall", async function (_ev: IpcMainEvent, payload) {
|
||||
if (!global.mainWindow) return;
|
||||
|
||||
const args = payload.args || [];
|
||||
let ret: any;
|
||||
|
||||
switch (payload.name) {
|
||||
case 'getUpdateFeedUrl':
|
||||
case "getUpdateFeedUrl":
|
||||
ret = autoUpdater.getFeedURL();
|
||||
break;
|
||||
case 'getSettingValue': {
|
||||
case "getSettingValue": {
|
||||
const [settingName] = args;
|
||||
const setting = Settings[settingName];
|
||||
ret = await setting.read();
|
||||
break;
|
||||
}
|
||||
case 'setSettingValue': {
|
||||
case "setSettingValue": {
|
||||
const [settingName, value] = args;
|
||||
const setting = Settings[settingName];
|
||||
await setting.write(value);
|
||||
break;
|
||||
}
|
||||
case 'setLanguage':
|
||||
case "setLanguage":
|
||||
global.appLocalization.setAppLocale(args[0]);
|
||||
break;
|
||||
case 'getAppVersion':
|
||||
case "getAppVersion":
|
||||
ret = app.getVersion();
|
||||
break;
|
||||
case 'focusWindow':
|
||||
case "focusWindow":
|
||||
if (global.mainWindow.isMinimized()) {
|
||||
global.mainWindow.restore();
|
||||
} else if (!global.mainWindow.isVisible()) {
|
||||
@ -102,31 +102,31 @@ ipcMain.on('ipcCall', async function(_ev: IpcMainEvent, payload) {
|
||||
global.mainWindow.focus();
|
||||
}
|
||||
break;
|
||||
case 'getConfig':
|
||||
case "getConfig":
|
||||
ret = global.vectorConfig;
|
||||
break;
|
||||
case 'navigateBack':
|
||||
case "navigateBack":
|
||||
if (global.mainWindow.webContents.canGoBack()) {
|
||||
global.mainWindow.webContents.goBack();
|
||||
}
|
||||
break;
|
||||
case 'navigateForward':
|
||||
case "navigateForward":
|
||||
if (global.mainWindow.webContents.canGoForward()) {
|
||||
global.mainWindow.webContents.goForward();
|
||||
}
|
||||
break;
|
||||
case 'setSpellCheckEnabled':
|
||||
if (typeof args[0] !== 'boolean') return;
|
||||
case "setSpellCheckEnabled":
|
||||
if (typeof args[0] !== "boolean") return;
|
||||
|
||||
global.mainWindow.webContents.session.setSpellCheckerEnabled(args[0]);
|
||||
global.store.set("spellCheckerEnabled", args[0]);
|
||||
break;
|
||||
|
||||
case 'getSpellCheckEnabled':
|
||||
case "getSpellCheckEnabled":
|
||||
ret = global.store.get("spellCheckerEnabled", true);
|
||||
break;
|
||||
|
||||
case 'setSpellCheckLanguages':
|
||||
case "setSpellCheckLanguages":
|
||||
try {
|
||||
global.mainWindow.webContents.session.setSpellCheckerLanguages(args[0]);
|
||||
} catch (er) {
|
||||
@ -134,18 +134,18 @@ ipcMain.on('ipcCall', async function(_ev: IpcMainEvent, payload) {
|
||||
}
|
||||
break;
|
||||
|
||||
case 'getSpellCheckLanguages':
|
||||
case "getSpellCheckLanguages":
|
||||
ret = global.mainWindow.webContents.session.getSpellCheckerLanguages();
|
||||
break;
|
||||
case 'getAvailableSpellCheckLanguages':
|
||||
case "getAvailableSpellCheckLanguages":
|
||||
ret = global.mainWindow.webContents.session.availableSpellCheckerLanguages;
|
||||
break;
|
||||
|
||||
case 'startSSOFlow':
|
||||
case "startSSOFlow":
|
||||
recordSSOSession(args[0]);
|
||||
break;
|
||||
|
||||
case 'getPickleKey':
|
||||
case "getPickleKey":
|
||||
try {
|
||||
ret = await keytar?.getPassword("element.io", `${args[0]}|${args[1]}`);
|
||||
// migrate from riot.im (remove once we think there will no longer be
|
||||
@ -160,7 +160,7 @@ ipcMain.on('ipcCall', async function(_ev: IpcMainEvent, payload) {
|
||||
}
|
||||
break;
|
||||
|
||||
case 'createPickleKey':
|
||||
case "createPickleKey":
|
||||
try {
|
||||
const pickleKey = await randomArray(32);
|
||||
await keytar?.setPassword("element.io", `${args[0]}|${args[1]}`, pickleKey);
|
||||
@ -170,7 +170,7 @@ ipcMain.on('ipcCall', async function(_ev: IpcMainEvent, payload) {
|
||||
}
|
||||
break;
|
||||
|
||||
case 'destroyPickleKey':
|
||||
case "destroyPickleKey":
|
||||
try {
|
||||
await keytar?.deletePassword("element.io", `${args[0]}|${args[1]}`);
|
||||
// migrate from riot.im (remove once we think there will no longer be
|
||||
@ -178,7 +178,7 @@ ipcMain.on('ipcCall', async function(_ev: IpcMainEvent, payload) {
|
||||
await keytar?.deletePassword("riot.im", `${args[0]}|${args[1]}`);
|
||||
} catch (e) {}
|
||||
break;
|
||||
case 'getDesktopCapturerSources':
|
||||
case "getDesktopCapturerSources":
|
||||
ret = (await desktopCapturer.getSources(args[0])).map((source) => ({
|
||||
id: source.id,
|
||||
name: source.name,
|
||||
@ -187,16 +187,15 @@ ipcMain.on('ipcCall', async function(_ev: IpcMainEvent, payload) {
|
||||
break;
|
||||
|
||||
default:
|
||||
global.mainWindow.webContents.send('ipcReply', {
|
||||
global.mainWindow.webContents.send("ipcReply", {
|
||||
id: payload.id,
|
||||
error: "Unknown IPC Call: " + payload.name,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
global.mainWindow.webContents.send('ipcReply', {
|
||||
global.mainWindow.webContents.send("ipcReply", {
|
||||
id: payload.id,
|
||||
reply: ret,
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -19,7 +19,7 @@ import type * as Keytar from "keytar"; // Hak dependency type
|
||||
let keytar: typeof Keytar | undefined;
|
||||
try {
|
||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||
keytar = require('keytar');
|
||||
keytar = require("keytar");
|
||||
} catch (e) {
|
||||
if ((<NodeJS.ErrnoException>e).code === "MODULE_NOT_FOUND") {
|
||||
console.log("Keytar isn't installed; secure key storage is disabled.");
|
||||
|
@ -16,9 +16,9 @@ limitations under the License.
|
||||
|
||||
import counterpart from "counterpart";
|
||||
|
||||
import type Store from 'electron-store';
|
||||
import type Store from "electron-store";
|
||||
|
||||
const FALLBACK_LOCALE = 'en';
|
||||
const FALLBACK_LOCALE = "en";
|
||||
|
||||
export function _td(text: string): string {
|
||||
return text;
|
||||
@ -44,11 +44,11 @@ export function _t(text: string, variables: IVariables = {}): string {
|
||||
Object.keys(variables).forEach((key) => {
|
||||
if (variables[key] === undefined) {
|
||||
console.warn("safeCounterpartTranslate called with undefined interpolation name: " + key);
|
||||
variables[key] = 'undefined';
|
||||
variables[key] = "undefined";
|
||||
}
|
||||
if (variables[key] === null) {
|
||||
console.warn("safeCounterpartTranslate called with null interpolation name: " + key);
|
||||
variables[key] = 'null';
|
||||
variables[key] = "null";
|
||||
}
|
||||
});
|
||||
let translated = counterpart.translate(text, variables);
|
||||
@ -71,10 +71,10 @@ export class AppLocalization {
|
||||
private readonly store: TypedStore;
|
||||
private readonly localizedComponents?: Set<Component>;
|
||||
|
||||
public constructor({ store, components = [] }: { store: TypedStore, components: Component[] }) {
|
||||
public constructor({ store, components = [] }: { store: TypedStore; components: Component[] }) {
|
||||
counterpart.registerTranslations(FALLBACK_LOCALE, this.fetchTranslationJson("en_EN"));
|
||||
counterpart.setFallbackLocale(FALLBACK_LOCALE);
|
||||
counterpart.setSeparator('|');
|
||||
counterpart.setSeparator("|");
|
||||
|
||||
if (Array.isArray(components)) {
|
||||
this.localizedComponents = new Set(components);
|
||||
@ -119,7 +119,7 @@ export class AppLocalization {
|
||||
locales = [locales];
|
||||
}
|
||||
|
||||
const loadedLocales = locales.filter(locale => {
|
||||
const loadedLocales = locales.filter((locale) => {
|
||||
const translations = this.fetchTranslationJson(locale);
|
||||
if (translations !== null) {
|
||||
counterpart.registerTranslations(locale, translations);
|
||||
@ -135,7 +135,7 @@ export class AppLocalization {
|
||||
|
||||
public resetLocalizedUI(): void {
|
||||
console.log("Resetting the UI components after locale change");
|
||||
this.localizedComponents?.forEach(componentSetup => {
|
||||
this.localizedComponents?.forEach((componentSetup) => {
|
||||
if (typeof componentSetup === "function") {
|
||||
componentSetup();
|
||||
}
|
||||
|
@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
import { ipcRenderer, contextBridge, IpcRendererEvent } from 'electron';
|
||||
import { ipcRenderer, contextBridge, IpcRendererEvent } from "electron";
|
||||
|
||||
// Expose only expected IPC wrapper APIs to the renderer process to avoid
|
||||
// handing out generalised messaging access.
|
||||
@ -36,22 +36,19 @@ const CHANNELS = [
|
||||
"userDownloadAction",
|
||||
];
|
||||
|
||||
contextBridge.exposeInMainWorld(
|
||||
"electron",
|
||||
{
|
||||
on(channel: string, listener: (event: IpcRendererEvent, ...args: any[]) => void): void {
|
||||
if (!CHANNELS.includes(channel)) {
|
||||
console.error(`Unknown IPC channel ${channel} ignored`);
|
||||
return;
|
||||
}
|
||||
ipcRenderer.on(channel, listener);
|
||||
},
|
||||
send(channel: string, ...args: any[]): void {
|
||||
if (!CHANNELS.includes(channel)) {
|
||||
console.error(`Unknown IPC channel ${channel} ignored`);
|
||||
return;
|
||||
}
|
||||
ipcRenderer.send(channel, ...args);
|
||||
},
|
||||
contextBridge.exposeInMainWorld("electron", {
|
||||
on(channel: string, listener: (event: IpcRendererEvent, ...args: any[]) => void): void {
|
||||
if (!CHANNELS.includes(channel)) {
|
||||
console.error(`Unknown IPC channel ${channel} ignored`);
|
||||
return;
|
||||
}
|
||||
ipcRenderer.on(channel, listener);
|
||||
},
|
||||
);
|
||||
send(channel: string, ...args: any[]): void {
|
||||
if (!CHANNELS.includes(channel)) {
|
||||
console.error(`Unknown IPC channel ${channel} ignored`);
|
||||
return;
|
||||
}
|
||||
ipcRenderer.send(channel, ...args);
|
||||
},
|
||||
});
|
||||
|
@ -67,7 +67,7 @@ function writeStore(data: Record<string, string>): void {
|
||||
}
|
||||
|
||||
export function recordSSOSession(sessionID: string): void {
|
||||
const userDataPath = app.getPath('userData');
|
||||
const userDataPath = app.getPath("userData");
|
||||
const store = readStore();
|
||||
for (const key in store) {
|
||||
// ensure each instance only has one (the latest) session ID to prevent the file growing unbounded
|
||||
@ -82,7 +82,7 @@ export function recordSSOSession(sessionID: string): void {
|
||||
|
||||
export function getProfileFromDeeplink(args: string[]): string | undefined {
|
||||
// check if we are passed a profile in the SSO callback url
|
||||
const deeplinkUrl = args.find(arg => arg.startsWith(PROTOCOL + '//'));
|
||||
const deeplinkUrl = args.find((arg) => arg.startsWith(PROTOCOL + "//"));
|
||||
if (deeplinkUrl?.includes(SEARCH_PARAM)) {
|
||||
const parsedUrl = new URL(deeplinkUrl);
|
||||
if (parsedUrl.protocol === PROTOCOL) {
|
||||
@ -98,25 +98,26 @@ export function protocolInit(): void {
|
||||
// get all args except `hidden` as it'd mean the app would not get focused
|
||||
// XXX: passing args to protocol handlers only works on Windows, so unpackaged deep-linking
|
||||
// --profile/--profile-dir are passed via the SEARCH_PARAM var in the callback url
|
||||
const args = process.argv.slice(1).filter(arg => arg !== "--hidden" && arg !== "-hidden");
|
||||
const args = process.argv.slice(1).filter((arg) => arg !== "--hidden" && arg !== "-hidden");
|
||||
if (app.isPackaged) {
|
||||
app.setAsDefaultProtocolClient('element', process.execPath, args);
|
||||
} else if (process.platform === 'win32') { // on Mac/Linux this would just cause the electron binary to open
|
||||
app.setAsDefaultProtocolClient("element", process.execPath, args);
|
||||
} else if (process.platform === "win32") {
|
||||
// on Mac/Linux this would just cause the electron binary to open
|
||||
// special handler for running without being packaged, e.g `electron .` by passing our app path to electron
|
||||
app.setAsDefaultProtocolClient('element', process.execPath, [app.getAppPath(), ...args]);
|
||||
app.setAsDefaultProtocolClient("element", process.execPath, [app.getAppPath(), ...args]);
|
||||
}
|
||||
|
||||
if (process.platform === 'darwin') {
|
||||
if (process.platform === "darwin") {
|
||||
// Protocol handler for macos
|
||||
app.on('open-url', function(ev, url) {
|
||||
app.on("open-url", function (ev, url) {
|
||||
ev.preventDefault();
|
||||
processUrl(url);
|
||||
});
|
||||
} else {
|
||||
// Protocol handler for win32/Linux
|
||||
app.on('second-instance', (ev, commandLine) => {
|
||||
app.on("second-instance", (ev, commandLine) => {
|
||||
const url = commandLine[commandLine.length - 1];
|
||||
if (!url.startsWith(PROTOCOL + '//')) return;
|
||||
if (!url.startsWith(PROTOCOL + "//")) return;
|
||||
processUrl(url);
|
||||
});
|
||||
}
|
||||
|
@ -34,7 +34,7 @@ let ReindexError: typeof ReindexErrorType;
|
||||
|
||||
try {
|
||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||
const seshatModule = require('matrix-seshat');
|
||||
const seshatModule = require("matrix-seshat");
|
||||
Seshat = seshatModule.Seshat;
|
||||
SeshatRecovery = seshatModule.SeshatRecovery;
|
||||
ReindexError = seshatModule.ReindexError;
|
||||
@ -75,29 +75,29 @@ const deleteContents = async (p: string): Promise<void> => {
|
||||
}
|
||||
};
|
||||
|
||||
ipcMain.on('seshat', async function(_ev: IpcMainEvent, payload): Promise<void> {
|
||||
ipcMain.on("seshat", async function (_ev: IpcMainEvent, payload): Promise<void> {
|
||||
if (!global.mainWindow) return;
|
||||
|
||||
// We do this here to ensure we get the path after --profile has been resolved
|
||||
const eventStorePath = path.join(app.getPath('userData'), 'EventStore');
|
||||
const eventStorePath = path.join(app.getPath("userData"), "EventStore");
|
||||
|
||||
const sendError = (id: string, e: Error): void => {
|
||||
const error = {
|
||||
message: e.message,
|
||||
};
|
||||
|
||||
global.mainWindow?.webContents.send('seshatReply', { id, error });
|
||||
global.mainWindow?.webContents.send("seshatReply", { id, error });
|
||||
};
|
||||
|
||||
const args = payload.args || [];
|
||||
let ret: any;
|
||||
|
||||
switch (payload.name) {
|
||||
case 'supportsEventIndexing':
|
||||
case "supportsEventIndexing":
|
||||
ret = seshatSupported;
|
||||
break;
|
||||
|
||||
case 'initEventIndex':
|
||||
case "initEventIndex":
|
||||
if (eventIndex === null) {
|
||||
const userId = args[0];
|
||||
const deviceId = args[1];
|
||||
@ -127,8 +127,7 @@ ipcMain.on('seshat', async function(_ev: IpcMainEvent, payload): Promise<void> {
|
||||
|
||||
try {
|
||||
await deleteContents(eventStorePath);
|
||||
} catch (e) {
|
||||
}
|
||||
} catch (e) {}
|
||||
} else {
|
||||
await recoveryIndex.reindex();
|
||||
}
|
||||
@ -142,7 +141,7 @@ ipcMain.on('seshat', async function(_ev: IpcMainEvent, payload): Promise<void> {
|
||||
}
|
||||
break;
|
||||
|
||||
case 'closeEventIndex':
|
||||
case "closeEventIndex":
|
||||
if (eventIndex !== null) {
|
||||
const index = eventIndex;
|
||||
eventIndex = null;
|
||||
@ -156,26 +155,24 @@ ipcMain.on('seshat', async function(_ev: IpcMainEvent, payload): Promise<void> {
|
||||
}
|
||||
break;
|
||||
|
||||
case 'deleteEventIndex': {
|
||||
case "deleteEventIndex": {
|
||||
try {
|
||||
await deleteContents(eventStorePath);
|
||||
} catch (e) {
|
||||
|
||||
}
|
||||
} catch (e) {}
|
||||
break;
|
||||
}
|
||||
|
||||
case 'isEventIndexEmpty':
|
||||
case "isEventIndexEmpty":
|
||||
if (eventIndex === null) ret = true;
|
||||
else ret = await eventIndex.isEmpty();
|
||||
break;
|
||||
|
||||
case 'isRoomIndexed':
|
||||
case "isRoomIndexed":
|
||||
if (eventIndex === null) ret = false;
|
||||
else ret = await eventIndex.isRoomIndexed(args[0]);
|
||||
break;
|
||||
|
||||
case 'addEventToIndex':
|
||||
case "addEventToIndex":
|
||||
try {
|
||||
eventIndex?.addEvent(args[0], args[1]);
|
||||
} catch (e) {
|
||||
@ -184,7 +181,7 @@ ipcMain.on('seshat', async function(_ev: IpcMainEvent, payload): Promise<void> {
|
||||
}
|
||||
break;
|
||||
|
||||
case 'deleteEvent':
|
||||
case "deleteEvent":
|
||||
try {
|
||||
ret = await eventIndex?.deleteEvent(args[0]);
|
||||
} catch (e) {
|
||||
@ -193,7 +190,7 @@ ipcMain.on('seshat', async function(_ev: IpcMainEvent, payload): Promise<void> {
|
||||
}
|
||||
break;
|
||||
|
||||
case 'commitLiveEvents':
|
||||
case "commitLiveEvents":
|
||||
try {
|
||||
ret = await eventIndex?.commit();
|
||||
} catch (e) {
|
||||
@ -202,7 +199,7 @@ ipcMain.on('seshat', async function(_ev: IpcMainEvent, payload): Promise<void> {
|
||||
}
|
||||
break;
|
||||
|
||||
case 'searchEventIndex':
|
||||
case "searchEventIndex":
|
||||
try {
|
||||
ret = await eventIndex?.search(args[0]);
|
||||
} catch (e) {
|
||||
@ -211,12 +208,11 @@ ipcMain.on('seshat', async function(_ev: IpcMainEvent, payload): Promise<void> {
|
||||
}
|
||||
break;
|
||||
|
||||
case 'addHistoricEvents':
|
||||
case "addHistoricEvents":
|
||||
if (eventIndex === null) ret = false;
|
||||
else {
|
||||
try {
|
||||
ret = await eventIndex.addHistoricEvents(
|
||||
args[0], args[1], args[2]);
|
||||
ret = await eventIndex.addHistoricEvents(args[0], args[1], args[2]);
|
||||
} catch (e) {
|
||||
sendError(payload.id, <Error>e);
|
||||
return;
|
||||
@ -224,7 +220,7 @@ ipcMain.on('seshat', async function(_ev: IpcMainEvent, payload): Promise<void> {
|
||||
}
|
||||
break;
|
||||
|
||||
case 'getStats':
|
||||
case "getStats":
|
||||
if (eventIndex === null) ret = 0;
|
||||
else {
|
||||
try {
|
||||
@ -236,7 +232,7 @@ ipcMain.on('seshat', async function(_ev: IpcMainEvent, payload): Promise<void> {
|
||||
}
|
||||
break;
|
||||
|
||||
case 'removeCrawlerCheckpoint':
|
||||
case "removeCrawlerCheckpoint":
|
||||
if (eventIndex === null) ret = false;
|
||||
else {
|
||||
try {
|
||||
@ -248,7 +244,7 @@ ipcMain.on('seshat', async function(_ev: IpcMainEvent, payload): Promise<void> {
|
||||
}
|
||||
break;
|
||||
|
||||
case 'addCrawlerCheckpoint':
|
||||
case "addCrawlerCheckpoint":
|
||||
if (eventIndex === null) ret = false;
|
||||
else {
|
||||
try {
|
||||
@ -260,7 +256,7 @@ ipcMain.on('seshat', async function(_ev: IpcMainEvent, payload): Promise<void> {
|
||||
}
|
||||
break;
|
||||
|
||||
case 'loadFileEvents':
|
||||
case "loadFileEvents":
|
||||
if (eventIndex === null) ret = [];
|
||||
else {
|
||||
try {
|
||||
@ -272,7 +268,7 @@ ipcMain.on('seshat', async function(_ev: IpcMainEvent, payload): Promise<void> {
|
||||
}
|
||||
break;
|
||||
|
||||
case 'loadCheckpoints':
|
||||
case "loadCheckpoints":
|
||||
if (eventIndex === null) ret = [];
|
||||
else {
|
||||
try {
|
||||
@ -283,7 +279,7 @@ ipcMain.on('seshat', async function(_ev: IpcMainEvent, payload): Promise<void> {
|
||||
}
|
||||
break;
|
||||
|
||||
case 'setUserVersion':
|
||||
case "setUserVersion":
|
||||
if (eventIndex === null) break;
|
||||
else {
|
||||
try {
|
||||
@ -295,7 +291,7 @@ ipcMain.on('seshat', async function(_ev: IpcMainEvent, payload): Promise<void> {
|
||||
}
|
||||
break;
|
||||
|
||||
case 'getUserVersion':
|
||||
case "getUserVersion":
|
||||
if (eventIndex === null) ret = 0;
|
||||
else {
|
||||
try {
|
||||
@ -308,14 +304,14 @@ ipcMain.on('seshat', async function(_ev: IpcMainEvent, payload): Promise<void> {
|
||||
break;
|
||||
|
||||
default:
|
||||
global.mainWindow.webContents.send('seshatReply', {
|
||||
global.mainWindow.webContents.send("seshatReply", {
|
||||
id: payload.id,
|
||||
error: "Unknown IPC Call: " + payload.name,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
global.mainWindow.webContents.send('seshatReply', {
|
||||
global.mainWindow.webContents.send("seshatReply", {
|
||||
id: payload.id,
|
||||
reply: ret,
|
||||
});
|
||||
|
@ -42,17 +42,19 @@ export const Settings: Record<string, Setting> = {
|
||||
global.store.set("warnBeforeExit", value);
|
||||
},
|
||||
},
|
||||
"Electron.alwaysShowMenuBar": { // not supported on macOS
|
||||
"Electron.alwaysShowMenuBar": {
|
||||
// not supported on macOS
|
||||
async read(): Promise<any> {
|
||||
return !global.mainWindow!.autoHideMenuBar;
|
||||
},
|
||||
async write(value: any): Promise<void> {
|
||||
global.store.set('autoHideMenuBar', !value);
|
||||
global.store.set("autoHideMenuBar", !value);
|
||||
global.mainWindow!.autoHideMenuBar = !value;
|
||||
global.mainWindow!.setMenuBarVisibility(value);
|
||||
},
|
||||
},
|
||||
"Electron.showTrayIcon": { // not supported on macOS
|
||||
"Electron.showTrayIcon": {
|
||||
// not supported on macOS
|
||||
async read(): Promise<any> {
|
||||
return tray.hasTray();
|
||||
},
|
||||
@ -63,15 +65,15 @@ export const Settings: Record<string, Setting> = {
|
||||
} else {
|
||||
tray.destroy();
|
||||
}
|
||||
global.store.set('minimizeToTray', value);
|
||||
global.store.set("minimizeToTray", value);
|
||||
},
|
||||
},
|
||||
"Electron.enableHardwareAcceleration": {
|
||||
async read(): Promise<any> {
|
||||
return !global.store.get('disableHardwareAcceleration', false);
|
||||
return !global.store.get("disableHardwareAcceleration", false);
|
||||
},
|
||||
async write(value: any): Promise<void> {
|
||||
global.store.set('disableHardwareAcceleration', !value);
|
||||
global.store.set("disableHardwareAcceleration", !value);
|
||||
},
|
||||
},
|
||||
};
|
||||
|
@ -23,29 +23,29 @@ function runUpdateExe(args: string[]): Promise<void> {
|
||||
// Note that there's an Update.exe in the app-x.x.x directory and one in the parent
|
||||
// directory: we need to run the one in the parent directory, because it discovers
|
||||
// information about the app by inspecting the directory it's run from.
|
||||
const updateExe = path.resolve(path.dirname(process.execPath), '..', 'Update.exe');
|
||||
const updateExe = path.resolve(path.dirname(process.execPath), "..", "Update.exe");
|
||||
console.log(`Spawning '${updateExe}' with args '${args}'`);
|
||||
return new Promise(resolve => {
|
||||
return new Promise((resolve) => {
|
||||
spawn(updateExe, args, {
|
||||
detached: true,
|
||||
}).on('close', resolve);
|
||||
}).on("close", resolve);
|
||||
});
|
||||
}
|
||||
|
||||
function checkSquirrelHooks(): boolean {
|
||||
if (process.platform !== 'win32') return false;
|
||||
if (process.platform !== "win32") return false;
|
||||
const cmd = process.argv[1];
|
||||
const target = path.basename(process.execPath);
|
||||
if (cmd === '--squirrel-install') {
|
||||
runUpdateExe(['--createShortcut=' + target]).then(() => app.quit());
|
||||
if (cmd === "--squirrel-install") {
|
||||
runUpdateExe(["--createShortcut=" + target]).then(() => app.quit());
|
||||
return true;
|
||||
} else if (cmd === '--squirrel-updated') {
|
||||
} else if (cmd === "--squirrel-updated") {
|
||||
app.quit();
|
||||
return true;
|
||||
} else if (cmd === '--squirrel-uninstall') {
|
||||
runUpdateExe(['--removeShortcut=' + target]).then(() => app.quit());
|
||||
} else if (cmd === "--squirrel-uninstall") {
|
||||
runUpdateExe(["--removeShortcut=" + target]).then(() => app.quit());
|
||||
return true;
|
||||
} else if (cmd === '--squirrel-obsolete') {
|
||||
} else if (cmd === "--squirrel-obsolete") {
|
||||
app.quit();
|
||||
return true;
|
||||
}
|
||||
|
24
src/tray.ts
24
src/tray.ts
@ -25,7 +25,7 @@ import { _t } from "./language-helper";
|
||||
let trayIcon: Tray | null = null;
|
||||
|
||||
export function hasTray(): boolean {
|
||||
return (trayIcon !== null);
|
||||
return trayIcon !== null;
|
||||
}
|
||||
|
||||
export function destroy(): void {
|
||||
@ -52,17 +52,17 @@ interface IConfig {
|
||||
|
||||
export function create(config: IConfig): void {
|
||||
// no trays on darwin
|
||||
if (process.platform === 'darwin' || trayIcon) return;
|
||||
if (process.platform === "darwin" || trayIcon) return;
|
||||
const defaultIcon = nativeImage.createFromPath(config.icon_path);
|
||||
|
||||
trayIcon = new Tray(defaultIcon);
|
||||
trayIcon.setToolTip(config.brand);
|
||||
initApplicationMenu();
|
||||
trayIcon.on('click', toggleWin);
|
||||
trayIcon.on("click", toggleWin);
|
||||
|
||||
let lastFavicon: string | null = null;
|
||||
global.mainWindow?.webContents.on('page-favicon-updated', async function(ev, favicons) {
|
||||
if (!favicons || favicons.length <= 0 || !favicons[0].startsWith('data:')) {
|
||||
global.mainWindow?.webContents.on("page-favicon-updated", async function (ev, favicons) {
|
||||
if (!favicons || favicons.length <= 0 || !favicons[0].startsWith("data:")) {
|
||||
if (lastFavicon !== null) {
|
||||
global.mainWindow?.setIcon(defaultIcon);
|
||||
trayIcon?.setImage(defaultIcon);
|
||||
@ -78,9 +78,9 @@ export function create(config: IConfig): void {
|
||||
let newFavicon = nativeImage.createFromDataURL(favicons[0]);
|
||||
|
||||
// Windows likes ico's too much.
|
||||
if (process.platform === 'win32') {
|
||||
if (process.platform === "win32") {
|
||||
try {
|
||||
const icoPath = path.join(app.getPath('temp'), 'win32_element_icon.ico');
|
||||
const icoPath = path.join(app.getPath("temp"), "win32_element_icon.ico");
|
||||
fs.writeFileSync(icoPath, await pngToIco(newFavicon.toPNG()));
|
||||
newFavicon = nativeImage.createFromPath(icoPath);
|
||||
} catch (e) {
|
||||
@ -92,7 +92,7 @@ export function create(config: IConfig): void {
|
||||
global.mainWindow?.setIcon(newFavicon);
|
||||
});
|
||||
|
||||
global.mainWindow?.webContents.on('page-title-updated', function(ev, title) {
|
||||
global.mainWindow?.webContents.on("page-title-updated", function (ev, title) {
|
||||
trayIcon?.setToolTip(title);
|
||||
});
|
||||
}
|
||||
@ -104,13 +104,13 @@ export function initApplicationMenu(): void {
|
||||
|
||||
const contextMenu = Menu.buildFromTemplate([
|
||||
{
|
||||
label: _t('Show/Hide'),
|
||||
label: _t("Show/Hide"),
|
||||
click: toggleWin,
|
||||
},
|
||||
{ type: 'separator' },
|
||||
{ type: "separator" },
|
||||
{
|
||||
label: _t('Quit'),
|
||||
click: function(): void {
|
||||
label: _t("Quit"),
|
||||
click: function (): void {
|
||||
app.quit();
|
||||
},
|
||||
},
|
||||
|
@ -37,33 +37,33 @@ function pollForUpdates(): void {
|
||||
autoUpdater.checkForUpdates();
|
||||
} else {
|
||||
console.log("Skipping update check as download already present");
|
||||
global.mainWindow?.webContents.send('update-downloaded', latestUpdateDownloaded);
|
||||
global.mainWindow?.webContents.send("update-downloaded", latestUpdateDownloaded);
|
||||
}
|
||||
} catch (e) {
|
||||
console.log('Couldn\'t check for update', e);
|
||||
console.log("Couldn't check for update", e);
|
||||
}
|
||||
}
|
||||
|
||||
export function start(updateBaseUrl: string): void {
|
||||
if (updateBaseUrl.slice(-1) !== '/') {
|
||||
updateBaseUrl = updateBaseUrl + '/';
|
||||
if (updateBaseUrl.slice(-1) !== "/") {
|
||||
updateBaseUrl = updateBaseUrl + "/";
|
||||
}
|
||||
try {
|
||||
let url: string;
|
||||
let serverType: "json" | undefined;
|
||||
|
||||
if (process.platform === 'darwin') {
|
||||
if (process.platform === "darwin") {
|
||||
// On macOS it takes a JSON file with a map between versions and their URLs
|
||||
url = `${updateBaseUrl}macos/releases.json`;
|
||||
serverType = "json";
|
||||
} else if (process.platform === 'win32') {
|
||||
} else if (process.platform === "win32") {
|
||||
// On windows it takes a base path and looks for files under that path.
|
||||
url = `${updateBaseUrl}win32/${process.arch}/`;
|
||||
} else {
|
||||
// Squirrel / electron only supports auto-update on these two platforms.
|
||||
// I'm not even going to try to guess which feed style they'd use if they
|
||||
// implemented it on Linux, or if it would be different again.
|
||||
console.log('Auto update not supported on this platform');
|
||||
console.log("Auto update not supported on this platform");
|
||||
return;
|
||||
}
|
||||
|
||||
@ -82,15 +82,15 @@ export function start(updateBaseUrl: string): void {
|
||||
}
|
||||
} catch (err) {
|
||||
// will fail if running in debug mode
|
||||
console.log('Couldn\'t enable update checking', err);
|
||||
console.log("Couldn't enable update checking", err);
|
||||
}
|
||||
}
|
||||
|
||||
ipcMain.on('install_update', installUpdate);
|
||||
ipcMain.on('check_updates', pollForUpdates);
|
||||
ipcMain.on("install_update", installUpdate);
|
||||
ipcMain.on("check_updates", pollForUpdates);
|
||||
|
||||
function ipcChannelSendUpdateStatus(status: boolean | string): void {
|
||||
global.mainWindow?.webContents.send('check_updates', status);
|
||||
global.mainWindow?.webContents.send("check_updates", status);
|
||||
}
|
||||
|
||||
interface ICachedUpdate {
|
||||
@ -102,23 +102,26 @@ interface ICachedUpdate {
|
||||
|
||||
// cache the latest update which has been downloaded as electron offers no api to read it
|
||||
let latestUpdateDownloaded: ICachedUpdate;
|
||||
autoUpdater.on('update-available', function() {
|
||||
ipcChannelSendUpdateStatus(true);
|
||||
}).on('update-not-available', function() {
|
||||
if (latestUpdateDownloaded) {
|
||||
// the only time we will get `update-not-available` if `latestUpdateDownloaded` is already set
|
||||
// is if the user used the Manual Update check and there is no update newer than the one we
|
||||
// have downloaded, so show it to them as the latest again.
|
||||
global.mainWindow?.webContents.send('update-downloaded', latestUpdateDownloaded);
|
||||
} else {
|
||||
ipcChannelSendUpdateStatus(false);
|
||||
}
|
||||
}).on('error', function(error) {
|
||||
ipcChannelSendUpdateStatus(error.message);
|
||||
});
|
||||
autoUpdater
|
||||
.on("update-available", function () {
|
||||
ipcChannelSendUpdateStatus(true);
|
||||
})
|
||||
.on("update-not-available", function () {
|
||||
if (latestUpdateDownloaded) {
|
||||
// the only time we will get `update-not-available` if `latestUpdateDownloaded` is already set
|
||||
// is if the user used the Manual Update check and there is no update newer than the one we
|
||||
// have downloaded, so show it to them as the latest again.
|
||||
global.mainWindow?.webContents.send("update-downloaded", latestUpdateDownloaded);
|
||||
} else {
|
||||
ipcChannelSendUpdateStatus(false);
|
||||
}
|
||||
})
|
||||
.on("error", function (error) {
|
||||
ipcChannelSendUpdateStatus(error.message);
|
||||
});
|
||||
|
||||
autoUpdater.on('update-downloaded', (ev, releaseNotes, releaseName, releaseDate, updateURL) => {
|
||||
autoUpdater.on("update-downloaded", (ev, releaseNotes, releaseName, releaseDate, updateURL) => {
|
||||
// forward to renderer
|
||||
latestUpdateDownloaded = { releaseNotes, releaseName, releaseDate, updateURL };
|
||||
global.mainWindow?.webContents.send('update-downloaded', latestUpdateDownloaded);
|
||||
global.mainWindow?.webContents.send("update-downloaded", latestUpdateDownloaded);
|
||||
});
|
||||
|
@ -22,7 +22,7 @@ export async function randomArray(size: number): Promise<string> {
|
||||
if (err) {
|
||||
reject(err);
|
||||
} else {
|
||||
resolve(buf.toString("base64").replace(/=+$/g, ''));
|
||||
resolve(buf.toString("base64").replace(/=+$/g, ""));
|
||||
}
|
||||
});
|
||||
});
|
||||
|
@ -14,125 +14,133 @@ See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
import { app, shell, Menu, MenuItem, MenuItemConstructorOptions } from 'electron';
|
||||
import { app, shell, Menu, MenuItem, MenuItemConstructorOptions } from "electron";
|
||||
|
||||
import { _t } from './language-helper';
|
||||
import { _t } from "./language-helper";
|
||||
|
||||
const isMac = process.platform === 'darwin';
|
||||
const isMac = process.platform === "darwin";
|
||||
|
||||
export function buildMenuTemplate(): Menu {
|
||||
// Menu template from http://electron.atom.io/docs/api/menu/, edited
|
||||
const template: Array<(MenuItemConstructorOptions) | (MenuItem)> = [
|
||||
const template: Array<MenuItemConstructorOptions | MenuItem> = [
|
||||
{
|
||||
label: _t('Edit'),
|
||||
accelerator: 'e',
|
||||
label: _t("Edit"),
|
||||
accelerator: "e",
|
||||
submenu: [
|
||||
{
|
||||
role: 'undo',
|
||||
label: _t('Undo'),
|
||||
role: "undo",
|
||||
label: _t("Undo"),
|
||||
},
|
||||
{
|
||||
role: 'redo',
|
||||
label: _t('Redo'),
|
||||
role: "redo",
|
||||
label: _t("Redo"),
|
||||
},
|
||||
{ type: 'separator' },
|
||||
{ type: "separator" },
|
||||
{
|
||||
role: 'cut',
|
||||
label: _t('Cut'),
|
||||
role: "cut",
|
||||
label: _t("Cut"),
|
||||
},
|
||||
{
|
||||
role: 'copy',
|
||||
label: _t('Copy'),
|
||||
role: "copy",
|
||||
label: _t("Copy"),
|
||||
},
|
||||
{
|
||||
role: 'paste',
|
||||
label: _t('Paste'),
|
||||
role: "paste",
|
||||
label: _t("Paste"),
|
||||
},
|
||||
{
|
||||
role: 'pasteAndMatchStyle',
|
||||
label: _t('Paste and Match Style'),
|
||||
role: "pasteAndMatchStyle",
|
||||
label: _t("Paste and Match Style"),
|
||||
},
|
||||
{
|
||||
role: 'delete',
|
||||
label: _t('Delete'),
|
||||
role: "delete",
|
||||
label: _t("Delete"),
|
||||
},
|
||||
{
|
||||
role: 'selectAll',
|
||||
label: _t('Select All'),
|
||||
role: "selectAll",
|
||||
label: _t("Select All"),
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
label: _t('View'),
|
||||
accelerator: 'V',
|
||||
label: _t("View"),
|
||||
accelerator: "V",
|
||||
submenu: [
|
||||
{ type: 'separator' },
|
||||
{ type: "separator" },
|
||||
{
|
||||
role: 'resetZoom',
|
||||
accelerator: 'CmdOrCtrl+Num0',
|
||||
role: "resetZoom",
|
||||
accelerator: "CmdOrCtrl+Num0",
|
||||
visible: false,
|
||||
},
|
||||
{
|
||||
role: 'zoomIn',
|
||||
accelerator: 'CmdOrCtrl+NumAdd',
|
||||
role: "zoomIn",
|
||||
accelerator: "CmdOrCtrl+NumAdd",
|
||||
visible: false,
|
||||
},
|
||||
{
|
||||
role: 'zoomOut',
|
||||
accelerator: 'CmdOrCtrl+NumSub',
|
||||
role: "zoomOut",
|
||||
accelerator: "CmdOrCtrl+NumSub",
|
||||
visible: false,
|
||||
},
|
||||
{
|
||||
role: 'resetZoom',
|
||||
label: _t('Actual Size'),
|
||||
role: "resetZoom",
|
||||
label: _t("Actual Size"),
|
||||
},
|
||||
{
|
||||
role: 'zoomIn',
|
||||
label: _t('Zoom In'),
|
||||
role: "zoomIn",
|
||||
label: _t("Zoom In"),
|
||||
},
|
||||
{
|
||||
role: 'zoomOut',
|
||||
label: _t('Zoom Out'),
|
||||
role: "zoomOut",
|
||||
label: _t("Zoom Out"),
|
||||
},
|
||||
{ type: 'separator' },
|
||||
{ type: "separator" },
|
||||
// in macOS the Preferences menu item goes in the first menu
|
||||
...(!isMac ? [{
|
||||
label: _t('Preferences'),
|
||||
click(): void { global.mainWindow?.webContents.send('preferences'); },
|
||||
}] : []),
|
||||
...(!isMac
|
||||
? [
|
||||
{
|
||||
label: _t("Preferences"),
|
||||
click(): void {
|
||||
global.mainWindow?.webContents.send("preferences");
|
||||
},
|
||||
},
|
||||
]
|
||||
: []),
|
||||
{
|
||||
role: 'togglefullscreen',
|
||||
label: _t('Toggle Full Screen'),
|
||||
role: "togglefullscreen",
|
||||
label: _t("Toggle Full Screen"),
|
||||
},
|
||||
{
|
||||
role: 'toggleDevTools',
|
||||
label: _t('Toggle Developer Tools'),
|
||||
role: "toggleDevTools",
|
||||
label: _t("Toggle Developer Tools"),
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
label: _t('Window'),
|
||||
accelerator: 'w',
|
||||
role: 'window',
|
||||
label: _t("Window"),
|
||||
accelerator: "w",
|
||||
role: "window",
|
||||
submenu: [
|
||||
{
|
||||
role: 'minimize',
|
||||
label: _t('Minimize'),
|
||||
role: "minimize",
|
||||
label: _t("Minimize"),
|
||||
},
|
||||
{
|
||||
role: 'close',
|
||||
label: _t('Close'),
|
||||
role: "close",
|
||||
label: _t("Close"),
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
label: _t('Help'),
|
||||
accelerator: 'h',
|
||||
role: 'help',
|
||||
label: _t("Help"),
|
||||
accelerator: "h",
|
||||
role: "help",
|
||||
submenu: [
|
||||
{
|
||||
label: _t('Element Help'),
|
||||
click(): void { shell.openExternal('https://element.io/help'); },
|
||||
label: _t("Element Help"),
|
||||
click(): void {
|
||||
shell.openExternal("https://element.io/help");
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
@ -142,92 +150,95 @@ export function buildMenuTemplate(): Menu {
|
||||
if (isMac) {
|
||||
template.unshift({
|
||||
// first macOS menu is the name of the app
|
||||
role: 'appMenu',
|
||||
role: "appMenu",
|
||||
label: app.name,
|
||||
submenu: [
|
||||
{
|
||||
role: 'about',
|
||||
label: _t('About') + ' ' + app.name,
|
||||
role: "about",
|
||||
label: _t("About") + " " + app.name,
|
||||
},
|
||||
{ type: 'separator' },
|
||||
{ type: "separator" },
|
||||
{
|
||||
label: _t('Preferences') + '…',
|
||||
accelerator: 'Command+,', // Mac-only accelerator
|
||||
click(): void { global.mainWindow?.webContents.send('preferences'); },
|
||||
label: _t("Preferences") + "…",
|
||||
accelerator: "Command+,", // Mac-only accelerator
|
||||
click(): void {
|
||||
global.mainWindow?.webContents.send("preferences");
|
||||
},
|
||||
},
|
||||
{ type: 'separator' },
|
||||
{ type: "separator" },
|
||||
{
|
||||
role: 'services',
|
||||
label: _t('Services'),
|
||||
role: "services",
|
||||
label: _t("Services"),
|
||||
submenu: [],
|
||||
},
|
||||
{ type: 'separator' },
|
||||
{ type: "separator" },
|
||||
{
|
||||
role: 'hide',
|
||||
label: _t('Hide'),
|
||||
role: "hide",
|
||||
label: _t("Hide"),
|
||||
},
|
||||
{
|
||||
role: 'hideOthers',
|
||||
label: _t('Hide Others'),
|
||||
role: "hideOthers",
|
||||
label: _t("Hide Others"),
|
||||
},
|
||||
{
|
||||
role: 'unhide',
|
||||
label: _t('Unhide'),
|
||||
role: "unhide",
|
||||
label: _t("Unhide"),
|
||||
},
|
||||
{ type: 'separator' },
|
||||
{ type: "separator" },
|
||||
{
|
||||
role: 'quit',
|
||||
label: _t('Quit'),
|
||||
role: "quit",
|
||||
label: _t("Quit"),
|
||||
},
|
||||
],
|
||||
});
|
||||
// Edit menu.
|
||||
// This has a 'speech' section on macOS
|
||||
(template[1].submenu as MenuItemConstructorOptions[]).push(
|
||||
{ type: 'separator' },
|
||||
{ type: "separator" },
|
||||
{
|
||||
label: _t('Speech'),
|
||||
label: _t("Speech"),
|
||||
submenu: [
|
||||
{
|
||||
role: 'startSpeaking',
|
||||
label: _t('Start Speaking'),
|
||||
role: "startSpeaking",
|
||||
label: _t("Start Speaking"),
|
||||
},
|
||||
{
|
||||
role: 'stopSpeaking',
|
||||
label: _t('Stop Speaking'),
|
||||
role: "stopSpeaking",
|
||||
label: _t("Stop Speaking"),
|
||||
},
|
||||
],
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
// Window menu.
|
||||
// This also has specific functionality on macOS
|
||||
template[3].submenu = [
|
||||
{
|
||||
label: _t('Close'),
|
||||
accelerator: 'CmdOrCtrl+W',
|
||||
role: 'close',
|
||||
label: _t("Close"),
|
||||
accelerator: "CmdOrCtrl+W",
|
||||
role: "close",
|
||||
},
|
||||
{
|
||||
label: _t('Minimize'),
|
||||
accelerator: 'CmdOrCtrl+M',
|
||||
role: 'minimize',
|
||||
label: _t("Minimize"),
|
||||
accelerator: "CmdOrCtrl+M",
|
||||
role: "minimize",
|
||||
},
|
||||
{
|
||||
label: _t('Zoom'),
|
||||
role: 'zoom',
|
||||
label: _t("Zoom"),
|
||||
role: "zoom",
|
||||
},
|
||||
{
|
||||
type: 'separator',
|
||||
type: "separator",
|
||||
},
|
||||
{
|
||||
label: _t('Bring All to Front'),
|
||||
role: 'front',
|
||||
label: _t("Bring All to Front"),
|
||||
role: "front",
|
||||
},
|
||||
];
|
||||
} else {
|
||||
template.unshift({
|
||||
label: _t('File'),
|
||||
accelerator: 'f',
|
||||
label: _t("File"),
|
||||
accelerator: "f",
|
||||
submenu: [
|
||||
// For some reason, 'about' does not seem to work on windows.
|
||||
/*{
|
||||
@ -235,8 +246,8 @@ export function buildMenuTemplate(): Menu {
|
||||
label: _t('About'),
|
||||
},*/
|
||||
{
|
||||
role: 'quit',
|
||||
label: _t('Quit'),
|
||||
role: "quit",
|
||||
label: _t("Quit"),
|
||||
},
|
||||
],
|
||||
});
|
||||
|
@ -28,22 +28,18 @@ import {
|
||||
DownloadItem,
|
||||
MenuItemConstructorOptions,
|
||||
IpcMainEvent,
|
||||
} from 'electron';
|
||||
import url from 'url';
|
||||
import fs from 'fs';
|
||||
import fetch from 'node-fetch';
|
||||
import { pipeline } from 'stream';
|
||||
import path from 'path';
|
||||
} from "electron";
|
||||
import url from "url";
|
||||
import fs from "fs";
|
||||
import fetch from "node-fetch";
|
||||
import { pipeline } from "stream";
|
||||
import path from "path";
|
||||
|
||||
import { _t } from './language-helper';
|
||||
import { _t } from "./language-helper";
|
||||
|
||||
const MAILTO_PREFIX = "mailto:";
|
||||
|
||||
const PERMITTED_URL_SCHEMES: string[] = [
|
||||
'http:',
|
||||
'https:',
|
||||
MAILTO_PREFIX,
|
||||
];
|
||||
const PERMITTED_URL_SCHEMES: string[] = ["http:", "https:", MAILTO_PREFIX];
|
||||
|
||||
function safeOpenURL(target: string): void {
|
||||
// openExternal passes the target to open/start/xdg-open,
|
||||
@ -70,7 +66,7 @@ function onWindowOrNavigate(ev: Event, target: string): void {
|
||||
}
|
||||
|
||||
function writeNativeImage(filePath: string, img: NativeImage): Promise<void> {
|
||||
switch (filePath.split('.').pop()?.toLowerCase()) {
|
||||
switch (filePath.split(".").pop()?.toLowerCase()) {
|
||||
case "jpg":
|
||||
case "jpeg":
|
||||
return fs.promises.writeFile(filePath, img.toJPEG(100));
|
||||
@ -85,7 +81,7 @@ function writeNativeImage(filePath: string, img: NativeImage): Promise<void> {
|
||||
function onLinkContextMenu(ev: Event, params: ContextMenuParams, webContents: WebContents): void {
|
||||
let url = params.linkURL || params.srcURL;
|
||||
|
||||
if (url.startsWith('vector://vector/webapp')) {
|
||||
if (url.startsWith("vector://vector/webapp")) {
|
||||
// Avoid showing a context menu for app icons
|
||||
if (params.hasImageContents) return;
|
||||
// Rewrite URL so that it can be used outside of the app
|
||||
@ -94,82 +90,90 @@ function onLinkContextMenu(ev: Event, params: ContextMenuParams, webContents: We
|
||||
|
||||
const popupMenu = new Menu();
|
||||
// No point trying to open blob: URLs in an external browser: it ain't gonna work.
|
||||
if (!url.startsWith('blob:')) {
|
||||
popupMenu.append(new MenuItem({
|
||||
label: url,
|
||||
click(): void {
|
||||
safeOpenURL(url);
|
||||
},
|
||||
}));
|
||||
if (!url.startsWith("blob:")) {
|
||||
popupMenu.append(
|
||||
new MenuItem({
|
||||
label: url,
|
||||
click(): void {
|
||||
safeOpenURL(url);
|
||||
},
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
if (params.hasImageContents) {
|
||||
popupMenu.append(new MenuItem({
|
||||
label: _t('Copy image'),
|
||||
accelerator: 'c',
|
||||
click(): void {
|
||||
webContents.copyImageAt(params.x, params.y);
|
||||
},
|
||||
}));
|
||||
popupMenu.append(
|
||||
new MenuItem({
|
||||
label: _t("Copy image"),
|
||||
accelerator: "c",
|
||||
click(): void {
|
||||
webContents.copyImageAt(params.x, params.y);
|
||||
},
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
// No point offering to copy a blob: URL either
|
||||
if (!url.startsWith('blob:')) {
|
||||
if (!url.startsWith("blob:")) {
|
||||
// Special-case e-mail URLs to strip the `mailto:` like modern browsers do
|
||||
if (url.startsWith(MAILTO_PREFIX)) {
|
||||
popupMenu.append(new MenuItem({
|
||||
label: _t('Copy email address'),
|
||||
accelerator: 'a',
|
||||
click(): void {
|
||||
clipboard.writeText(url.substr(MAILTO_PREFIX.length));
|
||||
},
|
||||
}));
|
||||
popupMenu.append(
|
||||
new MenuItem({
|
||||
label: _t("Copy email address"),
|
||||
accelerator: "a",
|
||||
click(): void {
|
||||
clipboard.writeText(url.substr(MAILTO_PREFIX.length));
|
||||
},
|
||||
}),
|
||||
);
|
||||
} else {
|
||||
popupMenu.append(new MenuItem({
|
||||
label: params.hasImageContents
|
||||
? _t('Copy image address')
|
||||
: _t('Copy link address'),
|
||||
accelerator: 'a',
|
||||
click(): void {
|
||||
clipboard.writeText(url);
|
||||
},
|
||||
}));
|
||||
popupMenu.append(
|
||||
new MenuItem({
|
||||
label: params.hasImageContents ? _t("Copy image address") : _t("Copy link address"),
|
||||
accelerator: "a",
|
||||
click(): void {
|
||||
clipboard.writeText(url);
|
||||
},
|
||||
}),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// XXX: We cannot easily save a blob from the main process as
|
||||
// only the renderer can resolve them so don't give the user an option to.
|
||||
if (params.hasImageContents && !url.startsWith('blob:')) {
|
||||
popupMenu.append(new MenuItem({
|
||||
label: _t('Save image as...'),
|
||||
accelerator: 's',
|
||||
async click(): Promise<void> {
|
||||
const targetFileName = params.suggestedFilename || params.altText || "image.png";
|
||||
const { filePath } = await dialog.showSaveDialog({
|
||||
defaultPath: targetFileName,
|
||||
});
|
||||
|
||||
if (!filePath) return; // user cancelled dialog
|
||||
|
||||
try {
|
||||
if (url.startsWith("data:")) {
|
||||
await writeNativeImage(filePath, nativeImage.createFromDataURL(url));
|
||||
} else {
|
||||
const resp = await fetch(url);
|
||||
if (!resp.ok) throw new Error(`unexpected response ${resp.statusText}`);
|
||||
if (!resp.body) throw new Error(`unexpected response has no body ${resp.statusText}`);
|
||||
pipeline(resp.body, fs.createWriteStream(filePath));
|
||||
}
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
dialog.showMessageBox({
|
||||
type: "error",
|
||||
title: _t("Failed to save image"),
|
||||
message: _t("The image failed to save"),
|
||||
if (params.hasImageContents && !url.startsWith("blob:")) {
|
||||
popupMenu.append(
|
||||
new MenuItem({
|
||||
label: _t("Save image as..."),
|
||||
accelerator: "s",
|
||||
async click(): Promise<void> {
|
||||
const targetFileName = params.suggestedFilename || params.altText || "image.png";
|
||||
const { filePath } = await dialog.showSaveDialog({
|
||||
defaultPath: targetFileName,
|
||||
});
|
||||
}
|
||||
},
|
||||
}));
|
||||
|
||||
if (!filePath) return; // user cancelled dialog
|
||||
|
||||
try {
|
||||
if (url.startsWith("data:")) {
|
||||
await writeNativeImage(filePath, nativeImage.createFromDataURL(url));
|
||||
} else {
|
||||
const resp = await fetch(url);
|
||||
if (!resp.ok) throw new Error(`unexpected response ${resp.statusText}`);
|
||||
if (!resp.body) throw new Error(`unexpected response has no body ${resp.statusText}`);
|
||||
pipeline(resp.body, fs.createWriteStream(filePath));
|
||||
}
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
dialog.showMessageBox({
|
||||
type: "error",
|
||||
title: _t("Failed to save image"),
|
||||
message: _t("The image failed to save"),
|
||||
});
|
||||
}
|
||||
},
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
// popup() requires an options object even for no options
|
||||
@ -181,7 +185,7 @@ function cutCopyPasteSelectContextMenus(params: ContextMenuParams): MenuItemCons
|
||||
const options: MenuItemConstructorOptions[] = [];
|
||||
|
||||
if (params.misspelledWord) {
|
||||
params.dictionarySuggestions.forEach(word => {
|
||||
params.dictionarySuggestions.forEach((word) => {
|
||||
options.push({
|
||||
label: word,
|
||||
click: (menuItem, browserWindow) => {
|
||||
@ -189,42 +193,52 @@ function cutCopyPasteSelectContextMenus(params: ContextMenuParams): MenuItemCons
|
||||
},
|
||||
});
|
||||
});
|
||||
options.push({
|
||||
type: 'separator',
|
||||
}, {
|
||||
label: _t('Add to dictionary'),
|
||||
click: (menuItem, browserWindow) => {
|
||||
browserWindow?.webContents.session.addWordToSpellCheckerDictionary(params.misspelledWord);
|
||||
options.push(
|
||||
{
|
||||
type: "separator",
|
||||
},
|
||||
}, {
|
||||
type: 'separator',
|
||||
});
|
||||
{
|
||||
label: _t("Add to dictionary"),
|
||||
click: (menuItem, browserWindow) => {
|
||||
browserWindow?.webContents.session.addWordToSpellCheckerDictionary(params.misspelledWord);
|
||||
},
|
||||
},
|
||||
{
|
||||
type: "separator",
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
options.push({
|
||||
role: 'cut',
|
||||
label: _t('Cut'),
|
||||
accelerator: 't',
|
||||
enabled: params.editFlags.canCut,
|
||||
}, {
|
||||
role: 'copy',
|
||||
label: _t('Copy'),
|
||||
accelerator: 'c',
|
||||
enabled: params.editFlags.canCopy,
|
||||
}, {
|
||||
role: 'paste',
|
||||
label: _t('Paste'),
|
||||
accelerator: 'p',
|
||||
enabled: params.editFlags.canPaste,
|
||||
}, {
|
||||
role: 'pasteAndMatchStyle',
|
||||
enabled: params.editFlags.canPaste,
|
||||
}, {
|
||||
role: 'selectAll',
|
||||
label: _t("Select All"),
|
||||
accelerator: 'a',
|
||||
enabled: params.editFlags.canSelectAll,
|
||||
});
|
||||
options.push(
|
||||
{
|
||||
role: "cut",
|
||||
label: _t("Cut"),
|
||||
accelerator: "t",
|
||||
enabled: params.editFlags.canCut,
|
||||
},
|
||||
{
|
||||
role: "copy",
|
||||
label: _t("Copy"),
|
||||
accelerator: "c",
|
||||
enabled: params.editFlags.canCopy,
|
||||
},
|
||||
{
|
||||
role: "paste",
|
||||
label: _t("Paste"),
|
||||
accelerator: "p",
|
||||
enabled: params.editFlags.canPaste,
|
||||
},
|
||||
{
|
||||
role: "pasteAndMatchStyle",
|
||||
enabled: params.editFlags.canPaste,
|
||||
},
|
||||
{
|
||||
role: "selectAll",
|
||||
label: _t("Select All"),
|
||||
accelerator: "a",
|
||||
enabled: params.editFlags.canSelectAll,
|
||||
},
|
||||
);
|
||||
return options;
|
||||
}
|
||||
|
||||
@ -239,9 +253,9 @@ function onSelectedContextMenu(ev: Event, params: ContextMenuParams): void {
|
||||
|
||||
function onEditableContextMenu(ev: Event, params: ContextMenuParams): void {
|
||||
const items: MenuItemConstructorOptions[] = [
|
||||
{ role: 'undo' },
|
||||
{ role: 'redo', enabled: params.editFlags.canRedo },
|
||||
{ type: 'separator' },
|
||||
{ role: "undo" },
|
||||
{ role: "redo", enabled: params.editFlags.canRedo },
|
||||
{ type: "separator" },
|
||||
...cutCopyPasteSelectContextMenus(params),
|
||||
];
|
||||
|
||||
@ -254,7 +268,7 @@ function onEditableContextMenu(ev: Event, params: ContextMenuParams): void {
|
||||
|
||||
let userDownloadIndex = 0;
|
||||
const userDownloadMap = new Map<number, string>(); // Map from id to path
|
||||
ipcMain.on('userDownloadAction', function(ev: IpcMainEvent, { id, open = false }) {
|
||||
ipcMain.on("userDownloadAction", function (ev: IpcMainEvent, { id, open = false }) {
|
||||
const path = userDownloadMap.get(id);
|
||||
if (open && path) {
|
||||
shell.openPath(path);
|
||||
@ -268,12 +282,12 @@ export default (webContents: WebContents): void => {
|
||||
return { action: "deny" };
|
||||
});
|
||||
|
||||
webContents.on('will-navigate', (ev: Event, target: string): void => {
|
||||
webContents.on("will-navigate", (ev: Event, target: string): void => {
|
||||
if (target.startsWith("vector://")) return;
|
||||
return onWindowOrNavigate(ev, target);
|
||||
});
|
||||
|
||||
webContents.on('context-menu', function(ev: Event, params: ContextMenuParams): void {
|
||||
webContents.on("context-menu", function (ev: Event, params: ContextMenuParams): void {
|
||||
if (params.linkURL || params.srcURL) {
|
||||
onLinkContextMenu(ev, params, webContents);
|
||||
} else if (params.selectionText) {
|
||||
@ -283,13 +297,13 @@ export default (webContents: WebContents): void => {
|
||||
}
|
||||
});
|
||||
|
||||
webContents.session.on('will-download', (event: Event, item: DownloadItem): void => {
|
||||
item.once('done', (event, state) => {
|
||||
if (state === 'completed') {
|
||||
webContents.session.on("will-download", (event: Event, item: DownloadItem): void => {
|
||||
item.once("done", (event, state) => {
|
||||
if (state === "completed") {
|
||||
const savePath = item.getSavePath();
|
||||
const id = userDownloadIndex++;
|
||||
userDownloadMap.set(id, savePath);
|
||||
webContents.send('userDownloadCompleted', {
|
||||
webContents.send("userDownloadCompleted", {
|
||||
id,
|
||||
name: path.basename(savePath),
|
||||
});
|
||||
|
@ -45,13 +45,13 @@ describe("App launch", () => {
|
||||
args,
|
||||
recordVideo: {
|
||||
dir: artifactsPath,
|
||||
}
|
||||
},
|
||||
});
|
||||
window = await app.firstWindow();
|
||||
}, 30000);
|
||||
|
||||
afterAll(async () => {
|
||||
await app?.close().catch(e => {
|
||||
await app?.close().catch((e) => {
|
||||
console.error(e);
|
||||
});
|
||||
fs.rmSync(tmpDir, { recursive: true });
|
||||
|
@ -1,24 +1,18 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"resolveJsonModule": true,
|
||||
"esModuleInterop": true,
|
||||
"module": "commonjs",
|
||||
"moduleResolution": "node",
|
||||
"target": "es2016",
|
||||
"sourceMap": false,
|
||||
"outDir": "./lib",
|
||||
"rootDir": "./src",
|
||||
"declaration": true,
|
||||
"typeRoots": ["src/@types"],
|
||||
"lib": [
|
||||
"es2019",
|
||||
"dom"
|
||||
],
|
||||
"types": ["jest", "node"],
|
||||
"strict": true
|
||||
},
|
||||
"include": [
|
||||
"./src/**/*.ts",
|
||||
"./tests/**/*.ts"
|
||||
]
|
||||
"compilerOptions": {
|
||||
"resolveJsonModule": true,
|
||||
"esModuleInterop": true,
|
||||
"module": "commonjs",
|
||||
"moduleResolution": "node",
|
||||
"target": "es2016",
|
||||
"sourceMap": false,
|
||||
"outDir": "./lib",
|
||||
"rootDir": "./src",
|
||||
"declaration": true,
|
||||
"typeRoots": ["src/@types"],
|
||||
"lib": ["es2019", "dom"],
|
||||
"types": ["jest", "node"],
|
||||
"strict": true
|
||||
},
|
||||
"include": ["./src/**/*.ts", "./tests/**/*.ts"]
|
||||
}
|
||||
|
13
yarn.lock
13
yarn.lock
@ -4493,6 +4493,11 @@ eslint-config-google@^0.14.0:
|
||||
resolved "https://registry.yarnpkg.com/eslint-config-google/-/eslint-config-google-0.14.0.tgz#4f5f8759ba6e11b424294a219dbfa18c508bcc1a"
|
||||
integrity sha512-WsbX4WbjuMvTdeVL6+J3rK1RGhCTqjsFjX7UMSMgZiyxxaNLkoJENbrGExzERFeoTpGw3F3FypTiWAP9ZXzkEw==
|
||||
|
||||
eslint-config-prettier@^8.5.0:
|
||||
version "8.5.0"
|
||||
resolved "https://registry.yarnpkg.com/eslint-config-prettier/-/eslint-config-prettier-8.5.0.tgz#5a81680ec934beca02c7b1a61cf8ca34b66feab1"
|
||||
integrity sha512-obmWKLUNCnhtQRKc+tmnYuQl0pFU1ibYJQ5BGhTVB08bHe9wC8qUeG7c08dj9XX+AuPj1YSGSQIHl1pnDHZR0Q==
|
||||
|
||||
eslint-import-resolver-node@^0.3.6:
|
||||
version "0.3.6"
|
||||
resolved "https://registry.yarnpkg.com/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.6.tgz#4048b958395da89668252001dbd9eca6b83bacbd"
|
||||
@ -4527,10 +4532,10 @@ eslint-plugin-import@^2.25.4:
|
||||
resolve "^1.22.0"
|
||||
tsconfig-paths "^3.14.1"
|
||||
|
||||
eslint-plugin-matrix-org@^0.8.0:
|
||||
version "0.8.0"
|
||||
resolved "https://registry.yarnpkg.com/eslint-plugin-matrix-org/-/eslint-plugin-matrix-org-0.8.0.tgz#daa1396900a8cb1c1d88f1a370e45fc32482cd9e"
|
||||
integrity sha512-/Poz/F8lXYDsmQa29iPSt+kO+Jn7ArvRdq10g0CCk8wbRS0sb2zb6fvd9xL1BgR5UDQL771V0l8X32etvY5yKA==
|
||||
eslint-plugin-matrix-org@^0.9.0:
|
||||
version "0.9.0"
|
||||
resolved "https://registry.yarnpkg.com/eslint-plugin-matrix-org/-/eslint-plugin-matrix-org-0.9.0.tgz#b2a5186052ddbfa7dc9878779bafa5d68681c7b4"
|
||||
integrity sha512-+j6JuMnFH421Z2vOxc+0YMt5Su5vD76RSatviy3zHBaZpgd+sOeAWoCLBHD5E7mMz5oKae3Y3wewCt9LRzq2Nw==
|
||||
|
||||
eslint-plugin-unicorn@^45.0.0:
|
||||
version "45.0.1"
|
||||
|
Loading…
Reference in New Issue
Block a user