2
.github/workflows/commitlint.yml
vendored
|
@ -21,7 +21,7 @@ jobs:
|
|||
fetch-depth: 0
|
||||
|
||||
- name: Setup Node and pnpm
|
||||
uses: silverhand-io/actions-node-pnpm-run-steps@v2
|
||||
uses: silverhand-io/actions-node-pnpm-run-steps@v1.2.3
|
||||
|
||||
- name: Commitlint
|
||||
run: npx commitlint --from HEAD~${{ github.event.pull_request.commits }} --to HEAD
|
||||
|
|
31
.github/workflows/integration-test.yml
vendored
|
@ -19,10 +19,17 @@ jobs:
|
|||
- uses: actions/checkout@v3
|
||||
|
||||
- name: Setup Node and pnpm
|
||||
uses: silverhand-io/actions-node-pnpm-run-steps@v2
|
||||
uses: silverhand-io/actions-node-pnpm-run-steps@v1.2.3
|
||||
|
||||
- name: Build
|
||||
run: pnpm -r build
|
||||
run: pnpm -- lerna run build --stream
|
||||
|
||||
- name: Add the mock connectors for integration tests only
|
||||
run: |
|
||||
pnpm add-connector @logto/connector-mock-sms
|
||||
pnpm add-connector @logto/connector-mock-email
|
||||
pnpm add-connector @logto/connector-mock-social
|
||||
working-directory: packages/core
|
||||
|
||||
- name: Package
|
||||
run: ./package.sh
|
||||
|
@ -53,14 +60,15 @@ jobs:
|
|||
cp tests/package.json ./
|
||||
|
||||
- name: Setup Node and pnpm
|
||||
uses: silverhand-io/actions-node-pnpm-run-steps@v2
|
||||
uses: silverhand-io/actions-node-pnpm-run-steps@v1.2.3
|
||||
with:
|
||||
run-install: false
|
||||
|
||||
# Setup integration test
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
cd tests
|
||||
mv tests /tmp/tests
|
||||
cd /tmp/tests
|
||||
pnpm i
|
||||
pnpm prepack
|
||||
|
||||
|
@ -73,22 +81,15 @@ jobs:
|
|||
name: integration-test-${{ github.sha }}
|
||||
|
||||
- name: Extract
|
||||
working-directory: tests
|
||||
run: |
|
||||
npm run cli init -- -p ../logto --db postgres://postgres:postgres@localhost:5432 --no-oc --du ../logto.tar.gz
|
||||
|
||||
- name: Add mock connectors
|
||||
working-directory: tests
|
||||
run: |
|
||||
npm run cli connector add @logto/connector-mock-sms @logto/connector-mock-email @logto/connector-mock-social -- -p ../logto
|
||||
run: tar -xzf logto.tar.gz
|
||||
|
||||
- name: Run Logto
|
||||
run: node . --from-root --all-yes &
|
||||
working-directory: logto/packages/core
|
||||
run: node . &
|
||||
env:
|
||||
INTEGRATION_TEST: true
|
||||
NODE_ENV: production
|
||||
DB_URL: postgres://postgres:postgres@localhost:5432
|
||||
DB_URL_DEFAULT: postgres://postgres:postgres@localhost:5432
|
||||
|
||||
- name: Sleep for 5 seconds
|
||||
run: sleep 5
|
||||
|
@ -96,7 +97,7 @@ jobs:
|
|||
# Test
|
||||
- name: Run tests
|
||||
run: |
|
||||
cd tests/packages/integration-tests
|
||||
cd /tmp/tests/packages/integration-tests
|
||||
pnpm start
|
||||
env:
|
||||
INTEGRATION_TESTS_LOGTO_URL: http://localhost:3001
|
||||
|
|
6
.github/workflows/main.yml
vendored
|
@ -19,7 +19,7 @@ jobs:
|
|||
- uses: actions/checkout@v3
|
||||
|
||||
- name: Setup Node and pnpm
|
||||
uses: silverhand-io/actions-node-pnpm-run-steps@v2
|
||||
uses: silverhand-io/actions-node-pnpm-run-steps@v1.2.3
|
||||
|
||||
- name: Build
|
||||
run: pnpm ci:build
|
||||
|
@ -33,7 +33,7 @@ jobs:
|
|||
- uses: actions/checkout@v3
|
||||
|
||||
- name: Setup Node and pnpm
|
||||
uses: silverhand-io/actions-node-pnpm-run-steps@v2
|
||||
uses: silverhand-io/actions-node-pnpm-run-steps@v1.2.3
|
||||
|
||||
- name: Prepack
|
||||
run: pnpm prepack
|
||||
|
@ -51,7 +51,7 @@ jobs:
|
|||
- uses: actions/checkout@v3
|
||||
|
||||
- name: Setup Node and pnpm
|
||||
uses: silverhand-io/actions-node-pnpm-run-steps@v2
|
||||
uses: silverhand-io/actions-node-pnpm-run-steps@v1.2.3
|
||||
|
||||
- name: Prepack
|
||||
run: pnpm prepack
|
||||
|
|
2
.github/workflows/master-codecov-report.yml
vendored
|
@ -14,7 +14,7 @@ jobs:
|
|||
- uses: actions/checkout@v3
|
||||
|
||||
- name: Setup Node and pnpm
|
||||
uses: silverhand-io/actions-node-pnpm-run-steps@v2
|
||||
uses: silverhand-io/actions-node-pnpm-run-steps@v1.2.3
|
||||
|
||||
- name: Prepack
|
||||
run: pnpm prepack
|
||||
|
|
7
.github/workflows/publish.yml
vendored
|
@ -36,7 +36,7 @@ jobs:
|
|||
git_commit_gpgsign: true
|
||||
|
||||
- name: Setup Node and pnpm
|
||||
uses: silverhand-io/actions-node-pnpm-run-steps@v2
|
||||
uses: silverhand-io/actions-node-pnpm-run-steps@v1.2.3
|
||||
|
||||
- name: Configure Git user
|
||||
run: |
|
||||
|
@ -46,10 +46,7 @@ jobs:
|
|||
- name: Publish to GitHub
|
||||
# add `no-verify-access` due to https://github.com/lerna/lerna/issues/2788
|
||||
run: |
|
||||
pnpm \
|
||||
--package=conventional-changelog-conventionalcommits \
|
||||
--package=lerna@^5.0.0 \
|
||||
dlx lerna publish \
|
||||
pnpm lerna publish \
|
||||
-m "release: %s" \
|
||||
--conventional-commits \
|
||||
--preid=${{ github.event.inputs.preid }} \
|
||||
|
|
6
.github/workflows/release.yml
vendored
|
@ -12,7 +12,6 @@ concurrency:
|
|||
|
||||
jobs:
|
||||
dockerize:
|
||||
environment: ${{ startsWith(github.ref, 'refs/tags/') && 'release' || '' }}
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
|
@ -80,7 +79,6 @@ jobs:
|
|||
|
||||
|
||||
create-github-release:
|
||||
environment: release
|
||||
runs-on: ubuntu-latest
|
||||
if: startsWith(github.ref, 'refs/tags/')
|
||||
|
||||
|
@ -90,7 +88,7 @@ jobs:
|
|||
fetch-depth: 0
|
||||
|
||||
- name: Setup Node and pnpm
|
||||
uses: silverhand-io/actions-node-pnpm-run-steps@v2
|
||||
uses: silverhand-io/actions-node-pnpm-run-steps@v1.2.3
|
||||
|
||||
- name: Extract changelog
|
||||
run: |
|
||||
|
@ -102,7 +100,7 @@ jobs:
|
|||
> /tmp/changelog.txt
|
||||
|
||||
- name: Build
|
||||
run: pnpm -r build
|
||||
run: pnpm -- lerna run build --stream
|
||||
|
||||
- name: Package
|
||||
run: ./package.sh
|
||||
|
|
4
.github/workflows/upload-annotations.yml
vendored
|
@ -22,13 +22,13 @@ jobs:
|
|||
- uses: actions/checkout@v3
|
||||
|
||||
- name: Setup Node and pnpm
|
||||
uses: silverhand-io/actions-node-pnpm-run-steps@v2
|
||||
uses: silverhand-io/actions-node-pnpm-run-steps@v1.2.3
|
||||
|
||||
- name: Prepack
|
||||
run: pnpm prepack
|
||||
|
||||
- name: Lint with Report
|
||||
run: pnpm -r --parallel lint:report && node merge-eslint-reports.js
|
||||
run: pnpm -- lerna run --parallel lint:report && node merge-eslint-reports.js
|
||||
|
||||
- name: Annotate Code Linting Results
|
||||
uses: ataylorme/eslint-annotate-action@1.2.0
|
||||
|
|
|
@ -8,15 +8,16 @@ tasks:
|
|||
pnpm prepack
|
||||
cd packages/core
|
||||
pnpm build
|
||||
pnpm add-official-connectors
|
||||
cd -
|
||||
pnpm cli connector add --official
|
||||
command: |
|
||||
export ENDPOINT=$(gp url 3001)
|
||||
pnpm cli db seed
|
||||
pnpm -r --parallel --filter=!@logto/integration-tests dev
|
||||
pnpm lerna --ignore=@logto/integration-test run --parallel dev
|
||||
env:
|
||||
ALL_YES: 1
|
||||
NO_INQUIRY: 0
|
||||
TRUST_PROXY_HEADER: 1
|
||||
DB_URL: postgres://postgres:p0stgr3s@127.0.0.1:5432
|
||||
DB_URL_DEFAULT: postgres://postgres:p0stgr3s@127.0.0.1:5432
|
||||
|
||||
ports:
|
||||
- name: Logto
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
#!/bin/sh
|
||||
. "$(dirname "$0")/_/husky.sh"
|
||||
|
||||
FORCE_COLOR=1 pnpm -r --filter "[HEAD]" precommit
|
||||
FORCE_COLOR=1 pnpm -- lerna run --concurrency 1 --stream precommit --since HEAD --exclude-dependents
|
||||
|
|
|
@ -11,11 +11,12 @@ RUN apk add --no-cache python3 make g++
|
|||
|
||||
# Install dependencies and build
|
||||
RUN pnpm i
|
||||
RUN pnpm -r build
|
||||
RUN pnpm -- lerna run build --stream
|
||||
|
||||
# Add official connectors
|
||||
WORKDIR /etc/logto/packages/core
|
||||
RUN pnpm add-official-connectors
|
||||
WORKDIR /etc/logto
|
||||
RUN pnpm cli connector add --official
|
||||
|
||||
# Prune dependencies for production
|
||||
RUN rm -rf node_modules packages/*/node_modules
|
||||
|
@ -29,4 +30,5 @@ FROM node:16-alpine as app
|
|||
WORKDIR /etc/logto
|
||||
COPY --from=builder /etc/logto .
|
||||
EXPOSE 3001
|
||||
ENV NO_INQUIRY true
|
||||
ENTRYPOINT ["npm", "start"]
|
||||
|
|
|
@ -12,7 +12,7 @@
|
|||
[](https://github.com/logto-io/logto/actions?query=branch%3Amaster)
|
||||
[](https://github.com/logto-io/logto/releases)
|
||||
[](https://app.codecov.io/gh/logto-io/logto)
|
||||
[](https://gitpod.io/#https://github.com/logto-io/demo)
|
||||
[](https://gitpod.io/#https://github.com/logto-io/logto)
|
||||
[](https://render.com/deploy?repo=https://github.com/logto-io/logto)
|
||||
|
||||
Logto[^info] helps you build the sign-in, auth, and user identity within minutes.
|
||||
|
@ -44,7 +44,7 @@ Boringly, we call it "[customer identity access management](https://en.wikipedia
|
|||
|
||||
#### Online demo (GitPod)
|
||||
|
||||
[Click here](https://gitpod.io/#https://github.com/logto-io/demo) to launch Logto via GitPod. Once you see the message like `App is running at https://3001-...gitpod.io` in the terminal, press Cmd (or Ctrl) and click the URL to continue your Logto journey.
|
||||
[Click here](https://gitpod.io/#https://github.com/logto-io/logto) to launch Logto via GitPod. Once you see the message like `App is running at https://3001-...gitpod.io` in the terminal, press Cmd (or Ctrl) and click the URL to continue your Logto journey.
|
||||
|
||||
#### Docker Compose
|
||||
|
||||
|
|
|
@ -1,14 +1,10 @@
|
|||
const { rules } = require('@commitlint/config-conventional');
|
||||
|
||||
const isCi = process.env.CI === 'true';
|
||||
|
||||
/** @type {import('@commitlint/types').UserConfig} **/
|
||||
module.exports = {
|
||||
extends: ['@commitlint/config-conventional'],
|
||||
rules: {
|
||||
'type-enum': [2, 'always', [...rules['type-enum'][2], 'api', 'release']],
|
||||
'scope-enum': [2, 'always', ['connector', 'console', 'core', 'demo-app', 'test', 'phrases', 'schemas', 'shared', 'ui', 'deps', 'connector-core', 'cli']],
|
||||
// Slightly increase the tolerance to allow the appending PR number
|
||||
...(isCi && { 'header-max-length': [2, 'always', 110] })
|
||||
'scope-enum': [2, 'always', ['connector', 'console', 'core', 'demo-app', 'test', 'phrases', 'schemas', 'shared', 'ui', 'deps', 'connector-core', 'cli']]
|
||||
},
|
||||
};
|
||||
|
|
|
@ -3,7 +3,7 @@ const fs = require('fs');
|
|||
const directories = fs.readdirSync('./packages');
|
||||
const reports = directories
|
||||
// Filter out docs temporarily
|
||||
.filter((dir) => !['docs', 'create'].includes(dir))
|
||||
.filter((dir) => dir !== 'docs')
|
||||
.map((dir) => fs.readFileSync(`./packages/${dir}/report.json`, { encoding: 'utf-8' }));
|
||||
const merged = [];
|
||||
|
||||
|
|
26
package.json
|
@ -5,22 +5,24 @@
|
|||
"scripts": {
|
||||
"preinstall": "npx only-allow pnpm",
|
||||
"version": "pnpm i --frozen-lockfile=false && git add pnpm-lock.yaml",
|
||||
"lerna": "lerna",
|
||||
"bootstrap": "lerna bootstrap",
|
||||
"prepare": "if test \"$NODE_ENV\" != \"production\" && test \"$CI\" != \"true\" ; then husky install ; fi",
|
||||
"prepack": "pnpm -r prepack",
|
||||
"dev": "pnpm -r prepack --incremental && pnpm -r --parallel --filter=!@logto/integration-tests dev",
|
||||
"start": "cd packages/core && NODE_ENV=production node .",
|
||||
"cli": "logto",
|
||||
"alteration": "logto db alt",
|
||||
"ci:build": "pnpm -r build",
|
||||
"ci:lint": "pnpm -r --parallel lint",
|
||||
"ci:stylelint": "pnpm -r --parallel stylelint",
|
||||
"ci:test": "pnpm -r --parallel test:ci"
|
||||
"prepack": "lerna run --stream prepack",
|
||||
"dev": "lerna run --stream prepack -- --incremental && lerna --ignore=@logto/integration-tests run --parallel dev",
|
||||
"start": "cd packages/core && NODE_ENV=production node . --from-root",
|
||||
"alteration": "cd packages/core && pnpm alteration",
|
||||
"ci:build": "lerna run --stream build",
|
||||
"ci:lint": "lerna run --parallel lint",
|
||||
"ci:stylelint": "lerna run --parallel stylelint",
|
||||
"ci:test": "lerna run --parallel test:ci"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@commitlint/cli": "^17.0.0",
|
||||
"@commitlint/config-conventional": "^17.0.0",
|
||||
"@commitlint/types": "^17.0.0",
|
||||
"husky": "^8.0.0",
|
||||
"lerna": "^5.0.0",
|
||||
"typescript": "^4.7.4"
|
||||
},
|
||||
"workspaces": {
|
||||
|
@ -43,12 +45,8 @@
|
|||
"pnpm": {
|
||||
"peerDependencyRules": {
|
||||
"allowedVersions": {
|
||||
"react": "^18.0.0",
|
||||
"jest": "^29.1.2"
|
||||
"react": "^18.0.0"
|
||||
}
|
||||
}
|
||||
},
|
||||
"dependencies": {
|
||||
"@logto/cli": "^1.0.0-beta.10"
|
||||
}
|
||||
}
|
||||
|
|
2
packages/cli/.gitignore
vendored
|
@ -1,2 +0,0 @@
|
|||
alteration-scripts/
|
||||
src/package-json.ts
|
|
@ -1,7 +0,0 @@
|
|||
import { merge, Config } from '@silverhand/jest-config';
|
||||
|
||||
const config: Config.InitialOptions = merge({
|
||||
roots: ['./src'],
|
||||
});
|
||||
|
||||
export default config;
|
|
@ -7,7 +7,8 @@
|
|||
"license": "MPL-2.0",
|
||||
"main": "lib/index.js",
|
||||
"bin": {
|
||||
"logto": "bin/logto"
|
||||
"logto": "bin/logto",
|
||||
"lg": "bin/logto"
|
||||
},
|
||||
"files": [
|
||||
"bin",
|
||||
|
@ -19,14 +20,11 @@
|
|||
},
|
||||
"scripts": {
|
||||
"precommit": "lint-staged",
|
||||
"prepare:package-json": "node -p \"'export const packageJson = ' + JSON.stringify(require('./package.json'), undefined, 2) + ';'\" > src/package-json.ts",
|
||||
"build": "rimraf lib && pnpm prepare:package-json && tsc -p tsconfig.build.json",
|
||||
"build": "rimraf lib && tsc",
|
||||
"start": "node .",
|
||||
"start:dev": "ts-node --files src/index.ts",
|
||||
"dev": "ts-node src/index.ts",
|
||||
"lint": "eslint --ext .ts src",
|
||||
"lint:report": "pnpm lint --format json --output-file report.json",
|
||||
"test": "jest",
|
||||
"test:ci": "jest",
|
||||
"prepack": "pnpm build"
|
||||
},
|
||||
"engines": {
|
||||
|
@ -36,41 +34,23 @@
|
|||
"url": "https://github.com/logto-io/logto/issues"
|
||||
},
|
||||
"dependencies": {
|
||||
"@logto/schemas": "^1.0.0-beta.10",
|
||||
"@logto/shared": "^1.0.0-beta.10",
|
||||
"@silverhand/essentials": "^1.3.0",
|
||||
"chalk": "^4.1.2",
|
||||
"decamelize": "^5.0.0",
|
||||
"dotenv": "^16.0.0",
|
||||
"fs-extra": "^10.1.0",
|
||||
"got": "^11.8.2",
|
||||
"hpagent": "^1.0.0",
|
||||
"inquirer": "^8.2.2",
|
||||
"nanoid": "^3.3.4",
|
||||
"ora": "^5.0.0",
|
||||
"p-retry": "^4.6.1",
|
||||
"roarr": "^7.11.0",
|
||||
"prompts": "^2.4.2",
|
||||
"semver": "^7.3.7",
|
||||
"slonik": "^30.0.0",
|
||||
"slonik-interceptor-preset": "^1.2.10",
|
||||
"slonik-sql-tag-raw": "^1.1.4",
|
||||
"tar": "^6.1.11",
|
||||
"yargs": "^17.6.0",
|
||||
"zod": "^3.18.0"
|
||||
"tar": "^6.1.11"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@silverhand/eslint-config": "1.2.0",
|
||||
"@silverhand/jest-config": "1.2.2",
|
||||
"@silverhand/ts-config": "1.2.1",
|
||||
"@types/fs-extra": "^9.0.13",
|
||||
"@types/inquirer": "^8.2.1",
|
||||
"@types/jest": "^29.1.2",
|
||||
"@silverhand/eslint-config": "1.0.0",
|
||||
"@silverhand/ts-config": "1.0.0",
|
||||
"@types/decompress": "^4.2.4",
|
||||
"@types/node": "^16.0.0",
|
||||
"@types/prompts": "^2.0.14",
|
||||
"@types/semver": "^7.3.12",
|
||||
"@types/tar": "^6.1.2",
|
||||
"@types/yargs": "^17.0.13",
|
||||
"eslint": "^8.21.0",
|
||||
"jest": "^29.1.2",
|
||||
"lint-staged": "^13.0.0",
|
||||
"prettier": "^2.7.1",
|
||||
"rimraf": "^3.0.2",
|
||||
|
@ -78,10 +58,7 @@
|
|||
"typescript": "^4.7.4"
|
||||
},
|
||||
"eslintConfig": {
|
||||
"extends": "@silverhand",
|
||||
"ignorePatterns": [
|
||||
"src/package-json.ts"
|
||||
]
|
||||
"extends": "@silverhand"
|
||||
},
|
||||
"prettier": "@silverhand/eslint-config/.prettierrc"
|
||||
}
|
||||
|
|
|
@ -1,44 +0,0 @@
|
|||
import { CommandModule } from 'yargs';
|
||||
|
||||
import { log } from '../../utilities';
|
||||
import { addConnectors, addOfficialConnectors, inquireInstancePath } from './utils';
|
||||
|
||||
const add: CommandModule<
|
||||
{ path?: string },
|
||||
{ packages?: string[]; path?: string; official: boolean }
|
||||
> = {
|
||||
command: ['add [packages...]', 'a', 'install', 'i'],
|
||||
describe: 'Add specific Logto connectors',
|
||||
builder: (yargs) =>
|
||||
yargs
|
||||
.positional('packages', {
|
||||
describe: 'The connector package names to add',
|
||||
type: 'string',
|
||||
array: true,
|
||||
default: undefined,
|
||||
})
|
||||
.option('official', {
|
||||
alias: 'o',
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
describe:
|
||||
'Add all official connectors.\n' +
|
||||
"If it's true, the specified package names will be ignored.",
|
||||
}),
|
||||
handler: async ({ packages: packageNames, path, official }) => {
|
||||
const instancePath = await inquireInstancePath(path);
|
||||
|
||||
if (official) {
|
||||
await addOfficialConnectors(instancePath);
|
||||
} else {
|
||||
if (!packageNames?.length) {
|
||||
log.error('No connector name provided');
|
||||
}
|
||||
await addConnectors(instancePath, packageNames);
|
||||
}
|
||||
|
||||
log.info('Restart your Logto instance to get the changes reflected.');
|
||||
},
|
||||
};
|
||||
|
||||
export default add;
|
|
@ -1,25 +0,0 @@
|
|||
import { noop } from '@silverhand/essentials';
|
||||
import { CommandModule } from 'yargs';
|
||||
|
||||
import add from './add';
|
||||
import list from './list';
|
||||
import remove from './remove';
|
||||
|
||||
const connector: CommandModule = {
|
||||
command: ['connector', 'c', 'connectors'],
|
||||
describe: 'Command for Logto connectors',
|
||||
builder: (yargs) =>
|
||||
yargs
|
||||
.option('path', {
|
||||
alias: 'p',
|
||||
type: 'string',
|
||||
describe: 'The path to your Logto instance directory',
|
||||
})
|
||||
.command(add)
|
||||
.command(list)
|
||||
.command(remove)
|
||||
.demandCommand(1),
|
||||
handler: noop,
|
||||
};
|
||||
|
||||
export default connector;
|
|
@ -1,30 +0,0 @@
|
|||
import chalk from 'chalk';
|
||||
import { CommandModule } from 'yargs';
|
||||
|
||||
import { getConnectorPackagesFrom, isOfficialConnector } from './utils';
|
||||
|
||||
const logConnectorNames = (type: string, names: string[]) => {
|
||||
if (names.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
console.log();
|
||||
console.log(chalk.blue(type));
|
||||
console.log(names.map((value) => ' ' + value).join('\n'));
|
||||
};
|
||||
|
||||
const list: CommandModule<{ path?: string }, { path?: string }> = {
|
||||
command: ['list', 'l'],
|
||||
describe: 'List added Logto connectors',
|
||||
handler: async ({ path: inputPath }) => {
|
||||
const packages = await getConnectorPackagesFrom(inputPath);
|
||||
const packageNames = packages.map(({ name }) => name);
|
||||
const officialPackages = packageNames.filter((name) => isOfficialConnector(name));
|
||||
const thirdPartyPackages = packageNames.filter((name) => !isOfficialConnector(name));
|
||||
|
||||
logConnectorNames('official'.toUpperCase(), officialPackages);
|
||||
logConnectorNames('3rd-party'.toUpperCase(), thirdPartyPackages);
|
||||
},
|
||||
};
|
||||
|
||||
export default list;
|
|
@ -1,59 +0,0 @@
|
|||
import chalk from 'chalk';
|
||||
import fsExtra from 'fs-extra';
|
||||
import { CommandModule } from 'yargs';
|
||||
|
||||
import { log } from '../../utilities';
|
||||
import { getConnectorPackagesFrom } from './utils';
|
||||
|
||||
const remove: CommandModule<{ path?: string }, { path?: string; packages?: string[] }> = {
|
||||
command: ['remove [packages...]', 'rm', 'delete'],
|
||||
describe: 'Remove existing Logto connectors',
|
||||
builder: (yargs) =>
|
||||
yargs.positional('packages', {
|
||||
describe: 'The connector package names to remove',
|
||||
type: 'string',
|
||||
array: true,
|
||||
default: undefined,
|
||||
}),
|
||||
handler: async ({ path: inputPath, packages: packageNames }) => {
|
||||
if (!packageNames?.length) {
|
||||
log.error('No connector name provided');
|
||||
}
|
||||
|
||||
const existingPackages = await getConnectorPackagesFrom(inputPath);
|
||||
const notFoundPackageNames = packageNames.filter(
|
||||
(current) => !existingPackages.some(({ name }) => current === name)
|
||||
);
|
||||
|
||||
if (notFoundPackageNames.length > 0) {
|
||||
log.error(
|
||||
`Cannot remove ${notFoundPackageNames
|
||||
.map((name) => chalk.green(name))
|
||||
.join(', ')}: not found in your Logto instance directory`
|
||||
);
|
||||
}
|
||||
|
||||
const okSymbol = Symbol('Connector removed');
|
||||
const result = await Promise.all(
|
||||
packageNames.map(async (current) => {
|
||||
const packageInfo = existingPackages.find(({ name }) => name === current);
|
||||
|
||||
try {
|
||||
await fsExtra.remove(packageInfo?.path ?? '');
|
||||
|
||||
return okSymbol;
|
||||
} catch (error: unknown) {
|
||||
log.warn(`Error while removing ${chalk.green(packageInfo?.name)}`);
|
||||
log.warn(error);
|
||||
|
||||
return error;
|
||||
}
|
||||
})
|
||||
);
|
||||
const errorCount = result.filter((value) => value !== okSymbol).length;
|
||||
|
||||
log.info(`Removed ${result.length - errorCount} connectors`);
|
||||
},
|
||||
};
|
||||
|
||||
export default remove;
|
|
@ -1,214 +0,0 @@
|
|||
import { exec } from 'child_process';
|
||||
import { existsSync } from 'fs';
|
||||
import { readFile, mkdir, unlink, readdir } from 'fs/promises';
|
||||
import path from 'path';
|
||||
import { promisify } from 'util';
|
||||
|
||||
import { conditionalString } from '@silverhand/essentials';
|
||||
import chalk from 'chalk';
|
||||
import { ensureDir, remove } from 'fs-extra';
|
||||
import inquirer from 'inquirer';
|
||||
import pRetry from 'p-retry';
|
||||
import tar from 'tar';
|
||||
import { z } from 'zod';
|
||||
|
||||
import { connectorDirectory } from '../../constants';
|
||||
import { log, oraPromise } from '../../utilities';
|
||||
import { defaultPath } from '../install/utils';
|
||||
|
||||
const coreDirectory = 'packages/core';
|
||||
const execPromise = promisify(exec);
|
||||
export const npmPackResultGuard = z
|
||||
.object({
|
||||
name: z.string(),
|
||||
version: z.string(),
|
||||
filename: z.string(),
|
||||
})
|
||||
.array();
|
||||
|
||||
const buildPathErrorMessage = (value: string) =>
|
||||
`The path ${chalk.green(value)} does not contain a Logto instance, please try another.`;
|
||||
|
||||
const validatePath = async (value: string) => {
|
||||
const corePackageJsonPath = path.resolve(path.join(value, coreDirectory, 'package.json'));
|
||||
|
||||
if (!existsSync(corePackageJsonPath)) {
|
||||
return buildPathErrorMessage(value);
|
||||
}
|
||||
|
||||
const packageJson = await readFile(corePackageJsonPath, { encoding: 'utf8' });
|
||||
const packageName = await z
|
||||
.object({ name: z.string() })
|
||||
.parseAsync(JSON.parse(packageJson))
|
||||
.then(({ name }) => name)
|
||||
.catch(() => '');
|
||||
|
||||
if (packageName !== '@logto/core') {
|
||||
return buildPathErrorMessage(value);
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
export const inquireInstancePath = async (initialPath?: string) => {
|
||||
const { instancePath } = await inquirer.prompt<{ instancePath: string }>(
|
||||
{
|
||||
name: 'instancePath',
|
||||
message: 'Where is your Logto instance?',
|
||||
type: 'input',
|
||||
default: defaultPath,
|
||||
filter: (value: string) => value.trim(),
|
||||
validate: validatePath,
|
||||
},
|
||||
{ instancePath: initialPath }
|
||||
);
|
||||
|
||||
// Validate for initialPath
|
||||
const validated = await validatePath(instancePath);
|
||||
|
||||
if (validated !== true) {
|
||||
log.error(validated);
|
||||
}
|
||||
|
||||
return instancePath;
|
||||
};
|
||||
|
||||
const packagePrefix = 'connector-';
|
||||
|
||||
export const normalizePackageName = (name: string) =>
|
||||
name
|
||||
.split('/')
|
||||
// Prepend prefix to the last fragment if needed
|
||||
.map((fragment, index, array) =>
|
||||
index === array.length - 1 && !fragment.startsWith(packagePrefix) && !fragment.startsWith('@')
|
||||
? packagePrefix + fragment
|
||||
: fragment
|
||||
)
|
||||
.join('/');
|
||||
|
||||
const getConnectorDirectory = (instancePath: string) =>
|
||||
path.join(instancePath, coreDirectory, connectorDirectory);
|
||||
|
||||
export const isOfficialConnector = (packageName: string) =>
|
||||
packageName.startsWith('@logto/connector-');
|
||||
|
||||
const getConnectorPackageName = async (directory: string) => {
|
||||
const filePath = path.join(directory, 'package.json');
|
||||
|
||||
if (!existsSync(filePath)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const json = await readFile(filePath, 'utf8');
|
||||
const { name } = z.object({ name: z.string() }).parse(JSON.parse(json));
|
||||
|
||||
if (name.startsWith('connector-') || Boolean(name.split('/')[1]?.startsWith('connector-'))) {
|
||||
return name;
|
||||
}
|
||||
};
|
||||
|
||||
export type ConnectorPackage = {
|
||||
name: string;
|
||||
path: string;
|
||||
};
|
||||
|
||||
export const getConnectorPackagesFrom = async (instancePath?: string) => {
|
||||
const directory = getConnectorDirectory(await inquireInstancePath(instancePath));
|
||||
const content = await readdir(directory, 'utf8');
|
||||
const rawPackages = await Promise.all(
|
||||
content.map(async (value) => {
|
||||
const currentDirectory = path.join(directory, value);
|
||||
|
||||
return { name: await getConnectorPackageName(currentDirectory), path: currentDirectory };
|
||||
})
|
||||
);
|
||||
|
||||
return rawPackages.filter(
|
||||
(packageInfo): packageInfo is ConnectorPackage => typeof packageInfo.name === 'string'
|
||||
);
|
||||
};
|
||||
|
||||
export const addConnectors = async (instancePath: string, packageNames: string[]) => {
|
||||
const cwd = getConnectorDirectory(instancePath);
|
||||
|
||||
if (!existsSync(cwd)) {
|
||||
await mkdir(cwd);
|
||||
}
|
||||
|
||||
log.info('Fetch connector metadata');
|
||||
|
||||
const results = await Promise.all(
|
||||
packageNames
|
||||
.map((name) => normalizePackageName(name))
|
||||
.map(async (packageName) => {
|
||||
const run = async () => {
|
||||
const { stdout } = await execPromise(`npm pack ${packageName} --json`, { cwd });
|
||||
const result = npmPackResultGuard.parse(JSON.parse(stdout));
|
||||
|
||||
if (!result[0]) {
|
||||
throw new Error(
|
||||
`Unable to execute ${chalk.green('npm pack')} on package ${chalk.green(packageName)}`
|
||||
);
|
||||
}
|
||||
|
||||
const { filename, name } = result[0];
|
||||
const escapedFilename = filename.replace(/\//g, '-').replace(/@/g, '');
|
||||
const tarPath = path.join(cwd, escapedFilename);
|
||||
const packageDirectory = path.join(cwd, name.replace(/\//g, '-'));
|
||||
|
||||
await remove(packageDirectory);
|
||||
await ensureDir(packageDirectory);
|
||||
await tar.extract({ cwd: packageDirectory, file: tarPath, strip: 1 });
|
||||
await unlink(tarPath);
|
||||
|
||||
log.succeed(`Added ${chalk.green(name)}`);
|
||||
};
|
||||
|
||||
try {
|
||||
await pRetry(run, { retries: 2 });
|
||||
} catch (error: unknown) {
|
||||
console.warn(`[${packageName}]`, error);
|
||||
|
||||
return packageName;
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
const errorPackages = results.filter(Boolean);
|
||||
const errorCount = errorPackages.length;
|
||||
|
||||
log.info(
|
||||
errorCount
|
||||
? `Finished with ${errorCount} error${conditionalString(errorCount > 1 && 's')}.`
|
||||
: 'Finished'
|
||||
);
|
||||
|
||||
if (errorCount) {
|
||||
log.warn('Failed to add ' + errorPackages.map((name) => chalk.green(name)).join(', '));
|
||||
}
|
||||
};
|
||||
|
||||
const officialConnectorPrefix = '@logto/connector-';
|
||||
|
||||
const fetchOfficialConnectorList = async () => {
|
||||
const { stdout } = await execPromise(`npm search ${officialConnectorPrefix} --json`);
|
||||
const packages = z
|
||||
.object({ name: z.string() })
|
||||
.transform(({ name }) => name)
|
||||
.array()
|
||||
.parse(JSON.parse(stdout));
|
||||
|
||||
return packages.filter((name) =>
|
||||
['mock', 'kit'].every(
|
||||
(excluded) => !name.slice(officialConnectorPrefix.length).startsWith(excluded)
|
||||
)
|
||||
);
|
||||
};
|
||||
|
||||
export const addOfficialConnectors = async (instancePath: string) => {
|
||||
const packages = await oraPromise(fetchOfficialConnectorList(), {
|
||||
text: 'Fetch official connector list',
|
||||
prefixText: chalk.blue('[info]'),
|
||||
});
|
||||
await addConnectors(instancePath, packages);
|
||||
};
|
|
@ -1,81 +0,0 @@
|
|||
import { createMockPool } from 'slonik';
|
||||
|
||||
import * as functions from '.';
|
||||
import * as queries from '../../../queries/logto-config';
|
||||
import { QueryType } from '../../../test-utilities';
|
||||
import { chooseAlterationsByVersion } from './version';
|
||||
|
||||
const mockQuery: jest.MockedFunction<QueryType> = jest.fn();
|
||||
|
||||
const pool = createMockPool({
|
||||
query: async (sql, values) => {
|
||||
return mockQuery(sql, values);
|
||||
},
|
||||
});
|
||||
|
||||
describe('getUndeployedAlterations()', () => {
|
||||
const files = Object.freeze([
|
||||
{ filename: '1.0.0-1663923770-a.js', path: '/alterations/1.0.0-1663923770-a.js' },
|
||||
{ filename: '1.0.0-1663923771-b.js', path: '/alterations/1.0.0-1663923771-b.js' },
|
||||
{ filename: '1.0.0-1663923772-c.js', path: '/alterations/1.0.0-1663923772-c.js' },
|
||||
]);
|
||||
|
||||
beforeEach(() => {
|
||||
// `getAlterationFiles()` will ensure the order
|
||||
jest.spyOn(functions, 'getAlterationFiles').mockResolvedValueOnce([...files]);
|
||||
});
|
||||
|
||||
it('returns all files if database timestamp is 0', async () => {
|
||||
jest.spyOn(queries, 'getCurrentDatabaseAlterationTimestamp').mockResolvedValueOnce(0);
|
||||
|
||||
await expect(functions.getUndeployedAlterations(pool)).resolves.toEqual(files);
|
||||
});
|
||||
|
||||
it('returns files whose timestamp is greater then database timestamp', async () => {
|
||||
jest
|
||||
.spyOn(queries, 'getCurrentDatabaseAlterationTimestamp')
|
||||
.mockResolvedValueOnce(1_663_923_770);
|
||||
|
||||
await expect(functions.getUndeployedAlterations(pool)).resolves.toEqual([files[1], files[2]]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('chooseAlterationsByVersion()', () => {
|
||||
const files = Object.freeze(
|
||||
[
|
||||
'1.0.0_beta.9-1663923770-a.js',
|
||||
'1.0.0_beta.9-1663923771-b.js',
|
||||
'1.0.0_beta.10-1663923772-c.js',
|
||||
'1.0.0_beta.11-1663923773-c.js',
|
||||
'1.0.0_beta.11-1663923774-c.js',
|
||||
'1.0.0-1663923775-c.js',
|
||||
'1.0.0-1663923776-c.js',
|
||||
'1.0.1-1663923777-c.js',
|
||||
'1.2.0-1663923778-c.js',
|
||||
'next-1663923778-c.js',
|
||||
'next-1663923779-c.js',
|
||||
'next-1663923780-c.js',
|
||||
'next1-1663923781-c.js',
|
||||
].map((filename) => ({ filename, path: '/alterations/' + filename }))
|
||||
);
|
||||
|
||||
it('chooses nothing when input version is invalid', async () => {
|
||||
await expect(chooseAlterationsByVersion(files, 'next1')).rejects.toThrow(
|
||||
'Invalid Version: next1'
|
||||
);
|
||||
await expect(chooseAlterationsByVersion([], 'ok')).rejects.toThrow('Invalid Version: ok');
|
||||
});
|
||||
|
||||
it('chooses correct alteration files', async () => {
|
||||
await Promise.all([
|
||||
expect(chooseAlterationsByVersion([], 'v1.0.0')).resolves.toEqual([]),
|
||||
expect(chooseAlterationsByVersion(files, 'v1.0.0')).resolves.toEqual(files.slice(0, 7)),
|
||||
expect(chooseAlterationsByVersion(files, 'v1.0.0-beta.10')).resolves.toEqual(
|
||||
files.slice(0, 3)
|
||||
),
|
||||
expect(chooseAlterationsByVersion(files, 'v1.1.0')).resolves.toEqual(files.slice(0, 8)),
|
||||
expect(chooseAlterationsByVersion(files, 'v1.2.0')).resolves.toEqual(files.slice(0, 9)),
|
||||
expect(chooseAlterationsByVersion(files, 'next')).resolves.toEqual(files.slice(0, 12)),
|
||||
]);
|
||||
});
|
||||
});
|
|
@ -1,162 +0,0 @@
|
|||
import path from 'path';
|
||||
|
||||
import { AlterationScript } from '@logto/schemas/lib/types/alteration';
|
||||
import { findPackage } from '@logto/shared';
|
||||
import { conditionalString } from '@silverhand/essentials';
|
||||
import chalk from 'chalk';
|
||||
import { copy, existsSync, remove, readdir } from 'fs-extra';
|
||||
import { DatabasePool } from 'slonik';
|
||||
import { CommandModule } from 'yargs';
|
||||
|
||||
import { createPoolFromConfig } from '../../../database';
|
||||
import {
|
||||
getCurrentDatabaseAlterationTimestamp,
|
||||
updateDatabaseTimestamp,
|
||||
} from '../../../queries/logto-config';
|
||||
import { getPathInModule, log } from '../../../utilities';
|
||||
import { AlterationFile } from './type';
|
||||
import { chooseAlterationsByVersion } from './version';
|
||||
|
||||
const alterationFilenameRegex = /-(\d+)-?.*\.js$/;
|
||||
|
||||
const getTimestampFromFilename = (filename: string) => {
|
||||
const match = alterationFilenameRegex.exec(filename);
|
||||
|
||||
if (!match?.[1]) {
|
||||
throw new Error(`Can not get timestamp: ${filename}`);
|
||||
}
|
||||
|
||||
return Number(match[1]);
|
||||
};
|
||||
|
||||
const importAlterationScript = async (filePath: string): Promise<AlterationScript> => {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
||||
const module = await import(filePath);
|
||||
|
||||
// eslint-disable-next-line no-restricted-syntax
|
||||
return module.default as AlterationScript;
|
||||
};
|
||||
|
||||
export const getAlterationFiles = async (): Promise<AlterationFile[]> => {
|
||||
const alterationDirectory = getPathInModule('@logto/schemas', 'alterations');
|
||||
|
||||
/**
|
||||
* We copy all alteration scripts to the CLI package root directory,
|
||||
* since they need a proper context that includes required dependencies (such as slonik) in `node_modules/`.
|
||||
* While the original `@logto/schemas` may remove them in production.
|
||||
*/
|
||||
const packageDirectory = await findPackage(
|
||||
// Until we migrate to ESM
|
||||
// eslint-disable-next-line unicorn/prefer-module
|
||||
__dirname
|
||||
);
|
||||
|
||||
const localAlterationDirectory = path.resolve(
|
||||
// Until we migrate to ESM
|
||||
// eslint-disable-next-line unicorn/prefer-module
|
||||
packageDirectory ?? __dirname,
|
||||
'alteration-scripts'
|
||||
);
|
||||
|
||||
if (!existsSync(alterationDirectory)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
// We need to copy alteration files to execute in the CLI context to make `slonik` available
|
||||
await remove(localAlterationDirectory);
|
||||
await copy(alterationDirectory, localAlterationDirectory);
|
||||
|
||||
const directory = await readdir(localAlterationDirectory);
|
||||
const files = directory.filter((file) => alterationFilenameRegex.test(file));
|
||||
|
||||
return files
|
||||
.slice()
|
||||
.sort((file1, file2) => getTimestampFromFilename(file1) - getTimestampFromFilename(file2))
|
||||
.map((filename) => ({ path: path.join(localAlterationDirectory, filename), filename }));
|
||||
};
|
||||
|
||||
export const getLatestAlterationTimestamp = async () => {
|
||||
const files = await getAlterationFiles();
|
||||
const lastFile = files[files.length - 1];
|
||||
|
||||
if (!lastFile) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return getTimestampFromFilename(lastFile.filename);
|
||||
};
|
||||
|
||||
export const getUndeployedAlterations = async (pool: DatabasePool) => {
|
||||
const databaseTimestamp = await getCurrentDatabaseAlterationTimestamp(pool);
|
||||
const files = await getAlterationFiles();
|
||||
|
||||
return files.filter(({ filename }) => getTimestampFromFilename(filename) > databaseTimestamp);
|
||||
};
|
||||
|
||||
const deployAlteration = async (
|
||||
pool: DatabasePool,
|
||||
{ path: filePath, filename }: AlterationFile
|
||||
) => {
|
||||
const { up } = await importAlterationScript(filePath);
|
||||
|
||||
try {
|
||||
await pool.transaction(async (connection) => {
|
||||
await up(connection);
|
||||
await updateDatabaseTimestamp(connection, getTimestampFromFilename(filename));
|
||||
});
|
||||
} catch (error: unknown) {
|
||||
console.error(error);
|
||||
|
||||
await pool.end();
|
||||
log.error(
|
||||
`Error ocurred during running alteration ${chalk.blue(filename)}.\n\n` +
|
||||
" This alteration didn't change anything since it was in a transaction.\n" +
|
||||
' Try to fix the error and deploy again.'
|
||||
);
|
||||
}
|
||||
|
||||
log.info(`Run alteration ${filename} succeeded`);
|
||||
};
|
||||
|
||||
const alteration: CommandModule<unknown, { action: string; target?: string }> = {
|
||||
command: ['alteration <action> [target]', 'alt', 'alter'],
|
||||
describe: 'Perform database alteration',
|
||||
builder: (yargs) =>
|
||||
yargs
|
||||
.positional('action', {
|
||||
describe: 'The action to perform, now it only accepts `deploy`',
|
||||
type: 'string',
|
||||
demandOption: true,
|
||||
})
|
||||
.positional('target', {
|
||||
describe: 'The target Logto version for alteration',
|
||||
type: 'string',
|
||||
}),
|
||||
handler: async ({ action, target }) => {
|
||||
if (action !== 'deploy') {
|
||||
log.error('Unsupported action');
|
||||
}
|
||||
|
||||
const pool = await createPoolFromConfig();
|
||||
const alterations = await chooseAlterationsByVersion(
|
||||
await getUndeployedAlterations(pool),
|
||||
target
|
||||
);
|
||||
|
||||
log.info(
|
||||
`Found ${alterations.length} alteration${conditionalString(
|
||||
alterations.length > 1 && 's'
|
||||
)} to deploy`
|
||||
);
|
||||
|
||||
// The await inside the loop is intended, alterations should run in order
|
||||
for (const alteration of alterations) {
|
||||
// eslint-disable-next-line no-await-in-loop
|
||||
await deployAlteration(pool, alteration);
|
||||
}
|
||||
|
||||
await pool.end();
|
||||
},
|
||||
};
|
||||
|
||||
export default alteration;
|
|
@ -1 +0,0 @@
|
|||
export type AlterationFile = { path: string; filename: string };
|
|
@ -1,78 +0,0 @@
|
|||
import { conditional } from '@silverhand/essentials';
|
||||
import chalk from 'chalk';
|
||||
import inquirer from 'inquirer';
|
||||
import { SemVer, compare, eq, gt } from 'semver';
|
||||
|
||||
import { findLastIndex, log } from '../../../utilities';
|
||||
import { AlterationFile } from './type';
|
||||
|
||||
const getVersionFromFilename = (filename: string) => {
|
||||
try {
|
||||
return new SemVer(filename.split('-')[0]?.replaceAll('_', '-') ?? 'unknown');
|
||||
} catch {}
|
||||
};
|
||||
|
||||
const latestTag = 'latest';
|
||||
const nextTag = 'next';
|
||||
|
||||
export const chooseAlterationsByVersion = async (
|
||||
alterations: readonly AlterationFile[],
|
||||
initialVersion?: string
|
||||
) => {
|
||||
if (initialVersion === nextTag) {
|
||||
const endIndex = findLastIndex(
|
||||
alterations,
|
||||
({ filename }) =>
|
||||
filename.startsWith(nextTag + '-') || Boolean(getVersionFromFilename(filename))
|
||||
);
|
||||
|
||||
if (endIndex === -1) {
|
||||
return [];
|
||||
}
|
||||
|
||||
log.info(`Deploy target ${chalk.green(nextTag)}`);
|
||||
|
||||
return alterations.slice(0, endIndex + 1);
|
||||
}
|
||||
|
||||
const versions = alterations
|
||||
.map(({ filename }) => getVersionFromFilename(filename))
|
||||
.filter((version): version is SemVer => version instanceof SemVer)
|
||||
// Cannot use `Set` to deduplicate since it's a class
|
||||
.filter((version, index, self) => index === self.findIndex((another) => eq(version, another)))
|
||||
.slice()
|
||||
.sort((i, j) => compare(j, i));
|
||||
const initialSemVersion = conditional(
|
||||
initialVersion && initialVersion !== latestTag && new SemVer(initialVersion)
|
||||
);
|
||||
|
||||
if (!versions[0]) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const { version: targetVersion } =
|
||||
initialVersion === latestTag
|
||||
? { version: versions[0] }
|
||||
: await inquirer.prompt<{ version: SemVer }>(
|
||||
{
|
||||
type: 'list',
|
||||
message: 'Choose the alteration target version',
|
||||
name: 'version',
|
||||
choices: versions.map((semVersion) => ({
|
||||
name: semVersion.version,
|
||||
value: semVersion,
|
||||
})),
|
||||
},
|
||||
{
|
||||
version: initialSemVersion,
|
||||
}
|
||||
);
|
||||
|
||||
log.info(`Deploy target ${chalk.green(targetVersion.version)}`);
|
||||
|
||||
return alterations.filter(({ filename }) => {
|
||||
const version = getVersionFromFilename(filename);
|
||||
|
||||
return version && !gt(version, targetVersion);
|
||||
});
|
||||
};
|
|
@ -1,97 +0,0 @@
|
|||
import { logtoConfigGuards, LogtoConfigKey, logtoConfigKeys } from '@logto/schemas';
|
||||
import { deduplicate } from '@silverhand/essentials';
|
||||
import chalk from 'chalk';
|
||||
import { CommandModule } from 'yargs';
|
||||
|
||||
import { createPoolFromConfig } from '../../database';
|
||||
import { getRowsByKeys, updateValueByKey } from '../../queries/logto-config';
|
||||
import { log } from '../../utilities';
|
||||
|
||||
const validKeysDisplay = chalk.green(logtoConfigKeys.join(', '));
|
||||
|
||||
type ValidateKeysFunction = {
|
||||
(keys: string[]): asserts keys is LogtoConfigKey[];
|
||||
(key: string): asserts key is LogtoConfigKey;
|
||||
};
|
||||
|
||||
const validateKeys: ValidateKeysFunction = (keys) => {
|
||||
const invalidKey = (Array.isArray(keys) ? keys : [keys]).find(
|
||||
// Using `.includes()` will result a type error
|
||||
// eslint-disable-next-line unicorn/prefer-includes
|
||||
(key) => !logtoConfigKeys.some((element) => element === key)
|
||||
);
|
||||
|
||||
if (invalidKey) {
|
||||
log.error(
|
||||
`Invalid config key ${chalk.red(invalidKey)} found, expected one of ${validKeysDisplay}`
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
export const getConfig: CommandModule<unknown, { key: string; keys: string[] }> = {
|
||||
command: 'get-config <key> [keys...]',
|
||||
describe: 'Get config value(s) of the given key(s) in Logto database',
|
||||
builder: (yargs) =>
|
||||
yargs
|
||||
.positional('key', {
|
||||
describe: `The key to get from database, one of ${validKeysDisplay}`,
|
||||
type: 'string',
|
||||
demandOption: true,
|
||||
})
|
||||
.positional('keys', {
|
||||
describe: 'The additional keys to get from database',
|
||||
type: 'string',
|
||||
array: true,
|
||||
default: [],
|
||||
}),
|
||||
handler: async ({ key, keys }) => {
|
||||
const queryKeys = deduplicate([key, ...keys]);
|
||||
validateKeys(queryKeys);
|
||||
|
||||
const pool = await createPoolFromConfig();
|
||||
const { rows } = await getRowsByKeys(pool, queryKeys);
|
||||
await pool.end();
|
||||
|
||||
console.log(
|
||||
queryKeys
|
||||
.map((currentKey) => {
|
||||
const value = rows.find(({ key }) => currentKey === key)?.value;
|
||||
|
||||
return (
|
||||
chalk.magenta(currentKey) +
|
||||
'=' +
|
||||
(value === undefined ? chalk.gray(value) : chalk.green(JSON.stringify(value)))
|
||||
);
|
||||
})
|
||||
.join('\n')
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
export const setConfig: CommandModule<unknown, { key: string; value: string }> = {
|
||||
command: 'set-config <key> <value>',
|
||||
describe: 'Set config value of the given key in Logto database',
|
||||
builder: (yargs) =>
|
||||
yargs
|
||||
.positional('key', {
|
||||
describe: `The key to get from database, one of ${validKeysDisplay}`,
|
||||
type: 'string',
|
||||
demandOption: true,
|
||||
})
|
||||
.positional('value', {
|
||||
describe: 'The value to set, should be a valid JSON string',
|
||||
type: 'string',
|
||||
demandOption: true,
|
||||
}),
|
||||
handler: async ({ key, value }) => {
|
||||
validateKeys(key);
|
||||
|
||||
const guarded = logtoConfigGuards[key].parse(JSON.parse(value));
|
||||
|
||||
const pool = await createPoolFromConfig();
|
||||
await updateValueByKey(pool, key, guarded);
|
||||
await pool.end();
|
||||
|
||||
log.info(`Update ${chalk.green(key)} succeeded`);
|
||||
},
|
||||
};
|
|
@ -1,16 +0,0 @@
|
|||
import { noop } from '@silverhand/essentials';
|
||||
import { CommandModule } from 'yargs';
|
||||
|
||||
import alteration from './alteration';
|
||||
import { getConfig, setConfig } from './config';
|
||||
import seed from './seed';
|
||||
|
||||
const database: CommandModule = {
|
||||
command: ['database', 'db'],
|
||||
describe: 'Commands for Logto database',
|
||||
builder: (yargs) =>
|
||||
yargs.command(getConfig).command(setConfig).command(seed).command(alteration).demandCommand(1),
|
||||
handler: noop,
|
||||
};
|
||||
|
||||
export default database;
|
|
@ -1,174 +0,0 @@
|
|||
import { readdir, readFile } from 'fs/promises';
|
||||
import path from 'path';
|
||||
|
||||
import { logtoConfigGuards, LogtoOidcConfigKey, seeds } from '@logto/schemas';
|
||||
import { buildApplicationSecret } from '@logto/shared';
|
||||
import chalk from 'chalk';
|
||||
import { DatabasePool, DatabaseTransactionConnection, sql } from 'slonik';
|
||||
import { raw } from 'slonik-sql-tag-raw';
|
||||
import { CommandModule } from 'yargs';
|
||||
import { z } from 'zod';
|
||||
|
||||
import { createPoolAndDatabaseIfNeeded, insertInto } from '../../../database';
|
||||
import {
|
||||
getRowsByKeys,
|
||||
isConfigsTableExists,
|
||||
updateDatabaseTimestamp,
|
||||
updateValueByKey,
|
||||
} from '../../../queries/logto-config';
|
||||
import { getPathInModule, log, oraPromise } from '../../../utilities';
|
||||
import { getLatestAlterationTimestamp } from '../alteration';
|
||||
import { oidcConfigReaders } from './oidc-config';
|
||||
|
||||
const createTables = async (connection: DatabaseTransactionConnection) => {
|
||||
const tableDirectory = getPathInModule('@logto/schemas', 'tables');
|
||||
const directoryFiles = await readdir(tableDirectory);
|
||||
const tableFiles = directoryFiles.filter((file) => file.endsWith('.sql'));
|
||||
const queries = await Promise.all(
|
||||
tableFiles.map<Promise<[string, string]>>(async (file) => [
|
||||
file,
|
||||
await readFile(path.join(tableDirectory, file), 'utf8'),
|
||||
])
|
||||
);
|
||||
|
||||
// Await in loop is intended for better error handling
|
||||
for (const [, query] of queries) {
|
||||
// eslint-disable-next-line no-await-in-loop
|
||||
await connection.query(sql`${raw(query)}`);
|
||||
}
|
||||
};
|
||||
|
||||
const seedTables = async (connection: DatabaseTransactionConnection) => {
|
||||
const {
|
||||
managementResource,
|
||||
defaultSignInExperience,
|
||||
createDefaultSetting,
|
||||
createDemoAppApplication,
|
||||
defaultRole,
|
||||
} = seeds;
|
||||
|
||||
await Promise.all([
|
||||
connection.query(insertInto(managementResource, 'resources')),
|
||||
connection.query(insertInto(createDefaultSetting(), 'settings')),
|
||||
connection.query(insertInto(defaultSignInExperience, 'sign_in_experiences')),
|
||||
connection.query(
|
||||
insertInto(createDemoAppApplication(buildApplicationSecret()), 'applications')
|
||||
),
|
||||
connection.query(insertInto(defaultRole, 'roles')),
|
||||
updateDatabaseTimestamp(connection, await getLatestAlterationTimestamp()),
|
||||
]);
|
||||
};
|
||||
|
||||
const seedOidcConfigs = async (pool: DatabaseTransactionConnection) => {
|
||||
const configGuard = z.object({
|
||||
key: z.nativeEnum(LogtoOidcConfigKey),
|
||||
value: z.unknown(),
|
||||
});
|
||||
const { rows } = await getRowsByKeys(pool, Object.values(LogtoOidcConfigKey));
|
||||
// Filter out valid keys that hold a valid value
|
||||
const result = await Promise.all(
|
||||
rows.map<Promise<LogtoOidcConfigKey | undefined>>(async (row) => {
|
||||
try {
|
||||
const { key, value } = await configGuard.parseAsync(row);
|
||||
await logtoConfigGuards[key].parseAsync(value);
|
||||
|
||||
return key;
|
||||
} catch {}
|
||||
})
|
||||
);
|
||||
const existingKeys = new Set(result.filter(Boolean));
|
||||
|
||||
const validOptions = Object.values(LogtoOidcConfigKey).filter((key) => {
|
||||
const included = existingKeys.has(key);
|
||||
|
||||
if (included) {
|
||||
log.info(`Key ${chalk.green(key)} exists, skipping`);
|
||||
}
|
||||
|
||||
return !included;
|
||||
});
|
||||
|
||||
// The awaits in loop is intended since we'd like to log info in sequence
|
||||
/* eslint-disable no-await-in-loop */
|
||||
for (const key of validOptions) {
|
||||
const { value, fromEnv } = await oidcConfigReaders[key]();
|
||||
|
||||
if (fromEnv) {
|
||||
log.info(`Read config ${chalk.green(key)} from env`);
|
||||
} else {
|
||||
log.info(`Generated config ${chalk.green(key)}`);
|
||||
}
|
||||
|
||||
await updateValueByKey(pool, key, value);
|
||||
}
|
||||
/* eslint-enable no-await-in-loop */
|
||||
|
||||
log.succeed('Seed OIDC config');
|
||||
};
|
||||
|
||||
const seedChoices = Object.freeze(['all', 'oidc'] as const);
|
||||
|
||||
type SeedChoice = typeof seedChoices[number];
|
||||
|
||||
export const seedByPool = async (pool: DatabasePool, type: SeedChoice) => {
|
||||
await pool.transaction(async (connection) => {
|
||||
if (type !== 'oidc') {
|
||||
await oraPromise(createTables(connection), {
|
||||
text: 'Create tables',
|
||||
prefixText: chalk.blue('[info]'),
|
||||
});
|
||||
await oraPromise(seedTables(connection), {
|
||||
text: 'Seed data',
|
||||
prefixText: chalk.blue('[info]'),
|
||||
});
|
||||
}
|
||||
|
||||
await seedOidcConfigs(connection);
|
||||
});
|
||||
};
|
||||
|
||||
const seed: CommandModule<Record<string, unknown>, { type: string; swe?: boolean }> = {
|
||||
command: 'seed [type]',
|
||||
describe: 'Create database then seed tables and data',
|
||||
builder: (yargs) =>
|
||||
yargs
|
||||
.option('swe', {
|
||||
describe: 'Skip the seeding process when Logto configs table exists',
|
||||
alias: 'skip-when-exists',
|
||||
type: 'boolean',
|
||||
})
|
||||
.positional('type', {
|
||||
describe: 'Optional seed type',
|
||||
type: 'string',
|
||||
choices: seedChoices,
|
||||
default: 'all',
|
||||
}),
|
||||
handler: async ({ type, swe }) => {
|
||||
const pool = await createPoolAndDatabaseIfNeeded();
|
||||
|
||||
if (swe && (await isConfigsTableExists(pool))) {
|
||||
log.info('Seeding skipped');
|
||||
await pool.end();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
// Cannot avoid `as` since the official type definition of `yargs` doesn't work.
|
||||
// The value of `type` can be ensured, so it's safe to use `as` here.
|
||||
// eslint-disable-next-line no-restricted-syntax
|
||||
await seedByPool(pool, type as SeedChoice);
|
||||
} catch (error: unknown) {
|
||||
console.error(error);
|
||||
console.log();
|
||||
log.warn(
|
||||
'Error ocurred during seeding your database.\n\n' +
|
||||
' Nothing has changed since the seeding process was in a transaction.\n' +
|
||||
' Try to fix the error and seed again.'
|
||||
);
|
||||
}
|
||||
await pool.end();
|
||||
},
|
||||
};
|
||||
|
||||
export default seed;
|
|
@ -1,90 +0,0 @@
|
|||
import { generateKeyPair } from 'crypto';
|
||||
import { readFile } from 'fs/promises';
|
||||
import { promisify } from 'util';
|
||||
|
||||
import { LogtoOidcConfigKey, LogtoOidcConfigType } from '@logto/schemas';
|
||||
import { getEnv, getEnvAsStringArray } from '@silverhand/essentials';
|
||||
import { nanoid } from 'nanoid';
|
||||
|
||||
const isBase64FormatPrivateKey = (key: string) => !key.includes('-');
|
||||
|
||||
/**
|
||||
* Each config reader will do the following things in order:
|
||||
* 1. Try to read value from env (mimic the behavior from the original core)
|
||||
* 2. Generate value if #1 doesn't work
|
||||
*/
|
||||
export const oidcConfigReaders: {
|
||||
[key in LogtoOidcConfigKey]: () => Promise<{
|
||||
value: LogtoOidcConfigType[key];
|
||||
fromEnv: boolean;
|
||||
}>;
|
||||
} = {
|
||||
/**
|
||||
* Try to read private keys with the following order:
|
||||
*
|
||||
* 1. From `process.env.OIDC_PRIVATE_KEYS`.
|
||||
* 2. Fetch path from `process.env.OIDC_PRIVATE_KEY_PATHS` then read from that path.
|
||||
*
|
||||
*
|
||||
* @returns The private keys for OIDC provider.
|
||||
* @throws An error when failed to read a private key.
|
||||
*/
|
||||
[LogtoOidcConfigKey.PrivateKeys]: async () => {
|
||||
// Direct keys in env
|
||||
const privateKeys = getEnvAsStringArray('OIDC_PRIVATE_KEYS');
|
||||
|
||||
if (privateKeys.length > 0) {
|
||||
return {
|
||||
value: privateKeys.map((key) => {
|
||||
if (isBase64FormatPrivateKey(key)) {
|
||||
return Buffer.from(key, 'base64').toString('utf8');
|
||||
}
|
||||
|
||||
return key;
|
||||
}),
|
||||
fromEnv: true,
|
||||
};
|
||||
}
|
||||
|
||||
// Read keys from files
|
||||
const privateKeyPaths = getEnvAsStringArray('OIDC_PRIVATE_KEY_PATHS');
|
||||
|
||||
if (privateKeyPaths.length > 0) {
|
||||
return {
|
||||
value: await Promise.all(privateKeyPaths.map(async (path) => readFile(path, 'utf8'))),
|
||||
fromEnv: true,
|
||||
};
|
||||
}
|
||||
|
||||
// Generate a new key
|
||||
const { privateKey } = await promisify(generateKeyPair)('rsa', {
|
||||
modulusLength: 4096,
|
||||
publicKeyEncoding: {
|
||||
type: 'spki',
|
||||
format: 'pem',
|
||||
},
|
||||
privateKeyEncoding: {
|
||||
type: 'pkcs8',
|
||||
format: 'pem',
|
||||
},
|
||||
});
|
||||
|
||||
return {
|
||||
value: [privateKey],
|
||||
fromEnv: false,
|
||||
};
|
||||
},
|
||||
[LogtoOidcConfigKey.CookieKeys]: async () => {
|
||||
const envKey = 'OIDC_COOKIE_KEYS';
|
||||
const keys = getEnvAsStringArray(envKey);
|
||||
|
||||
return { value: keys.length > 0 ? keys : [nanoid()], fromEnv: keys.length > 0 };
|
||||
},
|
||||
[LogtoOidcConfigKey.RefreshTokenReuseInterval]: async () => {
|
||||
const envKey = 'OIDC_REFRESH_TOKEN_REUSE_INTERVAL';
|
||||
const raw = Number(getEnv(envKey));
|
||||
const value = Math.max(3, raw || 0);
|
||||
|
||||
return { value, fromEnv: raw === value };
|
||||
},
|
||||
};
|
|
@ -1,110 +0,0 @@
|
|||
import chalk from 'chalk';
|
||||
import { CommandModule } from 'yargs';
|
||||
|
||||
import { getDatabaseUrlFromConfig } from '../../database';
|
||||
import { log } from '../../utilities';
|
||||
import { addOfficialConnectors } from '../connector/utils';
|
||||
import {
|
||||
validateNodeVersion,
|
||||
inquireInstancePath,
|
||||
validateDatabase,
|
||||
downloadRelease,
|
||||
seedDatabase,
|
||||
createEnv,
|
||||
logFinale,
|
||||
decompress,
|
||||
inquireOfficialConnectors,
|
||||
isUrl,
|
||||
} from './utils';
|
||||
|
||||
export type InstallArgs = {
|
||||
path?: string;
|
||||
skipSeed: boolean;
|
||||
officialConnectors?: boolean;
|
||||
downloadUrl?: string;
|
||||
};
|
||||
|
||||
const installLogto = async ({ path, skipSeed, officialConnectors, downloadUrl }: InstallArgs) => {
|
||||
validateNodeVersion();
|
||||
|
||||
// Get instance path
|
||||
const instancePath = await inquireInstancePath(path);
|
||||
|
||||
// Validate if user has a valid database
|
||||
await validateDatabase();
|
||||
|
||||
// Download and decompress
|
||||
const tarPath =
|
||||
!downloadUrl || isUrl(downloadUrl) ? await downloadRelease(downloadUrl) : downloadUrl;
|
||||
await decompress(instancePath, tarPath);
|
||||
|
||||
// Seed database
|
||||
if (skipSeed) {
|
||||
log.info(
|
||||
`Skipped database seeding.\n\n' + ' You can use the ${chalk.green(
|
||||
'db seed'
|
||||
)} command to seed database when ready.\n`
|
||||
);
|
||||
} else {
|
||||
await seedDatabase(instancePath);
|
||||
}
|
||||
|
||||
// Save to dot env
|
||||
await createEnv(instancePath, await getDatabaseUrlFromConfig());
|
||||
|
||||
// Add official connectors
|
||||
if (await inquireOfficialConnectors(officialConnectors)) {
|
||||
await addOfficialConnectors(instancePath);
|
||||
} else {
|
||||
log.info(
|
||||
'Skipped adding official connectors.\n\n' +
|
||||
` You can use the ${chalk.green('connector add')} command to add connectors at any time.\n`
|
||||
);
|
||||
}
|
||||
|
||||
// Finale
|
||||
logFinale(instancePath);
|
||||
};
|
||||
|
||||
const install: CommandModule<
|
||||
unknown,
|
||||
{
|
||||
p?: string;
|
||||
ss: boolean;
|
||||
oc?: boolean;
|
||||
du?: string;
|
||||
}
|
||||
> = {
|
||||
command: ['init', 'i', 'install'],
|
||||
describe: 'Download and run the latest Logto release',
|
||||
builder: (yargs) =>
|
||||
yargs.options({
|
||||
p: {
|
||||
alias: 'path',
|
||||
describe: 'Path of Logto, must be a non-existing path',
|
||||
type: 'string',
|
||||
},
|
||||
ss: {
|
||||
alias: 'skip-seed',
|
||||
describe: 'Skip Logto database seeding',
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
},
|
||||
oc: {
|
||||
alias: 'official-connectors',
|
||||
describe: 'Add official connectors after downloading Logto',
|
||||
type: 'boolean',
|
||||
},
|
||||
du: {
|
||||
alias: 'download-url',
|
||||
describe: 'URL for downloading Logto, can be a local path to tar',
|
||||
type: 'string',
|
||||
hidden: true,
|
||||
},
|
||||
}),
|
||||
handler: async ({ p, ss, oc, du }) => {
|
||||
await installLogto({ path: p, skipSeed: ss, officialConnectors: oc, downloadUrl: du });
|
||||
},
|
||||
};
|
||||
|
||||
export default install;
|
|
@ -1,180 +0,0 @@
|
|||
import { execSync } from 'child_process';
|
||||
import { existsSync } from 'fs';
|
||||
import { mkdir } from 'fs/promises';
|
||||
import os from 'os';
|
||||
import path from 'path';
|
||||
|
||||
import chalk from 'chalk';
|
||||
import { remove, writeFile } from 'fs-extra';
|
||||
import inquirer from 'inquirer';
|
||||
import * as semver from 'semver';
|
||||
import tar from 'tar';
|
||||
|
||||
import { createPoolAndDatabaseIfNeeded } from '../../database';
|
||||
import { cliConfig, ConfigKey, downloadFile, log, oraPromise, safeExecSync } from '../../utilities';
|
||||
import { seedByPool } from '../database/seed';
|
||||
|
||||
export const defaultPath = path.join(os.homedir(), 'logto');
|
||||
const pgRequired = new semver.SemVer('14.0.0');
|
||||
|
||||
export const validateNodeVersion = () => {
|
||||
const required = new semver.SemVer('16.0.0');
|
||||
const current = new semver.SemVer(execSync('node -v', { encoding: 'utf8', stdio: 'pipe' }));
|
||||
|
||||
if (required.compare(current) > 0) {
|
||||
log.error(`Logto requires NodeJS >=${required.version}, but ${current.version} found.`);
|
||||
}
|
||||
|
||||
if (current.major > required.major) {
|
||||
log.warn(
|
||||
`Logto is tested under NodeJS ^${required.version}, but version ${current.version} found.`
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
const validatePath = (value: string) =>
|
||||
existsSync(path.resolve(value))
|
||||
? `The path ${chalk.green(value)} already exists, please try another.`
|
||||
: true;
|
||||
|
||||
export const inquireInstancePath = async (initialPath?: string) => {
|
||||
const { instancePath } = await inquirer.prompt<{ instancePath: string }>(
|
||||
{
|
||||
name: 'instancePath',
|
||||
message: 'Where should we create your Logto instance?',
|
||||
type: 'input',
|
||||
default: defaultPath,
|
||||
filter: (value: string) => value.trim(),
|
||||
validate: validatePath,
|
||||
},
|
||||
{ instancePath: initialPath }
|
||||
);
|
||||
|
||||
// Validate for initialPath
|
||||
const validated = validatePath(instancePath);
|
||||
|
||||
if (validated !== true) {
|
||||
log.error(validated);
|
||||
}
|
||||
|
||||
return instancePath;
|
||||
};
|
||||
|
||||
export const validateDatabase = async () => {
|
||||
if (cliConfig.has(ConfigKey.DatabaseUrl)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const { hasPostgresUrl } = await inquirer.prompt<{ hasPostgresUrl?: boolean }>({
|
||||
name: 'hasPostgresUrl',
|
||||
message: `Logto requires PostgreSQL >=${pgRequired.version} but cannot find in the current environment.\n Do you have a remote PostgreSQL instance ready?`,
|
||||
type: 'confirm',
|
||||
when: () => {
|
||||
const pgOutput = safeExecSync('postgres --version') ?? '';
|
||||
// Filter out all brackets in the output since Homebrew will append `(Homebrew)`.
|
||||
const pgArray = pgOutput.split(' ').filter((value) => !value.startsWith('('));
|
||||
const pgCurrent = semver.coerce(pgArray[pgArray.length - 1]);
|
||||
|
||||
return !pgCurrent || pgCurrent.compare(pgRequired) < 0;
|
||||
},
|
||||
});
|
||||
|
||||
if (hasPostgresUrl === false) {
|
||||
log.error('Logto requires a Postgres instance to run.');
|
||||
}
|
||||
};
|
||||
|
||||
export const downloadRelease = async (url?: string) => {
|
||||
const tarFilePath = path.resolve(os.tmpdir(), './logto.tar.gz');
|
||||
|
||||
log.info(`Download Logto to ${tarFilePath}`);
|
||||
await downloadFile(
|
||||
url ?? 'https://github.com/logto-io/logto/releases/latest/download/logto.tar.gz',
|
||||
tarFilePath
|
||||
);
|
||||
|
||||
return tarFilePath;
|
||||
};
|
||||
|
||||
export const decompress = async (toPath: string, tarPath: string) => {
|
||||
const run = async () => {
|
||||
try {
|
||||
await mkdir(toPath);
|
||||
await tar.extract({ file: tarPath, cwd: toPath, strip: 1 });
|
||||
} catch (error: unknown) {
|
||||
log.error(error);
|
||||
}
|
||||
};
|
||||
|
||||
return oraPromise(
|
||||
run(),
|
||||
{
|
||||
text: `Decompress to ${toPath}`,
|
||||
prefixText: chalk.blue('[info]'),
|
||||
},
|
||||
true
|
||||
);
|
||||
};
|
||||
|
||||
export const seedDatabase = async (instancePath: string) => {
|
||||
try {
|
||||
const pool = await createPoolAndDatabaseIfNeeded();
|
||||
await seedByPool(pool, 'all');
|
||||
await pool.end();
|
||||
} catch (error: unknown) {
|
||||
console.error(error);
|
||||
|
||||
await oraPromise(remove(instancePath), {
|
||||
text: 'Clean up',
|
||||
prefixText: chalk.blue('[info]'),
|
||||
});
|
||||
|
||||
log.error(
|
||||
'Error occurred during seeding your Logto database. Nothing has changed since the seeding process was in a transaction.\n\n' +
|
||||
` To skip the database seeding, append ${chalk.green(
|
||||
'--skip-seed'
|
||||
)} to the command options.`
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
export const createEnv = async (instancePath: string, databaseUrl: string) => {
|
||||
const dotEnvPath = path.resolve(instancePath, '.env');
|
||||
await writeFile(dotEnvPath, `DB_URL=${databaseUrl}`, {
|
||||
encoding: 'utf8',
|
||||
});
|
||||
log.info(`Saved database URL to ${chalk.blue(dotEnvPath)}`);
|
||||
};
|
||||
|
||||
export const logFinale = (instancePath: string) => {
|
||||
const startCommand = `cd ${instancePath} && npm start`;
|
||||
log.info(
|
||||
`Use the command below to start Logto. Happy hacking!\n\n ${chalk.green(startCommand)}`
|
||||
);
|
||||
};
|
||||
|
||||
export const inquireOfficialConnectors = async (initialAnswer?: boolean) => {
|
||||
const { value } = await inquirer.prompt<{ value: boolean }>(
|
||||
{
|
||||
name: 'value',
|
||||
message: 'Do you want to add official connectors?',
|
||||
type: 'confirm',
|
||||
default: true,
|
||||
},
|
||||
{ value: initialAnswer }
|
||||
);
|
||||
|
||||
return value;
|
||||
};
|
||||
|
||||
export const isUrl = (string: string) => {
|
||||
try {
|
||||
// On purpose to test
|
||||
// eslint-disable-next-line no-new
|
||||
new URL(string);
|
||||
|
||||
return true;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
};
|
|
@ -1 +0,0 @@
|
|||
export const connectorDirectory = 'connectors';
|
|
@ -1,82 +0,0 @@
|
|||
import { SchemaLike } from '@logto/schemas';
|
||||
import { convertToPrimitiveOrSql } from '@logto/shared';
|
||||
import decamelize from 'decamelize';
|
||||
import { createPool, parseDsn, sql, stringifyDsn } from 'slonik';
|
||||
import { createInterceptors } from 'slonik-interceptor-preset';
|
||||
import { z } from 'zod';
|
||||
|
||||
import { ConfigKey, getCliConfigWithPrompt, log } from './utilities';
|
||||
|
||||
export const defaultDatabaseUrl = 'postgresql://localhost:5432/logto';
|
||||
|
||||
export const getDatabaseUrlFromConfig = async () =>
|
||||
(await getCliConfigWithPrompt({
|
||||
key: ConfigKey.DatabaseUrl,
|
||||
readableKey: 'Logto database URL',
|
||||
defaultValue: defaultDatabaseUrl,
|
||||
})) ?? '';
|
||||
|
||||
export const createPoolFromConfig = async () => {
|
||||
const databaseUrl = await getDatabaseUrlFromConfig();
|
||||
|
||||
return createPool(databaseUrl, {
|
||||
interceptors: createInterceptors(),
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Create a database pool with the URL in CLI config; if no URL found, prompt to input.
|
||||
* If the given database does not exists, it will try to create a new database by connecting to the maintenance database `postgres`.
|
||||
*
|
||||
* @returns A new database pool with the database URL in config.
|
||||
*/
|
||||
export const createPoolAndDatabaseIfNeeded = async () => {
|
||||
try {
|
||||
return await createPoolFromConfig();
|
||||
} catch (error: unknown) {
|
||||
const result = z.object({ code: z.string() }).safeParse(error);
|
||||
|
||||
// Database does not exist, try to create one
|
||||
// https://www.postgresql.org/docs/14/errcodes-appendix.html
|
||||
if (!(result.success && result.data.code === '3D000')) {
|
||||
log.error(error);
|
||||
}
|
||||
|
||||
const databaseUrl = await getDatabaseUrlFromConfig();
|
||||
const dsn = parseDsn(databaseUrl);
|
||||
// It's ok to fall back to '?' since:
|
||||
// - Database name is required to connect in the previous pool
|
||||
// - It will throw error when creating database using '?'
|
||||
const databaseName = dsn.databaseName ?? '?';
|
||||
const maintenancePool = await createPool(stringifyDsn({ ...dsn, databaseName: 'postgres' }), {
|
||||
interceptors: createInterceptors(),
|
||||
});
|
||||
await maintenancePool.query(sql`
|
||||
create database ${sql.identifier([databaseName])}
|
||||
with
|
||||
encoding = 'UTF8'
|
||||
connection_limit = -1;
|
||||
`);
|
||||
await maintenancePool.end();
|
||||
|
||||
log.succeed(`Created database ${databaseName}`);
|
||||
|
||||
return createPoolFromConfig();
|
||||
}
|
||||
};
|
||||
|
||||
export const insertInto = <T extends SchemaLike>(object: T, table: string) => {
|
||||
const keys = Object.keys(object);
|
||||
|
||||
return sql`
|
||||
insert into ${sql.identifier([table])}
|
||||
(${sql.join(
|
||||
keys.map((key) => sql.identifier([decamelize(key)])),
|
||||
sql`, `
|
||||
)})
|
||||
values (${sql.join(
|
||||
keys.map((key) => convertToPrimitiveOrSql(key, object[key] ?? null)),
|
||||
sql`, `
|
||||
)})
|
||||
`;
|
||||
};
|
|
@ -1,10 +0,0 @@
|
|||
declare module 'slonik-interceptor-preset' {
|
||||
import { Interceptor } from 'slonik';
|
||||
|
||||
export const createInterceptors: (config?: {
|
||||
benchmarkQueries: boolean;
|
||||
logQueries: boolean;
|
||||
normaliseQueries: boolean;
|
||||
transformFieldNames: boolean;
|
||||
}) => readonly Interceptor[];
|
||||
}
|
|
@ -1,55 +1,130 @@
|
|||
import { execSync } from 'child_process';
|
||||
import { existsSync } from 'fs';
|
||||
import { mkdir } from 'fs/promises';
|
||||
import os from 'os';
|
||||
import path from 'path';
|
||||
|
||||
import chalk from 'chalk';
|
||||
import dotenv from 'dotenv';
|
||||
import yargs from 'yargs';
|
||||
import { hideBin } from 'yargs/helpers';
|
||||
import ora from 'ora';
|
||||
import * as prompts from 'prompts';
|
||||
import * as semver from 'semver';
|
||||
import tar from 'tar';
|
||||
|
||||
import connector from './commands/connector';
|
||||
import database from './commands/database';
|
||||
import install from './commands/install';
|
||||
import { packageJson } from './package-json';
|
||||
import { cliConfig, ConfigKey } from './utilities';
|
||||
import { downloadFile, log, safeExecSync } from './utilities';
|
||||
|
||||
void yargs(hideBin(process.argv))
|
||||
.version(false)
|
||||
.option('env', {
|
||||
alias: ['e', 'env-file'],
|
||||
describe: 'The path to your `.env` file',
|
||||
type: 'string',
|
||||
})
|
||||
.option('db', {
|
||||
alias: ['db-url', 'database-url'],
|
||||
describe: 'The Postgres URL to Logto database',
|
||||
type: 'string',
|
||||
})
|
||||
.option('version', {
|
||||
alias: 'v',
|
||||
describe: 'Print Logto CLI version',
|
||||
type: 'boolean',
|
||||
global: false,
|
||||
})
|
||||
.middleware(({ version }) => {
|
||||
if (version) {
|
||||
console.log(packageJson.name + ' v' + packageJson.version);
|
||||
// eslint-disable-next-line unicorn/no-process-exit
|
||||
process.exit(0);
|
||||
const pgRequired = new semver.SemVer('14.0.0');
|
||||
|
||||
const validateNodeVersion = () => {
|
||||
const required = new semver.SemVer('16.0.0');
|
||||
const current = new semver.SemVer(execSync('node -v', { encoding: 'utf8', stdio: 'pipe' }));
|
||||
|
||||
if (required.compare(current) > 0) {
|
||||
log.error(`Logto requires NodeJS >=${required.version}, but ${current.version} found.`);
|
||||
}
|
||||
|
||||
if (current.major > required.major) {
|
||||
log.warn(
|
||||
`Logto is tested under NodeJS ^${required.version}, but version ${current.version} found.`
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
const getInstancePath = async () => {
|
||||
const response = await prompts.default(
|
||||
[
|
||||
{
|
||||
name: 'instancePath',
|
||||
message: 'Where should we create your logto instance?',
|
||||
type: 'text',
|
||||
initial: './logto',
|
||||
format: (value: string) => path.resolve(value.trim()),
|
||||
validate: (value: string) =>
|
||||
existsSync(value) ? 'That path already exists, please try another.' : true,
|
||||
},
|
||||
{
|
||||
name: 'hasPostgresUrl',
|
||||
message: `Logto requires PostgreSQL >=${pgRequired.version} but cannot find in the current environment.\n Do you have a remote PostgreSQL instance ready?`,
|
||||
type: () => {
|
||||
const pgOutput = safeExecSync('postgres --version') ?? '';
|
||||
// Filter out all brackets in the output since Homebrew will append `(Homebrew)`.
|
||||
const pgArray = pgOutput.split(' ').filter((value) => !value.startsWith('('));
|
||||
const pgCurrent = semver.coerce(pgArray[pgArray.length - 1]);
|
||||
|
||||
return (!pgCurrent || pgCurrent.compare(pgRequired) < 0) && 'confirm';
|
||||
},
|
||||
format: (previous) => {
|
||||
if (!previous) {
|
||||
log.error('Logto requires a Postgres instance to run.');
|
||||
}
|
||||
},
|
||||
},
|
||||
],
|
||||
{
|
||||
onCancel: () => {
|
||||
log.error('Operation cancelled');
|
||||
},
|
||||
}
|
||||
}, true)
|
||||
.middleware(({ env, db: databaseUrl }) => {
|
||||
dotenv.config({ path: env });
|
||||
);
|
||||
|
||||
const initialDatabaseUrl = databaseUrl ?? process.env[ConfigKey.DatabaseUrl];
|
||||
return String(response.instancePath);
|
||||
};
|
||||
|
||||
if (initialDatabaseUrl) {
|
||||
cliConfig.set(ConfigKey.DatabaseUrl, initialDatabaseUrl);
|
||||
}
|
||||
})
|
||||
.command(install)
|
||||
.command(database)
|
||||
.command(connector)
|
||||
.demandCommand(1)
|
||||
.showHelpOnFail(false, `Specify ${chalk.green('--help')} for available options`)
|
||||
.strict()
|
||||
.parserConfiguration({
|
||||
'dot-notation': false,
|
||||
})
|
||||
.parse();
|
||||
const tryStartInstance = async (instancePath: string) => {
|
||||
const response = await prompts.default({
|
||||
name: 'startInstance',
|
||||
message: 'Would you like to start Logto now?',
|
||||
type: 'confirm',
|
||||
initial: true,
|
||||
});
|
||||
|
||||
const yes = Boolean(response.startInstance);
|
||||
const startCommand = `cd ${instancePath} && npm start`;
|
||||
|
||||
if (yes) {
|
||||
execSync(startCommand, { stdio: 'inherit' });
|
||||
} else {
|
||||
log.info(`You can use ${startCommand} to start Logto. Happy hacking!`);
|
||||
}
|
||||
};
|
||||
|
||||
const downloadRelease = async () => {
|
||||
const tarFilePath = path.resolve(os.tmpdir(), './logto.tar.gz');
|
||||
|
||||
log.info(`Download Logto to ${tarFilePath}`);
|
||||
await downloadFile(
|
||||
'https://github.com/logto-io/logto/releases/latest/download/logto.tar.gz',
|
||||
tarFilePath
|
||||
);
|
||||
|
||||
return tarFilePath;
|
||||
};
|
||||
|
||||
const decompress = async (toPath: string, tarPath: string) => {
|
||||
const decompressSpinner = ora({
|
||||
text: `Decompress to ${toPath}`,
|
||||
prefixText: chalk.blue('[info]'),
|
||||
}).start();
|
||||
|
||||
try {
|
||||
await mkdir(toPath);
|
||||
await tar.extract({ file: tarPath, cwd: toPath, strip: 1 });
|
||||
} catch {
|
||||
decompressSpinner.fail();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
decompressSpinner.succeed();
|
||||
};
|
||||
|
||||
const main = async () => {
|
||||
validateNodeVersion();
|
||||
|
||||
const instancePath = await getInstancePath();
|
||||
const tarPath = await downloadRelease();
|
||||
|
||||
await decompress(instancePath, tarPath);
|
||||
await tryStartInstance(instancePath);
|
||||
};
|
||||
|
||||
void main();
|
||||
|
|
|
@ -1,102 +0,0 @@
|
|||
import { AlterationStateKey, LogtoConfigs } from '@logto/schemas';
|
||||
import { convertToIdentifiers } from '@logto/shared';
|
||||
import { createMockPool, createMockQueryResult, sql } from 'slonik';
|
||||
|
||||
import { expectSqlAssert, QueryType } from '../test-utilities';
|
||||
import { updateDatabaseTimestamp, getCurrentDatabaseAlterationTimestamp } from './logto-config';
|
||||
|
||||
const mockQuery: jest.MockedFunction<QueryType> = jest.fn();
|
||||
|
||||
const pool = createMockPool({
|
||||
query: async (sql, values) => {
|
||||
return mockQuery(sql, values);
|
||||
},
|
||||
});
|
||||
const { table, fields } = convertToIdentifiers(LogtoConfigs);
|
||||
const timestamp = 1_663_923_776;
|
||||
|
||||
describe('getCurrentDatabaseAlterationTimestamp()', () => {
|
||||
it('returns 0 if query failed (table not found)', async () => {
|
||||
mockQuery.mockRejectedValueOnce({ code: '42P01' });
|
||||
|
||||
await expect(getCurrentDatabaseAlterationTimestamp(pool)).resolves.toBe(0);
|
||||
});
|
||||
|
||||
it('returns 0 if the row is not found', async () => {
|
||||
const expectSql = sql`
|
||||
select * from ${table} where ${fields.key}=$1
|
||||
`;
|
||||
|
||||
mockQuery.mockImplementationOnce(async (sql, values) => {
|
||||
expectSqlAssert(sql, expectSql.sql);
|
||||
expect(values).toEqual([AlterationStateKey.AlterationState]);
|
||||
|
||||
return createMockQueryResult([]);
|
||||
});
|
||||
|
||||
await expect(getCurrentDatabaseAlterationTimestamp(pool)).resolves.toBe(0);
|
||||
});
|
||||
|
||||
it('returns 0 if the value is in bad format', async () => {
|
||||
const expectSql = sql`
|
||||
select * from ${table} where ${fields.key}=$1
|
||||
`;
|
||||
|
||||
mockQuery.mockImplementationOnce(async (sql, values) => {
|
||||
expectSqlAssert(sql, expectSql.sql);
|
||||
expect(values).toEqual([AlterationStateKey.AlterationState]);
|
||||
|
||||
return createMockQueryResult([{ value: 'some_value' }]);
|
||||
});
|
||||
|
||||
await expect(getCurrentDatabaseAlterationTimestamp(pool)).resolves.toBe(0);
|
||||
});
|
||||
|
||||
it('returns the timestamp from database', async () => {
|
||||
const expectSql = sql`
|
||||
select * from ${table} where ${fields.key}=$1
|
||||
`;
|
||||
|
||||
mockQuery.mockImplementationOnce(async (sql, values) => {
|
||||
expectSqlAssert(sql, expectSql.sql);
|
||||
expect(values).toEqual([AlterationStateKey.AlterationState]);
|
||||
|
||||
// @ts-expect-error createMockQueryResult doesn't support jsonb
|
||||
return createMockQueryResult([{ value: { timestamp, updatedAt: 'now' } }]);
|
||||
});
|
||||
|
||||
await expect(getCurrentDatabaseAlterationTimestamp(pool)).resolves.toEqual(timestamp);
|
||||
});
|
||||
});
|
||||
|
||||
describe('updateDatabaseTimestamp()', () => {
|
||||
const expectSql = sql`
|
||||
insert into ${table} (${fields.key}, ${fields.value})
|
||||
values ($1, $2::jsonb)
|
||||
on conflict (${fields.key}) do update set ${fields.value}=excluded.${fields.value}
|
||||
`;
|
||||
const updatedAt = '2022-09-21T06:32:46.583Z';
|
||||
|
||||
beforeAll(() => {
|
||||
jest.useFakeTimers();
|
||||
jest.setSystemTime(new Date(updatedAt));
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
jest.useRealTimers();
|
||||
});
|
||||
|
||||
it('sends upsert sql with timestamp and updatedAt', async () => {
|
||||
mockQuery.mockImplementationOnce(async (sql, values) => {
|
||||
expectSqlAssert(sql, expectSql.sql);
|
||||
expect(values).toEqual([
|
||||
AlterationStateKey.AlterationState,
|
||||
JSON.stringify({ timestamp, updatedAt }),
|
||||
]);
|
||||
|
||||
return createMockQueryResult([]);
|
||||
});
|
||||
|
||||
await updateDatabaseTimestamp(pool, timestamp);
|
||||
});
|
||||
});
|
|
@ -1,77 +0,0 @@
|
|||
import {
|
||||
AlterationState,
|
||||
LogtoConfig,
|
||||
logtoConfigGuards,
|
||||
LogtoConfigKey,
|
||||
LogtoConfigs,
|
||||
AlterationStateKey,
|
||||
} from '@logto/schemas';
|
||||
import { convertToIdentifiers } from '@logto/shared';
|
||||
import { Nullable } from '@silverhand/essentials';
|
||||
import { DatabasePool, DatabaseTransactionConnection, sql } from 'slonik';
|
||||
import { z } from 'zod';
|
||||
|
||||
const { table, fields } = convertToIdentifiers(LogtoConfigs);
|
||||
|
||||
export const isConfigsTableExists = async (pool: DatabasePool) => {
|
||||
const { rows } = await pool.query<Nullable<string>>(
|
||||
sql`select to_regclass(${LogtoConfigs.table})`
|
||||
);
|
||||
|
||||
return Boolean(rows[0]);
|
||||
};
|
||||
|
||||
export const getRowsByKeys = async (
|
||||
pool: DatabasePool | DatabaseTransactionConnection,
|
||||
keys: LogtoConfigKey[]
|
||||
) =>
|
||||
pool.query<LogtoConfig>(sql`
|
||||
select ${sql.join([fields.key, fields.value], sql`,`)} from ${table}
|
||||
where ${fields.key} in (${sql.join(keys, sql`,`)})
|
||||
`);
|
||||
|
||||
export const updateValueByKey = async <T extends LogtoConfigKey>(
|
||||
pool: DatabasePool | DatabaseTransactionConnection,
|
||||
key: T,
|
||||
value: z.infer<typeof logtoConfigGuards[T]>
|
||||
) =>
|
||||
pool.query(
|
||||
sql`
|
||||
insert into ${table} (${fields.key}, ${fields.value})
|
||||
values (${key}, ${sql.jsonb(value)})
|
||||
on conflict (${fields.key}) do update set ${fields.value}=excluded.${fields.value}
|
||||
`
|
||||
);
|
||||
|
||||
export const getCurrentDatabaseAlterationTimestamp = async (pool: DatabasePool) => {
|
||||
try {
|
||||
const result = await pool.maybeOne<LogtoConfig>(
|
||||
sql`select * from ${table} where ${fields.key}=${AlterationStateKey.AlterationState}`
|
||||
);
|
||||
const parsed = logtoConfigGuards[AlterationStateKey.AlterationState].safeParse(result?.value);
|
||||
|
||||
return (parsed.success && parsed.data.timestamp) || 0;
|
||||
} catch (error: unknown) {
|
||||
const result = z.object({ code: z.string() }).safeParse(error);
|
||||
|
||||
// Relation does not exist, treat as 0
|
||||
// https://www.postgresql.org/docs/14/errcodes-appendix.html
|
||||
if (result.success && result.data.code === '42P01') {
|
||||
return 0;
|
||||
}
|
||||
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
export const updateDatabaseTimestamp = async (
|
||||
connection: DatabaseTransactionConnection,
|
||||
timestamp: number
|
||||
) => {
|
||||
const value: AlterationState = {
|
||||
timestamp,
|
||||
updatedAt: new Date().toISOString(),
|
||||
};
|
||||
|
||||
return updateValueByKey(connection, AlterationStateKey.AlterationState, value);
|
||||
};
|
|
@ -1,26 +0,0 @@
|
|||
// Copied from core
|
||||
|
||||
import { QueryResult, QueryResultRow } from 'slonik';
|
||||
import { PrimitiveValueExpression } from 'slonik/dist/src/types.d';
|
||||
|
||||
export type QueryType = (
|
||||
sql: string,
|
||||
values: readonly PrimitiveValueExpression[]
|
||||
) => Promise<QueryResult<QueryResultRow>>;
|
||||
|
||||
/**
|
||||
* Slonik Query Mock Utils
|
||||
**/
|
||||
export const expectSqlAssert = (sql: string, expectSql: string) => {
|
||||
expect(
|
||||
sql
|
||||
.split('\n')
|
||||
.map((row) => row.trim())
|
||||
.filter(Boolean)
|
||||
).toEqual(
|
||||
expectSql
|
||||
.split('\n')
|
||||
.map((row) => row.trim())
|
||||
.filter(Boolean)
|
||||
);
|
||||
};
|
|
@ -1,12 +1,9 @@
|
|||
import { execSync } from 'child_process';
|
||||
import { createWriteStream } from 'fs';
|
||||
import path from 'path';
|
||||
|
||||
import { conditionalString, Optional } from '@silverhand/essentials';
|
||||
import chalk from 'chalk';
|
||||
import got, { Progress } from 'got';
|
||||
import { HttpsProxyAgent } from 'hpagent';
|
||||
import inquirer from 'inquirer';
|
||||
import ora from 'ora';
|
||||
|
||||
export const safeExecSync = (command: string) => {
|
||||
|
@ -17,23 +14,19 @@ export const safeExecSync = (command: string) => {
|
|||
|
||||
type Log = Readonly<{
|
||||
info: typeof console.log;
|
||||
succeed: typeof console.log;
|
||||
warn: typeof console.log;
|
||||
error: (...args: Parameters<typeof console.log>) => never;
|
||||
error: typeof console.log;
|
||||
}>;
|
||||
|
||||
export const log: Log = Object.freeze({
|
||||
info: (...args) => {
|
||||
console.log(chalk.blue('[info]'), ...args);
|
||||
},
|
||||
succeed: (...args) => {
|
||||
log.info(chalk.green('✔'), ...args);
|
||||
},
|
||||
warn: (...args) => {
|
||||
console.warn(chalk.yellow('[warn]'), ...args);
|
||||
console.log(chalk.yellow('[warn]'), ...args);
|
||||
},
|
||||
error: (...args) => {
|
||||
console.error(chalk.red('[error]'), ...args);
|
||||
console.log(chalk.red('[error]'), ...args);
|
||||
// eslint-disable-next-line unicorn/no-process-exit
|
||||
process.exit(1);
|
||||
},
|
||||
|
@ -75,107 +68,3 @@ export const downloadFile = async (url: string, destination: string) => {
|
|||
});
|
||||
});
|
||||
};
|
||||
|
||||
export const getPathInModule = (moduleName: string, relativePath = '/') =>
|
||||
// https://stackoverflow.com/a/49455609/12514940
|
||||
path.join(
|
||||
// Until we migrate to ESM
|
||||
// eslint-disable-next-line unicorn/prefer-module
|
||||
path.dirname(require.resolve(`${moduleName}/package.json`)),
|
||||
relativePath
|
||||
);
|
||||
|
||||
export const oraPromise = async <T>(
|
||||
promise: PromiseLike<T>,
|
||||
options?: ora.Options,
|
||||
exitOnError = false
|
||||
) => {
|
||||
const spinner = ora(options).start();
|
||||
|
||||
try {
|
||||
const result = await promise;
|
||||
spinner.succeed();
|
||||
|
||||
return result;
|
||||
} catch (error: unknown) {
|
||||
spinner.fail();
|
||||
|
||||
if (exitOnError) {
|
||||
log.error(error);
|
||||
}
|
||||
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
export enum ConfigKey {
|
||||
DatabaseUrl = 'DB_URL',
|
||||
}
|
||||
|
||||
export const cliConfig = new Map<ConfigKey, Optional<string>>();
|
||||
|
||||
export type GetCliConfigWithPrompt = {
|
||||
key: ConfigKey;
|
||||
readableKey: string;
|
||||
comments?: string;
|
||||
defaultValue?: string;
|
||||
};
|
||||
|
||||
export const getCliConfigWithPrompt = async ({
|
||||
key,
|
||||
readableKey,
|
||||
comments,
|
||||
defaultValue,
|
||||
}: GetCliConfigWithPrompt) => {
|
||||
if (cliConfig.has(key)) {
|
||||
return cliConfig.get(key);
|
||||
}
|
||||
|
||||
const { input } = await inquirer
|
||||
.prompt<{ input?: string }>({
|
||||
type: 'input',
|
||||
name: 'input',
|
||||
message: `Enter your ${readableKey}${conditionalString(comments && ' ' + comments)}`,
|
||||
default: defaultValue,
|
||||
})
|
||||
.catch(async (error) => {
|
||||
if (error.isTtyError) {
|
||||
log.error(`No ${readableKey} (${chalk.green(key)}) configured in option nor env`);
|
||||
}
|
||||
|
||||
// The type definition does not give us type except `any`, throw it directly will honor the original behavior.
|
||||
// eslint-disable-next-line @typescript-eslint/no-throw-literal
|
||||
throw error;
|
||||
});
|
||||
|
||||
cliConfig.set(key, input);
|
||||
|
||||
return input;
|
||||
};
|
||||
|
||||
// https://stackoverflow.com/a/53187807/12514940
|
||||
/**
|
||||
* Returns the index of the last element in the array where predicate is true, and -1
|
||||
* otherwise.
|
||||
* @param array The source array to search in
|
||||
* @param predicate find calls predicate once for each element of the array, in descending
|
||||
* order, until it finds one where predicate returns true. If such an element is found,
|
||||
* findLastIndex immediately returns that element index. Otherwise, findLastIndex returns -1.
|
||||
*/
|
||||
export function findLastIndex<T>(
|
||||
array: readonly T[],
|
||||
predicate: (value: T, index: number, object: readonly T[]) => boolean
|
||||
): number {
|
||||
// eslint-disable-next-line @silverhand/fp/no-let
|
||||
let { length } = array;
|
||||
|
||||
// eslint-disable-next-line @silverhand/fp/no-mutation
|
||||
while (length--) {
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
if (predicate(array[length]!, length, array)) {
|
||||
return length;
|
||||
}
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
|
|
@ -1,4 +0,0 @@
|
|||
{
|
||||
"extends": "./tsconfig",
|
||||
"include": ["src"],
|
||||
}
|
|
@ -4,12 +4,9 @@
|
|||
"outDir": "lib",
|
||||
"declaration": true,
|
||||
"module": "node16",
|
||||
"target": "es2022",
|
||||
"types": ["node", "jest"]
|
||||
"target": "es2022"
|
||||
},
|
||||
"include": [
|
||||
"src",
|
||||
"jest.config.ts"
|
||||
],
|
||||
"exclude": ["**/alteration-scripts"]
|
||||
"src"
|
||||
]
|
||||
}
|
||||
|
|
|
@ -1,6 +0,0 @@
|
|||
{
|
||||
"extends": "./tsconfig",
|
||||
"compilerOptions": {
|
||||
"allowJs": true
|
||||
}
|
||||
}
|
|
@ -18,8 +18,7 @@
|
|||
},
|
||||
"devDependencies": {
|
||||
"@fontsource/roboto-mono": "^4.5.7",
|
||||
"@logto/core-kit": "1.0.0-beta.18",
|
||||
"@logto/language-kit": "1.0.0-beta.16",
|
||||
"@logto/core-kit": "^1.0.0-beta.13",
|
||||
"@logto/phrases": "^1.0.0-beta.10",
|
||||
"@logto/phrases-ui": "^1.0.0-beta.10",
|
||||
"@logto/react": "1.0.0-beta.8",
|
||||
|
@ -29,11 +28,11 @@
|
|||
"@parcel/transformer-mdx": "2.7.0",
|
||||
"@parcel/transformer-sass": "2.7.0",
|
||||
"@parcel/transformer-svg-react": "2.7.0",
|
||||
"@silverhand/eslint-config": "1.2.0",
|
||||
"@silverhand/eslint-config-react": "1.2.1",
|
||||
"@silverhand/essentials": "^1.3.0",
|
||||
"@silverhand/ts-config": "1.2.1",
|
||||
"@silverhand/ts-config-react": "1.2.1",
|
||||
"@silverhand/eslint-config": "1.0.0",
|
||||
"@silverhand/eslint-config-react": "1.0.0",
|
||||
"@silverhand/essentials": "^1.2.1",
|
||||
"@silverhand/ts-config": "1.0.0",
|
||||
"@silverhand/ts-config-react": "1.0.0",
|
||||
"@tsconfig/docusaurus": "^1.0.5",
|
||||
"@types/color": "^3.0.3",
|
||||
"@types/lodash.kebabcase": "^4.1.6",
|
||||
|
@ -44,11 +43,9 @@
|
|||
"@types/react-modal": "^3.13.1",
|
||||
"@types/react-syntax-highlighter": "^15.5.1",
|
||||
"classnames": "^2.3.1",
|
||||
"clean-deep": "^3.4.0",
|
||||
"cross-env": "^7.0.3",
|
||||
"csstype": "^3.0.11",
|
||||
"dayjs": "^1.10.5",
|
||||
"deepmerge": "^4.2.2",
|
||||
"dnd-core": "^16.0.0",
|
||||
"eslint": "^8.21.0",
|
||||
"history": "^5.3.0",
|
||||
|
@ -92,7 +89,8 @@
|
|||
"eslintConfig": {
|
||||
"extends": "@silverhand/react",
|
||||
"rules": {
|
||||
"complexity": "off"
|
||||
"complexity": "off",
|
||||
"@typescript-eslint/prefer-nullish-coalescing": "off"
|
||||
}
|
||||
},
|
||||
"stylelint": {
|
||||
|
|
|
@ -1,5 +0,0 @@
|
|||
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path
|
||||
d="M14.7578 10.588L10.5912 14.7547C10.5119 14.8305 10.4185 14.89 10.3162 14.9297C10.1133 15.013 9.88572 15.013 9.68284 14.9297C9.58054 14.89 9.48709 14.8305 9.40784 14.7547L5.24117 10.588C5.16347 10.5103 5.10184 10.4181 5.05979 10.3166C5.01774 10.215 4.99609 10.1062 4.99609 9.99634C4.99609 9.77442 5.08425 9.5616 5.24117 9.40468C5.39809 9.24776 5.61092 9.1596 5.83284 9.1596C6.05475 9.1596 6.26758 9.24776 6.4245 9.40468L9.16617 12.1547V5.82968C9.16617 5.60866 9.25397 5.3967 9.41025 5.24042C9.56653 5.08414 9.77849 4.99634 9.9995 4.99634C10.2205 4.99634 10.4325 5.08414 10.5888 5.24042C10.745 5.3967 10.8328 5.60866 10.8328 5.82968V12.1547L13.5745 9.40468C13.652 9.32657 13.7441 9.26457 13.8457 9.22227C13.9472 9.17996 14.0562 9.15818 14.1662 9.15818C14.2762 9.15818 14.3851 9.17996 14.4867 9.22227C14.5882 9.26457 14.6804 9.32657 14.7578 9.40468C14.8359 9.48214 14.8979 9.57431 14.9402 9.67586C14.9826 9.77741 15.0043 9.88633 15.0043 9.99634C15.0043 10.1064 14.9826 10.2153 14.9402 10.3168C14.8979 10.4184 14.8359 10.5105 14.7578 10.588Z"
|
||||
fill="currentColor" />
|
||||
</svg>
|
Before Width: | Height: | Size: 1.2 KiB |
|
@ -1,5 +0,0 @@
|
|||
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path
|
||||
d="M14.7578 9.41199L10.5912 5.24532C10.5119 5.16946 10.4185 5.10999 10.3162 5.07032C10.1133 4.98698 9.88572 4.98698 9.68284 5.07032C9.58054 5.10999 9.48709 5.16946 9.40784 5.24532L5.24117 9.41199C5.16347 9.48969 5.10184 9.58193 5.05979 9.68345C5.01774 9.78497 4.99609 9.89377 4.99609 10.0037C4.99609 10.2256 5.08425 10.4384 5.24117 10.5953C5.39809 10.7522 5.61092 10.8404 5.83284 10.8404C6.05475 10.8404 6.26758 10.7522 6.4245 10.5953L9.16617 7.84532V14.1703C9.16617 14.3913 9.25397 14.6033 9.41025 14.7596C9.56653 14.9159 9.77849 15.0037 9.9995 15.0037C10.2205 15.0037 10.4325 14.9159 10.5888 14.7596C10.745 14.6033 10.8328 14.3913 10.8328 14.1703V7.84532L13.5745 10.5953C13.652 10.6734 13.7441 10.7354 13.8457 10.7777C13.9472 10.82 14.0562 10.8418 14.1662 10.8418C14.2762 10.8418 14.3851 10.82 14.4867 10.7777C14.5882 10.7354 14.6804 10.6734 14.7578 10.5953C14.8359 10.5179 14.8979 10.4257 14.9402 10.3241C14.9826 10.2226 15.0043 10.1137 15.0043 10.0037C15.0043 9.89365 14.9826 9.78473 14.9402 9.68318C14.8979 9.58163 14.8359 9.48946 14.7578 9.41199Z"
|
||||
fill="currentColor" />
|
||||
</svg>
|
Before Width: | Height: | Size: 1.2 KiB |
|
@ -1,4 +0,0 @@
|
|||
<svg width="20" height="20" viewBox="0 -2 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M10.3566 13.6562L4.69995 7.99958L10.3566 2.34292L11.3 3.28558L6.58528 7.99958L11.3 12.7136L10.3566 13.6562Z"
|
||||
fill="currentColor" />
|
||||
</svg>
|
Before Width: | Height: | Size: 251 B |
|
@ -1,6 +0,0 @@
|
|||
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<rect x="1.66602" y="1.66663" width="16.6667" height="16.6667" rx="4" fill="#5C5F60" />
|
||||
<path fillRule="evenodd" clipRule="evenodd"
|
||||
d="M8.31476 13.858L5.13295 10.441C4.95568 10.253 4.95568 9.947 5.13295 9.757L5.77568 9.074C5.95295 8.886 6.24113 8.886 6.4184 9.074L8.63657 11.466L13.5811 6.141C13.7584 5.953 14.0465 5.953 14.2238 6.141L14.8665 6.825C15.0438 7.013 15.0438 7.32 14.8665 7.507L8.95748 13.858C8.78021 14.046 8.49203 14.046 8.31476 13.858Z"
|
||||
fill="white" />
|
||||
</svg>
|
Before Width: | Height: | Size: 583 B |
|
@ -1,6 +0,0 @@
|
|||
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<rect x="1.66602" y="1.66663" width="16.6667" height="16.6667" rx="4" fill="#C4C7C7" />
|
||||
<path fillRule="evenodd" clipRule="evenodd"
|
||||
d="M8.31476 13.858L5.13295 10.441C4.95568 10.253 4.95568 9.947 5.13295 9.757L5.77568 9.074C5.95295 8.886 6.24113 8.886 6.4184 9.074L8.63657 11.466L13.5811 6.141C13.7584 5.953 14.0465 5.953 14.2238 6.141L14.8665 6.825C15.0438 7.013 15.0438 7.32 14.8665 7.507L8.95748 13.858C8.78021 14.046 8.49203 14.046 8.31476 13.858Z"
|
||||
fill="white" />
|
||||
</svg>
|
Before Width: | Height: | Size: 583 B |
|
@ -1,6 +0,0 @@
|
|||
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<rect x="1.66602" y="1.66663" width="16.6667" height="16.6667" rx="4" fill="#5D34F2" />
|
||||
<path fillRule="evenodd" clipRule="evenodd"
|
||||
d="M8.31476 13.858L5.13295 10.441C4.95568 10.253 4.95568 9.947 5.13295 9.757L5.77568 9.074C5.95295 8.886 6.24113 8.886 6.4184 9.074L8.63657 11.466L13.5811 6.141C13.7584 5.953 14.0465 5.953 14.2238 6.141L14.8665 6.825C15.0438 7.013 15.0438 7.32 14.8665 7.507L8.95748 13.858C8.78021 14.046 8.49203 14.046 8.31476 13.858Z"
|
||||
fill="white" />
|
||||
</svg>
|
Before Width: | Height: | Size: 583 B |
|
@ -1,5 +0,0 @@
|
|||
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fillRule="evenodd" clipRule="evenodd"
|
||||
d="M15.2463 3.20984H4.75244C3.90015 3.20984 3.20923 3.90076 3.20923 4.75305V15.2469C3.20923 16.0992 3.90015 16.7901 4.75244 16.7901H15.2463C16.0986 16.7901 16.7895 16.0992 16.7895 15.2469V4.75305C16.7895 3.90076 16.0986 3.20984 15.2463 3.20984ZM4.75244 1.66663C3.04785 1.66663 1.66602 3.04847 1.66602 4.75305V15.2469C1.66602 16.9515 3.04786 18.3333 4.75244 18.3333H15.2463C16.9508 18.3333 18.3327 16.9515 18.3327 15.2469V4.75305C18.3327 3.04846 16.9508 1.66663 15.2463 1.66663H4.75244Z"
|
||||
fill="#A9ACAC" />
|
||||
</svg>
|
Before Width: | Height: | Size: 662 B |
|
@ -1,14 +0,0 @@
|
|||
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path
|
||||
d="M1.66602 4.75305C1.66602 3.04847 3.04785 1.66663 4.75244 1.66663H15.2463C16.9508 1.66663 18.3327 3.04846 18.3327 4.75305V15.2469C18.3327 16.9515 16.9508 18.3333 15.2463 18.3333H4.75244C3.04786 18.3333 1.66602 16.9515 1.66602 15.2469V4.75305Z"
|
||||
fill="#191C1D" />
|
||||
<path
|
||||
d="M1.66602 4.75305C1.66602 3.04847 3.04785 1.66663 4.75244 1.66663H15.2463C16.9508 1.66663 18.3327 3.04846 18.3327 4.75305V15.2469C18.3327 16.9515 16.9508 18.3333 15.2463 18.3333H4.75244C3.04786 18.3333 1.66602 16.9515 1.66602 15.2469V4.75305Z"
|
||||
fill="#C4C7C7" fillOpacity="0.02" />
|
||||
<path
|
||||
d="M1.66602 4.75305C1.66602 3.04847 3.04785 1.66663 4.75244 1.66663H15.2463C16.9508 1.66663 18.3327 3.04846 18.3327 4.75305V15.2469C18.3327 16.9515 16.9508 18.3333 15.2463 18.3333H4.75244C3.04786 18.3333 1.66602 16.9515 1.66602 15.2469V4.75305Z"
|
||||
fill="#CABEFF" fillOpacity="0.14" />
|
||||
<path fillRule="evenodd" clipRule="evenodd"
|
||||
d="M15.2463 3.20984H4.75244C3.90015 3.20984 3.20923 3.90076 3.20923 4.75305V15.2469C3.20923 16.0992 3.90015 16.7901 4.75244 16.7901H15.2463C16.0986 16.7901 16.7895 16.0992 16.7895 15.2469V4.75305C16.7895 3.90076 16.0986 3.20984 15.2463 3.20984ZM4.75244 1.66663C3.04785 1.66663 1.66602 3.04847 1.66602 4.75305V15.2469C1.66602 16.9515 3.04786 18.3333 4.75244 18.3333H15.2463C16.9508 18.3333 18.3327 16.9515 18.3327 15.2469V4.75305C18.3327 3.04846 16.9508 1.66663 15.2463 1.66663H4.75244Z"
|
||||
fill="#5C5F60" />
|
||||
</svg>
|
Before Width: | Height: | Size: 1.5 KiB |
|
@ -1,8 +0,0 @@
|
|||
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path
|
||||
d="M1.66602 4.75305C1.66602 3.04847 3.04785 1.66663 4.75244 1.66663H15.2463C16.9508 1.66663 18.3327 3.04846 18.3327 4.75305V15.2469C18.3327 16.9515 16.9508 18.3333 15.2463 18.3333H4.75244C3.04786 18.3333 1.66602 16.9515 1.66602 15.2469V4.75305Z"
|
||||
fill="#EFF1F1" />
|
||||
<path fillRule="evenodd" clipRule="evenodd"
|
||||
d="M15.2463 3.20984H4.75244C3.90015 3.20984 3.20923 3.90076 3.20923 4.75305V15.2469C3.20923 16.0992 3.90015 16.7901 4.75244 16.7901H15.2463C16.0986 16.7901 16.7895 16.0992 16.7895 15.2469V4.75305C16.7895 3.90076 16.0986 3.20984 15.2463 3.20984ZM4.75244 1.66663C3.04785 1.66663 1.66602 3.04847 1.66602 4.75305V15.2469C1.66602 16.9515 3.04786 18.3333 4.75244 18.3333H15.2463C16.9508 18.3333 18.3327 16.9515 18.3327 15.2469V4.75305C18.3327 3.04846 16.9508 1.66663 15.2463 1.66663H4.75244Z"
|
||||
fill="#C4C7C7" />
|
||||
</svg>
|
Before Width: | Height: | Size: 942 B |
|
@ -1,5 +0,0 @@
|
|||
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fillRule="evenodd" clipRule="evenodd"
|
||||
d="M15.2463 3.20984H4.75244C3.90015 3.20984 3.20923 3.90076 3.20923 4.75305V15.2469C3.20923 16.0992 3.90015 16.7901 4.75244 16.7901H15.2463C16.0986 16.7901 16.7895 16.0992 16.7895 15.2469V4.75305C16.7895 3.90076 16.0986 3.20984 15.2463 3.20984ZM4.75244 1.66663C3.04785 1.66663 1.66602 3.04847 1.66602 4.75305V15.2469C1.66602 16.9515 3.04786 18.3333 4.75244 18.3333H15.2463C16.9508 18.3333 18.3327 16.9515 18.3327 15.2469V4.75305C18.3327 3.04846 16.9508 1.66663 15.2463 1.66663H4.75244Z"
|
||||
fill="#747778" />
|
||||
</svg>
|
Before Width: | Height: | Size: 662 B |
|
@ -1,5 +0,0 @@
|
|||
<svg width="17" height="16" viewBox="0 0 17 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path
|
||||
d="M8.98991 1.33325C7.67137 1.33325 6.38244 1.72425 5.28611 2.45679C4.18978 3.18933 3.3353 4.23052 2.83071 5.4487C2.32613 6.66687 2.19411 8.00731 2.45134 9.30052C2.70858 10.5937 3.34352 11.7816 4.27587 12.714C5.20822 13.6463 6.3961 14.2813 7.68931 14.5385C8.98252 14.7957 10.323 14.6637 11.5411 14.1591C12.7593 13.6545 13.8005 12.8 14.533 11.7037C15.2656 10.6074 15.6566 9.31846 15.6566 7.99992C15.6566 7.12444 15.4841 6.25753 15.1491 5.4487C14.8141 4.63986 14.323 3.90493 13.704 3.28587C13.0849 2.66682 12.35 2.17575 11.5411 1.84072C10.7323 1.50569 9.86539 1.33325 8.98991 1.33325ZM8.98991 13.3333C7.93508 13.3333 6.90393 13.0205 6.02687 12.4344C5.14981 11.8484 4.46622 11.0154 4.06255 10.0409C3.65889 9.06636 3.55327 7.994 3.75906 6.95944C3.96484 5.92487 4.4728 4.97456 5.21868 4.22868C5.96456 3.4828 6.91487 2.97485 7.94943 2.76906C8.984 2.56328 10.0564 2.66889 11.0309 3.07256C12.0054 3.47623 12.8384 4.15982 13.4244 5.03688C14.0104 5.91394 14.3232 6.94509 14.3232 7.99992C14.3232 9.41441 13.7613 10.771 12.7611 11.7712C11.761 12.7713 10.4044 13.3333 8.98991 13.3333ZM11.6566 7.33325H6.32324C6.14643 7.33325 5.97686 7.40349 5.85184 7.52851C5.72682 7.65354 5.65658 7.82311 5.65658 7.99992C5.65658 8.17673 5.72682 8.3463 5.85184 8.47132C5.97686 8.59635 6.14643 8.66659 6.32324 8.66659H11.6566C11.8334 8.66659 12.003 8.59635 12.128 8.47132C12.253 8.3463 12.3232 8.17673 12.3232 7.99992C12.3232 7.82311 12.253 7.65354 12.128 7.52851C12.003 7.40349 11.8334 7.33325 11.6566 7.33325Z"
|
||||
fill="currentColor" />
|
||||
</svg>
|
Before Width: | Height: | Size: 1.6 KiB |
|
@ -1,5 +0,0 @@
|
|||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path
|
||||
d="M8.00017 1.33325C6.68162 1.33325 5.39269 1.72425 4.29636 2.45679C3.20004 3.18933 2.34555 4.23052 1.84097 5.4487C1.33638 6.66687 1.20436 8.00731 1.4616 9.30052C1.71883 10.5937 2.35377 11.7816 3.28612 12.714C4.21847 13.6463 5.40636 14.2813 6.69956 14.5385C7.99277 14.7957 9.33321 14.6637 10.5514 14.1591C11.7696 13.6545 12.8108 12.8 13.5433 11.7037C14.2758 10.6074 14.6668 9.31846 14.6668 7.99992C14.6668 7.12444 14.4944 6.25753 14.1594 5.4487C13.8243 4.63986 13.3333 3.90493 12.7142 3.28587C12.0952 2.66682 11.3602 2.17575 10.5514 1.84072C9.74255 1.50569 8.87564 1.33325 8.00017 1.33325ZM8.00017 13.3333C6.94533 13.3333 5.91419 13.0205 5.03712 12.4344C4.16006 11.8484 3.47648 11.0154 3.07281 10.0409C2.66914 9.06636 2.56352 7.994 2.76931 6.95944C2.9751 5.92487 3.48305 4.97456 4.22893 4.22868C4.97481 3.4828 5.92512 2.97485 6.95968 2.76906C7.99425 2.56328 9.0666 2.66889 10.0411 3.07256C11.0157 3.47623 11.8486 4.15982 12.4347 5.03688C13.0207 5.91394 13.3335 6.94509 13.3335 7.99992C13.3335 9.41441 12.7716 10.771 11.7714 11.7712C10.7712 12.7713 9.41465 13.3333 8.00017 13.3333ZM10.6668 7.33325H8.66683V5.33325C8.66683 5.15644 8.59659 4.98687 8.47157 4.86185C8.34655 4.73682 8.17698 4.66659 8.00017 4.66659C7.82335 4.66659 7.65379 4.73682 7.52876 4.86185C7.40374 4.98687 7.3335 5.15644 7.3335 5.33325V7.33325H5.3335C5.15669 7.33325 4.98712 7.40349 4.86209 7.52851C4.73707 7.65354 4.66683 7.82311 4.66683 7.99992C4.66683 8.17673 4.73707 8.3463 4.86209 8.47132C4.98712 8.59635 5.15669 8.66659 5.3335 8.66659H7.3335V10.6666C7.3335 10.8434 7.40374 11.013 7.52876 11.138C7.65379 11.263 7.82335 11.3333 8.00017 11.3333C8.17698 11.3333 8.34655 11.263 8.47157 11.138C8.59659 11.013 8.66683 10.8434 8.66683 10.6666V8.66659H10.6668C10.8436 8.66659 11.0132 8.59635 11.1382 8.47132C11.2633 8.3463 11.3335 8.17673 11.3335 7.99992C11.3335 7.82311 11.2633 7.65354 11.1382 7.52851C11.0132 7.40349 10.8436 7.33325 10.6668 7.33325Z"
|
||||
fill="currentColor" />
|
||||
</svg>
|
Before Width: | Height: | Size: 2 KiB |
|
@ -1,7 +0,0 @@
|
|||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M2.48984 7.00451C2.65057 6.04014 3.48495 5.33331 4.46263 5.33331L11.537 5.33331C12.5146 5.33331 13.349 6.04014 13.5097 7.00452L14.3986 12.3378C14.6018 13.5569 13.6617 14.6666 12.4258 14.6666H3.57374C2.33786 14.6666 1.39777 13.5569 1.60095 12.3378L2.48984 7.00451ZM4.46263 6.66665C4.13673 6.66665 3.85861 6.90225 3.80503 7.22371L2.91614 12.557C2.84842 12.9634 3.16178 13.3333 3.57374 13.3333H12.4258C12.8378 13.3333 13.1512 12.9634 13.0834 12.557L12.1945 7.22371C12.141 6.90225 11.8628 6.66665 11.537 6.66665H4.46263Z" fill="currentColor"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M6 3.33331C6 2.22874 6.89543 1.33331 8 1.33331C9.10457 1.33331 10 2.22874 10 3.33331V5.33331H8.66667V3.33331C8.66667 2.96512 8.36819 2.66665 8 2.66665C7.63181 2.66665 7.33333 2.96512 7.33333 3.33331V5.33331H6V3.33331Z" fill="currentColor"/>
|
||||
<path d="M4 12.0002C4 11.632 4.29848 11.3336 4.66667 11.3336C5.03486 11.3336 5.33333 11.632 5.33333 12.0002V13.3336C5.33333 13.7017 5.03486 14.0002 4.66667 14.0002C4.29848 14.0002 4 13.7017 4 13.3336V12.0002Z" fill="currentColor"/>
|
||||
<path d="M6.6665 12C6.6665 11.6318 6.96498 11.3333 7.33317 11.3333C7.70136 11.3333 7.99984 11.6318 7.99984 12V13.3333C7.99984 13.7015 7.70136 14 7.33317 14C6.96498 14 6.6665 13.7015 6.6665 13.3333V12Z" fill="currentColor"/>
|
||||
<rect x="2.6665" y="8" width="10.6667" height="1.33333" rx="0.666667" fill="currentColor"/>
|
||||
</svg>
|
Before Width: | Height: | Size: 1.5 KiB |
|
@ -1,5 +0,0 @@
|
|||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path
|
||||
d="M13.4099 12.0002L19.7099 5.71019C19.8982 5.52188 20.004 5.26649 20.004 5.00019C20.004 4.73388 19.8982 4.47849 19.7099 4.29019C19.5216 4.10188 19.2662 3.99609 18.9999 3.99609C18.7336 3.99609 18.4782 4.10188 18.2899 4.29019L11.9999 10.5902L5.70994 4.29019C5.52164 4.10188 5.26624 3.99609 4.99994 3.99609C4.73364 3.99609 4.47824 4.10188 4.28994 4.29019C4.10164 4.47849 3.99585 4.73388 3.99585 5.00019C3.99585 5.26649 4.10164 5.52188 4.28994 5.71019L10.5899 12.0002L4.28994 18.2902C4.19621 18.3831 4.12182 18.4937 4.07105 18.6156C4.02028 18.7375 3.99414 18.8682 3.99414 19.0002C3.99414 19.1322 4.02028 19.2629 4.07105 19.3848C4.12182 19.5066 4.19621 19.6172 4.28994 19.7102C4.3829 19.8039 4.4935 19.8783 4.61536 19.9291C4.73722 19.9798 4.86793 20.006 4.99994 20.006C5.13195 20.006 5.26266 19.9798 5.38452 19.9291C5.50638 19.8783 5.61698 19.8039 5.70994 19.7102L11.9999 13.4102L18.2899 19.7102C18.3829 19.8039 18.4935 19.8783 18.6154 19.9291C18.7372 19.9798 18.8679 20.006 18.9999 20.006C19.132 20.006 19.2627 19.9798 19.3845 19.9291C19.5064 19.8783 19.617 19.8039 19.7099 19.7102C19.8037 19.6172 19.8781 19.5066 19.9288 19.3848C19.9796 19.2629 20.0057 19.1322 20.0057 19.0002C20.0057 18.8682 19.9796 18.7375 19.9288 18.6156C19.8781 18.4937 19.8037 18.3831 19.7099 18.2902L13.4099 12.0002Z"
|
||||
fill="currentColor" />
|
||||
</svg>
|
Before Width: | Height: | Size: 1.4 KiB |
|
@ -1,5 +0,0 @@
|
|||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path
|
||||
d="M8.4735 10.8599L8.3735 10.7799C8.33633 10.7547 8.29598 10.7345 8.2535 10.7199L8.1335 10.6666C8.02538 10.644 7.91334 10.6486 7.80745 10.68C7.70155 10.7114 7.60512 10.7687 7.52683 10.8466C7.46794 10.9114 7.42059 10.9858 7.38683 11.0666C7.33638 11.1881 7.32298 11.3217 7.34833 11.4508C7.37368 11.5799 7.43665 11.6985 7.52929 11.7919C7.62194 11.8853 7.74014 11.9491 7.869 11.9755C7.99787 12.0018 8.13164 11.9894 8.2535 11.9399C8.33316 11.9013 8.40718 11.8519 8.4735 11.7933C8.566 11.6995 8.62867 11.5805 8.65358 11.4511C8.6785 11.3218 8.66455 11.188 8.6135 11.0666C8.58026 10.9895 8.53276 10.9194 8.4735 10.8599ZM10.6668 1.33325H5.3335C4.80306 1.33325 4.29436 1.54397 3.91928 1.91904C3.54421 2.29411 3.3335 2.80282 3.3335 3.33325V12.6666C3.3335 13.197 3.54421 13.7057 3.91928 14.0808C4.29436 14.4559 4.80306 14.6666 5.3335 14.6666H10.6668C11.1973 14.6666 11.706 14.4559 12.081 14.0808C12.4561 13.7057 12.6668 13.197 12.6668 12.6666V3.33325C12.6668 2.80282 12.4561 2.29411 12.081 1.91904C11.706 1.54397 11.1973 1.33325 10.6668 1.33325ZM11.3335 12.6666C11.3335 12.8434 11.2633 13.013 11.1382 13.138C11.0132 13.263 10.8436 13.3333 10.6668 13.3333H5.3335C5.15669 13.3333 4.98712 13.263 4.86209 13.138C4.73707 13.013 4.66683 12.8434 4.66683 12.6666V3.33325C4.66683 3.15644 4.73707 2.98687 4.86209 2.86185C4.98712 2.73682 5.15669 2.66659 5.3335 2.66659H10.6668C10.8436 2.66659 11.0132 2.73682 11.1382 2.86185C11.2633 2.98687 11.3335 3.15644 11.3335 3.33325V12.6666Z"
|
||||
fill="currentColor" />
|
||||
</svg>
|
Before Width: | Height: | Size: 1.6 KiB |
|
@ -1,5 +0,0 @@
|
|||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path
|
||||
d="M6.6665 3.33341C6.53465 3.33341 6.40576 3.37251 6.29612 3.44577C6.18649 3.51902 6.10104 3.62314 6.05058 3.74496C6.00013 3.86678 5.98692 4.00082 6.01265 4.13014C6.03837 4.25946 6.10186 4.37825 6.1951 4.47149C6.28833 4.56472 6.40712 4.62821 6.53644 4.65394C6.66576 4.67966 6.79981 4.66646 6.92163 4.616C7.04344 4.56554 7.14756 4.48009 7.22082 4.37046C7.29407 4.26083 7.33317 4.13194 7.33317 4.00008C7.33317 3.82327 7.26293 3.6537 7.13791 3.52868C7.01288 3.40365 6.84331 3.33341 6.6665 3.33341ZM3.99984 3.33341C3.86798 3.33341 3.73909 3.37251 3.62946 3.44577C3.51982 3.51902 3.43438 3.62314 3.38392 3.74496C3.33346 3.86678 3.32026 4.00082 3.34598 4.13014C3.3717 4.25946 3.4352 4.37825 3.52843 4.47149C3.62167 4.56472 3.74046 4.62821 3.86978 4.65394C3.9991 4.67966 4.13314 4.66646 4.25496 4.616C4.37678 4.56554 4.4809 4.48009 4.55415 4.37046C4.6274 4.26083 4.6665 4.13194 4.6665 4.00008C4.6665 3.82327 4.59627 3.6537 4.47124 3.52868C4.34622 3.40365 4.17665 3.33341 3.99984 3.33341ZM9.33317 3.33341C9.20132 3.33341 9.07242 3.37251 8.96279 3.44577C8.85316 3.51902 8.76771 3.62314 8.71725 3.74496C8.66679 3.86678 8.65359 4.00082 8.67931 4.13014C8.70504 4.25946 8.76853 4.37825 8.86177 4.47149C8.955 4.56472 9.07379 4.62821 9.20311 4.65394C9.33243 4.67966 9.46648 4.66646 9.58829 4.616C9.71011 4.56554 9.81423 4.48009 9.88748 4.37046C9.96074 4.26083 9.99984 4.13194 9.99984 4.00008C9.99984 3.82327 9.9296 3.6537 9.80457 3.52868C9.67955 3.40365 9.50998 3.33341 9.33317 3.33341ZM13.3332 0.666748H2.6665C2.13607 0.666748 1.62736 0.877462 1.25229 1.25253C0.877218 1.62761 0.666504 2.13631 0.666504 2.66675V13.3334C0.666504 13.8638 0.877218 14.3726 1.25229 14.7476C1.62736 15.1227 2.13607 15.3334 2.6665 15.3334H13.3332C13.8636 15.3334 14.3723 15.1227 14.7474 14.7476C15.1225 14.3726 15.3332 13.8638 15.3332 13.3334V2.66675C15.3332 2.13631 15.1225 1.62761 14.7474 1.25253C14.3723 0.877462 13.8636 0.666748 13.3332 0.666748ZM13.9998 13.3334C13.9998 13.5102 13.9296 13.6798 13.8046 13.8048C13.6796 13.9298 13.51 14.0001 13.3332 14.0001H2.6665C2.48969 14.0001 2.32012 13.9298 2.1951 13.8048C2.07008 13.6798 1.99984 13.5102 1.99984 13.3334V7.33341H13.9998V13.3334ZM13.9998 6.00008H1.99984V2.66675C1.99984 2.48994 2.07008 2.32037 2.1951 2.19534C2.32012 2.07032 2.48969 2.00008 2.6665 2.00008H13.3332C13.51 2.00008 13.6796 2.07032 13.8046 2.19534C13.9296 2.32037 13.9998 2.48994 13.9998 2.66675V6.00008Z"
|
||||
fill="currentColor" />
|
||||
</svg>
|
Before Width: | Height: | Size: 2.5 KiB |
|
@ -1,5 +0,0 @@
|
|||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path
|
||||
d="M12.6668 2H3.3335C2.80306 2 2.29436 2.21071 1.91928 2.58579C1.54421 2.96086 1.3335 3.46957 1.3335 4V9.33333C1.3335 9.86377 1.54421 10.3725 1.91928 10.7475C2.29436 11.1226 2.80306 11.3333 3.3335 11.3333H7.3335V12.6667H4.66683C4.49002 12.6667 4.32045 12.7369 4.19543 12.8619C4.0704 12.987 4.00016 13.1565 4.00016 13.3333C4.00016 13.5101 4.0704 13.6797 4.19543 13.8047C4.32045 13.9298 4.49002 14 4.66683 14H11.3335C11.5103 14 11.6799 13.9298 11.8049 13.8047C11.9299 13.6797 12.0002 13.5101 12.0002 13.3333C12.0002 13.1565 11.9299 12.987 11.8049 12.8619C11.6799 12.7369 11.5103 12.6667 11.3335 12.6667H8.66683V11.3333H12.6668C13.1973 11.3333 13.706 11.1226 14.081 10.7475C14.4561 10.3725 14.6668 9.86377 14.6668 9.33333V4C14.6668 3.46957 14.4561 2.96086 14.081 2.58579C13.706 2.21071 13.1973 2 12.6668 2ZM13.3335 9.33333C13.3335 9.51014 13.2633 9.67971 13.1382 9.80474C13.0132 9.92976 12.8436 10 12.6668 10H3.3335C3.15669 10 2.98712 9.92976 2.86209 9.80474C2.73707 9.67971 2.66683 9.51014 2.66683 9.33333V4C2.66683 3.82319 2.73707 3.65362 2.86209 3.5286C2.98712 3.40357 3.15669 3.33333 3.3335 3.33333H12.6668C12.8436 3.33333 13.0132 3.40357 13.1382 3.5286C13.2633 3.65362 13.3335 3.82319 13.3335 4V9.33333Z"
|
||||
fill="currentColor" />
|
||||
</svg>
|
Before Width: | Height: | Size: 1.3 KiB |
|
@ -1,5 +0,0 @@
|
|||
<svg width="18" height="18" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path
|
||||
d="M10.667 17.3333H2.33366C1.88705 17.3488 1.45405 17.1782 1.13806 16.8622C0.822075 16.5462 0.651469 16.1132 0.666989 15.6666V7.33329C0.651469 6.88669 0.822075 6.45369 1.13806 6.1377C1.45405 5.82171 1.88705 5.6511 2.33366 5.66662H5.66699V2.33329C5.65147 1.88669 5.82208 1.45368 6.13806 1.1377C6.45405 0.821709 6.88705 0.651103 7.33366 0.666623H15.667C16.1136 0.651103 16.5466 0.821709 16.8626 1.1377C17.1786 1.45368 17.3492 1.88669 17.3337 2.33329V10.6666C17.3489 11.1132 17.1782 11.546 16.8623 11.8619C16.5464 12.1779 16.1135 12.3486 15.667 12.3333H12.3337V15.6666C12.3489 16.1132 12.1782 16.546 11.8623 16.8619C11.5464 17.1779 11.1135 17.3486 10.667 17.3333ZM2.33366 7.33329V15.6666H10.667V12.3333H7.33366C6.88712 12.3486 6.45427 12.1779 6.13834 11.8619C5.82241 11.546 5.65172 11.1132 5.66699 10.6666V7.33329H2.33366ZM7.33366 2.33329V10.6666H15.667V2.33329H7.33366Z"
|
||||
fill="currentColor" />
|
||||
</svg>
|
Before Width: | Height: | Size: 1,011 B |
|
@ -1,5 +0,0 @@
|
|||
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path
|
||||
d="M8.33333 15.0001C8.55435 15.0001 8.76631 14.9123 8.92259 14.756C9.07887 14.5997 9.16667 14.3878 9.16667 14.1667V9.16675C9.16667 8.94573 9.07887 8.73377 8.92259 8.57749C8.76631 8.42121 8.55435 8.33342 8.33333 8.33342C8.11232 8.33342 7.90036 8.42121 7.74408 8.57749C7.5878 8.73377 7.5 8.94573 7.5 9.16675V14.1667C7.5 14.3878 7.5878 14.5997 7.74408 14.756C7.90036 14.9123 8.11232 15.0001 8.33333 15.0001ZM16.6667 5.00008H13.3333V4.16675C13.3333 3.50371 13.0699 2.86782 12.6011 2.39898C12.1323 1.93014 11.4964 1.66675 10.8333 1.66675H9.16667C8.50363 1.66675 7.86774 1.93014 7.3989 2.39898C6.93006 2.86782 6.66667 3.50371 6.66667 4.16675V5.00008H3.33333C3.11232 5.00008 2.90036 5.08788 2.74408 5.24416C2.5878 5.40044 2.5 5.6124 2.5 5.83342C2.5 6.05443 2.5878 6.26639 2.74408 6.42267C2.90036 6.57895 3.11232 6.66675 3.33333 6.66675H4.16667V15.8334C4.16667 16.4965 4.43006 17.1323 4.8989 17.6012C5.36774 18.07 6.00363 18.3334 6.66667 18.3334H13.3333C13.9964 18.3334 14.6323 18.07 15.1011 17.6012C15.5699 17.1323 15.8333 16.4965 15.8333 15.8334V6.66675H16.6667C16.8877 6.66675 17.0996 6.57895 17.2559 6.42267C17.4122 6.26639 17.5 6.05443 17.5 5.83342C17.5 5.6124 17.4122 5.40044 17.2559 5.24416C17.0996 5.08788 16.8877 5.00008 16.6667 5.00008ZM8.33333 4.16675C8.33333 3.94573 8.42113 3.73377 8.57741 3.57749C8.73369 3.42121 8.94565 3.33341 9.16667 3.33341H10.8333C11.0543 3.33341 11.2663 3.42121 11.4226 3.57749C11.5789 3.73377 11.6667 3.94573 11.6667 4.16675V5.00008H8.33333V4.16675ZM14.1667 15.8334C14.1667 16.0544 14.0789 16.2664 13.9226 16.4227C13.7663 16.579 13.5543 16.6667 13.3333 16.6667H6.66667C6.44565 16.6667 6.23369 16.579 6.07741 16.4227C5.92113 16.2664 5.83333 16.0544 5.83333 15.8334V6.66675H14.1667V15.8334ZM11.6667 15.0001C11.8877 15.0001 12.0996 14.9123 12.2559 14.756C12.4122 14.5997 12.5 14.3878 12.5 14.1667V9.16675C12.5 8.94573 12.4122 8.73377 12.2559 8.57749C12.0996 8.42121 11.8877 8.33342 11.6667 8.33342C11.4457 8.33342 11.2337 8.42121 11.0774 8.57749C10.9211 8.73377 10.8333 8.94573 10.8333 9.16675V14.1667C10.8333 14.3878 10.9211 14.5997 11.0774 14.756C11.2337 14.9123 11.4457 15.0001 11.6667 15.0001Z"
|
||||
fill="currentColor" />
|
||||
</svg>
|
Before Width: | Height: | Size: 2.2 KiB |
|
@ -1,20 +0,0 @@
|
|||
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path
|
||||
d="M6 10C6 10.8284 6.67157 11.5 7.5 11.5C8.32843 11.5 9 10.8284 9 10C9 9.17157 8.32843 8.5 7.5 8.5C6.67157 8.5 6 9.17157 6 10Z"
|
||||
fill="currentColor" />
|
||||
<path
|
||||
d="M6 5.5C6 6.32843 6.67157 7 7.5 7C8.32843 7 9 6.32843 9 5.5C9 4.67157 8.32843 4 7.5 4C6.67157 4 6 4.67157 6 5.5Z"
|
||||
fill="currentColor" />
|
||||
<path
|
||||
d="M6 14.5C6 15.3284 6.67157 16 7.5 16C8.32843 16 9 15.3284 9 14.5C9 13.6716 8.32843 13 7.5 13C6.67157 13 6 13.6716 6 14.5Z"
|
||||
fill="currentColor" />
|
||||
<path
|
||||
d="M11 10C11 10.8284 11.6716 11.5 12.5 11.5C13.3284 11.5 14 10.8284 14 10C14 9.17157 13.3284 8.5 12.5 8.5C11.6716 8.5 11 9.17157 11 10Z"
|
||||
fill="currentColor" />
|
||||
<path
|
||||
d="M11 5.5C11 6.32843 11.6716 7 12.5 7C13.3284 7 14 6.32843 14 5.5C14 4.67157 13.3284 4 12.5 4C11.6716 4 11 4.67157 11 5.5Z"
|
||||
fill="currentColor" />
|
||||
<path
|
||||
d="M11 14.5C11 15.3284 11.6716 16 12.5 16C13.3284 16 14 15.3284 14 14.5C14 13.6716 13.3284 13 12.5 13C11.6716 13 11 13.6716 11 14.5Z"
|
||||
fill="currentColor" />
|
||||
</svg>
|
Before Width: | Height: | Size: 1.1 KiB |
|
@ -1,5 +0,0 @@
|
|||
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fillRule="evenodd" clipRule="evenodd"
|
||||
d="M3.66988 2.79338C3.27935 3.18391 3.27935 3.81707 3.66988 4.2076L5.30804 5.84576C3.10993 7.40492 1.66699 9.55153 1.66699 10.0003C1.66699 10.722 5.39795 15.8337 10.0003 15.8337C11.468 15.8337 12.847 15.3139 14.0447 14.5824L16.3978 16.9355C16.7883 17.326 17.4215 17.326 17.812 16.9355C18.2025 16.545 18.2025 15.9118 17.812 15.5213L5.08409 2.79338C4.69356 2.40286 4.0604 2.40286 3.66988 2.79338ZM12.0732 12.6109L7.38973 7.92745C6.9373 8.4965 6.66699 9.21685 6.66699 10.0003C6.66699 11.8413 8.15938 13.3337 10.0003 13.3337C10.7838 13.3337 11.5042 13.0634 12.0732 12.6109ZM18.3337 10.0003C18.3337 10.2663 17.8268 11.1287 16.9563 12.1117L9.07884 4.23427C9.38142 4.1904 9.68888 4.16699 10.0003 4.16699C14.6027 4.16699 18.3337 9.27866 18.3337 10.0003Z"
|
||||
fill="currentColor" />
|
||||
</svg>
|
Before Width: | Height: | Size: 927 B |
|
@ -1,8 +0,0 @@
|
|||
<svg width="18" height="12" viewBox="0 0 18 12" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path
|
||||
d="M11.5003 5.99996C11.5003 7.38067 10.381 8.49996 9.00033 8.49996C7.61961 8.49996 6.50033 7.38067 6.50033 5.99996C6.50033 4.61925 7.61961 3.49996 9.00033 3.49996C10.381 3.49996 11.5003 4.61925 11.5003 5.99996Z"
|
||||
fill="currentColor" />
|
||||
<path
|
||||
d="M17.2457 5.62728C15.4472 2.03025 12.2529 0.166626 9.00033 0.166626C5.74772 0.166626 2.55349 2.03025 0.75497 5.62728C0.637666 5.86189 0.637666 6.13803 0.75497 6.37264C2.55349 9.96967 5.74772 11.8333 9.00033 11.8333C12.2529 11.8333 15.4472 9.96967 17.2457 6.37264C17.363 6.13803 17.363 5.86189 17.2457 5.62728ZM9.00033 10.1666C6.52634 10.1666 3.99871 8.81597 2.44171 5.99996C3.99871 3.18394 6.52634 1.83329 9.00033 1.83329C11.4743 1.83329 14.0019 3.18394 15.5589 5.99996C14.0019 8.81597 11.4743 10.1666 9.00033 10.1666Z"
|
||||
fill="currentColor" />
|
||||
</svg>
|
Before Width: | Height: | Size: 915 B |
|
@ -1,5 +0,0 @@
|
|||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path
|
||||
d="M14.2664 11.9937L19.5317 6.7284C20.1561 6.10454 20.1561 5.09229 19.5317 4.4679C18.9073 3.84403 17.8961 3.84403 17.2717 4.4679L12.0065 9.73317L6.74119 4.46843C6.11732 3.84403 5.10508 3.84403 4.48068 4.46843C3.85682 5.09229 3.85682 6.10454 4.48068 6.72893L9.74596 11.9937L4.4679 17.2717C3.84403 17.8956 3.84403 18.9078 4.4679 19.5317C4.77956 19.8439 5.18925 20 5.59788 20C6.00651 20 6.4162 19.8439 6.7284 19.5317L12.0059 14.2542L17.2707 19.5189C17.5829 19.8311 17.992 19.9872 18.4007 19.9872C18.8093 19.9872 19.2184 19.8311 19.5306 19.5189C20.155 18.8951 20.155 17.8828 19.5306 17.2589L14.2664 11.9937Z"
|
||||
fill="currentColor" />
|
||||
</svg>
|
Before Width: | Height: | Size: 747 B |
|
@ -1,11 +0,0 @@
|
|||
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path
|
||||
d="M9.99984 1.66675C8.35166 1.66675 6.7405 2.15549 5.37009 3.07117C3.99968 3.98685 2.93158 5.28834 2.30084 6.81105C1.67011 8.33377 1.50509 10.0093 1.82663 11.6258C2.14817 13.2423 2.94185 14.7272 4.10728 15.8926C5.27272 17.0581 6.75758 17.8517 8.37409 18.1733C9.9906 18.4948 11.6662 18.3298 13.1889 17.6991C14.7116 17.0683 16.0131 16.0002 16.9288 14.6298C17.8444 13.2594 18.3332 11.6483 18.3332 10.0001C18.3332 8.90573 18.1176 7.8221 17.6988 6.81105C17.28 5.80001 16.6662 4.88135 15.8924 4.10752C15.1186 3.3337 14.1999 2.71987 13.1889 2.30109C12.1778 1.8823 11.0942 1.66675 9.99984 1.66675Z"
|
||||
fill="currentColor" />
|
||||
<path
|
||||
d="M9.41081 9.41066C9.56709 9.25438 9.77906 9.16658 10.0001 9.16658C10.2211 9.16658 10.433 9.25438 10.5893 9.41066C10.7456 9.56694 10.8334 9.7789 10.8334 9.99991V13.3332C10.8334 13.5543 10.7456 13.7662 10.5893 13.9225C10.433 14.0788 10.2211 14.1666 10.0001 14.1666C9.77906 14.1666 9.56709 14.0788 9.41081 13.9225C9.25453 13.7662 9.16674 13.5543 9.16674 13.3332V9.99991C9.16674 9.7789 9.25453 9.56694 9.41081 9.41066Z"
|
||||
fill="white" />
|
||||
<path
|
||||
d="M9.6834 5.89991C9.88629 5.81657 10.1139 5.81657 10.3167 5.89991C10.419 5.93958 10.5125 5.99905 10.5917 6.07491C10.744 6.23461 10.8303 6.44594 10.8334 6.66658C10.8327 6.80307 10.7985 6.9373 10.7338 7.05747C10.6691 7.17765 10.5759 7.28009 10.4623 7.3558C10.3488 7.43151 10.2183 7.47817 10.0825 7.49167C9.94671 7.50518 9.80965 7.48511 9.6834 7.43325C9.58242 7.39105 9.4894 7.33186 9.4084 7.25825C9.33117 7.18038 9.27006 7.08803 9.22859 6.9865C9.18712 6.88497 9.1661 6.77625 9.16674 6.66658C9.16392 6.55727 9.18675 6.44881 9.2334 6.34991C9.2756 6.24893 9.33479 6.15591 9.4084 6.07491C9.48766 5.99905 9.58111 5.93958 9.6834 5.89991Z"
|
||||
fill="white" />
|
||||
</svg>
|
Before Width: | Height: | Size: 1.8 KiB |
|
@ -1,5 +0,0 @@
|
|||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path
|
||||
d="M12.71 15.5402L18.36 9.88023C18.4537 9.78726 18.5281 9.67666 18.5789 9.5548C18.6296 9.43294 18.6558 9.30224 18.6558 9.17023C18.6558 9.03821 18.6296 8.90751 18.5789 8.78565C18.5281 8.66379 18.4537 8.55319 18.36 8.46023C18.1726 8.27398 17.9191 8.16943 17.655 8.16943C17.3908 8.16943 17.1373 8.27398 16.95 8.46023L11.95 13.4102L6.99996 8.46023C6.8126 8.27398 6.55915 8.16943 6.29496 8.16943C6.03078 8.16943 5.77733 8.27398 5.58996 8.46023C5.49548 8.55284 5.42031 8.66329 5.36881 8.78516C5.31731 8.90704 5.29051 9.03792 5.28996 9.17023C5.29051 9.30253 5.31731 9.43342 5.36881 9.55529C5.42031 9.67717 5.49548 9.78761 5.58996 9.88023L11.24 15.5402C11.3336 15.6417 11.4473 15.7227 11.5738 15.7781C11.7003 15.8336 11.8369 15.8622 11.975 15.8622C12.1131 15.8622 12.2497 15.8336 12.3762 15.7781C12.5027 15.7227 12.6163 15.6417 12.71 15.5402Z"
|
||||
fill="currentColor" />
|
||||
</svg>
|
Before Width: | Height: | Size: 978 B |
|
@ -1,5 +0,0 @@
|
|||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path
|
||||
d="M11.29 8.45977L5.64004 14.1198C5.54631 14.2127 5.47191 14.3233 5.42115 14.4452C5.37038 14.5671 5.34424 14.6978 5.34424 14.8298C5.34424 14.9618 5.37038 15.0925 5.42115 15.2144C5.47191 15.3362 5.54631 15.4468 5.64004 15.5398C5.8274 15.726 6.08085 15.8306 6.34504 15.8306C6.60922 15.8306 6.86267 15.726 7.05004 15.5398L12.05 10.5898L17 15.5398C17.1874 15.726 17.4409 15.8306 17.705 15.8306C17.9692 15.8306 18.2227 15.726 18.41 15.5398C18.5045 15.4472 18.5797 15.3367 18.6312 15.2148C18.6827 15.093 18.7095 14.9621 18.71 14.8298C18.7095 14.6975 18.6827 14.5666 18.6312 14.4447C18.5797 14.3228 18.5045 14.2124 18.41 14.1198L12.76 8.45977C12.6664 8.35827 12.5527 8.27726 12.4262 8.22185C12.2997 8.16645 12.1631 8.13784 12.025 8.13784C11.8869 8.13784 11.7503 8.16645 11.6238 8.22185C11.4973 8.27726 11.3837 8.35827 11.29 8.45977Z"
|
||||
fill="currentColor" />
|
||||
</svg>
|
Before Width: | Height: | Size: 969 B |
|
@ -1,81 +0,0 @@
|
|||
<svg width="891" height="274" viewBox="0 0 891 274" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path
|
||||
d="M375.888 195.694C378.108 195.694 379.218 194.584 379.218 192.363V181.26C379.218 179.039 378.108 177.929 375.888 177.929H312.616V65.786C312.616 63.5654 311.506 62.455 309.285 62.455H291.525C289.305 62.455 288.195 63.5654 288.195 65.786V192.363C288.195 194.584 289.305 195.694 291.525 195.694H375.888Z"
|
||||
fill="currentColor" />
|
||||
<path
|
||||
d="M468.052 60.2344H439.191C413.29 60.2344 400.339 73.1882 400.339 99.0959V159.054C400.339 184.961 413.29 197.915 439.191 197.915H468.052C493.953 197.915 506.903 184.961 506.903 159.054V99.0959C506.903 73.1882 493.953 60.2344 468.052 60.2344ZM482.482 159.054C482.482 173.118 475.452 180.15 461.392 180.15H445.851C431.79 180.15 424.76 173.118 424.76 159.054V99.0959C424.76 85.0317 431.79 77.9996 445.851 77.9996H461.392C475.452 77.9996 482.482 85.0317 482.482 99.0959V159.054Z"
|
||||
fill="currentColor" />
|
||||
<path
|
||||
d="M634.508 117.971H590.106C587.886 117.971 586.776 119.082 586.776 121.302V132.406C586.776 134.626 587.886 135.737 590.106 135.737H613.417V159.054C613.417 173.118 606.386 180.15 592.326 180.15H579.005C564.945 180.15 557.915 173.118 557.915 159.054V101.317C557.915 87.2524 564.945 80.2203 579.005 80.2203H625.627C627.847 80.2203 628.957 79.1099 628.957 76.8893V65.786C628.957 63.5654 627.847 62.455 625.627 62.455H572.345C546.444 62.455 533.494 75.4089 533.494 101.317V159.054C533.494 184.961 546.444 197.915 572.345 197.915H598.986C624.887 197.915 637.838 184.961 637.838 159.054V121.302C637.838 119.082 636.728 117.971 634.508 117.971Z"
|
||||
fill="currentColor" />
|
||||
<path
|
||||
d="M758.86 62.455H661.176C658.956 62.455 657.846 63.5654 657.846 65.786V76.8893C657.846 79.1099 658.956 80.2203 661.176 80.2203H697.807V192.363C697.807 194.584 698.917 195.694 701.138 195.694H718.898C721.118 195.694 722.228 194.584 722.228 192.363V80.2203H758.86C761.08 80.2203 762.19 79.1099 762.19 76.8893V65.786C762.19 63.5654 761.08 62.455 758.86 62.455Z"
|
||||
fill="currentColor" />
|
||||
<path
|
||||
d="M851.074 60.2344H822.213C796.312 60.2344 783.362 73.1882 783.362 99.0959V159.054C783.362 184.961 796.312 197.915 822.213 197.915H851.074C876.975 197.915 889.926 184.961 889.926 159.054V99.0959C889.926 73.1882 876.975 60.2344 851.074 60.2344ZM865.505 159.054C865.505 173.118 858.474 180.15 844.414 180.15H828.873C814.813 180.15 807.782 173.118 807.782 159.054V99.0959C807.782 85.0317 814.813 77.9996 828.873 77.9996H844.414C858.474 77.9996 865.505 85.0317 865.505 99.0959V159.054Z"
|
||||
fill="currentColor" />
|
||||
<path
|
||||
d="M0 37.5117C0 28.6752 7.16344 21.5117 16 21.5117H186.166C195.003 21.5117 202.166 28.6752 202.166 37.5117V220.638C202.166 229.474 195.003 236.638 186.166 236.638H16C7.16344 236.638 0 229.474 0 220.638V37.5117Z"
|
||||
fill="url(#paint0_linear_935_668)" />
|
||||
<path d="M231.201 273.748L34.949 235.025L318.305 238.252L231.201 273.748Z" fill="url(#paint1_linear_935_668)" />
|
||||
<path
|
||||
d="M0 35.3888C0 27.4266 5.85456 20.676 13.7367 19.5497L132.286 2.60972C141.925 1.23235 150.549 8.7118 150.549 18.4488V239.702C150.549 249.439 141.925 256.919 132.286 255.542L13.7367 238.602C5.85457 237.475 0 230.725 0 222.762V35.3888Z"
|
||||
fill="url(#paint2_linear_935_668)" />
|
||||
<g filter="url(#filter0_d_935_668)">
|
||||
<ellipse cx="116.138" cy="129.076" rx="12.9042" ry="12.9076" fill="url(#paint3_linear_935_668)" />
|
||||
</g>
|
||||
<g filter="url(#filter1_d_935_668)">
|
||||
<rect x="109.686" y="123.697" width="12.9042" height="34.4202" rx="6.45212" fill="url(#paint4_linear_935_668)" />
|
||||
</g>
|
||||
<defs>
|
||||
<linearGradient id="paint0_linear_935_668" x1="4.83909" y1="136.604" x2="205.001" y2="72.5859"
|
||||
gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#FFF06A" />
|
||||
<stop offset="1" stop-color="#EC78FF" />
|
||||
</linearGradient>
|
||||
<linearGradient id="paint1_linear_935_668" x1="118.827" y1="241.479" x2="240.344" y2="266.206"
|
||||
gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#ECB6FF" />
|
||||
<stop offset="1" stop-color="#FFF4F4" stop-opacity="0" />
|
||||
</linearGradient>
|
||||
<linearGradient id="paint2_linear_935_668" x1="-12.3666" y1="147.899" x2="162.382" y2="110.271"
|
||||
gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#492EF3" />
|
||||
<stop offset="1" stop-color="#CF69FF" />
|
||||
</linearGradient>
|
||||
<linearGradient id="paint3_linear_935_668" x1="116.138" y1="116.168" x2="110.759" y2="141.983"
|
||||
gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#E1BFFF" />
|
||||
<stop offset="1" stop-color="#AF68EE" />
|
||||
</linearGradient>
|
||||
<linearGradient id="paint4_linear_935_668" x1="116.138" y1="123.697" x2="100.89" y2="151.138"
|
||||
gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#F8F2FF" />
|
||||
<stop offset="1" stop-color="#D185F5" />
|
||||
</linearGradient>
|
||||
<filter id="filter0_d_935_668" x="102.234" y="116.168" width="27.8085" height="27.8145" filterUnits="userSpaceOnUse"
|
||||
color-interpolation-filters="sRGB">
|
||||
<feFlood flood-opacity="0" result="BackgroundImageFix" />
|
||||
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0"
|
||||
result="hardAlpha" />
|
||||
<feOffset dy="1" />
|
||||
<feGaussianBlur stdDeviation="0.5" />
|
||||
<feComposite in2="hardAlpha" operator="out" />
|
||||
<feColorMatrix type="matrix" values="0 0 0 0 0.104304 0 0 0 0 0.0825 0 0 0 0 0.2 0 0 0 0.02 0" />
|
||||
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_935_668" />
|
||||
<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow_935_668" result="shape" />
|
||||
</filter>
|
||||
<filter id="filter1_d_935_668" x="105.686" y="123.697" width="18.9043" height="41.4199" filterUnits="userSpaceOnUse"
|
||||
color-interpolation-filters="sRGB">
|
||||
<feFlood flood-opacity="0" result="BackgroundImageFix" />
|
||||
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0"
|
||||
result="hardAlpha" />
|
||||
<feOffset dx="-1" dy="4" />
|
||||
<feGaussianBlur stdDeviation="1.5" />
|
||||
<feComposite in2="hardAlpha" operator="out" />
|
||||
<feColorMatrix type="matrix" values="0 0 0 0 0.104304 0 0 0 0 0.0825 0 0 0 0 0.2 0 0 0 0.08 0" />
|
||||
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_935_668" />
|
||||
<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow_935_668" result="shape" />
|
||||
</filter>
|
||||
</defs>
|
||||
</svg>
|
Before Width: | Height: | Size: 6.2 KiB |
|
@ -1,5 +0,0 @@
|
|||
<svg width="18" height="18" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path
|
||||
d="M9.00008 0.666748C7.35191 0.666748 5.74074 1.15549 4.37033 2.07117C2.99992 2.98685 1.93182 4.28834 1.30109 5.81105C0.670359 7.33377 0.505331 9.00933 0.826874 10.6258C1.14842 12.2423 1.94209 13.7272 3.10753 14.8926C4.27297 16.0581 5.75782 16.8517 7.37433 17.1733C8.99084 17.4948 10.6664 17.3298 12.1891 16.6991C13.7118 16.0683 15.0133 15.0002 15.929 13.6298C16.8447 12.2594 17.3334 10.6483 17.3334 9.00008C17.3334 7.90573 17.1179 6.8221 16.6991 5.81105C16.2803 4.80001 15.6665 3.88135 14.8926 3.10752C14.1188 2.3337 13.2002 1.71987 12.1891 1.30109C11.1781 0.882296 10.0944 0.666748 9.00008 0.666748ZM9.00008 15.6667C7.68154 15.6667 6.39261 15.2758 5.29628 14.5432C4.19996 13.8107 3.34547 12.7695 2.84089 11.5513C2.3363 10.3331 2.20428 8.99269 2.46152 7.69948C2.71875 6.40627 3.35369 5.21839 4.28604 4.28604C5.21839 3.35369 6.40628 2.71875 7.69948 2.46151C8.99269 2.20428 10.3331 2.3363 11.5513 2.84088C12.7695 3.34547 13.8107 4.19995 14.5432 5.29628C15.2758 6.39261 15.6668 7.68154 15.6668 9.00008C15.6668 10.7682 14.9644 12.4639 13.7141 13.7141C12.4639 14.9644 10.7682 15.6667 9.00008 15.6667ZM12.3334 8.16675H5.66675C5.44574 8.16675 5.23378 8.25455 5.0775 8.41083C4.92122 8.56711 4.83342 8.77907 4.83342 9.00008C4.83342 9.2211 4.92122 9.43306 5.0775 9.58934C5.23378 9.74562 5.44574 9.83342 5.66675 9.83342H12.3334C12.5544 9.83342 12.7664 9.74562 12.9227 9.58934C13.079 9.43306 13.1668 9.2211 13.1668 9.00008C13.1668 8.77907 13.079 8.56711 12.9227 8.41083C12.7664 8.25455 12.5544 8.16675 12.3334 8.16675Z"
|
||||
fill="currentColor" />
|
||||
</svg>
|
Before Width: | Height: | Size: 1.6 KiB |
|
@ -1,5 +0,0 @@
|
|||
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path
|
||||
d="M10.0002 5.83333C10.3298 5.83333 10.652 5.73559 10.9261 5.55245C11.2002 5.36931 11.4138 5.10902 11.54 4.80447C11.6661 4.49993 11.6991 4.16482 11.6348 3.84152C11.5705 3.51822 11.4118 3.22124 11.1787 2.98816C10.9456 2.75507 10.6486 2.59633 10.3253 2.53203C10.002 2.46772 9.6669 2.50072 9.36236 2.62687C9.05781 2.75301 8.79752 2.96664 8.61438 3.24072C8.43125 3.5148 8.3335 3.83703 8.3335 4.16667C8.3335 4.60869 8.50909 5.03262 8.82165 5.34518C9.13421 5.65774 9.55814 5.83333 10.0002 5.83333ZM10.0002 14.1667C9.67053 14.1667 9.3483 14.2644 9.07421 14.4476C8.80013 14.6307 8.58651 14.891 8.46036 15.1955C8.33422 15.5001 8.30121 15.8352 8.36552 16.1585C8.42983 16.4818 8.58857 16.7788 8.82165 17.0118C9.05474 17.2449 9.35171 17.4037 9.67501 17.468C9.99832 17.5323 10.3334 17.4993 10.638 17.3731C10.9425 17.247 11.2028 17.0334 11.3859 16.7593C11.5691 16.4852 11.6668 16.163 11.6668 15.8333C11.6668 15.3913 11.4912 14.9674 11.1787 14.6548C10.8661 14.3423 10.4422 14.1667 10.0002 14.1667ZM10.0002 8.33333C9.67053 8.33333 9.3483 8.43108 9.07421 8.61422C8.80013 8.79735 8.58651 9.05765 8.46036 9.3622C8.33422 9.66674 8.30121 10.0018 8.36552 10.3252C8.42983 10.6485 8.58857 10.9454 8.82165 11.1785C9.05474 11.4116 9.35171 11.5703 9.67501 11.6346C9.99832 11.699 10.3334 11.6659 10.638 11.5398C10.9425 11.4137 11.2028 11.2 11.3859 10.926C11.5691 10.6519 11.6668 10.3296 11.6668 10C11.6668 9.55797 11.4912 9.13405 11.1787 8.82149C10.8661 8.50893 10.4422 8.33333 10.0002 8.33333Z"
|
||||
fill="currentColor" />
|
||||
</svg>
|
Before Width: | Height: | Size: 1.6 KiB |
|
@ -1,17 +0,0 @@
|
|||
<svg width="55" height="10" viewBox="0 0 55 10" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path opacity="0.35"
|
||||
d="M35.2003 3.00015C35.2003 2.04286 35.9763 1.26682 36.9336 1.26682H50.267C51.2243 1.26682 52.0003 2.04286 52.0003 3.00015V7.80016C52.0003 8.75745 51.2243 9.53349 50.267 9.53349H36.9336C35.9763 9.53349 35.2003 8.75745 35.2003 7.80015V3.00015Z"
|
||||
stroke="currentColor" strokeWidth="0.8" />
|
||||
<path opacity="0.4"
|
||||
d="M53.2002 3.80017V7.00017C53.844 6.72915 54.2626 6.09868 54.2626 5.40017C54.2626 4.70166 53.844 4.07119 53.2002 3.80017Z"
|
||||
fill="currentColor" />
|
||||
<path
|
||||
d="M36.4004 3.53346C36.4004 2.94436 36.878 2.4668 37.4671 2.4668H49.7337C50.3228 2.4668 50.8004 2.94436 50.8004 3.53346V7.2668C50.8004 7.8559 50.3228 8.33346 49.7337 8.33346H37.4671C36.878 8.33346 36.4004 7.8559 36.4004 7.2668V3.53346Z"
|
||||
fill="currentColor" />
|
||||
<path fillRule="evenodd" clipRule="evenodd"
|
||||
d="M24.6645 2.68649C26.4372 2.68656 28.1421 3.36771 29.4269 4.58915C29.5236 4.68345 29.6782 4.68226 29.7735 4.58649L30.6983 3.65315C30.7466 3.60457 30.7735 3.53877 30.7731 3.47031C30.7727 3.40184 30.745 3.33636 30.6962 3.28835C27.3241 0.0567145 22.0043 0.0567145 18.6322 3.28835C18.5833 3.33633 18.5556 3.40179 18.5552 3.47025C18.5547 3.53872 18.5816 3.60454 18.6298 3.65315L19.5549 4.58649C19.6501 4.68241 19.8048 4.6836 19.9015 4.58915C21.1864 3.36763 22.8916 2.68648 24.6645 2.68649ZM24.6643 5.72309C25.6383 5.72303 26.5775 6.08506 27.2995 6.73882C27.3972 6.83161 27.551 6.8296 27.6462 6.73429L28.5699 5.80096C28.6186 5.752 28.6455 5.68559 28.6448 5.61658C28.6441 5.54756 28.6158 5.48171 28.5662 5.43376C26.3676 3.38865 22.9629 3.38865 20.7643 5.43376C20.7146 5.48171 20.6863 5.5476 20.6857 5.61663C20.685 5.68566 20.7121 5.75207 20.7608 5.80096L21.6843 6.73429C21.7795 6.8296 21.9333 6.83161 22.031 6.73882C22.7525 6.08549 23.691 5.72349 24.6643 5.72309ZM26.5148 7.76614C26.5162 7.83535 26.489 7.90207 26.4396 7.95055L24.8417 9.56309C24.7949 9.61048 24.731 9.63715 24.6644 9.63715C24.5977 9.63715 24.5339 9.61048 24.487 9.56309L22.8889 7.95055C22.8395 7.90203 22.8124 7.83529 22.8138 7.76608C22.8153 7.69688 22.8453 7.63134 22.8966 7.58495C23.9171 6.72185 25.4116 6.72185 26.4321 7.58495C26.4834 7.63138 26.5133 7.69694 26.5148 7.76614Z"
|
||||
fill="currentColor" />
|
||||
<path fillRule="evenodd" clipRule="evenodd"
|
||||
d="M13.7336 1.13342H12.9336C12.4918 1.13342 12.1336 1.4916 12.1336 1.93342V8.86676C12.1336 9.30858 12.4918 9.66676 12.9336 9.66676H13.7336C14.1754 9.66676 14.5336 9.30858 14.5336 8.86676V1.93342C14.5336 1.4916 14.1754 1.13342 13.7336 1.13342ZM9.20039 3.00012H10.0004C10.4422 3.00012 10.8004 3.35829 10.8004 3.80012V8.86679C10.8004 9.30862 10.4422 9.66679 10.0004 9.66679H9.20039C8.75856 9.66679 8.40039 9.30862 8.40039 8.86679V3.80012C8.40039 3.35829 8.75856 3.00012 9.20039 3.00012ZM6.2668 4.86672H5.4668C5.02497 4.86672 4.6668 5.2249 4.6668 5.66672V8.86672C4.6668 9.30855 5.02497 9.66672 5.4668 9.66672H6.2668C6.70862 9.66672 7.0668 9.30855 7.0668 8.86672V5.66672C7.0668 5.2249 6.70862 4.86672 6.2668 4.86672ZM2.53359 6.46672H1.73359C1.29177 6.46672 0.933594 6.8249 0.933594 7.26672V8.86672C0.933594 9.30855 1.29177 9.66672 1.73359 9.66672H2.53359C2.97542 9.66672 3.33359 9.30855 3.33359 8.86672V7.26672C3.33359 6.8249 2.97542 6.46672 2.53359 6.46672Z"
|
||||
fill="currentColor" />
|
||||
</svg>
|
Before Width: | Height: | Size: 3.2 KiB |
|
@ -1,5 +0,0 @@
|
|||
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path
|
||||
d="M15.8335 9.16671H10.8335V4.16671C10.8335 3.94569 10.7457 3.73373 10.5894 3.57745C10.4331 3.42117 10.2212 3.33337 10.0002 3.33337C9.77915 3.33337 9.56719 3.42117 9.41091 3.57745C9.25463 3.73373 9.16683 3.94569 9.16683 4.16671V9.16671H4.16683C3.94582 9.16671 3.73385 9.2545 3.57757 9.41078C3.42129 9.56706 3.3335 9.77903 3.3335 10C3.3335 10.2211 3.42129 10.433 3.57757 10.5893C3.73385 10.7456 3.94582 10.8334 4.16683 10.8334H9.16683V15.8334C9.16683 16.0544 9.25463 16.2663 9.41091 16.4226C9.56719 16.5789 9.77915 16.6667 10.0002 16.6667C10.2212 16.6667 10.4331 16.5789 10.5894 16.4226C10.7457 16.2663 10.8335 16.0544 10.8335 15.8334V10.8334H15.8335C16.0545 10.8334 16.2665 10.7456 16.4228 10.5893C16.579 10.433 16.6668 10.2211 16.6668 10C16.6668 9.77903 16.579 9.56706 16.4228 9.41078C16.2665 9.2545 16.0545 9.16671 15.8335 9.16671Z"
|
||||
fill="currentColor" />
|
||||
</svg>
|
Before Width: | Height: | Size: 977 B |
|
@ -1,5 +0,0 @@
|
|||
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path
|
||||
d="M10.0003 1.66675C7.86398 1.67284 5.81157 2.49918 4.26699 3.97508V2.50008C4.26699 2.27907 4.1792 2.06711 4.02291 1.91083C3.86663 1.75455 3.65467 1.66675 3.43366 1.66675C3.21265 1.66675 3.00068 1.75455 2.8444 1.91083C2.68812 2.06711 2.60033 2.27907 2.60033 2.50008V6.25008C2.60033 6.4711 2.68812 6.68306 2.8444 6.83934C3.00068 6.99562 3.21265 7.08342 3.43366 7.08342H7.18366C7.40467 7.08342 7.61663 6.99562 7.77291 6.83934C7.92919 6.68306 8.01699 6.4711 8.01699 6.25008C8.01699 6.02907 7.92919 5.81711 7.77291 5.66083C7.61663 5.50455 7.40467 5.41675 7.18366 5.41675H5.18366C6.25514 4.29705 7.68325 3.58563 9.22244 3.40481C10.7616 3.22398 12.3157 3.58506 13.6175 4.42595C14.9193 5.26684 15.8875 6.53501 16.3556 8.01241C16.8237 9.48981 16.7624 11.0841 16.1823 12.5212C15.6023 13.9584 14.5396 15.1485 13.1771 15.887C11.8146 16.6255 10.2374 16.8663 8.71662 16.5679C7.19585 16.2695 5.82655 15.4505 4.84415 14.2519C3.86176 13.0533 3.32765 11.5498 3.33366 10.0001C3.33366 9.77907 3.24586 9.56711 3.08958 9.41083C2.9333 9.25455 2.72134 9.16675 2.50033 9.16675C2.27931 9.16675 2.06735 9.25455 1.91107 9.41083C1.75479 9.56711 1.66699 9.77907 1.66699 10.0001C1.66699 11.6483 2.15573 13.2594 3.07141 14.6298C3.98709 16.0002 5.28858 17.0683 6.8113 17.6991C8.33401 18.3298 10.0096 18.4948 11.6261 18.1733C13.2426 17.8517 14.7274 17.0581 15.8929 15.8926C17.0583 14.7272 17.852 13.2423 18.1735 11.6258C18.4951 10.0093 18.3301 8.33377 17.6993 6.81105C17.0686 5.28834 16.0005 3.98685 14.6301 3.07117C13.2597 2.15549 11.6485 1.66675 10.0003 1.66675ZM10.0003 6.66675C9.77931 6.66675 9.56735 6.75455 9.41107 6.91083C9.25479 7.06711 9.16699 7.27907 9.16699 7.50008V10.0001C9.16699 10.2211 9.25479 10.4331 9.41107 10.5893C9.56735 10.7456 9.77931 10.8334 10.0003 10.8334H11.667C11.888 10.8334 12.1 10.7456 12.2562 10.5893C12.4125 10.4331 12.5003 10.2211 12.5003 10.0001C12.5003 9.77907 12.4125 9.56711 12.2562 9.41083C12.1 9.25455 11.888 9.16675 11.667 9.16675H10.8337V7.50008C10.8337 7.27907 10.7459 7.06711 10.5896 6.91083C10.4333 6.75455 10.2213 6.66675 10.0003 6.66675Z"
|
||||
fill="currentColor" />
|
||||
</svg>
|
Before Width: | Height: | Size: 2.1 KiB |
|
@ -1,5 +0,0 @@
|
|||
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fillRule="evenodd" clipRule="evenodd"
|
||||
d="M8 12C5.791 12 4 10.209 4 8C4 5.791 5.791 4 8 4C10.209 4 12 5.791 12 8C12 10.209 10.209 12 8 12ZM17.707 16.293L12.887 11.473C13.585 10.492 14 9.296 14 8C14 4.687 11.313 2 8 2C4.687 2 2 4.687 2 8C2 11.313 4.687 14 8 14C9.296 14 10.492 13.585 11.473 12.887L16.293 17.707C16.488 17.902 16.744 18 17 18C17.256 18 17.512 17.902 17.707 17.707C18.098 17.316 18.098 16.684 17.707 16.293Z"
|
||||
fill="currentColor" />
|
||||
</svg>
|
Before Width: | Height: | Size: 564 B |
|
@ -1,5 +0,0 @@
|
|||
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path
|
||||
d="M10.4917 10.8333L8.57508 12.7416C8.49697 12.8191 8.43498 12.9112 8.39267 13.0128C8.35037 13.1143 8.32858 13.2232 8.32858 13.3333C8.32858 13.4433 8.35037 13.5522 8.39267 13.6537C8.43498 13.7553 8.49697 13.8475 8.57508 13.9249C8.65255 14.003 8.74472 14.065 8.84627 14.1073C8.94782 14.1496 9.05674 14.1714 9.16675 14.1714C9.27676 14.1714 9.38568 14.1496 9.48723 14.1073C9.58878 14.065 9.68095 14.003 9.75842 13.9249L13.0917 10.5916C13.1676 10.5123 13.2271 10.4189 13.2667 10.3166C13.3501 10.1137 13.3501 9.88614 13.2667 9.68325C13.2271 9.58096 13.1676 9.48751 13.0917 9.40825L9.75842 6.07492C9.68072 5.99722 9.58847 5.93559 9.48696 5.89354C9.38544 5.85149 9.27663 5.82984 9.16675 5.82984C9.05687 5.82984 8.94806 5.85149 8.84654 5.89354C8.74502 5.93559 8.65278 5.99722 8.57508 6.07492C8.49738 6.15262 8.43575 6.24486 8.3937 6.34638C8.35165 6.4479 8.33001 6.5567 8.33001 6.66659C8.33001 6.77647 8.35165 6.88528 8.3937 6.9868C8.43575 7.08831 8.49738 7.18056 8.57508 7.25825L10.4917 9.16659H2.50008C2.27907 9.16659 2.06711 9.25439 1.91083 9.41067C1.75455 9.56695 1.66675 9.77891 1.66675 9.99992C1.66675 10.2209 1.75455 10.4329 1.91083 10.5892C2.06711 10.7455 2.27907 10.8333 2.50008 10.8333H10.4917ZM10.0001 1.66659C8.44266 1.65963 6.91445 2.08926 5.5888 2.90675C4.26316 3.72423 3.19313 4.89685 2.50008 6.29159C2.40063 6.4905 2.38426 6.72077 2.45459 6.93175C2.52491 7.14273 2.67617 7.31713 2.87508 7.41659C3.07399 7.51604 3.30427 7.53241 3.51525 7.46208C3.72622 7.39176 3.90063 7.2405 4.00008 7.04159C4.52691 5.97769 5.32828 5.07377 6.32137 4.42324C7.31447 3.77272 8.46327 3.39919 9.64904 3.34125C10.8348 3.28331 12.0146 3.54307 13.0663 4.09368C14.1181 4.64429 15.0038 5.46578 15.6318 6.47325C16.2599 7.48072 16.6075 8.63763 16.6387 9.82441C16.67 11.0112 16.3838 12.1848 15.8097 13.2239C15.2356 14.2631 14.3944 15.13 13.373 15.7353C12.3517 16.3405 11.1873 16.662 10.0001 16.6666C8.75748 16.672 7.53854 16.3269 6.48316 15.671C5.42777 15.015 4.57871 14.0748 4.03341 12.9583C3.93396 12.7593 3.75956 12.6081 3.54858 12.5378C3.3376 12.4674 3.10733 12.4838 2.90841 12.5833C2.7095 12.6827 2.55825 12.8571 2.48792 13.0681C2.41759 13.2791 2.43396 13.5093 2.53341 13.7083C3.1941 15.0378 4.19802 16.1668 5.44131 16.9783C6.6846 17.7898 8.12212 18.2544 9.60518 18.3241C11.0882 18.3937 12.563 18.0659 13.8769 17.3745C15.1908 16.6831 16.2961 15.6532 17.0785 14.3914C17.8609 13.1296 18.292 11.6817 18.3272 10.1974C18.3624 8.71314 18.0004 7.24641 17.2786 5.94895C16.5569 4.65148 15.5016 3.57039 14.222 2.81751C12.9423 2.06462 11.4848 1.66728 10.0001 1.66659Z"
|
||||
fill="currentColor" />
|
||||
</svg>
|
Before Width: | Height: | Size: 2.6 KiB |
|
@ -1,5 +0,0 @@
|
|||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path
|
||||
d="M19.6767 4.22177C19.039 3.80823 18.1876 3.98978 17.7745 4.62705L10.0676 16.5065L6.30911 13.0313C5.75115 12.5155 4.88143 12.5494 4.36565 13.1074C3.84988 13.6649 3.8838 14.5355 4.44176 15.0508L9.37673 19.6135C9.37673 19.6135 9.5184 19.7355 9.58396 19.7781C9.81503 19.9285 10.0745 20 10.3313 20C10.7815 20 11.223 19.779 11.4866 19.3728L20.082 6.12396C20.4955 5.48668 20.314 4.63485 19.6767 4.22177Z"
|
||||
fill="currentColor" />
|
||||
</svg>
|
Before Width: | Height: | Size: 542 B |
|
@ -1,5 +0,0 @@
|
|||
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path
|
||||
d="M15.5917 6.00822C15.5142 5.93011 15.4221 5.86811 15.3205 5.82581C15.219 5.7835 15.11 5.76172 15 5.76172C14.89 5.76172 14.7811 5.7835 14.6796 5.82581C14.578 5.86811 14.4858 5.93011 14.4084 6.00822L8.20004 12.2249L5.59171 9.60822C5.51127 9.53052 5.41632 9.46942 5.31227 9.42842C5.20823 9.38742 5.09713 9.36731 4.98531 9.36924C4.87349 9.37118 4.76315 9.39512 4.66058 9.4397C4.55802 9.48427 4.46524 9.54862 4.38754 9.62905C4.30984 9.70949 4.24875 9.80444 4.20774 9.90848C4.16674 10.0125 4.14663 10.1236 4.14856 10.2354C4.1505 10.3473 4.17444 10.4576 4.21902 10.5602C4.2636 10.6627 4.32794 10.7555 4.40837 10.8332L7.60837 14.0332C7.68584 14.1113 7.77801 14.1733 7.87956 14.2156C7.98111 14.2579 8.09003 14.2797 8.20004 14.2797C8.31005 14.2797 8.41897 14.2579 8.52052 14.2156C8.62207 14.1733 8.71424 14.1113 8.79171 14.0332L15.5917 7.23322C15.6763 7.15518 15.7438 7.06047 15.79 6.95506C15.8361 6.84964 15.86 6.7358 15.86 6.62072C15.86 6.50563 15.8361 6.3918 15.79 6.28638C15.7438 6.18096 15.6763 6.08625 15.5917 6.00822V6.00822Z"
|
||||
fill="currentColor" />
|
||||
</svg>
|
Before Width: | Height: | Size: 1.1 KiB |
|
@ -1,5 +0,0 @@
|
|||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path
|
||||
d="M7.99992 7.33331C7.82311 7.33331 7.65354 7.40355 7.52852 7.52858C7.40349 7.6536 7.33325 7.82317 7.33325 7.99998V10.6666C7.33325 10.8435 7.40349 11.013 7.52852 11.1381C7.65354 11.2631 7.82311 11.3333 7.99992 11.3333C8.17673 11.3333 8.3463 11.2631 8.47133 11.1381C8.59635 11.013 8.66659 10.8435 8.66659 10.6666V7.99998C8.66659 7.82317 8.59635 7.6536 8.47133 7.52858C8.3463 7.40355 8.17673 7.33331 7.99992 7.33331ZM8.25325 4.71998C8.09095 4.6533 7.9089 4.6533 7.74659 4.71998C7.66475 4.75171 7.58999 4.79929 7.52659 4.85998C7.4677 4.92478 7.42035 4.99919 7.38659 5.07998C7.34927 5.1591 7.331 5.24586 7.33325 5.33331C7.33275 5.42105 7.34956 5.50803 7.38274 5.58925C7.41592 5.67048 7.4648 5.74435 7.52659 5.80665C7.59139 5.86553 7.6658 5.91289 7.74659 5.94665C7.84759 5.98814 7.95723 6.00419 8.06589 5.99339C8.17454 5.98259 8.27888 5.94526 8.36973 5.88469C8.46058 5.82412 8.53517 5.74217 8.58693 5.64603C8.63869 5.54989 8.66604 5.4425 8.66659 5.33331C8.66413 5.1568 8.59508 4.98774 8.47325 4.85998C8.40985 4.79929 8.33509 4.75171 8.25325 4.71998ZM7.99992 1.33331C6.68138 1.33331 5.39245 1.72431 4.29612 2.45685C3.19979 3.18939 2.34531 4.23058 1.84072 5.44876C1.33614 6.66693 1.20412 8.00738 1.46135 9.30058C1.71859 10.5938 2.35353 11.7817 3.28588 12.714C4.21823 13.6464 5.40611 14.2813 6.69932 14.5385C7.99253 14.7958 9.33297 14.6638 10.5511 14.1592C11.7693 13.6546 12.8105 12.8001 13.5431 11.7038C14.2756 10.6075 14.6666 9.31852 14.6666 7.99998C14.6666 7.1245 14.4941 6.25759 14.1591 5.44876C13.8241 4.63992 13.333 3.90499 12.714 3.28593C12.0949 2.66688 11.36 2.17581 10.5511 1.84078C9.74231 1.50575 8.8754 1.33331 7.99992 1.33331ZM7.99992 13.3333C6.94509 13.3333 5.91394 13.0205 5.03688 12.4345C4.15982 11.8484 3.47623 11.0155 3.07256 10.041C2.6689 9.06642 2.56328 7.99406 2.76907 6.9595C2.97485 5.92493 3.48281 4.97462 4.22869 4.22874C4.97457 3.48286 5.92487 2.97491 6.95944 2.76912C7.99401 2.56334 9.06636 2.66895 10.0409 3.07262C11.0154 3.47629 11.8484 4.15988 12.4344 5.03694C13.0205 5.914 13.3333 6.94515 13.3333 7.99998C13.3333 9.41447 12.7714 10.771 11.7712 11.7712C10.771 12.7714 9.41441 13.3333 7.99992 13.3333Z"
|
||||
fill="currentColor" />
|
||||
</svg>
|
Before Width: | Height: | Size: 2.2 KiB |
|
@ -16,7 +16,7 @@
|
|||
.icon {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
color: var(--color-text-secondary);
|
||||
color: var(--color-icon);
|
||||
}
|
||||
|
||||
.content {
|
||||
|
|
|
@ -2,8 +2,8 @@ import { AdminConsoleKey } from '@logto/phrases';
|
|||
import classNames from 'classnames';
|
||||
import { ReactNode } from 'react';
|
||||
|
||||
import Info from '@/assets/images/info.svg';
|
||||
import LinkButton from '@/components/LinkButton';
|
||||
import Info from '@/icons/Info';
|
||||
|
||||
import Button from '../Button';
|
||||
import * as styles from './index.module.scss';
|
||||
|
@ -38,7 +38,7 @@ const Alert = ({
|
|||
)}
|
||||
{action && onClick && (
|
||||
<div className={styles.action}>
|
||||
<Button title={action} type="text" size="small" onClick={onClick} />
|
||||
<Button title={action} type="plain" onClick={onClick} />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
|
|
@ -26,7 +26,7 @@
|
|||
|
||||
.description {
|
||||
font: var(--font-body-medium);
|
||||
color: var(--color-text-secondary);
|
||||
color: var(--color-caption);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
import classNames from 'classnames';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import Logo from '@/assets/images/logo.svg';
|
||||
import Spacer from '@/components/Spacer';
|
||||
import Logo from '@/icons/Logo';
|
||||
import GetStartedProgress from '@/pages/GetStarted/components/GetStartedProgress';
|
||||
|
||||
import UserInfo from '../UserInfo';
|
||||
|
|
|
@ -36,7 +36,7 @@
|
|||
|
||||
.role {
|
||||
font: var(--font-body-small);
|
||||
color: var(--color-text-secondary);
|
||||
color: var(--color-caption);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -58,7 +58,7 @@
|
|||
}
|
||||
|
||||
.signOutIcon {
|
||||
color: var(--color-text-secondary);
|
||||
color: var(--color-icon);
|
||||
}
|
||||
|
||||
.spinner {
|
||||
|
|
|
@ -1,13 +1,12 @@
|
|||
import { useLogto, IdTokenClaims } from '@logto/react';
|
||||
import classNames from 'classnames';
|
||||
import { useEffect, useRef, useState } from 'react';
|
||||
import { useEffect, useRef, useState, MouseEvent } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import SignOut from '@/assets/images/sign-out.svg';
|
||||
import Dropdown, { DropdownItem } from '@/components/Dropdown';
|
||||
import { Ring as Spinner } from '@/components/Spinner';
|
||||
import { generateAvatarPlaceHolderById } from '@/consts/avatars';
|
||||
import { onKeyDownHandler } from '@/utilities/a11y';
|
||||
import SignOut from '@/icons/SignOut';
|
||||
|
||||
import UserInfoSkeleton from '../UserInfoSkeleton';
|
||||
import * as styles from './index.module.scss';
|
||||
|
@ -44,18 +43,13 @@ const UserInfo = () => {
|
|||
<>
|
||||
<div
|
||||
ref={anchorRef}
|
||||
role="button"
|
||||
tabIndex={0}
|
||||
className={classNames(styles.container, showDropdown && styles.active)}
|
||||
onKeyDown={onKeyDownHandler(() => {
|
||||
setShowDropdown(true);
|
||||
})}
|
||||
onClick={() => {
|
||||
setShowDropdown(true);
|
||||
}}
|
||||
>
|
||||
{/* TODO: revert after SDK updated */}
|
||||
<img src={picture ? String(picture) : generateAvatarPlaceHolderById(id)} alt="avatar" />
|
||||
<img src={picture ? String(picture) : generateAvatarPlaceHolderById(id)} />
|
||||
<div className={styles.wrapper}>
|
||||
<div className={styles.name}>{username}</div>
|
||||
</div>
|
||||
|
@ -72,7 +66,7 @@ const UserInfo = () => {
|
|||
<DropdownItem
|
||||
className={classNames(styles.dropdownItem, isLoading && styles.loading)}
|
||||
icon={<SignOut className={styles.signOutIcon} />}
|
||||
onClick={(event) => {
|
||||
onClick={(event: MouseEvent<HTMLLIElement>) => {
|
||||
event.stopPropagation();
|
||||
|
||||
if (isLoading) {
|
||||
|
|
|
@ -4,10 +4,8 @@ import { useTranslation } from 'react-i18next';
|
|||
|
||||
import ErrorDark from '@/assets/images/error-dark.svg';
|
||||
import Error from '@/assets/images/error.svg';
|
||||
import KeyboardArrowDown from '@/assets/images/keyboard-arrow-down.svg';
|
||||
import KeyboardArrowUp from '@/assets/images/keyboard-arrow-up.svg';
|
||||
import { useTheme } from '@/hooks/use-theme';
|
||||
import { onKeyDownHandler } from '@/utilities/a11y';
|
||||
import { KeyboardArrowDown, KeyboardArrowUp } from '@/icons/Arrow';
|
||||
|
||||
import * as styles from './index.module.scss';
|
||||
|
||||
|
@ -35,12 +33,7 @@ const AppError = ({ title, errorCode, errorMessage, callStack, children }: Props
|
|||
{errorMessage}
|
||||
{callStack && (
|
||||
<span
|
||||
role="button"
|
||||
tabIndex={0}
|
||||
className={styles.expander}
|
||||
onKeyDown={onKeyDownHandler(() => {
|
||||
setIsDetailsOpen(!isDetailsOpen);
|
||||
})}
|
||||
onClick={() => {
|
||||
setIsDetailsOpen(!isDetailsOpen);
|
||||
}}
|
||||
|
|
|
@ -17,7 +17,7 @@ const ApplicationName = ({ applicationId, isLink = false }: Props) => {
|
|||
const { data } = useSWR<Application>(!isAdminConsole && `/api/applications/${applicationId}`);
|
||||
const { t } = useTranslation(undefined, { keyPrefix: 'admin_console' });
|
||||
|
||||
const name = (isAdminConsole ? <>Admin Console ({t('system_app')})</> : data?.name) ?? '-';
|
||||
const name = (isAdminConsole ? <>Admin Console ({t('system_app')})</> : data?.name) || '-';
|
||||
|
||||
if (isLink && !isAdminConsole) {
|
||||
return (
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
import classNames from 'classnames';
|
||||
import { Link } from 'react-router-dom';
|
||||
|
||||
import Failed from '@/assets/images/failed.svg';
|
||||
import Success from '@/assets/images/success.svg';
|
||||
import { logEventTitle } from '@/consts/logs';
|
||||
import Failed from '@/icons/Failed';
|
||||
import Success from '@/icons/Success';
|
||||
|
||||
import * as styles from './index.module.scss';
|
||||
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
align-items: center;
|
||||
|
||||
.title {
|
||||
color: var(--color-text-secondary);
|
||||
color: var(--color-caption);
|
||||
font: var(--font-body-medium);
|
||||
}
|
||||
|
||||
|
|
|
@ -53,8 +53,9 @@
|
|||
height: 30px;
|
||||
padding: 0 _.unit(3);
|
||||
|
||||
&.text {
|
||||
&.plain {
|
||||
height: 24px;
|
||||
padding: 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -62,9 +63,9 @@
|
|||
height: 36px;
|
||||
padding: 0 _.unit(4);
|
||||
|
||||
&.text {
|
||||
font: var(--font-subhead-1);
|
||||
&.plain {
|
||||
height: 28px;
|
||||
padding: 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -72,17 +73,16 @@
|
|||
height: 44px;
|
||||
padding: 0 _.unit(6);
|
||||
|
||||
&.text {
|
||||
// same as medium
|
||||
font: var(--font-subhead-1);
|
||||
height: 28px;
|
||||
&.plain {
|
||||
height: 28px; // same as medium
|
||||
padding: 0;
|
||||
}
|
||||
}
|
||||
|
||||
&.default {
|
||||
background: var(--color-layer-1);
|
||||
color: var(--color-text);
|
||||
border-color: var(--color-border);
|
||||
border-color: var(--color-outline);
|
||||
border-width: 1px;
|
||||
border-style: solid;
|
||||
|
||||
|
@ -196,13 +196,11 @@
|
|||
}
|
||||
}
|
||||
|
||||
&.text {
|
||||
&.plain {
|
||||
background: none;
|
||||
border-color: none;
|
||||
font: var(--font-label-large);
|
||||
font: var(--font-body-medium);
|
||||
color: var(--color-text-link);
|
||||
padding: _.unit(0.5) _.unit(1);
|
||||
border-radius: 4px;
|
||||
|
||||
&:disabled {
|
||||
color: var(--color-disabled);
|
||||
|
@ -213,7 +211,7 @@
|
|||
}
|
||||
|
||||
&:not(:disabled):hover {
|
||||
background-color: var(--color-hover-variant);
|
||||
text-decoration: underline;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,7 +8,7 @@ import { Ring as Spinner } from '@/components/Spinner';
|
|||
import DangerousRaw from '../DangerousRaw';
|
||||
import * as styles from './index.module.scss';
|
||||
|
||||
export type ButtonType = 'primary' | 'danger' | 'outline' | 'text' | 'default' | 'branding';
|
||||
export type ButtonType = 'primary' | 'danger' | 'outline' | 'plain' | 'default' | 'branding';
|
||||
|
||||
type BaseProps = Omit<HTMLProps<HTMLButtonElement>, 'type' | 'size' | 'title'> & {
|
||||
htmlType?: 'button' | 'submit' | 'reset';
|
||||
|
|
|
@ -10,7 +10,7 @@
|
|||
|
||||
.subtitle {
|
||||
margin-top: _.unit(1);
|
||||
color: var(--color-text-secondary);
|
||||
color: var(--color-caption);
|
||||
}
|
||||
|
||||
&.large {
|
||||
|
|
|
@ -1,13 +1,13 @@
|
|||
import { AppearanceMode } from '@logto/schemas';
|
||||
|
||||
import CheckBoxSelectedDisabledDark from '@/assets/images/check-box-selected-disabled-dark.svg';
|
||||
import CheckBoxSelectedDisabled from '@/assets/images/check-box-selected-disabled.svg';
|
||||
import CheckBoxSelected from '@/assets/images/check-box-selected.svg';
|
||||
import CheckBoxUnselectedDark from '@/assets/images/check-box-unselected-dark.svg';
|
||||
import CheckBoxUnselectedDisabledDark from '@/assets/images/check-box-unselected-disabled-dark.svg';
|
||||
import CheckBoxUnselectedDisabled from '@/assets/images/check-box-unselected-disabled.svg';
|
||||
import CheckBoxUnselected from '@/assets/images/check-box-unselected.svg';
|
||||
import { useTheme } from '@/hooks/use-theme';
|
||||
import CheckBoxSelected from '@/icons/CheckBoxSelected';
|
||||
import CheckBoxSelectedDisabled from '@/icons/CheckBoxSelectedDisabled';
|
||||
import CheckBoxSelectedDisabledDark from '@/icons/CheckBoxSelectedDisabledDark';
|
||||
import CheckBoxUnselected from '@/icons/CheckBoxUnselected';
|
||||
import CheckBoxUnselectedDark from '@/icons/CheckBoxUnselectedDark';
|
||||
import CheckBoxUnselectedDisabled from '@/icons/CheckBoxUnselectedDisabled';
|
||||
import CheckBoxUnselectedDisabledDark from '@/icons/CheckBoxUnselectedDisabledDark';
|
||||
|
||||
type Props = {
|
||||
className?: string;
|
||||
|
|
|
@ -33,11 +33,6 @@
|
|||
|
||||
.copyIcon {
|
||||
margin-left: _.unit(3);
|
||||
|
||||
svg {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,10 +2,9 @@ import classNames from 'classnames';
|
|||
import { MouseEventHandler, useEffect, useMemo, useRef, useState } from 'react';
|
||||
import { TFuncKey, useTranslation } from 'react-i18next';
|
||||
|
||||
import Copy from '@/assets/images/copy.svg';
|
||||
import EyeClosed from '@/assets/images/eye-closed.svg';
|
||||
import Eye from '@/assets/images/eye.svg';
|
||||
import { onKeyDownHandler } from '@/utilities/a11y';
|
||||
import Copy from '@/icons/Copy';
|
||||
import Eye from '@/icons/Eye';
|
||||
import EyeClosed from '@/icons/EyeClosed';
|
||||
|
||||
import IconButton from '../IconButton';
|
||||
import Tooltip from '../Tooltip';
|
||||
|
@ -58,11 +57,6 @@ const CopyToClipboard = ({
|
|||
return (
|
||||
<div
|
||||
className={classNames(styles.container, styles[variant], className)}
|
||||
role="button"
|
||||
tabIndex={0}
|
||||
onKeyDown={onKeyDownHandler((event) => {
|
||||
event.stopPropagation();
|
||||
})}
|
||||
onClick={(event) => {
|
||||
event.stopPropagation();
|
||||
}}
|
||||
|
|
|
@ -40,7 +40,6 @@ const DeleteConfirmModal = ({
|
|||
{children}
|
||||
{expectedInput && (
|
||||
<TextInput
|
||||
// eslint-disable-next-line jsx-a11y/no-autofocus
|
||||
autoFocus
|
||||
value={input}
|
||||
placeholder={inputPlaceholder}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import { AdminConsoleKey } from '@logto/phrases';
|
||||
import ReactModal from 'react-modal';
|
||||
|
||||
import Close from '@/assets/images/close.svg';
|
||||
import Close from '@/icons/Close';
|
||||
|
||||
import CardTitle from '../CardTitle';
|
||||
import IconButton from '../IconButton';
|
||||
|
@ -20,8 +20,6 @@ const Drawer = ({ title, subtitle, isOpen, children, onClose }: Props) => {
|
|||
return (
|
||||
<ReactModal
|
||||
shouldCloseOnOverlayClick
|
||||
// Styling purpose
|
||||
// eslint-disable-next-line jsx-a11y/aria-role
|
||||
role="drawer"
|
||||
isOpen={isOpen}
|
||||
className={styles.content}
|
||||
|
|
|
@ -1,12 +1,10 @@
|
|||
import classNames from 'classnames';
|
||||
import { MouseEvent, KeyboardEvent, ReactNode } from 'react';
|
||||
|
||||
import { onKeyDownHandler } from '@/utilities/a11y';
|
||||
import { MouseEvent, ReactNode } from 'react';
|
||||
|
||||
import * as styles from './DropdownItem.module.scss';
|
||||
|
||||
type Props = {
|
||||
onClick?: (event: MouseEvent<HTMLLIElement> | KeyboardEvent<HTMLLIElement>) => void;
|
||||
onClick?: (event: MouseEvent<HTMLLIElement>) => void;
|
||||
className?: string;
|
||||
children: ReactNode | Record<string, unknown>;
|
||||
icon?: ReactNode;
|
||||
|
@ -22,13 +20,7 @@ const DropdownItem = ({
|
|||
iconClassName,
|
||||
type = 'default',
|
||||
}: Props) => (
|
||||
<li
|
||||
role="menuitem"
|
||||
tabIndex={0}
|
||||
className={classNames(styles.item, styles[type], className)}
|
||||
onKeyDown={onKeyDownHandler(onClick)}
|
||||
onClick={onClick}
|
||||
>
|
||||
<li className={classNames(styles.item, styles[type], className)} onClick={onClick}>
|
||||
{icon && <span className={classNames(styles.icon, iconClassName)}>{icon}</span>}
|
||||
{children}
|
||||
</li>
|
||||
|
|