chore(deps): update dependency eslint to v9 - abandoned (#3594)

Co-authored-by: qwerty287 <qwerty287@posteo.de>
Co-authored-by: qwerty287 <80460567+qwerty287@users.noreply.github.com>
Co-authored-by: Anbraten <6918444+anbraten@users.noreply.github.com>
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
This commit is contained in:
renovate[bot] 2024-06-06 15:16:59 +02:00 committed by GitHub
parent c72468478d
commit 22414744b0
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
110 changed files with 4623 additions and 3689 deletions

View file

@ -13,6 +13,7 @@
"words": [
"abool",
"anbraten",
"antfu",
"apimachinery",
"autoincr",
"autoscaler",
@ -67,6 +68,7 @@
"httpsig",
"HTTPURL",
"httputil",
"ianvs",
"iconify",
"Infof",
"Informatyka",
@ -144,6 +146,7 @@
"tmpfs",
"tmpl",
"tolerations",
"tseslint",
"ttlcache",
"typecheck",
"Typeflag",

10
.vscode/settings.json vendored
View file

@ -9,5 +9,13 @@
"go.lintTool": "golangci-lint",
"go.lintFlags": ["--fast"],
"eslint.workingDirectories": ["./web"],
"prettier.ignorePath": "./web/.prettierignore"
"prettier.ignorePath": "./web/.prettierignore",
// Enable the ESlint flat config support
// (remove this if your ESLint extension above v3.0.5)
"eslint.experimental.useFlatConfig": true,
// Auto fix
"editor.codeActionsOnSave": {
"source.fixAll.eslint": "explicit",
"source.organizeImports": "never"
}
}

View file

@ -1,9 +0,0 @@
# don't lint build output (make sure it's set to your correct build folder name)
dist
coverage/
package.json
tsconfig.eslint.json
tsconfig.json
src/assets/locales/
src/assets/dayjsLocales/
components.d.ts

View file

@ -1,161 +0,0 @@
// cSpell:ignore TSES
// @ts-check
/** @type {import('@typescript-eslint/experimental-utils').TSESLint.Linter.Config} */
/* eslint-env node */
module.exports = {
env: {
browser: true,
},
reportUnusedDisableDirectives: true,
parser: 'vue-eslint-parser',
parserOptions: {
project: ['./tsconfig.eslint.json'],
tsconfigRootDir: __dirname,
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore see https://github.com/vuejs/vue-eslint-parser#parseroptionsparser
parser: '@typescript-eslint/parser',
sourceType: 'module',
extraFileExtensions: ['.vue'],
},
plugins: ['@typescript-eslint', 'import', 'simple-import-sort'],
extends: [
'eslint:recommended',
'plugin:@typescript-eslint/recommended',
'airbnb-base',
'airbnb-typescript/base',
'plugin:import/errors',
'plugin:import/warnings',
'plugin:import/typescript',
'plugin:promise/recommended',
'plugin:vue/vue3-recommended',
'plugin:prettier/recommended',
'plugin:vue-scoped-css/recommended',
],
rules: {
// enable scope analysis rules
'no-unused-vars': 'off',
'@typescript-eslint/no-unused-vars': 'error',
'no-use-before-define': 'off',
'@typescript-eslint/no-use-before-define': 'error',
'no-shadow': 'off',
'@typescript-eslint/no-shadow': 'error',
'no-redeclare': 'off',
'@typescript-eslint/no-redeclare': 'error',
// make typescript eslint rules even more strict
'@typescript-eslint/no-explicit-any': 'error',
'@typescript-eslint/explicit-module-boundary-types': 'off',
'@typescript-eslint/no-non-null-assertion': 'error',
// SOURCE: https://github.com/iamturns/eslint-config-airbnb-typescript/blob/4aec5702be5b4e74e0e2f40bc78b4bc961681de1/lib/shared.js#L41
'@typescript-eslint/naming-convention': [
'error',
// Allow camelCase variables (23.2), PascalCase variables (23.8), and UPPER_CASE variables (23.10)
{
selector: 'variable',
format: ['camelCase', 'PascalCase', 'UPPER_CASE'],
leadingUnderscore: 'allow',
},
// Allow camelCase functions (23.2), and PascalCase functions (23.8)
{
selector: 'function',
format: ['camelCase', 'PascalCase'],
},
// Airbnb recommends PascalCase for classes (23.3), and although Airbnb does not make TypeScript recommendations, we are assuming this rule would similarly apply to anything "type like", including interfaces, type aliases, and enums
{
selector: 'typeLike',
format: ['PascalCase'],
},
],
'import/no-unresolved': 'off', // disable as this is handled by tsc itself
'import/first': 'error',
'import/newline-after-import': 'error',
'import/no-cycle': 'error',
'import/no-relative-parent-imports': 'error',
'import/no-duplicates': 'error',
'import/no-extraneous-dependencies': 'error',
'import/extensions': 'off',
'import/prefer-default-export': 'off',
'simple-import-sort/imports': 'error',
'simple-import-sort/exports': 'error',
'promise/prefer-await-to-then': 'error',
'promise/prefer-await-to-callbacks': 'error',
'no-underscore-dangle': 'off',
'no-else-return': ['error', { allowElseIf: false }],
'no-return-assign': ['error', 'always'],
'no-return-await': 'error',
'no-useless-return': 'error',
'no-restricted-imports': [
'error',
{
patterns: ['src', 'dist'],
},
],
'no-console': 'warn',
'no-useless-concat': 'error',
'prefer-const': 'error',
'spaced-comment': ['error', 'always'],
'object-shorthand': ['error', 'always'],
'no-useless-rename': 'error',
eqeqeq: 'error',
'vue/attribute-hyphenation': 'error',
// enable in accordance with https://github.com/prettier/eslint-config-prettier#vuehtml-self-closing
'vue/html-self-closing': [
'error',
{
html: {
void: 'any',
},
},
],
'vue/no-static-inline-styles': 'error',
'vue/v-on-function-call': 'error',
'vue/no-useless-v-bind': 'error',
'vue/no-useless-mustaches': 'error',
'vue/no-useless-concat': 'error',
'vue/no-boolean-default': 'error',
'vue/html-button-has-type': 'error',
'vue/component-name-in-template-casing': 'error',
'vue/match-component-file-name': [
'error',
{
extensions: ['vue'],
shouldMatchCase: true,
},
],
'vue/require-name-property': 'error',
'vue/v-for-delimiter-style': 'error',
'vue/no-empty-component-block': 'error',
'vue/no-duplicate-attr-inheritance': 'error',
'vue/no-unused-properties': [
'error',
{
groups: ['props', 'data', 'computed', 'methods', 'setup'],
},
],
'vue/new-line-between-multi-line-property': 'error',
'vue/padding-line-between-blocks': 'error',
'vue/multi-word-component-names': 'off',
'vue/no-reserved-component-names': 'off',
// css rules
'vue-scoped-css/no-unused-selector': 'error',
'vue-scoped-css/no-parsing-error': 'error',
'vue-scoped-css/require-scoped': 'error',
// enable in accordance with https://github.com/prettier/eslint-config-prettier#curly
curly: ['error', 'all'],
// risky because of https://github.com/prettier/eslint-plugin-prettier#arrow-body-style-and-prefer-arrow-callback-issue
'arrow-body-style': 'error',
'prefer-arrow-callback': 'error',
},
};

15
web/.prettierrc.js Normal file
View file

@ -0,0 +1,15 @@
import { readFile } from 'node:fs/promises';
const config = JSON.parse(await readFile(new URL('../.prettierrc.json', import.meta.url)));
export default {
...config,
plugins: ['@ianvs/prettier-plugin-sort-imports'],
importOrder: [
'<THIRD_PARTY_MODULES>', // Imports not matched by other special words or groups.
'', // Empty string will match any import not matched by other special words or groups.
'^(#|@|~|\\$)(/.*)$',
'',
'^[./]',
],
};

119
web/eslint.config.js Normal file
View file

@ -0,0 +1,119 @@
// cSpell:ignore tseslint
// @ts-check
import antfu from '@antfu/eslint-config';
import js from '@eslint/js';
import vueI18n from '@intlify/eslint-plugin-vue-i18n';
import eslintPluginVueScopedCSS from 'eslint-plugin-vue-scoped-css';
export default antfu(
{
stylistic: false,
typescript: {
tsconfigPath: './tsconfig.json',
},
vue: true,
// Disable jsonc and yaml support
jsonc: false,
yaml: false,
},
js.configs.recommended,
// eslintPromise.configs.recommended,
// TypeScript
//...tseslint.configs.recommended,
//...tseslint.configs.recommendedTypeChecked,
//...tseslint.configs.strictTypeChecked,
//...tseslint.configs.stylisticTypeChecked,
{
rules: {
'import/order': 'off',
'sort-imports': 'off',
},
},
...eslintPluginVueScopedCSS.configs['flat/recommended'],
// Vue
{
files: ['**/*.vue'],
rules: {
'vue/multi-word-component-names': 'off',
'vue/html-self-closing': [
'error',
{
html: {
void: 'always',
normal: 'always',
component: 'always',
},
svg: 'always',
math: 'always',
},
],
'vue/block-order': [
'error',
{
order: ['template', 'script', 'style'],
},
],
'vue/singleline-html-element-content-newline': ['off'],
},
},
// Vue I18n
...vueI18n.configs['flat/recommended'],
{
rules: {
'@intlify/vue-i18n/no-raw-text': [
'error',
{
attributes: {
'/.+/': ['label'],
},
},
],
'@intlify/vue-i18n/key-format-style': ['error', 'snake_case'],
'@intlify/vue-i18n/no-duplicate-keys-in-locale': 'error',
'@intlify/vue-i18n/no-dynamic-keys': 'error',
'@intlify/vue-i18n/no-deprecated-i18n-component': 'error',
'@intlify/vue-i18n/no-deprecated-tc': 'error',
'@intlify/vue-i18n/no-i18n-t-path-prop': 'error',
'@intlify/vue-i18n/no-missing-keys-in-other-locales': 'off',
'@intlify/vue-i18n/valid-message-syntax': 'error',
'@intlify/vue-i18n/no-missing-keys': 'error',
'@intlify/vue-i18n/no-unknown-locale': 'error',
'@intlify/vue-i18n/no-unused-keys': ['error', { extensions: ['.ts', '.vue'] }],
'@intlify/vue-i18n/prefer-sfc-lang-attr': 'error',
'@intlify/vue-i18n/no-html-messages': 'error',
'@intlify/vue-i18n/prefer-linked-key-with-paren': 'error',
'@intlify/vue-i18n/sfc-locale-attr': 'error',
},
settings: {
'vue-i18n': {
localeDir: './src/assets/locales/en.json',
// Specify the version of `vue-i18n` you are using.
// If not specified, the message will be parsed twice.
messageSyntaxVersion: '^9.0.0',
},
},
},
// Ignore list
{
ignores: [
'dist',
'coverage/',
'package.json',
'tsconfig.eslint.json',
'tsconfig.json',
'src/assets/locales/**/*',
'!src/assets/locales/en.json',
'src/assets/dayjsLocales/',
'components.d.ts',
],
},
);

View file

@ -3,6 +3,7 @@
"author": "Woodpecker CI",
"version": "0.0.0",
"license": "Apache-2.0",
"type": "module",
"engines": {
"node": ">=14"
},
@ -10,7 +11,7 @@
"start": "vite",
"build": "vite build --base=/BASE_PATH",
"serve": "vite preview",
"lint": "eslint --max-warnings 0 --ext .js,.ts,.vue,.json .",
"lint": "eslint --max-warnings 0 .",
"format": "prettier --write .",
"format:check": "prettier -c .",
"typecheck": "vue-tsc --noEmit",
@ -18,57 +19,54 @@
},
"dependencies": {
"@intlify/unplugin-vue-i18n": "^4.0.0",
"@kyvg/vue3-notification": "^3.1.3",
"@vueuse/core": "^10.7.2",
"@kyvg/vue3-notification": "^3.2.1",
"@vueuse/core": "^10.10.0",
"ansi_up": "^6.0.2",
"dayjs": "^1.11.10",
"dayjs": "^1.11.11",
"fuse.js": "^7.0.0",
"js-base64": "^3.7.6",
"js-base64": "^3.7.7",
"lodash": "^4.17.21",
"node-emoji": "^2.1.3",
"pinia": "^2.1.7",
"prismjs": "^1.29.0",
"semver": "^7.5.4",
"vue": "^3.4.15",
"vue-i18n": "^9.9.0",
"vue-router": "^4.2.5"
"semver": "^7.6.2",
"vue": "^3.4.27",
"vue-i18n": "^9.13.1",
"vue-router": "^4.3.2"
},
"devDependencies": {
"@iconify/json": "^2.2.171",
"@types/lodash": "^4.14.202",
"@types/node": "^20.11.5",
"@types/node-emoji": "^2.0.0",
"@types/prismjs": "^1.26.3",
"@types/semver": "^7.5.6",
"@typescript-eslint/eslint-plugin": "^7.0.0",
"@typescript-eslint/parser": "^7.0.0",
"@vitejs/plugin-vue": "^5.0.3",
"@vue/compiler-sfc": "^3.4.15",
"@vue/test-utils": "^2.4.5",
"eslint": "^8.56.0",
"eslint-config-airbnb-base": "^15.0.0",
"eslint-config-airbnb-typescript": "^18.0.0",
"eslint-config-prettier": "^9.1.0",
"eslint-plugin-import": "^2.29.1",
"eslint-plugin-prettier": "^5.1.3",
"eslint-plugin-promise": "^6.1.1",
"eslint-plugin-simple-import-sort": "^12.0.0",
"eslint-plugin-vue": "^9.20.1",
"eslint-plugin-vue-scoped-css": "^2.7.2",
"jsdom": "^24.0.0",
"prettier": "^3.2.4",
"replace-in-file": "^7.1.0",
"@antfu/eslint-config": "^2.20.0",
"@eslint/js": "^9.4.0",
"@ianvs/prettier-plugin-sort-imports": "^4.2.1",
"@iconify/json": "^2.2.216",
"@intlify/eslint-plugin-vue-i18n": "3.0.0-next.13",
"@types/eslint__js": "^8.42.3",
"@types/lodash": "^4.17.4",
"@types/node": "^20.14.2",
"@types/node-emoji": "^2.1.0",
"@types/prismjs": "^1.26.4",
"@types/semver": "^7.5.8",
"@types/tinycolor2": "^1.4.6",
"@vitejs/plugin-vue": "^5.0.5",
"@vue/compiler-sfc": "^3.4.27",
"@vue/test-utils": "^2.4.6",
"eslint": "^9.4.0",
"eslint-plugin-promise": "^6.2.0",
"eslint-plugin-vue-scoped-css": "^2.8.0",
"jsdom": "^24.1.0",
"prettier": "^3.3.0",
"replace-in-file": "^7.2.0",
"tinycolor2": "^1.6.0",
"typescript": "5.4.5",
"unplugin-icons": "^0.18.2",
"typescript-eslint": "^7.12.0",
"unplugin-icons": "^0.18.5",
"unplugin-vue-components": "^0.26.0",
"vite": "^5.0.12",
"vite": "^5.2.12",
"vite-plugin-prismjs": "^0.0.11",
"vite-plugin-windicss": "^1.9.3",
"vite-svg-loader": "^5.1.0",
"vitest": "^1.5.0",
"vue-eslint-parser": "^9.4.0",
"vue-tsc": "^2.0.0",
"vitest": "^1.6.0",
"vue-tsc": "^2.0.19",
"windicss": "^3.5.6"
},
"pnpm": {

File diff suppressed because it is too large Load diff

View file

@ -31,7 +31,7 @@ const apiClient = useApiClient();
const { notify } = useNotifications();
const i18n = useI18n();
// eslint-disable-next-line promise/prefer-await-to-callbacks
// TODO reenable with eslint-plugin-promise eslint-disable-next-line promise/prefer-await-to-callbacks
apiClient.setErrorHandler((err) => {
if (err.status === 404) {
notify({ title: i18n.t('errors.not_found'), type: 'error' });

View file

@ -10,7 +10,6 @@
"search": "Search…",
"username": "Username",
"password": "Password",
"url": "URL",
"back": "Back",
"unknown_error": "An unknown error occurred",
"documentation_for": "Documentation for \"{topic}\"",
@ -25,11 +24,6 @@
},
"time": {
"template": "MMM D, YYYY, HH:mm z",
"weeks_short": "w",
"days_short": "d",
"hours_short": "h",
"min_short": "min",
"sec_short": "sec",
"not_started": "not started yet"
},
"repo": {
@ -65,12 +59,10 @@
"user_none": "This organization / user does not have any projects yet.",
"not_allowed": "You are not allowed to access this repository",
"enable": {
"reload": "Reload repositories",
"enable": "Enable",
"enabled": "Already enabled",
"disabled": "Disabled",
"success": "Repository enabled",
"list_reloaded": "Repository list reloaded"
"success": "Repository enabled"
},
"open_in_forge": "Open repository in forge",
"settings": {
@ -209,7 +201,6 @@
"tasks": "Tasks",
"config": "Config",
"files": "Changed files ({files})",
"no_files": "No files have been changed.",
"no_pipelines": "No pipelines have been started yet.",
"no_pipeline_steps": "No pipeline steps available!",
"step_not_started": "This step hasn't started yet.",
@ -444,7 +435,6 @@
"events": "Available at following events",
"pr_warning": "Please be careful with this option as a bad actor can submit a malicious pull request that exposes your secrets."
},
"plugins_only": "Only available for plugins",
"edit": "Edit secret",
"delete": "Delete secret"
},

View file

@ -147,7 +147,7 @@ import { useAsyncAction } from '~/compositions/useAsyncAction';
import { useDate } from '~/compositions/useDate';
import useNotifications from '~/compositions/useNotifications';
import { usePagination } from '~/compositions/usePaginate';
import { Agent } from '~/lib/api/types';
import type { Agent } from '~/lib/api/types';
const apiClient = useApiClient();
const notifications = useNotifications();
@ -175,14 +175,14 @@ const { doSubmit: saveAgent, isLoading: isSaving } = useAsyncAction(async () =>
selectedAgent.value = await apiClient.createAgent(selectedAgent.value);
}
notifications.notify({
title: t(isEditingAgent.value ? 'admin.settings.agents.saved' : 'admin.settings.agents.created'),
title: isEditingAgent.value ? t('admin.settings.agents.saved') : t('admin.settings.agents.created'),
type: 'success',
});
resetPage();
});
const { doSubmit: deleteAgent, isLoading: isDeleting } = useAsyncAction(async (_agent: Agent) => {
// eslint-disable-next-line no-restricted-globals, no-alert
// eslint-disable-next-line no-alert
if (!confirm(t('admin.settings.agents.delete_confirm'))) {
return;
}

View file

@ -15,8 +15,9 @@
target="_blank"
rel="noopener noreferrer"
class="underline"
>{{ version.latest }}</a
>
{{ version.latest }}
</a>
<span v-else>
{{ version.latest }}
</span>

View file

@ -43,7 +43,7 @@ import useApiClient from '~/compositions/useApiClient';
import { useAsyncAction } from '~/compositions/useAsyncAction';
import useNotifications from '~/compositions/useNotifications';
import { usePagination } from '~/compositions/usePaginate';
import { Org } from '~/lib/api/types';
import type { Org } from '~/lib/api/types';
const apiClient = useApiClient();
const notifications = useNotifications();
@ -56,7 +56,7 @@ async function loadOrgs(page: number): Promise<Org[] | null> {
const { resetPage, data: orgs } = usePagination(loadOrgs);
const { doSubmit: deleteOrg, isLoading: isDeleting } = useAsyncAction(async (_org: Org) => {
// eslint-disable-next-line no-restricted-globals, no-alert
// eslint-disable-next-line no-alert
if (!confirm(t('admin.settings.orgs.delete_confirm'))) {
return;
}

View file

@ -85,7 +85,7 @@ import ListItem from '~/components/atomic/ListItem.vue';
import Settings from '~/components/layout/Settings.vue';
import useApiClient from '~/compositions/useApiClient';
import useNotifications from '~/compositions/useNotifications';
import { QueueInfo } from '~/lib/api/types/queue';
import type { QueueInfo } from '~/lib/api/types';
import AdminQueueStats from './queue/AdminQueueStats.vue';

View file

@ -33,7 +33,7 @@
</div>
</ListItem>
<div v-if="repos?.length === 0" class="ml-2">{{ $t('admin.settings.orgs.none') }}</div>
<div v-if="repos?.length === 0" class="ml-2">{{ $t('admin.settings.repos.none') }}</div>
</div>
</Settings>
</template>
@ -49,7 +49,7 @@ import useApiClient from '~/compositions/useApiClient';
import { useAsyncAction } from '~/compositions/useAsyncAction';
import useNotifications from '~/compositions/useNotifications';
import { usePagination } from '~/compositions/usePaginate';
import { Repo } from '~/lib/api/types';
import type { Repo } from '~/lib/api/types';
const apiClient = useApiClient();
const notifications = useNotifications();

View file

@ -41,7 +41,8 @@ import useApiClient from '~/compositions/useApiClient';
import { useAsyncAction } from '~/compositions/useAsyncAction';
import useNotifications from '~/compositions/useNotifications';
import { usePagination } from '~/compositions/usePaginate';
import { Secret, WebhookEvents } from '~/lib/api/types';
import type { Secret } from '~/lib/api/types';
import { WebhookEvents } from '~/lib/api/types';
const emptySecret: Partial<Secret> = {
name: '',
@ -74,7 +75,7 @@ const { doSubmit: createSecret, isLoading: isSaving } = useAsyncAction(async ()
await apiClient.createGlobalSecret(selectedSecret.value);
}
notifications.notify({
title: i18n.t(isEditingSecret.value ? 'secrets.saved' : 'secrets.created'),
title: isEditingSecret.value ? i18n.t('secrets.saved') : i18n.t('secrets.created'),
type: 'success',
});
selectedSecret.value = undefined;

View file

@ -97,7 +97,7 @@ import useApiClient from '~/compositions/useApiClient';
import { useAsyncAction } from '~/compositions/useAsyncAction';
import useNotifications from '~/compositions/useNotifications';
import { usePagination } from '~/compositions/usePaginate';
import { User } from '~/lib/api/types';
import type { User } from '~/lib/api/types';
const apiClient = useApiClient();
const notifications = useNotifications();
@ -135,7 +135,7 @@ const { doSubmit: saveUser, isLoading: isSaving } = useAsyncAction(async () => {
});
const { doSubmit: deleteUser, isLoading: isDeleting } = useAsyncAction(async (_user: User) => {
// eslint-disable-next-line no-restricted-globals, no-alert
// eslint-disable-next-line no-alert
if (!confirm(t('admin.settings.users.delete_confirm'))) {
return;
}

View file

@ -54,14 +54,14 @@
import { computed } from 'vue';
import { useI18n } from 'vue-i18n';
import { QueueStats } from '~/lib/api/types/queue';
const { t } = useI18n();
import type { QueueStats } from '~/lib/api/types/queue';
const props = defineProps<{
stats?: QueueStats;
}>();
const { t } = useI18n();
const total = computed(() => {
if (!props.stats) {
return 0;

View file

@ -36,9 +36,10 @@
<script lang="ts" setup>
import { computed, useAttrs } from 'vue';
import { RouteLocationRaw } from 'vue-router';
import type { RouteLocationRaw } from 'vue-router';
import Icon, { IconNames } from '~/components/atomic/Icon.vue';
import type { IconNames } from '~/components/atomic/Icon.vue';
import Icon from '~/components/atomic/Icon.vue';
const props = withDefaults(
defineProps<{

View file

@ -1,11 +1,12 @@
<template>
<a
:href="`${docsUrl}`"
:title="$t('documentation_for', { topic: topic })"
:title="$t('documentation_for', { topic })"
target="_blank"
class="text-wp-link-100 hover:text-wp-link-200 cursor-pointer mt-1"
><Icon name="question" class="!w-4 !h-4"
/></a>
>
<Icon name="question" class="!w-4 !h-4" />
</a>
</template>
<script lang="ts" setup>

View file

@ -28,9 +28,9 @@
</template>
<script lang="ts" setup>
import { RouteLocationRaw } from 'vue-router';
import type { RouteLocationRaw } from 'vue-router';
import Icon, { IconNames } from '~/components/atomic/Icon.vue';
import Icon, { type IconNames } from '~/components/atomic/Icon.vue';
defineProps<{
icon?: IconNames;

View file

@ -12,7 +12,7 @@
</template>
<script lang="ts" setup>
import { RouteLocationRaw } from 'vue-router';
import type { RouteLocationRaw } from 'vue-router';
defineProps<{
clickable?: boolean;

View file

@ -1,7 +1,7 @@
import '~/style/prism.css';
import Prism from 'prismjs';
import { computed, defineComponent, h, toRef, VNode } from 'vue';
import { computed, defineComponent, h, toRef, type VNode } from 'vue';
declare type Data = Record<string, unknown>;

View file

@ -14,7 +14,7 @@
import { computed, toRef } from 'vue';
import Checkbox from './Checkbox.vue';
import { CheckboxOption } from './form.types';
import type { CheckboxOption } from './form.types';
const props = defineProps<{
modelValue?: CheckboxOption['value'][];

View file

@ -3,10 +3,10 @@
<div class="flex items-center mb-2">
<label class="text-wp-text-100 font-bold" :for="id" v-bind="$attrs">{{ label }}</label>
<DocsLink v-if="docsUrl" :topic="label" :url="docsUrl" class="ml-2" />
<slot v-else-if="$slots['titleActions']" name="titleActions" />
<slot v-else-if="$slots.titleActions" name="titleActions" />
</div>
<slot :id="id" />
<div v-if="$slots['description']" class="ml-1 text-wp-text-alt-100">
<div v-if="$slots.description" class="ml-1 text-wp-text-alt-100">
<slot name="description" />
</div>
</div>

View file

@ -20,7 +20,7 @@ const modelValue = toRef(props, 'modelValue');
const innerValue = computed({
get: () => modelValue.value.toString(),
set: (value) => {
emit('update:modelValue', parseFloat(value));
emit('update:modelValue', Number.parseFloat(value));
},
});
</script>

View file

@ -18,7 +18,7 @@
<script lang="ts" setup>
import { computed, toRef } from 'vue';
import { RadioOption } from './form.types';
import type { RadioOption } from './form.types';
const props = defineProps<{
modelValue: string;

View file

@ -13,7 +13,7 @@
<script lang="ts" setup>
import { computed, toRef } from 'vue';
import { SelectOption } from './form.types';
import type { SelectOption } from './form.types';
const props = defineProps<{
modelValue: string;

View file

@ -1,8 +1,8 @@
export type SelectOption = {
export interface SelectOption {
value: string;
text: string;
description?: string;
};
}
export type RadioOption = SelectOption;

View file

@ -7,8 +7,8 @@
<script setup lang="ts">
import { computed, onMounted, ref } from 'vue';
import { IconNames } from '~/components/atomic/Icon.vue';
import { Tab, useTabsClient } from '~/compositions/useTabs';
import type { IconNames } from '~/components/atomic/Icon.vue';
import { useTabsClient, type Tab } from '~/compositions/useTabs';
const props = defineProps<{
id?: string;

View file

@ -24,7 +24,7 @@
<script setup lang="ts">
import { useRoute, useRouter } from 'vue-router';
import { Tab, useTabsClient } from '~/compositions/useTabs';
import { useTabsClient, type Tab } from '~/compositions/useTabs';
const router = useRouter();
const route = useRoute();

View file

@ -25,7 +25,7 @@
<script lang="ts" setup>
import { cloneDeep } from 'lodash';
import { computed, inject, Ref, ref } from 'vue';
import { computed, inject, ref, type Ref } from 'vue';
import { useI18n } from 'vue-i18n';
import Button from '~/components/atomic/Button.vue';
@ -36,7 +36,7 @@ import useApiClient from '~/compositions/useApiClient';
import { useAsyncAction } from '~/compositions/useAsyncAction';
import useNotifications from '~/compositions/useNotifications';
import { usePagination } from '~/compositions/usePaginate';
import { Org, Secret, WebhookEvents } from '~/lib/api/types';
import { WebhookEvents, type Org, type Secret } from '~/lib/api/types';
const emptySecret: Partial<Secret> = {
name: '',
@ -78,7 +78,7 @@ const { doSubmit: createSecret, isLoading: isSaving } = useAsyncAction(async ()
await apiClient.createOrgSecret(org.value.id, selectedSecret.value);
}
notifications.notify({
title: i18n.t(isEditingSecret.value ? 'secrets.saved' : 'secrets.created'),
title: isEditingSecret.value ? i18n.t('secrets.saved') : i18n.t('secrets.created'),
type: 'success',
});
selectedSecret.value = undefined;

View file

@ -8,8 +8,10 @@
params: { repoId: pipeline.repo_id },
}"
class="underline"
>{{ repo?.owner }} / {{ repo?.name }}</router-link
>
<!-- eslint-disable-next-line @intlify/vue-i18n/no-raw-text -->
{{ repo?.owner }} / {{ repo?.name }}
</router-link>
<span class="whitespace-nowrap overflow-hidden overflow-ellipsis" :title="message">{{ title }}</span>
<div class="flex flex-col mt-2">
<div class="flex space-x-2 items-center" :title="created">
@ -31,7 +33,7 @@ import { computed, toRef } from 'vue';
import Icon from '~/components/atomic/Icon.vue';
import PipelineStatusIcon from '~/components/repo/pipeline/PipelineStatusIcon.vue';
import usePipeline from '~/compositions/usePipeline';
import { PipelineFeed } from '~/lib/api/types';
import type { PipelineFeed } from '~/lib/api/types';
import { useRepoStore } from '~/store/repos';
const props = defineProps<{

View file

@ -24,13 +24,16 @@
</div>
<div class="w-full md:w-auto md:mx-4 flex items-center min-w-0">
<!-- eslint-disable-next-line @intlify/vue-i18n/no-raw-text -->
<span class="text-wp-text-alt-100 <md:hidden">#{{ pipeline.number }}</span>
<!-- eslint-disable-next-line @intlify/vue-i18n/no-raw-text -->
<span class="text-wp-text-alt-100 <md:hidden mx-2">-</span>
<span
class="text-wp-text-100 <md:underline whitespace-nowrap overflow-hidden overflow-ellipsis"
:title="message"
>{{ title }}</span
>
{{ title }}
</span>
</div>
<div
@ -75,7 +78,7 @@ import { pipelineStatusColors } from '~/components/repo/pipeline/pipeline-status
import PipelineRunningIcon from '~/components/repo/pipeline/PipelineRunningIcon.vue';
import PipelineStatusIcon from '~/components/repo/pipeline/PipelineStatusIcon.vue';
import usePipeline from '~/compositions/usePipeline';
import { Pipeline } from '~/lib/api/types';
import type { Pipeline } from '~/lib/api/types';
const props = defineProps<{
pipeline: Pipeline;

View file

@ -18,7 +18,7 @@
<script lang="ts" setup>
import Panel from '~/components/layout/Panel.vue';
import PipelineItem from '~/components/repo/pipeline/PipelineItem.vue';
import { Pipeline } from '~/lib/api/types';
import type { Pipeline } from '~/lib/api/types';
defineProps<{
pipelines: Pipeline[] | undefined;

View file

@ -60,8 +60,9 @@
'bg-opacity-30 bg-blue-600': isSelected(line),
underline: isSelected(line),
}"
>{{ line.number }}</a
>
{{ line.number }}
</a>
<!-- eslint-disable vue/no-v-html -->
<span
class="align-top whitespace-pre-wrap break-words"
@ -80,8 +81,9 @@
'bg-opacity-40 dark:bg-opacity-50 bg-yellow-600 dark:bg-yellow-800': line.type === 'warning',
'bg-opacity-30 bg-blue-600': isSelected(line),
}"
>{{ formatTime(line.time) }}</span
>
{{ formatTime(line.time) }}
</span>
</div>
</div>
@ -110,7 +112,7 @@ import { useStorage } from '@vueuse/core';
import { AnsiUp } from 'ansi_up';
import { decode } from 'js-base64';
import { debounce } from 'lodash';
import { computed, inject, nextTick, onMounted, Ref, ref, toRef, watch } from 'vue';
import { computed, inject, nextTick, onMounted, ref, toRef, watch, type Ref } from 'vue';
import { useI18n } from 'vue-i18n';
import { useRoute } from 'vue-router';
@ -118,16 +120,16 @@ import IconButton from '~/components/atomic/IconButton.vue';
import PipelineStatusIcon from '~/components/repo/pipeline/PipelineStatusIcon.vue';
import useApiClient from '~/compositions/useApiClient';
import useNotifications from '~/compositions/useNotifications';
import { Pipeline, Repo, RepoPermissions } from '~/lib/api/types';
import type { Pipeline, Repo, RepoPermissions } from '~/lib/api/types';
import { findStep, isStepFinished, isStepRunning } from '~/utils/helpers';
type LogLine = {
interface LogLine {
index: number;
number: number;
text: string;
time?: number;
type: 'error' | 'warning' | null;
};
}
const props = defineProps<{
pipeline: Pipeline;
@ -246,7 +248,7 @@ async function download() {
downloadInProgress.value = true;
logs = await apiClient.getLogs(repo.value.id, pipeline.value.number, step.value.id);
} catch (e) {
notifications.notifyError(e, i18n.t('repo.pipeline.log_download_error'));
notifications.notifyError(e as Error, i18n.t('repo.pipeline.log_download_error'));
return;
} finally {
downloadInProgress.value = false;
@ -312,7 +314,7 @@ async function deleteLogs() {
}
// TODO: use proper dialog (copy-pasted from web/src/components/secrets/SecretList.vue:deleteSecret)
// eslint-disable-next-line no-alert, no-restricted-globals
// eslint-disable-next-line no-alert
if (!confirm(i18n.t('repo.pipeline.log_delete_confirm'))) {
return;
}
@ -321,7 +323,7 @@ async function deleteLogs() {
await apiClient.deleteLogs(repo.value.id, pipeline.value.number, step.value.id);
log.value = [];
} catch (e) {
notifications.notifyError(e, i18n.t('repo.pipeline.log_delete_error'));
notifications.notifyError(e as Error, i18n.t('repo.pipeline.log_delete_error'));
}
}

View file

@ -1,7 +1,7 @@
<template>
<div
class="flex items-center justify-center"
:title="$t('repo.pipeline.status.status', { status: $t(`repo.pipeline.status.${status}`) })"
:title="$t('repo.pipeline.status.status', { status: statusDescriptions[status] })"
>
<Icon
:name="service ? 'settings' : `status-${status}`"
@ -18,8 +18,10 @@
</template>
<script lang="ts" setup>
import { useI18n } from 'vue-i18n';
import Icon from '~/components/atomic/Icon.vue';
import { PipelineStatus } from '~/lib/api/types';
import type { PipelineStatus } from '~/lib/api/types';
import { pipelineStatusColors } from './pipeline-status';
@ -27,4 +29,22 @@ defineProps<{
status: PipelineStatus;
service?: boolean;
}>();
const { t } = useI18n();
const statusDescriptions = {
blocked: t('repo.pipeline.status.blocked'),
declined: t('repo.pipeline.status.declined'),
error: t('repo.pipeline.status.error'),
failure: t('repo.pipeline.status.failure'),
killed: t('repo.pipeline.status.killed'),
pending: t('repo.pipeline.status.pending'),
running: t('repo.pipeline.status.running'),
skipped: t('repo.pipeline.status.skipped'),
started: t('repo.pipeline.status.started'),
success: t('repo.pipeline.status.success'),
} satisfies {
// eslint-disable-next-line no-unused-vars
[_ in PipelineStatus]: string;
};
</script>

View file

@ -7,7 +7,7 @@ import { computed, toRef } from 'vue';
import { useDate } from '~/compositions/useDate';
import { useElapsedTime } from '~/compositions/useElapsedTime';
import { PipelineStep, PipelineWorkflow } from '~/lib/api/types';
import type { PipelineStep, PipelineWorkflow } from '~/lib/api/types';
const props = defineProps<{
step?: PipelineStep;

View file

@ -119,7 +119,7 @@
</template>
<script lang="ts" setup>
import { computed, inject, Ref, ref, toRef } from 'vue';
import { computed, inject, ref, toRef, type Ref } from 'vue';
import Badge from '~/components/atomic/Badge.vue';
import Icon from '~/components/atomic/Icon.vue';
@ -127,7 +127,7 @@ import Panel from '~/components/layout/Panel.vue';
import PipelineStatusIcon from '~/components/repo/pipeline/PipelineStatusIcon.vue';
import PipelineStepDuration from '~/components/repo/pipeline/PipelineStepDuration.vue';
import usePipeline from '~/compositions/usePipeline';
import { Pipeline, PipelineConfig, PipelineStep, StepType } from '~/lib/api/types';
import { StepType, type Pipeline, type PipelineConfig, type PipelineStep } from '~/lib/api/types';
const props = defineProps<{
pipeline: Pipeline;

View file

@ -1,4 +1,4 @@
import { PipelineStatus } from '~/lib/api/types';
import type { PipelineStatus } from '~/lib/api/types';
export const pipelineStatusColors: Record<PipelineStatus, 'green' | 'gray' | 'red' | 'blue' | 'orange'> = {
blocked: 'gray',

View file

@ -42,7 +42,7 @@
</template>
<script lang="ts" setup>
import { computed, inject, Ref } from 'vue';
import { computed, inject, type Ref } from 'vue';
import { useI18n } from 'vue-i18n';
import { useRouter } from 'vue-router';
@ -51,7 +51,7 @@ import Settings from '~/components/layout/Settings.vue';
import useApiClient from '~/compositions/useApiClient';
import { useAsyncAction } from '~/compositions/useAsyncAction';
import useNotifications from '~/compositions/useNotifications';
import { Repo } from '~/lib/api/types';
import type { Repo } from '~/lib/api/types';
const apiClient = useApiClient();
const router = useRouter();
@ -75,7 +75,7 @@ const { doSubmit: deleteRepo, isLoading: isDeletingRepo } = useAsyncAction(async
}
// TODO: use proper dialog
// eslint-disable-next-line no-alert, no-restricted-globals
// eslint-disable-next-line no-alert
if (!confirm(i18n.t('repo.settings.actions.delete.confirm'))) {
return;
}

View file

@ -41,16 +41,16 @@
<script lang="ts" setup>
import { useStorage } from '@vueuse/core';
import { computed, inject, onMounted, Ref, ref, watch } from 'vue';
import { computed, inject, onMounted, ref, watch, type Ref } from 'vue';
import { SelectOption } from '~/components/form/form.types';
import type { SelectOption } from '~/components/form/form.types';
import InputField from '~/components/form/InputField.vue';
import SelectField from '~/components/form/SelectField.vue';
import Settings from '~/components/layout/Settings.vue';
import useApiClient from '~/compositions/useApiClient';
import useConfig from '~/compositions/useConfig';
import { usePaginate } from '~/compositions/usePaginate';
import { Repo } from '~/lib/api/types';
import type { Repo } from '~/lib/api/types';
const apiClient = useApiClient();
const repo = inject<Ref<Repo>>('repo');

View file

@ -18,8 +18,9 @@
>
<span>{{ cron.name }}</span>
<span v-if="cron.next_exec && cron.next_exec > 0" class="ml-auto">
{{ $t('repo.settings.crons.next_exec') }}: {{ date.toLocaleString(new Date(cron.next_exec * 1000)) }}</span
>
<!-- eslint-disable-next-line @intlify/vue-i18n/no-raw-text -->
{{ $t('repo.settings.crons.next_exec') }}: {{ date.toLocaleString(new Date(cron.next_exec * 1000)) }}
</span>
<span v-else class="ml-auto">{{ $t('repo.settings.crons.not_executed_yet') }}</span>
<IconButton icon="play" class="ml-auto w-8 h-8" :title="$t('repo.settings.crons.run')" @click="runCron(cron)" />
<IconButton icon="edit" class="w-8 h-8" :title="$t('repo.settings.crons.edit')" @click="selectedCron = cron" />
@ -69,6 +70,7 @@
<div v-if="isEditingCron" class="ml-auto mb-4">
<span v-if="selectedCron.next_exec && selectedCron.next_exec > 0" class="text-wp-text-100">
<!-- eslint-disable-next-line @intlify/vue-i18n/no-raw-text -->
{{ $t('repo.settings.crons.next_exec') }}:
{{ date.toLocaleString(new Date(selectedCron.next_exec * 1000)) }}
</span>
@ -90,7 +92,7 @@
</template>
<script lang="ts" setup>
import { computed, inject, Ref, ref } from 'vue';
import { computed, inject, ref, type Ref } from 'vue';
import { useI18n } from 'vue-i18n';
import Button from '~/components/atomic/Button.vue';
@ -104,7 +106,7 @@ import { useAsyncAction } from '~/compositions/useAsyncAction';
import { useDate } from '~/compositions/useDate';
import useNotifications from '~/compositions/useNotifications';
import { usePagination } from '~/compositions/usePaginate';
import { Cron, Repo } from '~/lib/api/types';
import type { Cron, Repo } from '~/lib/api/types';
import router from '~/router';
const apiClient = useApiClient();
@ -141,7 +143,7 @@ const { doSubmit: createCron, isLoading: isSaving } = useAsyncAction(async () =>
await apiClient.createCron(repo.value.id, selectedCron.value);
}
notifications.notify({
title: i18n.t(isEditingCron.value ? 'repo.settings.crons.saved' : i18n.t('repo.settings.crons.created')),
title: isEditingCron.value ? i18n.t('repo.settings.crons.saved') : i18n.t('repo.settings.crons.created'),
type: 'success',
});
selectedCron.value = undefined;

View file

@ -15,6 +15,7 @@
<template #description>
<i18n-t keypath="repo.settings.general.pipeline_path.desc" tag="p" class="text-sm text-wp-text-alt-100">
<span class="code-box-inline px-1">{{ $t('repo.settings.general.pipeline_path.desc_path_example') }}</span>
<!-- eslint-disable-next-line @intlify/vue-i18n/no-raw-text -->
<span class="code-box-inline px-1">/</span>
</i18n-t>
</template>
@ -97,13 +98,13 @@
</template>
<script lang="ts" setup>
import { inject, onMounted, Ref, ref } from 'vue';
import { inject, onMounted, ref, type Ref } from 'vue';
import { useI18n } from 'vue-i18n';
import Button from '~/components/atomic/Button.vue';
import Checkbox from '~/components/form/Checkbox.vue';
import CheckboxesField from '~/components/form/CheckboxesField.vue';
import { CheckboxOption, RadioOption } from '~/components/form/form.types';
import type { CheckboxOption, RadioOption } from '~/components/form/form.types';
import InputField from '~/components/form/InputField.vue';
import NumberField from '~/components/form/NumberField.vue';
import RadioField from '~/components/form/RadioField.vue';
@ -113,7 +114,7 @@ import useApiClient from '~/compositions/useApiClient';
import { useAsyncAction } from '~/compositions/useAsyncAction';
import useAuthentication from '~/compositions/useAuthentication';
import useNotifications from '~/compositions/useNotifications';
import { Repo, RepoSettings, RepoVisibility, WebhookEvents } from '~/lib/api/types';
import { RepoVisibility, WebhookEvents, type Repo, type RepoSettings } from '~/lib/api/types';
import { useRepoStore } from '~/store/repos';
const apiClient = useApiClient();

View file

@ -75,7 +75,7 @@
</template>
<script lang="ts" setup>
import { computed, inject, Ref, ref } from 'vue';
import { computed, inject, ref, type Ref } from 'vue';
import { useI18n } from 'vue-i18n';
import Button from '~/components/atomic/Button.vue';
@ -88,8 +88,7 @@ import useApiClient from '~/compositions/useApiClient';
import { useAsyncAction } from '~/compositions/useAsyncAction';
import useNotifications from '~/compositions/useNotifications';
import { usePagination } from '~/compositions/usePaginate';
import { Repo } from '~/lib/api/types';
import { Registry } from '~/lib/api/types/registry';
import type { Registry, Repo } from '~/lib/api/types';
const apiClient = useApiClient();
const notifications = useNotifications();
@ -124,9 +123,9 @@ const { doSubmit: createRegistry, isLoading: isSaving } = useAsyncAction(async (
await apiClient.createRegistry(repo.value.id, selectedRegistry.value);
}
notifications.notify({
title: i18n.t(
isEditingRegistry.value ? 'repo.settings.registries.saved' : i18n.t('repo.settings.registries.created'),
),
title: isEditingRegistry.value
? i18n.t('repo.settings.registries.saved')
: i18n.t('repo.settings.registries.created'),
type: 'success',
});
selectedRegistry.value = undefined;

View file

@ -25,7 +25,7 @@
<script lang="ts" setup>
import { cloneDeep } from 'lodash';
import { computed, inject, Ref, ref } from 'vue';
import { computed, inject, ref, type Ref } from 'vue';
import { useI18n } from 'vue-i18n';
import Button from '~/components/atomic/Button.vue';
@ -36,7 +36,7 @@ import useApiClient from '~/compositions/useApiClient';
import { useAsyncAction } from '~/compositions/useAsyncAction';
import useNotifications from '~/compositions/useNotifications';
import { usePagination } from '~/compositions/usePaginate';
import { Repo, Secret, WebhookEvents } from '~/lib/api/types';
import { WebhookEvents, type Repo, type Secret } from '~/lib/api/types';
const emptySecret: Partial<Secret> = {
name: '',
@ -77,9 +77,7 @@ const { resetPage, data: _secrets } = usePagination(loadSecrets, () => !selected
const secrets = computed(() => {
const secretsList: Record<string, Secret & { edit?: boolean; level: 'repo' | 'org' | 'global' }> = {};
// eslint-disable-next-line no-restricted-syntax
for (const level of ['repo', 'org', 'global']) {
// eslint-disable-next-line no-restricted-syntax
for (const secret of _secrets.value) {
if (
((level === 'repo' && secret.repo_id !== 0 && secret.org_id === 0) ||
@ -118,7 +116,7 @@ const { doSubmit: createSecret, isLoading: isSaving } = useAsyncAction(async ()
await apiClient.createSecret(repo.value.id, selectedSecret.value);
}
notifications.notify({
title: i18n.t(isEditingSecret.value ? 'secrets.saved' : 'secrets.created'),
title: isEditingSecret.value ? i18n.t('secrets.saved') : i18n.t('secrets.created'),
type: 'success',
});
selectedSecret.value = undefined;

View file

@ -59,10 +59,10 @@ import { useI18n } from 'vue-i18n';
import Button from '~/components/atomic/Button.vue';
import CheckboxesField from '~/components/form/CheckboxesField.vue';
import { CheckboxOption } from '~/components/form/form.types';
import type { CheckboxOption } from '~/components/form/form.types';
import InputField from '~/components/form/InputField.vue';
import TextField from '~/components/form/TextField.vue';
import { Secret, WebhookEvents } from '~/lib/api/types';
import { WebhookEvents, type Secret } from '~/lib/api/types';
const props = defineProps<{
modelValue: Partial<Secret>;

View file

@ -42,7 +42,7 @@ import { useI18n } from 'vue-i18n';
import Badge from '~/components/atomic/Badge.vue';
import IconButton from '~/components/atomic/IconButton.vue';
import ListItem from '~/components/atomic/ListItem.vue';
import { Secret } from '~/lib/api/types';
import type { Secret } from '~/lib/api/types';
const props = defineProps<{
modelValue: (Secret & { edit?: boolean })[];
@ -64,8 +64,8 @@ function editSecret(secret: Secret) {
function deleteSecret(secret: Secret) {
// TODO: use proper dialog
// eslint-disable-next-line no-alert, no-restricted-globals
if (!confirm(i18n.t('repo.settings.secrets.delete_confirm'))) {
// eslint-disable-next-line no-alert
if (!confirm(i18n.t('secrets.delete_confirm'))) {
return;
}
emit('delete', secret);

View file

@ -23,8 +23,9 @@
:href="`${address}/swagger/index.html`"
target="_blank"
class="ml-4 text-wp-link-100 hover:text-wp-link-200"
>{{ $t('user.settings.cli_and_api.swagger_ui') }}</a
>
{{ $t('user.settings.cli_and_api.swagger_ui') }}
</a>
</template>
<pre class="code-box">{{ usageWithCurl }}</pre>
</InputField>

View file

@ -4,7 +4,7 @@
<div class="ml-2">
<h1 class="text-xl text-wp-text-100">{{ $t('secrets.secrets') }}</h1>
<p class="text-sm text-wp-text-alt-100">
{{ $t('secrets.desc') }}
{{ $t('user.settings.secrets.desc') }}
<DocsLink :topic="$t('secrets.secrets')" url="docs/usage/secrets" />
</p>
</div>
@ -51,7 +51,7 @@ import { useAsyncAction } from '~/compositions/useAsyncAction';
import useAuthentication from '~/compositions/useAuthentication';
import useNotifications from '~/compositions/useNotifications';
import { usePagination } from '~/compositions/usePaginate';
import { Secret, WebhookEvents } from '~/lib/api/types';
import { WebhookEvents, type Secret } from '~/lib/api/types';
const emptySecret: Partial<Secret> = {
name: '',
@ -92,7 +92,7 @@ const { doSubmit: createSecret, isLoading: isSaving } = useAsyncAction(async ()
await apiClient.createOrgSecret(user.org_id, selectedSecret.value);
}
notifications.notify({
title: i18n.t(isEditingSecret.value ? 'user.settings.secrets.saved' : 'user.settings.secrets.created'),
title: isEditingSecret.value ? i18n.t('secrets.saved') : i18n.t('secrets.created'),
type: 'success',
});
selectedSecret.value = undefined;
@ -101,7 +101,7 @@ const { doSubmit: createSecret, isLoading: isSaving } = useAsyncAction(async ()
const { doSubmit: deleteSecret, isLoading: isDeleting } = useAsyncAction(async (_secret: Secret) => {
await apiClient.deleteOrgSecret(user.org_id, _secret.name);
notifications.notify({ title: i18n.t('user.settings.secrets.deleted'), type: 'success' });
notifications.notify({ title: i18n.t('secrets.deleted'), type: 'success' });
resetPage();
});

View file

@ -9,7 +9,7 @@ export default (): WoodpeckerClient => {
const config = useConfig();
const server = config.rootPath;
const token = null;
const csrf = config.csrf || null;
const csrf = config.csrf ?? null;
apiClient = new WoodpeckerClient(server, token, csrf);
}

View file

@ -4,14 +4,7 @@ import useNotifications from '~/compositions/useNotifications';
const notifications = useNotifications();
export type UseSubmitOptions = {
showErrorNotification: false;
};
export function useAsyncAction<T extends unknown[]>(
action: (...a: T) => void | Promise<void>,
options?: UseSubmitOptions,
) {
export function useAsyncAction<T extends unknown[]>(action: (...a: T) => void | Promise<void>) {
const isLoading = ref(false);
async function doSubmit(...a: T) {
@ -23,10 +16,8 @@ export function useAsyncAction<T extends unknown[]>(
try {
await action(...a);
} catch (error) {
if (options?.showErrorNotification) {
notifications.notify({ title: (error as Error).message, type: 'error' });
}
}
isLoading.value = false;
}

View file

@ -8,7 +8,7 @@ export default () =>
user: useConfig().user,
authenticate(url?: string) {
if (url) {
if (url !== undefined) {
const config = useUserConfig();
config.setUserConfig('redirectUrl', url);
}

View file

@ -1,4 +1,4 @@
import { User } from '~/lib/api/types';
import type { User } from '~/lib/api/types';
declare global {
interface Window {
@ -13,11 +13,11 @@ declare global {
}
export default () => ({
user: window.WOODPECKER_USER || null,
user: window.WOODPECKER_USER ?? null,
version: window.WOODPECKER_VERSION,
skipVersionCheck: window.WOODPECKER_SKIP_VERSION_CHECK || false,
csrf: window.WOODPECKER_CSRF || null,
forge: window.WOODPECKER_FORGE || null,
rootPath: window.WOODPECKER_ROOT_PATH || '',
enableSwagger: window.WOODPECKER_ENABLE_SWAGGER || false,
skipVersionCheck: window.WOODPECKER_SKIP_VERSION_CHECK === true || false,
csrf: window.WOODPECKER_CSRF ?? null,
forge: window.WOODPECKER_FORGE ?? null,
rootPath: window.WOODPECKER_ROOT_PATH ?? '',
enableSwagger: window.WOODPECKER_ENABLE_SWAGGER === true || false,
});

View file

@ -29,7 +29,7 @@ export function useDate() {
async function setDayjsLocale(locale: string) {
if (!addedLocales.includes(locale)) {
const l = await import(`~/assets/dayjsLocales/${locale}.js`);
const l = (await import(`~/assets/dayjsLocales/${locale}.js`)) as { default: string };
dayjs.locale(l.default);
} else {
dayjs.locale(locale);

View file

@ -1,4 +1,4 @@
import { computed, onBeforeUnmount, onMounted, Ref, ref, watch } from 'vue';
import { computed, onBeforeUnmount, onMounted, ref, watch, type Ref } from 'vue';
export function useElapsedTime(running: Ref<boolean>, startTime: Ref<number | undefined>) {
const time = ref<number | undefined>(startTime.value);

View file

@ -2,7 +2,7 @@ import { computed, ref, watch } from 'vue';
import useConfig from '~/compositions/useConfig';
import { useTheme } from '~/compositions/useTheme';
import { PipelineStatus } from '~/lib/api/types';
import type { PipelineStatus } from '~/lib/api/types';
const { theme } = useTheme();
const darkMode = computed(() => theme.value);

View file

@ -16,9 +16,9 @@ export const i18n = createI18n({
});
const loadLocaleMessages = async (locale: string) => {
const { default: messages } = await import(`~/assets/locales/${locale}.json`);
const messages = (await import(`~/assets/locales/${locale}.json`)) as { default: any };
i18n.global.setLocaleMessage(locale, messages);
i18n.global.setLocaleMessage(locale, messages.default);
return nextTick();
};
@ -31,6 +31,6 @@ export const setI18nLanguage = async (lang: string): Promise<void> => {
await setDayjsLocale(lang);
};
loadLocaleMessages(fallbackLocale);
loadLocaleMessages(userLanguage);
setDayjsLocale(userLanguage);
loadLocaleMessages(fallbackLocale).catch(console.error);
loadLocaleMessages(userLanguage).catch(console.error);
setDayjsLocale(userLanguage).catch(console.error);

View file

@ -1,12 +1,13 @@
import { inject as vueInject, InjectionKey, provide as vueProvide, Ref } from 'vue';
import type { InjectionKey, Ref } from 'vue';
import { inject as vueInject, provide as vueProvide } from 'vue';
import { Org, OrgPermissions, Repo } from '~/lib/api/types';
import type { Org, OrgPermissions, Repo } from '~/lib/api/types';
export type InjectKeys = {
export interface InjectKeys {
repo: Ref<Repo>;
org: Ref<Org | undefined>;
'org-permissions': Ref<OrgPermissions | undefined>;
};
}
export function inject<T extends keyof InjectKeys>(key: T): InjectKeys[T] {
const value = vueInject<InjectKeys[T]>(key);

View file

@ -1,13 +1,12 @@
import Notifications, { NotificationsOptions, notify } from '@kyvg/vue3-notification';
import Notifications, { notify, type NotificationsOptions } from '@kyvg/vue3-notification';
export const notifications = Notifications;
function notifyError(err: unknown, args: NotificationsOptions | string = {}): void {
// eslint-disable-next-line no-console
function notifyError(err: Error, args: NotificationsOptions | string = {}): void {
console.error(err);
const mArgs = typeof args === 'string' ? { title: args } : args;
const title = mArgs?.title || (err as Error)?.message || `${err}`;
const title = mArgs?.title ?? err?.message ?? err?.toString();
notify({ type: 'error', ...mArgs, title });
}

View file

@ -1,6 +1,6 @@
import { shallowMount } from '@vue/test-utils';
import { describe, expect, it } from 'vitest';
import { type Ref, watch } from 'vue';
import { watch, type Ref } from 'vue';
import { usePagination } from './usePaginate';
@ -18,11 +18,11 @@ async function waitForState<T>(ref: Ref<T>, expected: T): Promise<void> {
});
}
// eslint-disable-next-line promise/prefer-await-to-callbacks
// TODO enable again with eslint-plugin-promise eslint-disable-next-line promise/prefer-await-to-callbacks
export const mountComposition = (cb: () => void) => {
const wrapper = shallowMount({
setup() {
// eslint-disable-next-line promise/prefer-await-to-callbacks
// TODO enable again with eslint-plugin-promise eslint-disable-next-line promise/prefer-await-to-callbacks
cb();
return {};
},
@ -119,7 +119,7 @@ describe('usePaginate', () => {
usePaginationComposition.nextPage();
await waitForState(usePaginationComposition.loading, false);
usePaginationComposition.resetPage();
void usePaginationComposition.resetPage();
await waitForState(usePaginationComposition.loading, false);
expect(usePaginationComposition.data.value.length).toBe(3);

View file

@ -1,12 +1,11 @@
import { useInfiniteScroll } from '@vueuse/core';
import { onMounted, Ref, ref, UnwrapRef, watch } from 'vue';
import { onMounted, ref, watch, type Ref, type UnwrapRef } from 'vue';
export async function usePaginate<T>(getSingle: (page: number) => Promise<T[]>): Promise<T[]> {
let hasMore = true;
let page = 1;
const result: T[] = [];
while (hasMore) {
// eslint-disable-next-line no-await-in-loop
const singleRes = await getSingle(page);
result.push(...singleRes);
hasMore = singleRes.length !== 0;

View file

@ -1,9 +1,9 @@
import { computed, Ref } from 'vue';
import { computed, type Ref } from 'vue';
import { useI18n } from 'vue-i18n';
import { useDate } from '~/compositions/useDate';
import { useElapsedTime } from '~/compositions/useElapsedTime';
import { Pipeline } from '~/lib/api/types';
import type { Pipeline } from '~/lib/api/types';
import { convertEmojis } from '~/utils/emoji';
const { toLocaleString, timeAgo, prettyDuration } = useDate();

View file

@ -5,20 +5,20 @@ import { usePipelineStore } from '~/store/pipelines';
import useAuthentication from './useAuthentication';
const { userConfig, setUserConfig } = useUserConfig();
const userConfig = useUserConfig();
export default () => {
const pipelineStore = usePipelineStore();
const { isAuthenticated } = useAuthentication();
const isOpen = computed(() => userConfig.value.isPipelineFeedOpen && !!isAuthenticated);
const isOpen = computed(() => userConfig.userConfig.value.isPipelineFeedOpen && !!isAuthenticated);
function toggle() {
setUserConfig('isPipelineFeedOpen', !userConfig.value.isPipelineFeedOpen);
userConfig.setUserConfig('isPipelineFeedOpen', !userConfig.userConfig.value.isPipelineFeedOpen);
}
function close() {
setUserConfig('isPipelineFeedOpen', false);
userConfig.setUserConfig('isPipelineFeedOpen', false);
}
const sortedPipelines = toRef(pipelineStore, 'pipelineFeed');

View file

@ -1,7 +1,7 @@
import Fuse from 'fuse.js';
import { computed, Ref } from 'vue';
import { computed, type Ref } from 'vue';
import { Repo } from '~/lib/api/types';
import type { Repo } from '~/lib/api/types';
/*
* Compares Repos lexicographically using owner/name .
@ -9,7 +9,7 @@ import { Repo } from '~/lib/api/types';
function repoCompare(a: Repo, b: Repo) {
const x = `${a.owner}/${a.name}`;
const y = `${b.owner}/${b.name}`;
// eslint-disable-next-line no-nested-ternary
return x === y ? 0 : x > y ? 1 : -1;
}

View file

@ -1,4 +1,4 @@
import { RouteLocationRaw, useRouter } from 'vue-router';
import { useRouter, type RouteLocationRaw } from 'vue-router';
export function useRouteBack(to: RouteLocationRaw) {
const router = useRouter();

View file

@ -1,12 +1,12 @@
import { inject, onMounted, provide, Ref, ref } from 'vue';
import { inject, onMounted, provide, ref, type Ref } from 'vue';
import { useRoute } from 'vue-router';
export type Tab = {
export interface Tab {
id: string;
title: string;
icon?: string;
iconClass?: string;
};
}
export function useTabsProvider({
activeTab,
@ -29,7 +29,7 @@ export function useTabsProvider({
}
const hashTab = route.hash.replace(/^#/, '');
// eslint-disable-next-line no-param-reassign
activeTab.value = hashTab || tabs.value[0].id;
});
}

View file

@ -2,10 +2,10 @@ import { computed, ref } from 'vue';
const USER_CONFIG_KEY = 'woodpecker-user-config';
type UserConfig = {
interface UserConfig {
isPipelineFeedOpen: boolean;
redirectUrl: string;
};
}
const defaultUserConfig: UserConfig = {
isPipelineFeedOpen: false,
@ -14,11 +14,11 @@ const defaultUserConfig: UserConfig = {
function loadUserConfig(): UserConfig {
const lsData = localStorage.getItem(USER_CONFIG_KEY);
if (!lsData) {
if (lsData === null) {
return defaultUserConfig;
}
return JSON.parse(lsData);
return JSON.parse(lsData) as UserConfig;
}
const config = ref<UserConfig>(loadUserConfig());

View file

@ -5,11 +5,11 @@ import { onMounted, ref } from 'vue';
import useAuthentication from './useAuthentication';
import useConfig from './useConfig';
type VersionInfo = {
interface VersionInfo {
latest: string;
rc: string;
next: string;
};
}
const version = ref<{
latest: string | undefined;
@ -22,10 +22,9 @@ const version = ref<{
async function fetchVersion(): Promise<VersionInfo | undefined> {
try {
const resp = await fetch('https://woodpecker-ci.org/version.json');
const json = await resp.json();
const json = (await resp.json()) as VersionInfo;
return json;
} catch (error) {
// eslint-disable-next-line no-console
console.error('Failed to fetch version info', error);
return undefined;
}
@ -45,7 +44,7 @@ export function useVersion() {
const usesNext = current.startsWith('next');
const { user } = useAuthentication();
if (config.skipVersionCheck || !user?.admin) {
if (config.skipVersionCheck || user?.admin !== true) {
version.value = {
latest: undefined,
current,

View file

@ -1,27 +1,28 @@
export type ApiError = {
export interface ApiError {
status: number;
message: string;
};
}
export function encodeQueryString(_params: Record<string, string | number | boolean | undefined> = {}): string {
const params: Record<string, string | number | boolean> = {};
type QueryParams = Record<string, string | number | boolean>;
Object.keys(_params).forEach((key) => {
const val = _params[key];
export function encodeQueryString(_params: unknown = {}): string {
const __params = _params as QueryParams;
const params: QueryParams = {};
Object.keys(__params).forEach((key) => {
const val = __params[key];
if (val !== undefined) {
params[key] = val;
}
});
return params
? Object.keys(params)
return Object.keys(params)
.sort()
.map((key) => {
const val = params[key];
return `${encodeURIComponent(key)}=${encodeURIComponent(val)}`;
})
.join('&')
: '';
.join('&');
}
export default class ApiClient {
@ -43,11 +44,11 @@ export default class ApiClient {
const res = await fetch(`${this.server}${path}`, {
method,
headers: {
...(method !== 'GET' && this.csrf ? { 'X-CSRF-TOKEN': this.csrf } : {}),
...(this.token ? { Authorization: `Bearer ${this.token}` } : {}),
...(data ? { 'Content-Type': 'application/json' } : {}),
...(method !== 'GET' && this.csrf !== null ? { 'X-CSRF-TOKEN': this.csrf } : {}),
...(this.token !== null ? { Authorization: `Bearer ${this.token}` } : {}),
...(data !== undefined ? { 'Content-Type': 'application/json' } : {}),
},
body: data ? JSON.stringify(data) : undefined,
body: data !== undefined ? JSON.stringify(data) : undefined,
});
if (!res.ok) {
@ -62,7 +63,7 @@ export default class ApiClient {
}
const contentType = res.headers.get('Content-Type');
if (contentType && contentType.startsWith('application/json')) {
if (contentType !== null && contentType.startsWith('application/json')) {
return res.json();
}
@ -87,15 +88,15 @@ export default class ApiClient {
_subscribe<T>(path: string, callback: (data: T) => void, opts = { reconnect: true }) {
const query = encodeQueryString({
access_token: this.token || undefined,
access_token: this.token ?? undefined,
});
let _path = this.server ? this.server + path : path;
_path = this.token ? `${_path}?${query}` : _path;
_path = this.token !== null ? `${_path}?${query}` : _path;
const events = new EventSource(_path);
events.onmessage = (event) => {
const data = JSON.parse(event.data) as T;
// eslint-disable-next-line promise/prefer-await-to-callbacks
const data = JSON.parse(event.data as string) as T;
// TODO enable again with eslint-plugin-promise eslint-disable-next-line promise/prefer-await-to-callbacks
callback(data);
};

View file

@ -1,5 +1,5 @@
import ApiClient, { encodeQueryString } from './client';
import {
import type {
Agent,
Cron,
Org,
@ -18,27 +18,27 @@ import {
User,
} from './types';
type RepoListOptions = {
interface RepoListOptions {
all?: boolean;
};
}
// PipelineOptions is the data for creating a new pipeline
type PipelineOptions = {
interface PipelineOptions {
branch: string;
variables: Record<string, string>;
};
}
type DeploymentOptions = {
interface DeploymentOptions {
id: string;
environment: string;
task: string;
variables: Record<string, string>;
};
}
type PaginationOptions = {
interface PaginationOptions {
page?: number;
perPage?: number;
};
}
export default class WoodpeckerClient extends ApiClient {
getRepoList(opts?: RepoListOptions): Promise<Repo[]> {
@ -336,10 +336,10 @@ export default class WoodpeckerClient extends ApiClient {
}
repairAllRepos(): Promise<unknown> {
return this._post(`/api/repos/repair`) as Promise<unknown>;
return this._post(`/api/repos/repair`);
}
// eslint-disable-next-line promise/prefer-await-to-callbacks
// TODO enable again with eslint-plugin-promise eslint-disable-next-line promise/prefer-await-to-callbacks
on(callback: (data: { pipeline?: Pipeline; repo?: Repo }) => void): EventSource {
return this._subscribe('/api/stream/events', callback, {
reconnect: true,
@ -350,7 +350,7 @@ export default class WoodpeckerClient extends ApiClient {
repoId: number,
pipeline: number,
step: number,
// eslint-disable-next-line promise/prefer-await-to-callbacks
// TODO enable again with eslint-plugin-promise eslint-disable-next-line promise/prefer-await-to-callbacks
callback: (data: PipelineLog) => void,
): EventSource {
return this._subscribe(`/api/stream/logs/${repoId}/${pipeline}/${step}`, callback, {

View file

@ -1,4 +1,4 @@
export type Agent = {
export interface Agent {
id: number;
name: string;
token: string;
@ -10,4 +10,4 @@ export type Agent = {
capacity: number;
version: string;
no_schedule: boolean;
};
}

View file

@ -1,7 +1,7 @@
export type Cron = {
export interface Cron {
id: number;
name: string;
branch: string;
schedule: string;
next_exec: number;
};
}

View file

@ -1,12 +1,12 @@
// A version control organization.
export type Org = {
export interface Org {
// The name of the organization.
id: number;
name: string;
is_user: boolean;
};
}
export type OrgPermissions = {
export interface OrgPermissions {
member: boolean;
admin: boolean;
};
}

View file

@ -1,14 +1,14 @@
import { WebhookEvents } from './webhook';
import type { WebhookEvents } from './webhook';
export type PipelineError<D = unknown> = {
export interface PipelineError<D = unknown> {
type: string;
message: string;
data?: D;
is_warning: boolean;
};
}
// A pipeline for a repository.
export type Pipeline = {
export interface Pipeline {
id: number;
// The pipeline number.
@ -89,7 +89,7 @@ export type Pipeline = {
workflows?: PipelineWorkflow[];
changed_files?: string[];
};
}
export type PipelineStatus =
| 'blocked'
@ -103,7 +103,7 @@ export type PipelineStatus =
| 'started'
| 'success';
export type PipelineWorkflow = {
export interface PipelineWorkflow {
id: number;
pipeline_id: number;
pid: number;
@ -115,9 +115,9 @@ export type PipelineWorkflow = {
agent_id?: number;
error?: string;
children: PipelineStep[];
};
}
export type PipelineStep = {
export interface PipelineStep {
id: number;
uuid: string;
pipeline_id: number;
@ -130,21 +130,22 @@ export type PipelineStep = {
end_time?: number;
error?: string;
type?: StepType;
};
}
export type PipelineLog = {
export interface PipelineLog {
id: number;
step_id: number;
time: number;
line: number;
data: string; // base64 encoded
type: number;
};
}
export type PipelineFeed = Pipeline & {
repo_id: number;
};
/* eslint-disable no-unused-vars */
export enum StepType {
Clone = 'clone',
Service = 'service',
@ -152,3 +153,4 @@ export enum StepType {
Commands = 'commands',
Cache = 'cache',
}
/* eslint-enable */

View file

@ -1,6 +1,6 @@
// A config for a pipeline.
export type PipelineConfig = {
export interface PipelineConfig {
hash: string;
name: string;
data: string;
};
}

View file

@ -1,7 +1,7 @@
// A version control pull request.
export type PullRequest = {
export interface PullRequest {
// The index of the pull request.
index: string;
// The title of the pull request.
title: string;
};
}

View file

@ -1,4 +1,4 @@
export type Task = {
export interface Task {
id: number;
data: string;
labels: { [key: string]: string };
@ -6,20 +6,20 @@ export type Task = {
dep_status: { [key: string]: string };
run_on: string[];
agent_id: number;
};
}
export type QueueStats = {
export interface QueueStats {
worker_count: number;
pending_count: number;
waiting_on_deps_count: number;
running_count: number;
completed_count: number;
};
}
export type QueueInfo = {
export interface QueueInfo {
pending: Task[];
waiting_on_deps: Task[];
running: Task[];
stats: QueueStats;
paused: boolean;
};
}

View file

@ -1,6 +1,6 @@
export type Registry = {
export interface Registry {
id: string;
address: string;
username: string;
password: string;
};
}

View file

@ -1,5 +1,5 @@
// A version control repository.
export type Repo = {
export interface Repo {
// Is the repo currently active or not
active: boolean;
@ -70,13 +70,15 @@ export type Repo = {
cancel_previous_pipeline_events: string[];
netrc_only_trusted: boolean;
};
}
/* eslint-disable no-unused-vars */
export enum RepoVisibility {
Public = 'public',
Private = 'private',
Internal = 'internal',
}
/* eslint-enable */
export type RepoSettings = Pick<
Repo,
@ -91,9 +93,9 @@ export type RepoSettings = Pick<
| 'netrc_only_trusted'
>;
export type RepoPermissions = {
export interface RepoPermissions {
pull: boolean;
push: boolean;
admin: boolean;
synced: number;
};
}

View file

@ -1,6 +1,6 @@
import { WebhookEvents } from './webhook';
import type { WebhookEvents } from './webhook';
export type Secret = {
export interface Secret {
id: string;
repo_id: number;
org_id: number;
@ -8,4 +8,4 @@ export type Secret = {
value: string;
events: WebhookEvents[];
images: string[];
};
}

View file

@ -1,5 +1,5 @@
// The user account.
export type User = {
export interface User {
id: number;
// The unique identifier for the account.
@ -20,4 +20,4 @@ export type User = {
org_id: number;
// The ID of the org assigned to the user.
};
}

View file

@ -1,3 +1,4 @@
/* eslint-disable no-unused-vars */
export enum WebhookEvents {
Push = 'push',
Tag = 'tag',
@ -8,3 +9,4 @@ export enum WebhookEvents {
Cron = 'cron',
Manual = 'manual',
}
/* eslint-enable */

View file

@ -11,6 +11,7 @@ import { i18n } from '~/compositions/useI18n';
import { notifications } from '~/compositions/useNotifications';
import router from '~/router';
// eslint-disable-next-line ts/no-unsafe-argument
const app = createApp(App);
app.use(router);

View file

@ -1,5 +1,5 @@
import { Component } from 'vue';
import { createRouter, createWebHistory, RouteRecordRaw } from 'vue-router';
import type { Component } from 'vue';
import { createRouter, createWebHistory, type RouteRecordRaw } from 'vue-router';
import useAuthentication from '~/compositions/useAuthentication';
import useConfig from '~/compositions/useConfig';

View file

@ -1,8 +1,8 @@
import { defineStore } from 'pinia';
import { computed, reactive, Ref, ref } from 'vue';
import { computed, reactive, ref, type Ref } from 'vue';
import useApiClient from '~/compositions/useApiClient';
import { Pipeline, PipelineFeed, PipelineWorkflow } from '~/lib/api/types';
import type { Pipeline, PipelineFeed, PipelineWorkflow } from '~/lib/api/types';
import { useRepoStore } from '~/store/repos';
import { comparePipelines, comparePipelinesWithStatus, isPipelineActive } from '~/utils/helpers';
@ -13,7 +13,7 @@ export const usePipelineStore = defineStore('pipelines', () => {
const pipelines: Map<number, Map<number, Pipeline>> = reactive(new Map());
function setPipeline(repoId: number, pipeline: Pipeline) {
const repoPipelines = pipelines.get(repoId) || new Map();
const repoPipelines = pipelines.get(repoId) || new Map<number, Pipeline>();
repoPipelines.set(pipeline.number, {
...(repoPipelines.get(pipeline.number) || {}),
...pipeline,
@ -27,7 +27,7 @@ export const usePipelineStore = defineStore('pipelines', () => {
function getPipeline(repoId: Ref<number>, _pipelineNumber: Ref<string>) {
return computed(() => {
const pipelineNumber = parseInt(_pipelineNumber.value, 10);
const pipelineNumber = Number.parseInt(_pipelineNumber.value, 10);
return pipelines.get(repoId.value)?.get(pipelineNumber);
});
}

View file

@ -1,8 +1,8 @@
import { defineStore } from 'pinia';
import { computed, reactive, Ref, ref } from 'vue';
import { computed, reactive, ref, type Ref } from 'vue';
import useApiClient from '~/compositions/useApiClient';
import { Repo } from '~/lib/api/types';
import type { Repo } from '~/lib/api/types';
export const useRepoStore = defineStore('repos', () => {
const apiClient = useApiClient();

View file

@ -1,4 +1,4 @@
import { Pipeline, PipelineStep, PipelineWorkflow, Repo } from '~/lib/api/types';
import type { Pipeline, PipelineStep, PipelineWorkflow, Repo } from '~/lib/api/types';
export function findStep(workflows: PipelineWorkflow[], pid: number): PipelineStep | undefined {
return workflows.reduce(
@ -24,20 +24,16 @@ export function findStep(workflows: PipelineWorkflow[], pid: number): PipelineSt
}
/**
* Returns true if the process is in a completed state.
*
* @param {Object} step - The process object.
* @returns {boolean}
* @param {object} step - The process object.
* @returns {boolean} true if the process is in a completed state
*/
export function isStepFinished(step: PipelineStep): boolean {
return step.state !== 'running' && step.state !== 'pending';
}
/**
* Returns true if the process is running.
*
* @param {Object} step - The process object.
* @returns {boolean}
* @param {object} step - The process object.
* @returns {boolean} true if the process is running
*/
export function isStepRunning(step: PipelineStep): boolean {
return step.state === 'running';
@ -45,9 +41,9 @@ export function isStepRunning(step: PipelineStep): boolean {
/**
* Compare two pipelines by creation timestamp.
* @param {Object} a - A pipeline.
* @param {Object} b - A pipeline.
* @returns {number}
* @param {object} a - A pipeline.
* @param {object} b - A pipeline.
* @returns {number} 0 if created at the same time, < 0 if b was create before a, > 0 otherwise
*/
export function comparePipelines(a: Pipeline, b: Pipeline): number {
return (b.created_at || -1) - (a.created_at || -1);
@ -56,9 +52,9 @@ export function comparePipelines(a: Pipeline, b: Pipeline): number {
/**
* Compare two pipelines by the status.
* Giving pending, running, or started higher priority than other status
* @param {Object} a - A pipeline.
* @param {Object} b - A pipeline.
* @returns {number}
* @param {object} a - A pipeline.
* @param {object} b - A pipeline.
* @returns {number} 0 if status same priority, < 0 if b has higher priority, > 0 otherwise
*/
export function comparePipelinesWithStatus(a: Pipeline, b: Pipeline): number {
const bPriority = ['pending', 'running', 'started'].includes(b.status) ? 1 : 0;
@ -70,11 +66,9 @@ export function isPipelineActive(pipeline: Pipeline): boolean {
return ['pending', 'running', 'started'].includes(pipeline.status);
}
export function repoSlug(ownerOrRepo: Repo): string;
export function repoSlug(ownerOrRepo: string, name: string): string;
export function repoSlug(ownerOrRepo: string | Repo, name?: string): string {
if (typeof ownerOrRepo === 'string') {
if (!name) {
if (name === undefined) {
throw new Error('Please provide a name as well');
}

View file

@ -1,10 +1,8 @@
<template>
<div class="flex flex-col h-full w-full items-center justify-center">
<p class="text-2xl mb-8">{{ $t('not_found.not_found') }}</p>
<span
><router-link class="text-blue-400" replace :to="{ name: 'home' }">{{
$t('not_found.back_home')
}}</router-link></span
>
<router-link class="text-blue-400" replace :to="{ name: 'home' }">
{{ $t('not_found.back_home') }}
</router-link>
</div>
</template>

View file

@ -45,7 +45,7 @@ import { useAsyncAction } from '~/compositions/useAsyncAction';
import useNotifications from '~/compositions/useNotifications';
import { useRepoSearch } from '~/compositions/useRepoSearch';
import { useRouteBack } from '~/compositions/useRouteBack';
import { Repo } from '~/lib/api/types';
import type { Repo } from '~/lib/api/types';
const router = useRouter();
const apiClient = useApiClient();

View file

@ -8,13 +8,12 @@ import { useRoute, useRouter } from 'vue-router';
import useApiClient from '~/compositions/useApiClient';
const apiClient = useApiClient();
const route = useRoute();
const router = useRouter();
const props = defineProps<{
orgName: string;
}>();
const apiClient = useApiClient();
const route = useRoute();
const router = useRouter();
onMounted(async () => {
const org = await apiClient.lookupOrg(props.orgName);

View file

@ -4,6 +4,7 @@
<span>
<router-link :to="{ name: 'org' }" class="hover:underline">
{{ org.name }}
<!-- eslint-disable-next-line @intlify/vue-i18n/no-raw-text -->
</router-link>
/
{{ $t('settings') }}

View file

@ -25,13 +25,13 @@ import IconButton from '~/components/atomic/IconButton.vue';
import Scaffold from '~/components/layout/scaffold/Scaffold.vue';
import useApiClient from '~/compositions/useApiClient';
import { provide } from '~/compositions/useInjectProvide';
import { Org, OrgPermissions } from '~/lib/api/types';
import type { Org, OrgPermissions } from '~/lib/api/types';
const props = defineProps<{
orgId: string;
}>();
const orgId = computed(() => parseInt(props.orgId, 10));
const orgId = computed(() => Number.parseInt(props.orgId, 10));
const apiClient = useApiClient();
const org = ref<Org>();

View file

@ -6,10 +6,10 @@
</template>
<script lang="ts" setup>
import { computed, inject, Ref, toRef } from 'vue';
import { computed, inject, toRef, type Ref } from 'vue';
import PipelineList from '~/components/repo/pipeline/PipelineList.vue';
import { Pipeline, Repo, RepoPermissions } from '~/lib/api/types';
import type { Pipeline, Repo, RepoPermissions } from '~/lib/api/types';
const props = defineProps<{
branch: string;

View file

@ -21,13 +21,13 @@
</template>
<script lang="ts" setup>
import { computed, inject, Ref, watch } from 'vue';
import { computed, inject, watch, type Ref } from 'vue';
import Badge from '~/components/atomic/Badge.vue';
import ListItem from '~/components/atomic/ListItem.vue';
import useApiClient from '~/compositions/useApiClient';
import { usePagination } from '~/compositions/usePaginate';
import { Repo } from '~/lib/api/types';
import type { Repo } from '~/lib/api/types';
const apiClient = useApiClient();

View file

@ -8,14 +8,13 @@ import { useRoute, useRouter } from 'vue-router';
import useApiClient from '~/compositions/useApiClient';
const apiClient = useApiClient();
const route = useRoute();
const router = useRouter();
const props = defineProps<{
repoOwner: string;
repoName: string;
}>();
const apiClient = useApiClient();
const route = useRoute();
const router = useRouter();
onMounted(async () => {
const repo = await apiClient.lookupRepo(props.repoOwner, props.repoName);

View file

@ -3,10 +3,10 @@
</template>
<script lang="ts" setup>
import { inject, Ref } from 'vue';
import { inject, type Ref } from 'vue';
import PipelineList from '~/components/repo/pipeline/PipelineList.vue';
import { Pipeline, Repo, RepoPermissions } from '~/lib/api/types';
import type { Pipeline, Repo, RepoPermissions } from '~/lib/api/types';
const repo = inject<Ref<Repo>>('repo');
const repoPermissions = inject<Ref<RepoPermissions>>('repo-permissions');

View file

@ -6,10 +6,10 @@
</template>
<script lang="ts" setup>
import { computed, inject, Ref, toRef } from 'vue';
import { computed, inject, toRef, type Ref } from 'vue';
import PipelineList from '~/components/repo/pipeline/PipelineList.vue';
import { Pipeline, Repo, RepoPermissions } from '~/lib/api/types';
import type { Pipeline, Repo, RepoPermissions } from '~/lib/api/types';
const props = defineProps<{
pullRequest: string;

View file

@ -7,7 +7,9 @@
class="text-wp-text-100"
:to="{ name: 'repo-pull-request', params: { pullRequest: pullRequest.index } }"
>
<!-- eslint-disable-next-line @intlify/vue-i18n/no-raw-text -->
<span class="text-wp-text-alt-100 <md:hidden">#{{ pullRequest.index }}</span>
<!-- eslint-disable-next-line @intlify/vue-i18n/no-raw-text -->
<span class="text-wp-text-alt-100 <md:hidden mx-2">-</span>
<span class="text-wp-text-100 <md:underline whitespace-nowrap overflow-hidden overflow-ellipsis">{{
pullRequest.title
@ -24,14 +26,14 @@
</template>
<script lang="ts" setup>
import { inject, Ref, watch } from 'vue';
import { inject, watch, type Ref } from 'vue';
import Icon from '~/components/atomic/Icon.vue';
import ListItem from '~/components/atomic/ListItem.vue';
import Panel from '~/components/layout/Panel.vue';
import useApiClient from '~/compositions/useApiClient';
import { usePagination } from '~/compositions/usePaginate';
import { PullRequest, Repo } from '~/lib/api/types';
import type { PullRequest, Repo } from '~/lib/api/types';
const apiClient = useApiClient();

View file

@ -4,10 +4,12 @@
<span>
<router-link :to="{ name: 'org', params: { orgId: repo!.org_id } }" class="hover:underline">
{{ repo!.owner }}
<!-- eslint-disable-next-line @intlify/vue-i18n/no-raw-text -->
</router-link>
/
<router-link :to="{ name: 'repo' }" class="hover:underline">
{{ repo!.name }}
<!-- eslint-disable-next-line @intlify/vue-i18n/no-raw-text -->
</router-link>
/
{{ $t('settings') }}
@ -36,7 +38,7 @@
</template>
<script lang="ts" setup>
import { inject, onMounted, Ref } from 'vue';
import { inject, onMounted, type Ref } from 'vue';
import { useI18n } from 'vue-i18n';
import { useRouter } from 'vue-router';
@ -50,7 +52,7 @@ import RegistriesTab from '~/components/repo/settings/RegistriesTab.vue';
import SecretsTab from '~/components/repo/settings/SecretsTab.vue';
import useNotifications from '~/compositions/useNotifications';
import { useRouteBack } from '~/compositions/useRouteBack';
import { Repo, RepoPermissions } from '~/lib/api/types';
import type { Repo, RepoPermissions } from '~/lib/api/types';
const notifications = useNotifications();
const router = useRouter();

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