Use centrally typed inject provide in Vue (#5113)

Co-authored-by: Robert Kaussow <mail@thegeeklab.de>
This commit is contained in:
Lukas 2025-04-23 09:40:33 +02:00 committed by GitHub
parent cc4b362410
commit 208ac771d4
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
31 changed files with 123 additions and 320 deletions

View file

@ -39,7 +39,7 @@ import TextField from '~/components/form/TextField.vue';
import Panel from '~/components/layout/Panel.vue';
import Popup from '~/components/layout/Popup.vue';
import useApiClient from '~/compositions/useApiClient';
import { inject } from '~/compositions/useInjectProvide';
import { requiredInject } from '~/compositions/useInjectProvide';
const props = defineProps<{
open: boolean;
@ -51,7 +51,7 @@ const emit = defineEmits<{
}>();
const apiClient = useApiClient();
const repo = inject('repo');
const repo = requiredInject('repo');
const router = useRouter();
const payload = ref<{

View file

@ -111,16 +111,16 @@ import { useStorage } from '@vueuse/core';
import { AnsiUp } from 'ansi_up';
import { decode } from 'js-base64';
import { debounce } from 'lodash';
import { computed, inject, nextTick, onBeforeUnmount, onMounted, ref, toRef, watch } from 'vue';
import type { Ref } from 'vue';
import { computed, nextTick, onBeforeUnmount, onMounted, ref, toRef, watch } from 'vue';
import { useI18n } from 'vue-i18n';
import { useRoute } from 'vue-router';
import IconButton from '~/components/atomic/IconButton.vue';
import PipelineStatusIcon from '~/components/repo/pipeline/PipelineStatusIcon.vue';
import useApiClient from '~/compositions/useApiClient';
import { requiredInject } from '~/compositions/useInjectProvide';
import useNotifications from '~/compositions/useNotifications';
import type { Pipeline, PipelineStep, PipelineWorkflow, Repo, RepoPermissions } from '~/lib/api/types';
import type { Pipeline, PipelineStep, PipelineWorkflow } from '~/lib/api/types';
interface LogLine {
index: number;
@ -143,8 +143,8 @@ const notifications = useNotifications();
const i18n = useI18n();
const pipeline = toRef(props, 'pipeline');
const stepId = toRef(props, 'stepId');
const repo = inject<Ref<Repo>>('repo');
const repoPermissions = inject<Ref<RepoPermissions>>('repo-permissions');
const repo = requiredInject('repo');
const repoPermissions = requiredInject('repo-permissions');
const apiClient = useApiClient();
const route = useRoute();
@ -286,10 +286,6 @@ async function loadLogs() {
return;
}
if (!repo) {
throw new Error('Unexpected: "repo" should be provided at this place');
}
log.value = undefined;
logBuffer.value = [];
ansiUp.value = new AnsiUp();

View file

@ -118,17 +118,17 @@
</template>
<script lang="ts" setup>
import { computed, inject, nextTick, ref, toRef, useTemplateRef, watch } from 'vue';
import type { Ref } from 'vue';
import { computed, nextTick, ref, toRef, useTemplateRef, watch } from 'vue';
import Badge from '~/components/atomic/Badge.vue';
import Icon from '~/components/atomic/Icon.vue';
import Panel from '~/components/layout/Panel.vue';
import PipelineStatusIcon from '~/components/repo/pipeline/PipelineStatusIcon.vue';
import PipelineStepDuration from '~/components/repo/pipeline/PipelineStepDuration.vue';
import { requiredInject } from '~/compositions/useInjectProvide';
import usePipeline from '~/compositions/usePipeline';
import { StepType } from '~/lib/api/types';
import type { Pipeline, PipelineConfig, PipelineStep } from '~/lib/api/types';
import type { Pipeline, PipelineStep } from '~/lib/api/types';
const props = defineProps<{
pipeline: Pipeline;
@ -142,7 +142,7 @@ defineEmits<{
const pipeline = toRef(props, 'pipeline');
const selectedStepId = toRef(props, 'selectedStepId');
const { prettyRef } = usePipeline(pipeline);
const pipelineConfigs = inject<Ref<PipelineConfig[]>>('pipeline-configs');
const pipelineConfigs = requiredInject('pipeline-configs');
const workflowsCollapsed = ref<Record<PipelineStep['id'], boolean>>(
pipeline.value.workflows && pipeline.value.workflows.length > 1

View file

@ -1,23 +1,25 @@
import type { InjectionKey, Ref } from 'vue';
import { inject as vueInject, provide as vueProvide } from 'vue';
import type { Org, OrgPermissions, Pipeline, PipelineConfig, Repo } from '~/lib/api/types';
import type { Org, OrgPermissions, Pipeline, PipelineConfig, Repo, RepoPermissions } from '~/lib/api/types';
import type { Tab } from './useTabs';
export interface InjectKeys {
repo: Ref<Repo>;
org: Ref<Org | undefined>;
'org-permissions': Ref<OrgPermissions | undefined>;
pipeline: Ref<Pipeline | undefined>;
'repo-permissions': Ref<RepoPermissions>;
org: Ref<Org>;
'org-permissions': Ref<OrgPermissions>;
pipeline: Ref<Pipeline>;
'pipeline-configs': Ref<PipelineConfig[] | undefined>;
tabs: Ref<Tab[]>;
pipelines: Ref<Pipeline[]>;
}
export function inject<T extends keyof InjectKeys>(key: T): InjectKeys[T] {
export function requiredInject<T extends keyof InjectKeys>(key: T): InjectKeys[T] {
const value = vueInject<InjectKeys[T]>(key);
if (value === undefined) {
throw new Error(`Please provide a value for ${key}`);
throw new Error(`Unexpected: ${key} should be provided at this place`);
}
return value;
}

View file

@ -3,7 +3,7 @@ import type { RouteLocationRaw } from 'vue-router';
import type { IconNames } from '~/components/atomic/Icon.vue';
import { inject, provide } from './useInjectProvide';
import { provide, requiredInject } from './useInjectProvide';
export interface Tab {
to: RouteLocationRaw;
@ -20,6 +20,6 @@ export function useTabsProvider() {
}
export function useTabsClient() {
const tabs = inject('tabs');
const tabs = requiredInject('tabs');
return { tabs };
}

View file

@ -28,7 +28,7 @@ import { computed, onMounted, ref } from 'vue';
import IconButton from '~/components/atomic/IconButton.vue';
import Scaffold from '~/components/layout/scaffold/Scaffold.vue';
import RepoItem from '~/components/repo/RepoItem.vue';
import { inject } from '~/compositions/useInjectProvide';
import { requiredInject } from '~/compositions/useInjectProvide';
import useRepos from '~/compositions/useRepos';
import { useRepoSearch } from '~/compositions/useRepoSearch';
import { useRepoStore } from '~/store/repos';
@ -36,8 +36,8 @@ import { useRepoStore } from '~/store/repos';
const repoStore = useRepoStore();
const { repoWithLastPipeline, sortReposByLastActivity } = useRepos();
const org = inject('org');
const orgPermissions = inject('org-permissions');
const org = requiredInject('org');
const orgPermissions = requiredInject('org-permissions');
const search = ref('');
const repos = computed(() =>

View file

@ -19,6 +19,7 @@
</template>
<script lang="ts" setup>
import type { Ref } from 'vue';
import { computed, onMounted, ref, watch } from 'vue';
import { useRoute } from 'vue-router';
@ -38,8 +39,8 @@ const route = useRoute();
const org = ref<Org>();
const orgPermissions = ref<OrgPermissions>();
provide('org', org);
provide('org-permissions', orgPermissions);
provide('org', org as Ref<Org>); // can't be undefined because of v-if in template
provide('org-permissions', orgPermissions as Ref<OrgPermissions>); // can't be undefined because of v-if in template
async function load() {
org.value = await apiClient.getOrg(orgId.value);

View file

@ -9,18 +9,13 @@
</template>
<script lang="ts" setup>
import { inject } from 'vue';
import type { Ref } from 'vue';
import AgentManager from '~/components/agent/AgentManager.vue';
import useApiClient from '~/compositions/useApiClient';
import type { Agent, Org } from '~/lib/api/types';
import { requiredInject } from '~/compositions/useInjectProvide';
import type { Agent } from '~/lib/api/types';
const apiClient = useApiClient();
const org = inject<Ref<Org>>('org');
if (org === undefined) {
throw new Error('Unexpected: "org" should be provided at this place');
}
const org = requiredInject('org');
const loadAgents = (page: number) => apiClient.getOrgAgents(org.value.id, { page });
const createAgent = (agent: Partial<Agent>) => apiClient.createOrgAgent(org.value.id, agent);

View file

@ -34,8 +34,7 @@
<script lang="ts" setup>
import { cloneDeep } from 'lodash';
import { computed, inject, ref } from 'vue';
import type { Ref } from 'vue';
import { computed, ref } from 'vue';
import { useI18n } from 'vue-i18n';
import Button from '~/components/atomic/Button.vue';
@ -44,9 +43,10 @@ import RegistryEdit from '~/components/registry/RegistryEdit.vue';
import RegistryList from '~/components/registry/RegistryList.vue';
import useApiClient from '~/compositions/useApiClient';
import { useAsyncAction } from '~/compositions/useAsyncAction';
import { requiredInject } from '~/compositions/useInjectProvide';
import useNotifications from '~/compositions/useNotifications';
import { usePagination } from '~/compositions/usePaginate';
import type { Org, Registry } from '~/lib/api/types';
import type { Registry } from '~/lib/api/types';
const emptyRegistry: Partial<Registry> = {
address: '',
@ -58,25 +58,17 @@ const apiClient = useApiClient();
const notifications = useNotifications();
const i18n = useI18n();
const org = inject<Ref<Org>>('org');
const org = requiredInject('org');
const selectedRegistry = ref<Partial<Registry>>();
const isEditing = computed(() => !!selectedRegistry.value?.id);
async function loadRegistries(page: number): Promise<Registry[] | null> {
if (!org?.value) {
throw new Error("Unexpected: Can't load org");
}
return apiClient.getOrgRegistryList(org.value.id, { page });
}
const { resetPage, data: registries } = usePagination(loadRegistries, () => !selectedRegistry.value);
const { doSubmit: createRegistry, isLoading: isSaving } = useAsyncAction(async () => {
if (!org?.value) {
throw new Error("Unexpected: Can't load org");
}
if (!selectedRegistry.value) {
throw new Error("Unexpected: Can't get registry");
}
@ -95,10 +87,6 @@ const { doSubmit: createRegistry, isLoading: isSaving } = useAsyncAction(async (
});
const { doSubmit: deleteRegistry, isLoading: isDeleting } = useAsyncAction(async (_registry: Registry) => {
if (!org?.value) {
throw new Error("Unexpected: Can't load org");
}
await apiClient.deleteOrgRegistry(org.value.id, _registry.address);
notifications.notify({ title: i18n.t('registries.deleted'), type: 'success' });
resetPage();

View file

@ -25,8 +25,7 @@
<script lang="ts" setup>
import { cloneDeep } from 'lodash';
import { computed, inject, ref } from 'vue';
import type { Ref } from 'vue';
import { computed, ref } from 'vue';
import { useI18n } from 'vue-i18n';
import Button from '~/components/atomic/Button.vue';
@ -35,10 +34,11 @@ import SecretEdit from '~/components/secrets/SecretEdit.vue';
import SecretList from '~/components/secrets/SecretList.vue';
import useApiClient from '~/compositions/useApiClient';
import { useAsyncAction } from '~/compositions/useAsyncAction';
import { requiredInject } from '~/compositions/useInjectProvide';
import useNotifications from '~/compositions/useNotifications';
import { usePagination } from '~/compositions/usePaginate';
import { WebhookEvents } from '~/lib/api/types';
import type { Org, Secret } from '~/lib/api/types';
import type { Secret } from '~/lib/api/types';
const emptySecret: Partial<Secret> = {
name: '',
@ -51,25 +51,17 @@ const apiClient = useApiClient();
const notifications = useNotifications();
const i18n = useI18n();
const org = inject<Ref<Org>>('org');
const org = requiredInject('org');
const selectedSecret = ref<Partial<Secret>>();
const isEditingSecret = computed(() => !!selectedSecret.value?.id);
async function loadSecrets(page: number): Promise<Secret[] | null> {
if (!org?.value) {
throw new Error("Unexpected: Can't load org");
}
return apiClient.getOrgSecretList(org.value.id, { page });
}
const { resetPage, data: secrets } = usePagination(loadSecrets, () => !selectedSecret.value);
const { doSubmit: createSecret, isLoading: isSaving } = useAsyncAction(async () => {
if (!org?.value) {
throw new Error("Unexpected: Can't load org");
}
if (!selectedSecret.value) {
throw new Error("Unexpected: Can't get secret");
}
@ -88,10 +80,6 @@ const { doSubmit: createSecret, isLoading: isSaving } = useAsyncAction(async ()
});
const { doSubmit: deleteSecret, isLoading: isDeleting } = useAsyncAction(async (_secret: Secret) => {
if (!org?.value) {
throw new Error("Unexpected: Can't load org");
}
await apiClient.deleteOrgSecret(org.value.id, _secret.name);
notifications.notify({ title: i18n.t('secrets.deleted'), type: 'success' });
resetPage();

View file

@ -32,7 +32,7 @@ import { useRouter } from 'vue-router';
import Scaffold from '~/components/layout/scaffold/Scaffold.vue';
import Tab from '~/components/layout/scaffold/Tab.vue';
import useConfig from '~/compositions/useConfig';
import { inject } from '~/compositions/useInjectProvide';
import { requiredInject } from '~/compositions/useInjectProvide';
import useNotifications from '~/compositions/useNotifications';
import { useRouteBack } from '~/compositions/useRouteBack';
@ -40,8 +40,8 @@ const notifications = useNotifications();
const router = useRouter();
const i18n = useI18n();
const org = inject('org');
const orgPermissions = inject('org-permissions');
const org = requiredInject('org');
const orgPermissions = requiredInject('org-permissions');
onMounted(async () => {
if (!orgPermissions.value?.admin) {

View file

@ -6,26 +6,21 @@
</template>
<script lang="ts" setup>
import { computed, inject, toRef } from 'vue';
import type { Ref } from 'vue';
import { computed, toRef } from 'vue';
import PipelineList from '~/components/repo/pipeline/PipelineList.vue';
import type { Pipeline, Repo, RepoPermissions } from '~/lib/api/types';
import { requiredInject } from '~/compositions/useInjectProvide';
const props = defineProps<{
branch: string;
}>();
const branch = toRef(props, 'branch');
const repo = inject<Ref<Repo>>('repo');
const repoPermissions = inject<Ref<RepoPermissions>>('repo-permissions');
if (!repo || !repoPermissions) {
throw new Error('Unexpected: "repo" & "repoPermissions" should be provided at this place');
}
const repo = requiredInject('repo');
const allPipelines = inject<Ref<Pipeline[]>>('pipelines');
const allPipelines = requiredInject('pipelines');
const pipelines = computed(() =>
allPipelines?.value.filter(
allPipelines.value.filter(
(b) => b.branch === branch.value && b.event !== 'pull_request' && b.event !== 'pull_request_closed',
),
);

View file

@ -21,29 +21,21 @@
</template>
<script lang="ts" setup>
import { computed, inject, watch } from 'vue';
import type { Ref } from 'vue';
import { computed, watch } from 'vue';
import Badge from '~/components/atomic/Badge.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 { requiredInject } from '~/compositions/useInjectProvide';
import { usePagination } from '~/compositions/usePaginate';
import type { Repo } from '~/lib/api/types';
const apiClient = useApiClient();
const repo = inject<Ref<Repo>>('repo');
if (!repo) {
throw new Error('Unexpected: "repo" should be provided at this place');
}
const repo = requiredInject('repo');
async function loadBranches(page: number): Promise<string[]> {
if (!repo) {
throw new Error('Unexpected: "repo" should be provided at this place');
}
return apiClient.getRepoBranches(repo.value.id, { page });
}

View file

@ -26,8 +26,7 @@
<script lang="ts" setup>
import { useNotification } from '@kyvg/vue3-notification';
import type { Ref } from 'vue';
import { computed, onMounted, ref, inject as vueInject } from 'vue';
import { computed, onMounted, ref } from 'vue';
import { useI18n } from 'vue-i18n';
import { useRouter } from 'vue-router';
@ -38,9 +37,8 @@ import KeyValueEditor from '~/components/form/KeyValueEditor.vue';
import SelectField from '~/components/form/SelectField.vue';
import Panel from '~/components/layout/Panel.vue';
import useApiClient from '~/compositions/useApiClient';
import { inject } from '~/compositions/useInjectProvide';
import { requiredInject } from '~/compositions/useInjectProvide';
import { usePaginate } from '~/compositions/usePaginate';
import type { RepoPermissions } from '~/lib/api/types';
defineProps<{
open: boolean;
@ -54,11 +52,8 @@ const apiClient = useApiClient();
const notifications = useNotification();
const i18n = useI18n();
const repo = inject('repo');
const repoPermissions = vueInject<Ref<RepoPermissions>>('repo-permissions');
if (!repoPermissions) {
throw new Error('Unexpected: "repo" and "repoPermissions" should be provided at this place');
}
const repo = requiredInject('repo');
const repoPermissions = requiredInject('repo-permissions');
const router = useRouter();
const branches = ref<{ text: string; value: string }[]>([]);

View file

@ -3,17 +3,9 @@
</template>
<script lang="ts" setup>
import { inject } from 'vue';
import type { Ref } from 'vue';
import PipelineList from '~/components/repo/pipeline/PipelineList.vue';
import type { Pipeline, Repo, RepoPermissions } from '~/lib/api/types';
import { requiredInject } from '~/compositions/useInjectProvide';
const repo = inject<Ref<Repo>>('repo');
const repoPermissions = inject<Ref<RepoPermissions>>('repo-permissions');
if (!repo || !repoPermissions) {
throw new Error('Unexpected: "repo" & "repoPermissions" should be provided at this place');
}
const pipelines = inject<Ref<Pipeline[]>>('pipelines');
const repo = requiredInject('repo');
const pipelines = requiredInject('pipelines');
</script>

View file

@ -6,28 +6,23 @@
</template>
<script lang="ts" setup>
import { computed, inject, toRef } from 'vue';
import type { Ref } from 'vue';
import { computed, toRef } from 'vue';
import PipelineList from '~/components/repo/pipeline/PipelineList.vue';
import type { Pipeline, Repo, RepoPermissions } from '~/lib/api/types';
import { requiredInject } from '~/compositions/useInjectProvide';
const props = defineProps<{
pullRequest: string;
}>();
const pullRequest = toRef(props, 'pullRequest');
const repo = inject<Ref<Repo>>('repo');
const repoPermissions = inject<Ref<RepoPermissions>>('repo-permissions');
if (!repo || !repoPermissions) {
throw new Error('Unexpected: "repo" and "repoPermissions" should be provided at this place');
}
const repo = requiredInject('repo');
if (!repo.value.pr_enabled || !repo.value.allow_pr) {
throw new Error('Unexpected: pull requests are disabled for repo');
}
const allPipelines = inject<Ref<Pipeline[]>>('pipelines');
const allPipelines = requiredInject('pipelines');
const pipelines = computed(() =>
allPipelines?.value.filter(
allPipelines.value.filter(
(b) =>
(b.event === 'pull_request' || b.event === 'pull_request_closed') &&
b.ref

View file

@ -26,31 +26,24 @@
</template>
<script lang="ts" setup>
import { inject, watch } from 'vue';
import type { Ref } from 'vue';
import { watch } 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 { requiredInject } from '~/compositions/useInjectProvide';
import { usePagination } from '~/compositions/usePaginate';
import type { PullRequest, Repo } from '~/lib/api/types';
import type { PullRequest } from '~/lib/api/types';
const apiClient = useApiClient();
const repo = inject<Ref<Repo>>('repo');
if (!repo) {
throw new Error('Unexpected: "repo" should be provided at this place');
}
const repo = requiredInject('repo');
if (!repo.value.pr_enabled || !repo.value.allow_pr) {
throw new Error('Unexpected: pull requests are disabled for repo');
}
async function loadPullRequests(page: number): Promise<PullRequest[]> {
if (!repo) {
throw new Error('Unexpected: "repo" should be provided at this place');
}
return apiClient.getRepoPullRequests(repo.value.id, { page });
}

View file

@ -54,7 +54,8 @@
</template>
<script lang="ts" setup>
import { computed, onMounted, provide, ref, toRef, watch } from 'vue';
import type { Ref } from 'vue';
import { computed, onMounted, ref, toRef, watch } from 'vue';
import { useI18n } from 'vue-i18n';
import { useRoute, useRouter } from 'vue-router';
@ -67,9 +68,10 @@ import useApiClient from '~/compositions/useApiClient';
import useAuthentication from '~/compositions/useAuthentication';
import useConfig from '~/compositions/useConfig';
import { useForgeStore } from '~/compositions/useForgeStore';
import { provide } from '~/compositions/useInjectProvide';
import useNotifications from '~/compositions/useNotifications';
import useRepos from '~/compositions/useRepos';
import type { Forge, RepoPermissions } from '~/lib/api/types';
import type { Forge, Repo, RepoPermissions } from '~/lib/api/types';
import { usePipelineStore } from '~/store/pipelines';
import { useRepoStore } from '~/store/repos';
@ -94,8 +96,8 @@ const { updateLastAccess } = useRepos();
const repo = repoStore.getRepo(repositoryId);
const repoPermissions = ref<RepoPermissions>();
const pipelines = pipelineStore.getRepoPipelines(repositoryId);
provide('repo', repo);
provide('repo-permissions', repoPermissions);
provide('repo', repo as Ref<Repo>); // can't be undefined because of v-if in template
provide('repo-permissions', repoPermissions as Ref<RepoPermissions>); // can't be undefined because of v-if in template
provide('pipelines', pipelines);
const forge = ref<Forge>();
const forgeIcon = computed<IconNames>(() => {

View file

@ -62,8 +62,7 @@
</template>
<script lang="ts" setup>
import { computed, inject, toRef } from 'vue';
import type { Ref } from 'vue';
import { computed, toRef } from 'vue';
import { useI18n } from 'vue-i18n';
import { useRoute, useRouter } from 'vue-router';
@ -75,8 +74,9 @@ import PipelineLog from '~/components/repo/pipeline/PipelineLog.vue';
import PipelineStepList from '~/components/repo/pipeline/PipelineStepList.vue';
import useApiClient from '~/compositions/useApiClient';
import { useAsyncAction } from '~/compositions/useAsyncAction';
import { requiredInject } from '~/compositions/useInjectProvide';
import useNotifications from '~/compositions/useNotifications';
import type { Pipeline, PipelineStep, Repo, RepoPermissions } from '~/lib/api/types';
import type { PipelineStep } from '~/lib/api/types';
const props = defineProps<{
stepId?: string | null;
@ -88,12 +88,9 @@ const route = useRoute();
const notifications = useNotifications();
const i18n = useI18n();
const pipeline = inject<Ref<Pipeline>>('pipeline');
const repo = inject<Ref<Repo>>('repo');
const repoPermissions = inject<Ref<RepoPermissions>>('repo-permissions');
if (!repo || !repoPermissions || !pipeline) {
throw new Error('Unexpected: "repo", "repoPermissions" & "pipeline" should be provided at this place');
}
const pipeline = requiredInject('pipeline');
const repo = requiredInject('repo');
const repoPermissions = requiredInject('repo-permissions');
const stepId = toRef(props, 'stepId');
@ -139,19 +136,11 @@ const selectedStepId = computed({
});
const { doSubmit: approvePipeline, isLoading: isApprovingPipeline } = useAsyncAction(async () => {
if (!repo) {
throw new Error('Unexpected: Repo is undefined');
}
await apiClient.approvePipeline(repo.value.id, `${pipeline.value.number}`);
notifications.notify({ title: i18n.t('repo.pipeline.protected.approve_success'), type: 'success' });
});
const { doSubmit: declinePipeline, isLoading: isDecliningPipeline } = useAsyncAction(async () => {
if (!repo) {
throw new Error('Unexpected: Repo is undefined');
}
await apiClient.declinePipeline(repo.value.id, `${pipeline.value.number}`);
notifications.notify({ title: i18n.t('repo.pipeline.protected.decline_success'), type: 'success' });
});

View file

@ -1,20 +1,14 @@
<template>
<Panel>
<ul class="w-full list-inside list-disc">
<li v-for="file in pipeline!.changed_files" :key="file">{{ file }}</li>
<li v-for="file in pipeline.changed_files" :key="file">{{ file }}</li>
</ul>
</Panel>
</template>
<script lang="ts" setup>
import { inject } from 'vue';
import type { Ref } from 'vue';
import Panel from '~/components/layout/Panel.vue';
import type { Pipeline } from '~/lib/api/types';
import { requiredInject } from '~/compositions/useInjectProvide';
const pipeline = inject<Ref<Pipeline>>('pipeline');
if (!pipeline) {
throw new Error('Unexpected: "pipeline" should be provided at this place');
}
const pipeline = requiredInject('pipeline');
</script>

View file

@ -18,12 +18,9 @@ import { computed } from 'vue';
import SyntaxHighlight from '~/components/atomic/SyntaxHighlight';
import Panel from '~/components/layout/Panel.vue';
import { inject } from '~/compositions/useInjectProvide';
import { requiredInject } from '~/compositions/useInjectProvide';
const pipelineConfigs = inject('pipeline-configs');
if (!pipelineConfigs) {
throw new Error('Unexpected: "pipelineConfigs" should be provided at this place');
}
const pipelineConfigs = requiredInject('pipeline-configs');
const pipelineConfigsDecoded = computed(
() =>

View file

@ -18,24 +18,23 @@
</template>
<script setup lang="ts">
import { computed, inject, ref } from 'vue';
import type { Ref } from 'vue';
import { computed, ref } from 'vue';
import { useI18n } from 'vue-i18n';
import Button from '~/components/atomic/Button.vue';
import InputField from '~/components/form/InputField.vue';
import Panel from '~/components/layout/Panel.vue';
import useApiClient from '~/compositions/useApiClient';
import { requiredInject } from '~/compositions/useInjectProvide';
import useNotifications from '~/compositions/useNotifications';
import type { Pipeline, Repo, RepoPermissions } from '~/lib/api/types';
const { t } = useI18n();
const apiClient = useApiClient();
const notifications = useNotifications();
const repo = inject<Ref<Repo>>('repo');
const pipeline = inject<Ref<Pipeline>>('pipeline');
const repoPermissions = inject<Ref<RepoPermissions>>('repo-permissions');
const repo = requiredInject('repo');
const pipeline = requiredInject('pipeline');
const repoPermissions = requiredInject('repo-permissions');
const isLoading = ref(false);

View file

@ -48,19 +48,14 @@
</template>
<script lang="ts" setup>
import { inject } from 'vue';
import type { Ref } from 'vue';
import DocsLink from '~/components/atomic/DocsLink.vue';
import Icon from '~/components/atomic/Icon.vue';
import RenderMarkdown from '~/components/atomic/RenderMarkdown.vue';
import Panel from '~/components/layout/Panel.vue';
import type { Pipeline, PipelineError } from '~/lib/api/types';
import { requiredInject } from '~/compositions/useInjectProvide';
import type { PipelineError } from '~/lib/api/types';
const pipeline = inject<Ref<Pipeline>>('pipeline');
if (!pipeline) {
throw new Error('Unexpected: "pipeline" should be provided at this place');
}
const pipeline = requiredInject('pipeline');
function isLinterError(error: PipelineError): error is PipelineError<{ file?: string; field: string }> {
return error.type === 'linter';

View file

@ -102,7 +102,7 @@
</template>
<script lang="ts" setup>
import { computed, inject, onBeforeUnmount, onMounted, ref, toRef, watch } from 'vue';
import { computed, onBeforeUnmount, onMounted, ref, toRef, watch } from 'vue';
import type { Ref } from 'vue';
import { useI18n } from 'vue-i18n';
import { useRoute, useRouter } from 'vue-router';
@ -116,11 +116,11 @@ import PipelineStatusIcon from '~/components/repo/pipeline/PipelineStatusIcon.vu
import useApiClient from '~/compositions/useApiClient';
import { useAsyncAction } from '~/compositions/useAsyncAction';
import { useFavicon } from '~/compositions/useFavicon';
import { provide } from '~/compositions/useInjectProvide';
import { provide, requiredInject } from '~/compositions/useInjectProvide';
import useNotifications from '~/compositions/useNotifications';
import usePipeline from '~/compositions/usePipeline';
import { useRouteBack } from '~/compositions/useRouteBack';
import type { PipelineConfig, Repo, RepoPermissions } from '~/lib/api/types';
import type { Pipeline, PipelineConfig } from '~/lib/api/types';
import { usePipelineStore } from '~/store/pipelines';
const props = defineProps<{
@ -139,15 +139,12 @@ const pipelineStore = usePipelineStore();
const pipelineId = toRef(props, 'pipelineId');
const _repoId = toRef(props, 'repoId');
const repositoryId = computed(() => Number.parseInt(_repoId.value, 10));
const repo = inject<Ref<Repo>>('repo');
const repoPermissions = inject<Ref<RepoPermissions>>('repo-permissions');
if (!repo || !repoPermissions) {
throw new Error('Unexpected: "repo" & "repoPermissions" should be provided at this place');
}
const repo = requiredInject('repo');
const repoPermissions = requiredInject('repo-permissions');
const pipeline = pipelineStore.getPipeline(repositoryId, pipelineId);
const { since, duration, created, message, shortMessage } = usePipeline(pipeline);
provide('pipeline', pipeline);
provide('pipeline', pipeline as Ref<Pipeline>); // can't be undefined because of v-if in template
const pipelineConfigs = ref<PipelineConfig[]>();
provide('pipeline-configs', pipelineConfigs);
@ -163,10 +160,6 @@ watch(
const showDeployPipelinePopup = ref(false);
async function loadPipeline(): Promise<void> {
if (!repo) {
throw new Error('Unexpected: Repo is undefined');
}
await pipelineStore.loadPipeline(repo.value.id, Number.parseInt(pipelineId.value, 10));
if (!pipeline.value?.number) {
@ -177,10 +170,6 @@ async function loadPipeline(): Promise<void> {
}
const { doSubmit: cancelPipeline, isLoading: isCancelingPipeline } = useAsyncAction(async () => {
if (!repo) {
throw new Error('Unexpected: Repo is undefined');
}
if (!pipeline.value?.number) {
throw new Error('Unexpected: Pipeline number not found');
}
@ -190,10 +179,6 @@ const { doSubmit: cancelPipeline, isLoading: isCancelingPipeline } = useAsyncAct
});
const { doSubmit: restartPipeline, isLoading: isRestartingPipeline } = useAsyncAction(async () => {
if (!repo) {
throw new Error('Unexpected: Repo is undefined');
}
const newPipeline = await apiClient.restartPipeline(repo.value.id, pipelineId.value, {
fork: true,
});

View file

@ -42,8 +42,7 @@
</template>
<script lang="ts" setup>
import { computed, inject } from 'vue';
import type { Ref } from 'vue';
import { computed } from 'vue';
import { useI18n } from 'vue-i18n';
import { useRouter } from 'vue-router';
@ -51,30 +50,22 @@ import Button from '~/components/atomic/Button.vue';
import Settings from '~/components/layout/Settings.vue';
import useApiClient from '~/compositions/useApiClient';
import { useAsyncAction } from '~/compositions/useAsyncAction';
import { requiredInject } from '~/compositions/useInjectProvide';
import useNotifications from '~/compositions/useNotifications';
import type { Repo } from '~/lib/api/types';
const apiClient = useApiClient();
const router = useRouter();
const notifications = useNotifications();
const i18n = useI18n();
const repo = inject<Ref<Repo>>('repo');
const repo = requiredInject('repo');
const { doSubmit: repairRepo, isLoading: isRepairingRepo } = useAsyncAction(async () => {
if (!repo) {
throw new Error('Unexpected: Repo should be set');
}
await apiClient.repairRepo(repo.value.id);
notifications.notify({ title: i18n.t('repo.settings.actions.repair.success'), type: 'success' });
});
const { doSubmit: deleteRepo, isLoading: isDeletingRepo } = useAsyncAction(async () => {
if (!repo) {
throw new Error('Unexpected: Repo should be set');
}
// TODO: use proper dialog
// eslint-disable-next-line no-alert
if (!confirm(i18n.t('repo.settings.actions.delete.confirm'))) {
@ -87,19 +78,11 @@ const { doSubmit: deleteRepo, isLoading: isDeletingRepo } = useAsyncAction(async
});
const { doSubmit: activateRepo, isLoading: isActivatingRepo } = useAsyncAction(async () => {
if (!repo) {
throw new Error('Unexpected: Repo should be set');
}
await apiClient.activateRepo(repo.value.forge_remote_id);
notifications.notify({ title: i18n.t('repo.settings.actions.enable.success'), type: 'success' });
});
const { doSubmit: deactivateRepo, isLoading: isDeactivatingRepo } = useAsyncAction(async () => {
if (!repo) {
throw new Error('Unexpected: Repo should be set');
}
await apiClient.deleteRepo(repo.value.id, false);
notifications.notify({ title: i18n.t('repo.settings.actions.disable.success'), type: 'success' });
await router.replace({ name: 'repos' });

View file

@ -41,8 +41,7 @@
<script lang="ts" setup>
import { useStorage } from '@vueuse/core';
import { computed, inject, onMounted, ref, watch } from 'vue';
import type { Ref } from 'vue';
import { computed, onMounted, ref, watch } from 'vue';
import type { SelectOption } from '~/components/form/form.types';
import InputField from '~/components/form/InputField.vue';
@ -50,27 +49,19 @@ 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 { requiredInject } from '~/compositions/useInjectProvide';
import { usePaginate } from '~/compositions/usePaginate';
import type { Repo } from '~/lib/api/types';
const apiClient = useApiClient();
const repo = inject<Ref<Repo>>('repo');
const repo = requiredInject('repo');
const badgeType = useStorage('woodpecker:last-badge-type', 'markdown');
if (!repo) {
throw new Error('Unexpected: "repo" should be provided at this place');
}
const defaultBranch = computed(() => repo.value.default_branch);
const branches = ref<SelectOption[]>([]);
const branch = ref<string>('');
async function loadBranches() {
if (!repo) {
throw new Error('Unexpected: "repo" should be provided at this place');
}
branches.value = (await usePaginate((page) => apiClient.getRepoBranches(repo.value.id, { page })))
.map((b) => ({
value: b,
@ -96,10 +87,6 @@ const repoUrl = computed(
);
const badgeContent = computed(() => {
if (!repo) {
throw new Error('Unexpected: "repo" should be provided at this place');
}
if (badgeType.value === 'url') {
return `${baseUrl}${badgeUrl.value}`;
}

View file

@ -105,8 +105,7 @@
</template>
<script lang="ts" setup>
import { computed, inject, ref } from 'vue';
import type { Ref } from 'vue';
import { computed, ref } from 'vue';
import { useI18n } from 'vue-i18n';
import Button from '~/components/atomic/Button.vue';
@ -118,35 +117,28 @@ import Settings from '~/components/layout/Settings.vue';
import useApiClient from '~/compositions/useApiClient';
import { useAsyncAction } from '~/compositions/useAsyncAction';
import { useDate } from '~/compositions/useDate';
import { requiredInject } from '~/compositions/useInjectProvide';
import useNotifications from '~/compositions/useNotifications';
import { usePagination } from '~/compositions/usePaginate';
import type { Cron, Repo } from '~/lib/api/types';
import type { Cron } from '~/lib/api/types';
import router from '~/router';
const apiClient = useApiClient();
const notifications = useNotifications();
const i18n = useI18n();
const repo = inject<Ref<Repo>>('repo');
const repo = requiredInject('repo');
const selectedCron = ref<Partial<Cron>>();
const isEditingCron = computed(() => !!selectedCron.value?.id);
const date = useDate();
async function loadCrons(page: number): Promise<Cron[] | null> {
if (!repo?.value) {
throw new Error("Unexpected: Can't load repo");
}
return apiClient.getCronList(repo.value.id, { page });
}
const { resetPage, data: crons } = usePagination(loadCrons, () => !selectedCron.value);
const { doSubmit: createCron, isLoading: isSaving } = useAsyncAction(async () => {
if (!repo?.value) {
throw new Error("Unexpected: Can't load repo");
}
if (!selectedCron.value) {
throw new Error("Unexpected: Can't get cron");
}
@ -165,20 +157,12 @@ const { doSubmit: createCron, isLoading: isSaving } = useAsyncAction(async () =>
});
const { doSubmit: deleteCron, isLoading: isDeleting } = useAsyncAction(async (_cron: Cron) => {
if (!repo?.value) {
throw new Error("Unexpected: Can't load repo");
}
await apiClient.deleteCron(repo.value.id, _cron.id);
notifications.notify({ title: i18n.t('repo.settings.crons.deleted'), type: 'success' });
resetPage();
});
const { doSubmit: runCron } = useAsyncAction(async (_cron: Cron) => {
if (!repo?.value) {
throw new Error("Unexpected: Can't load repo");
}
const pipeline = await apiClient.runCron(repo.value.id, _cron.id);
await router.push({
name: 'repo-pipeline',

View file

@ -171,8 +171,7 @@
</template>
<script lang="ts" setup>
import { inject, onMounted, ref } from 'vue';
import type { Ref } from 'vue';
import { onMounted, ref } from 'vue';
import { useI18n } from 'vue-i18n';
import Button from '~/components/atomic/Button.vue';
@ -187,9 +186,10 @@ import Settings from '~/components/layout/Settings.vue';
import useApiClient from '~/compositions/useApiClient';
import { useAsyncAction } from '~/compositions/useAsyncAction';
import useAuthentication from '~/compositions/useAuthentication';
import { requiredInject } from '~/compositions/useInjectProvide';
import useNotifications from '~/compositions/useNotifications';
import { RepoRequireApproval, RepoVisibility, WebhookEvents } from '~/lib/api/types';
import type { Repo, RepoSettings } from '~/lib/api/types';
import type { RepoSettings } from '~/lib/api/types';
import { useRepoStore } from '~/store/repos';
const apiClient = useApiClient();
@ -198,14 +198,10 @@ const { user } = useAuthentication();
const repoStore = useRepoStore();
const i18n = useI18n();
const repo = inject<Ref<Repo>>('repo');
const repo = requiredInject('repo');
const repoSettings = ref<RepoSettings>();
function loadRepoSettings() {
if (!repo) {
throw new Error('Unexpected: Repo should be set');
}
repoSettings.value = {
config_file: repo.value.config_file,
timeout: repo.value.timeout,
@ -221,19 +217,11 @@ function loadRepoSettings() {
}
async function loadRepo() {
if (!repo) {
throw new Error('Unexpected: Repo should be set');
}
await repoStore.loadRepo(repo.value.id);
loadRepoSettings();
}
const { doSubmit: saveRepoSettings, isLoading: isSaving } = useAsyncAction(async () => {
if (!repo) {
throw new Error('Unexpected: Repo should be set');
}
if (!repoSettings.value) {
throw new Error('Unexpected: Repo-Settings should be set');
}

View file

@ -30,8 +30,7 @@
<script lang="ts" setup>
import { cloneDeep } from 'lodash';
import { computed, inject, ref } from 'vue';
import type { Ref } from 'vue';
import { computed, ref } from 'vue';
import { useI18n } from 'vue-i18n';
import Button from '~/components/atomic/Button.vue';
@ -40,9 +39,10 @@ import RegistryEdit from '~/components/registry/RegistryEdit.vue';
import RegistryList from '~/components/registry/RegistryList.vue';
import useApiClient from '~/compositions/useApiClient';
import { useAsyncAction } from '~/compositions/useAsyncAction';
import { requiredInject } from '~/compositions/useInjectProvide';
import useNotifications from '~/compositions/useNotifications';
import { usePagination } from '~/compositions/usePaginate';
import type { Registry, Repo } from '~/lib/api/types';
import type { Registry } from '~/lib/api/types';
const emptyRegistry: Partial<Registry> = {
address: '',
@ -54,25 +54,17 @@ const apiClient = useApiClient();
const notifications = useNotifications();
const i18n = useI18n();
const repo = inject<Ref<Repo>>('repo');
const repo = requiredInject('repo');
const selectedRegistry = ref<Partial<Registry>>();
const isEditingRegistry = computed(() => !!selectedRegistry.value?.id);
async function loadRegistries(page: number): Promise<Registry[] | null> {
if (!repo?.value) {
throw new Error("Unexpected: Can't load repo");
}
return apiClient.getRegistryList(repo.value.id, { page });
}
const { resetPage, data: registries } = usePagination(loadRegistries, () => !selectedRegistry.value);
const { doSubmit: createRegistry, isLoading: isSaving } = useAsyncAction(async () => {
if (!repo?.value) {
throw new Error("Unexpected: Can't load repo");
}
if (!selectedRegistry.value) {
throw new Error("Unexpected: Can't get registry");
}
@ -91,10 +83,6 @@ const { doSubmit: createRegistry, isLoading: isSaving } = useAsyncAction(async (
});
const { doSubmit: deleteRegistry, isLoading: isDeleting } = useAsyncAction(async (_registry: Registry) => {
if (!repo?.value) {
throw new Error("Unexpected: Can't load repo");
}
const registryAddress = encodeURIComponent(_registry.address);
await apiClient.deleteRegistry(repo.value.id, registryAddress);
notifications.notify({ title: i18n.t('registries.deleted'), type: 'success' });

View file

@ -2,7 +2,7 @@
<Scaffold enable-tabs :go-back="goBack">
<template #title>
<span>
<router-link :to="{ name: 'org', params: { orgId: repo!.org_id } }" class="hover:underline">{{
<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>
@ -28,30 +28,22 @@
</template>
<script lang="ts" setup>
import { inject, onMounted } from 'vue';
import type { Ref } from 'vue';
import { onMounted } from 'vue';
import { useI18n } from 'vue-i18n';
import { useRouter } from 'vue-router';
import Scaffold from '~/components/layout/scaffold/Scaffold.vue';
import Tab from '~/components/layout/scaffold/Tab.vue';
import { requiredInject } from '~/compositions/useInjectProvide';
import useNotifications from '~/compositions/useNotifications';
import { useRouteBack } from '~/compositions/useRouteBack';
import type { Repo, RepoPermissions } from '~/lib/api/types';
const notifications = useNotifications();
const router = useRouter();
const i18n = useI18n();
const repoPermissions = inject<Ref<RepoPermissions>>('repo-permissions');
if (!repoPermissions) {
throw new Error('Unexpected: "repoPermissions" should be provided at this place');
}
const repo = inject<Ref<Repo>>('repo');
if (!repo) {
throw new Error('Unexpected: "repo" should be provided at this place');
}
const repoPermissions = requiredInject('repo-permissions');
const repo = requiredInject('repo');
onMounted(async () => {
if (!repoPermissions.value.admin) {

View file

@ -25,8 +25,7 @@
<script lang="ts" setup>
import { cloneDeep } from 'lodash';
import { computed, inject, ref } from 'vue';
import type { Ref } from 'vue';
import { computed, ref } from 'vue';
import { useI18n } from 'vue-i18n';
import Button from '~/components/atomic/Button.vue';
@ -35,10 +34,11 @@ import SecretEdit from '~/components/secrets/SecretEdit.vue';
import SecretList from '~/components/secrets/SecretList.vue';
import useApiClient from '~/compositions/useApiClient';
import { useAsyncAction } from '~/compositions/useAsyncAction';
import { requiredInject } from '~/compositions/useInjectProvide';
import useNotifications from '~/compositions/useNotifications';
import { usePagination } from '~/compositions/usePaginate';
import { WebhookEvents } from '~/lib/api/types';
import type { Repo, Secret } from '~/lib/api/types';
import type { Secret } from '~/lib/api/types';
const emptySecret: Partial<Secret> = {
name: '',
@ -51,15 +51,11 @@ const apiClient = useApiClient();
const notifications = useNotifications();
const i18n = useI18n();
const repo = inject<Ref<Repo>>('repo');
const repo = requiredInject('repo');
const selectedSecret = ref<Partial<Secret>>();
const isEditingSecret = computed(() => !!selectedSecret.value?.id);
async function loadSecrets(page: number, level: 'repo' | 'org' | 'global'): Promise<Secret[] | null> {
if (!repo?.value) {
throw new Error("Unexpected: Can't load repo");
}
switch (level) {
case 'repo':
return apiClient.getSecretList(repo.value.id, { page });
@ -103,10 +99,6 @@ const secrets = computed(() => {
});
const { doSubmit: createSecret, isLoading: isSaving } = useAsyncAction(async () => {
if (!repo?.value) {
throw new Error("Unexpected: Can't load repo");
}
if (!selectedSecret.value) {
throw new Error("Unexpected: Can't get secret");
}
@ -125,10 +117,6 @@ const { doSubmit: createSecret, isLoading: isSaving } = useAsyncAction(async ()
});
const { doSubmit: deleteSecret, isLoading: isDeleting } = useAsyncAction(async (_secret: Secret) => {
if (!repo?.value) {
throw new Error("Unexpected: Can't load repo");
}
await apiClient.deleteSecret(repo.value.id, _secret.name);
notifications.notify({ title: i18n.t('secrets.deleted'), type: 'success' });
await resetPage();