Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
473d6b2434
commit
99f7d9e732
|
@ -2,36 +2,6 @@
|
|||
# Cop supports --auto-correct.
|
||||
Layout/HashAlignment:
|
||||
Exclude:
|
||||
- 'ee/app/graphql/types/vulnerabilities/asset_type.rb'
|
||||
- 'ee/app/graphql/types/vulnerabilities/container_image_type.rb'
|
||||
- 'ee/app/graphql/types/vulnerabilities/link_type.rb'
|
||||
- 'ee/app/graphql/types/vulnerability/external_issue_link_type.rb'
|
||||
- 'ee/app/graphql/types/vulnerability/issue_link_type.rb'
|
||||
- 'ee/app/graphql/types/vulnerability_details/base_type.rb'
|
||||
- 'ee/app/graphql/types/vulnerability_details/boolean_type.rb'
|
||||
- 'ee/app/graphql/types/vulnerability_details/code_type.rb'
|
||||
- 'ee/app/graphql/types/vulnerability_details/commit_type.rb'
|
||||
- 'ee/app/graphql/types/vulnerability_details/diff_type.rb'
|
||||
- 'ee/app/graphql/types/vulnerability_details/file_location_type.rb'
|
||||
- 'ee/app/graphql/types/vulnerability_details/int_type.rb'
|
||||
- 'ee/app/graphql/types/vulnerability_details/list_type.rb'
|
||||
- 'ee/app/graphql/types/vulnerability_details/markdown_type.rb'
|
||||
- 'ee/app/graphql/types/vulnerability_details/module_location_type.rb'
|
||||
- 'ee/app/graphql/types/vulnerability_details/table_type.rb'
|
||||
- 'ee/app/graphql/types/vulnerability_details/text_type.rb'
|
||||
- 'ee/app/graphql/types/vulnerability_details/url_type.rb'
|
||||
- 'ee/app/graphql/types/vulnerability_location/container_scanning_type.rb'
|
||||
- 'ee/app/graphql/types/vulnerability_location/coverage_fuzzing_type.rb'
|
||||
- 'ee/app/graphql/types/vulnerability_location/dast_type.rb'
|
||||
- 'ee/app/graphql/types/vulnerability_location/dependency_scanning_type.rb'
|
||||
- 'ee/app/graphql/types/vulnerability_location/generic_type.rb'
|
||||
- 'ee/app/graphql/types/vulnerability_location/sast_type.rb'
|
||||
- 'ee/app/graphql/types/vulnerability_location/secret_detection_type.rb'
|
||||
- 'ee/app/graphql/types/vulnerability_request_response_header_type.rb'
|
||||
- 'ee/app/graphql/types/vulnerability_request_type.rb'
|
||||
- 'ee/app/graphql/types/vulnerability_response_type.rb'
|
||||
- 'ee/app/graphql/types/vulnerability_scanner_type.rb'
|
||||
- 'ee/app/graphql/types/vulnerability_type.rb'
|
||||
- 'ee/app/graphql/types/vulnerable_dependency_type.rb'
|
||||
- 'ee/app/graphql/types/vulnerable_kubernetes_resource_type.rb'
|
||||
- 'ee/app/graphql/types/vulnerable_projects_by_grade_type.rb'
|
||||
|
|
|
@ -38,10 +38,8 @@ export function formatIssue(issue) {
|
|||
|
||||
export function formatListIssues(listIssues) {
|
||||
const boardItems = {};
|
||||
let listItemsCount;
|
||||
|
||||
const listData = listIssues.nodes.reduce((map, list) => {
|
||||
listItemsCount = list.issuesCount;
|
||||
let sortedIssues = list.issues.edges.map((issueNode) => ({
|
||||
...issueNode.node,
|
||||
}));
|
||||
|
@ -67,7 +65,7 @@ export function formatListIssues(listIssues) {
|
|||
};
|
||||
}, {});
|
||||
|
||||
return { listData, boardItems, listItemsCount };
|
||||
return { listData, boardItems };
|
||||
}
|
||||
|
||||
export function formatListsPageInfo(lists) {
|
||||
|
|
|
@ -117,7 +117,7 @@ export default {
|
|||
return 'issues';
|
||||
},
|
||||
itemsTooltipLabel() {
|
||||
return n__(`%d issue`, `%d issues`, this.boardLists?.issuesCount);
|
||||
return n__(`%d issue`, `%d issues`, this.boardList?.issuesCount);
|
||||
},
|
||||
chevronTooltip() {
|
||||
return this.list.collapsed ? this.$options.i18n.expand : this.$options.i18n.collapse;
|
||||
|
|
|
@ -17,7 +17,6 @@ query BoardListsEE(
|
|||
lists(id: $id, issueFilters: $filters) {
|
||||
nodes {
|
||||
id
|
||||
issuesCount
|
||||
listType
|
||||
issues(first: $first, filters: $filters, after: $after) {
|
||||
edges {
|
||||
|
@ -41,7 +40,6 @@ query BoardListsEE(
|
|||
lists(id: $id, issueFilters: $filters) {
|
||||
nodes {
|
||||
id
|
||||
issuesCount
|
||||
listType
|
||||
issues(first: $first, filters: $filters, after: $after) {
|
||||
edges {
|
||||
|
|
|
@ -11,7 +11,7 @@ const updateListItemsCount = ({ state, listId, value }) => {
|
|||
if (state.issuableType === issuableTypes.epic) {
|
||||
Vue.set(state.boardLists, listId, { ...list, epicsCount: list.epicsCount + value });
|
||||
} else {
|
||||
Vue.set(state.boardLists, listId, { ...list, issuesCount: list.issuesCount + value });
|
||||
Vue.set(state.boardLists, listId, { ...list });
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -2,10 +2,10 @@
|
|||
import { GlAlert, GlFormGroup, GlForm, GlFormCombobox, GlButton, GlFormInput } from '@gitlab/ui';
|
||||
import { getIdFromGraphQLId } from '~/graphql_shared/utils';
|
||||
import { __, s__ } from '~/locale';
|
||||
import projectWorkItemsQuery from '../../graphql/project_work_items.query.graphql';
|
||||
import projectWorkItemTypesQuery from '~/work_items/graphql/project_work_item_types.query.graphql';
|
||||
import updateWorkItemMutation from '../../graphql/update_work_item.mutation.graphql';
|
||||
import createWorkItemMutation from '../../graphql/create_work_item.mutation.graphql';
|
||||
import { WORK_ITEM_TYPE_IDS } from '../../constants';
|
||||
import { TASK_TYPE_NAME } from '../../constants';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
|
@ -35,23 +35,15 @@ export default {
|
|||
},
|
||||
},
|
||||
apollo: {
|
||||
availableWorkItems: {
|
||||
query: projectWorkItemsQuery,
|
||||
debounce: 200,
|
||||
workItemTypes: {
|
||||
query: projectWorkItemTypesQuery,
|
||||
variables() {
|
||||
return {
|
||||
projectPath: this.projectPath,
|
||||
searchTerm: this.search?.title || this.search,
|
||||
types: ['TASK'],
|
||||
fullPath: this.projectPath,
|
||||
};
|
||||
},
|
||||
skip() {
|
||||
return this.search.length === 0;
|
||||
},
|
||||
update(data) {
|
||||
return data.workspace.workItems.edges
|
||||
.filter((wi) => !this.childrenIds.includes(wi.node.id))
|
||||
.map((wi) => wi.node);
|
||||
return data.workspace?.workItemTypes?.nodes;
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -82,6 +74,9 @@ export default {
|
|||
addOrCreateMethod() {
|
||||
return this.childToCreateTitle ? this.createChild : this.addChild;
|
||||
},
|
||||
taskWorkItemType() {
|
||||
return this.workItemTypes.find((type) => type.name === TASK_TYPE_NAME)?.id;
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
getIdFromGraphQLId,
|
||||
|
@ -124,7 +119,7 @@ export default {
|
|||
input: {
|
||||
title: this.search?.title || this.search,
|
||||
projectPath: this.projectPath,
|
||||
workItemTypeId: WORK_ITEM_TYPE_IDS.TASK,
|
||||
workItemTypeId: this.taskWorkItemType,
|
||||
hierarchyWidget: {
|
||||
parentId: this.issuableGid,
|
||||
},
|
||||
|
|
|
@ -37,10 +37,6 @@ export const WORK_ITEM_STATUS_TEXT = {
|
|||
OPEN: s__('WorkItem|Open'),
|
||||
};
|
||||
|
||||
export const WORK_ITEM_TYPE_IDS = {
|
||||
TASK: 'gid://gitlab/WorkItems::Type/5',
|
||||
};
|
||||
|
||||
export const WORK_ITEMS_TYPE_MAP = {
|
||||
[WORK_ITEM_TYPE_ENUM_INCIDENT]: {
|
||||
icon: `issue-type-incident`,
|
||||
|
|
|
@ -14,11 +14,22 @@ module Projects
|
|||
def show
|
||||
end
|
||||
|
||||
def cleanup_tags
|
||||
registry_settings_enabled!
|
||||
|
||||
@hide_search_settings = true
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def packages_and_registries_settings_enabled!
|
||||
render_404 unless can?(current_user, :view_package_registry_project_settings, project)
|
||||
end
|
||||
|
||||
def registry_settings_enabled!
|
||||
render_404 unless Gitlab.config.registry.enabled &&
|
||||
can?(current_user, :admin_container_image, project)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -63,4 +63,27 @@ module PackagesHelper
|
|||
Gitlab.config.packages.enabled &&
|
||||
Ability.allowed?(current_user, :admin_package, project)
|
||||
end
|
||||
|
||||
def cleanup_settings_data
|
||||
{
|
||||
project_id: @project.id,
|
||||
project_path: @project.full_path,
|
||||
cadence_options: cadence_options.to_json,
|
||||
keep_n_options: keep_n_options.to_json,
|
||||
older_than_options: older_than_options.to_json,
|
||||
is_admin: current_user&.admin.to_s,
|
||||
admin_settings_path: ci_cd_admin_application_settings_path(anchor: 'js-registry-settings'),
|
||||
enable_historic_entries: container_expiration_policies_historic_entry_enabled?.to_s,
|
||||
help_page_path: help_page_path('user/packages/container_registry/reduce_container_registry_storage', anchor: 'cleanup-policy'),
|
||||
show_cleanup_policy_link: show_cleanup_policy_link(@project).to_s,
|
||||
tags_regex_help_page_path: help_page_path('user/packages/container_registry/reduce_container_registry_storage', anchor: 'regex-pattern-examples')
|
||||
}
|
||||
end
|
||||
|
||||
def settings_data
|
||||
cleanup_settings_data.merge(
|
||||
show_container_registry_settings: show_container_registry_settings(@project).to_s,
|
||||
show_package_registry_settings: show_package_registry_settings(@project).to_s
|
||||
)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -0,0 +1,6 @@
|
|||
- add_to_breadcrumbs _('Packages & Registries'), project_settings_packages_and_registries_path(@project)
|
||||
- breadcrumb_title s_('ContainerRegistry|Clean up image tags')
|
||||
- page_title s_('ContainerRegistry|Clean up image tags'), _('Packages & Registries')
|
||||
- @content_class = 'limit-container-width' unless fluid_layout
|
||||
|
||||
#js-registry-settings-cleanup-image-tags{ data: cleanup_settings_data }
|
|
@ -2,16 +2,4 @@
|
|||
- page_title _('Packages & Registries')
|
||||
- @content_class = 'limit-container-width' unless fluid_layout
|
||||
|
||||
#js-registry-settings{ data: { project_id: @project.id,
|
||||
project_path: @project.full_path,
|
||||
cadence_options: cadence_options.to_json,
|
||||
keep_n_options: keep_n_options.to_json,
|
||||
older_than_options: older_than_options.to_json,
|
||||
is_admin: current_user&.admin.to_s,
|
||||
show_container_registry_settings: show_container_registry_settings(@project).to_s,
|
||||
show_package_registry_settings: show_package_registry_settings(@project).to_s,
|
||||
admin_settings_path: ci_cd_admin_application_settings_path(anchor: 'js-registry-settings'),
|
||||
enable_historic_entries: container_expiration_policies_historic_entry_enabled?.to_s,
|
||||
help_page_path: help_page_path('user/packages/container_registry/reduce_container_registry_storage', anchor: 'cleanup-policy'),
|
||||
show_cleanup_policy_link: show_cleanup_policy_link(@project).to_s,
|
||||
tags_regex_help_page_path: help_page_path('user/packages/container_registry/reduce_container_registry_storage', anchor: 'regex-pattern-examples') } }
|
||||
#js-registry-settings{ data: settings_data }
|
||||
|
|
|
@ -156,7 +156,9 @@ constraints(::Constraints::ProjectUrlConstrainer.new) do
|
|||
end
|
||||
end
|
||||
|
||||
resource :packages_and_registries, only: [:show]
|
||||
resource :packages_and_registries, only: [:show] do
|
||||
get '/cleanup_image_tags', to: 'packages_and_registries#cleanup_tags'
|
||||
end
|
||||
end
|
||||
|
||||
resources :usage_quotas, only: [:index]
|
||||
|
|
|
@ -813,26 +813,12 @@ information, see the [relevant documentation](monitoring.md#monitor-gitaly-concu
|
|||
## Control groups
|
||||
|
||||
FLAG:
|
||||
On self-managed GitLab, by default cgroups are not available. To make it available, ask an administrator to
|
||||
On self-managed GitLab, by default repository cgroups are not available. To make it available, ask an administrator to
|
||||
[enable the feature flag](../feature_flags.md) named `gitaly_run_cmds_in_cgroup`.
|
||||
|
||||
Gitaly shells out to Git for many of its operations. Git can consume a lot of resources for certain operations,
|
||||
especially for large repositories.
|
||||
|
||||
Control groups (cgroups) in Linux allow limits to be imposed on how much memory and CPU can be consumed.
|
||||
See the [`cgroups` Linux man page](https://man7.org/linux/man-pages/man7/cgroups.7.html) for more information.
|
||||
cgroups can be useful for protecting the system against resource exhaustion because of overcomsumption of memory and CPU.
|
||||
|
||||
Gitaly has built-in cgroups control. When configured, Gitaly assigns Git
|
||||
processes to a cgroup based on the repository the Git command is operating in.
|
||||
Each cgroup has a memory and CPU limit. When a cgroup reaches its:
|
||||
|
||||
- Memory limit, the kernel looks through the processes for a candidate to kill.
|
||||
- CPU limit, processes are not killed, but the processes are prevented from consuming more CPU than allowed.
|
||||
|
||||
The main reason to configure cgroups for your GitLab installation is that it
|
||||
protects against system resource starvation due to a few large repositories or
|
||||
bad actors.
|
||||
cgroups can be useful for protecting the system against resource exhaustion because of over consumption of memory and CPU.
|
||||
|
||||
Some Git operations are expensive by nature. `git clone`, for instance,
|
||||
spawns a `git-upload-pack` process on the server that can consume a lot of memory
|
||||
|
@ -840,33 +826,33 @@ for large repositories. For example, a client that keeps on cloning a
|
|||
large repository over and over again. This situation could potentially use up all of the
|
||||
memory on a server, causing other operations to fail for other users.
|
||||
|
||||
There are many ways someone can create a repository that can consume large amounts of memory when cloned or downloaded.
|
||||
A repository can consume large amounts of memory for many reasons when cloned or downloaded.
|
||||
Using cgroups allows the kernel to kill these operations before they hog up all system resources.
|
||||
|
||||
### Configure cgroups in Gitaly
|
||||
Gitaly shells out to Git for many of its operations. Git can consume a lot of resources for certain operations,
|
||||
especially for large repositories.
|
||||
|
||||
Two ways of configuring cgroups are available.
|
||||
Gitaly has built-in cgroups control. When configured, Gitaly assigns Git processes to a cgroup based on the repository
|
||||
the Git command is operating in. These cgroups are called repository cgroups. Each repository cgroup:
|
||||
|
||||
#### Configure cgroups (new method)
|
||||
- Has a memory and CPU limit.
|
||||
- Contains the Git processes for a single repository.
|
||||
- Uses a consistent hash to ensure a Git process for a given repository always ends up in the same cgroup.
|
||||
|
||||
> This method of configuring cgroups introduced in GitLab 15.1.
|
||||
When a repository cgroup reaches its:
|
||||
|
||||
Gitaly creates a pool of cgroups that are isolated based on the repository used in the Git command to be placed under one of these cgroups.
|
||||
- Memory limit, the kernel looks through the processes for a candidate to kill.
|
||||
- CPU limit, processes are not killed, but the processes are prevented from consuming more CPU than allowed.
|
||||
|
||||
To configure cgroups in Gitaly, add `gitaly['cgroups']` to `/etc/gitlab/gitlab.rb`.
|
||||
You configure repository cgroups for your GitLab installation to protect against system resource starvation from a few
|
||||
large repositories or bad actors.
|
||||
|
||||
For example:
|
||||
### Configure repository cgroups (new method)
|
||||
|
||||
```ruby
|
||||
# in /etc/gitlab/gitlab.rb
|
||||
gitaly['cgroups_mountpoint'] = "/sys/fs/cgroup"
|
||||
gitaly['cgroups_hierarchy_root'] =>"gitaly"
|
||||
gitaly['cgroups_memory_bytes'] = 64424509440, # 60gb
|
||||
gitaly['cgroups_cpu_shares'] = 1024
|
||||
gitaly['cgroups_repositories_count'] => 1000,
|
||||
gitaly['cgroups_repositories_memory_bytes'] => 32212254720 # 20gb
|
||||
gitaly['cgroups_repositories_cpu_shares'] => 512
|
||||
```
|
||||
> This method of configuring repository cgroups was introduced in GitLab 15.1.
|
||||
|
||||
To configure repository cgroups in Gitaly using the new method, use the following settings for the new configuration method
|
||||
to `gitaly['cgroups']` in `/etc/gitlab/gitlab.rb`:
|
||||
|
||||
- `cgroups_mountpoint` is where the parent cgroup directory is mounted. Defaults to `/sys/fs/cgroup`.
|
||||
- `cgroups_hierarchy_root` is the parent cgroup under which Gitaly creates groups, and
|
||||
|
@ -875,7 +861,7 @@ gitaly['cgroups_repositories_cpu_shares'] => 512
|
|||
when Gitaly starts.
|
||||
- `cgroups_memory_bytes` is the total memory limit that is imposed collectively on all
|
||||
Git processes that Gitaly spawns. 0 implies no limit.
|
||||
- `cgroups_cpu_shares` is the cpu limit that is imposed collectively on all Git
|
||||
- `cgroups_cpu_shares` is the CPU limit that is imposed collectively on all Git
|
||||
processes that Gitaly spawns. 0 implies no limit. The maximum is 1024 shares,
|
||||
which represents 100% of CPU.
|
||||
- `cgroups_repositories_count` is the number of cgroups in the cgroups pool. Each time a new Git
|
||||
|
@ -884,31 +870,29 @@ gitaly['cgroups_repositories_cpu_shares'] => 512
|
|||
Git commands to these cgroups, so a Git command for a repository is
|
||||
always assigned to the same cgroup.
|
||||
- `cgroups_repositories_memory_bytes` is the total memory limit imposed on all Git processes contained in a repository cgroup.
|
||||
A repository cgroup is one that contains Git processes for one or more repositories.
|
||||
A consistent hash is used to assign repositories to these cgroups such that a Git process for a given repository always ends up in the same cgroup.
|
||||
0 implies no limit. This value cannot exceed that of the top level `cgroups_memory_bytes`.
|
||||
- `cgroups_repositories_cpu_shares` is the CPU limit that is imposed on all Git processes contained in a repository cgroup.
|
||||
A repository cgroup is one that contains Git processes for one or more repositories.
|
||||
A consistent hash is used to assign repositories to these cgroups such that a Git process for a given repository always ends up in the same cgroup.
|
||||
0 implies no limit. The maximum is 1024 shares, which represents 100% of CPU.
|
||||
This value cannot exceed that of the top level`cgroups_cpu_shares`.
|
||||
|
||||
#### Configure cgroups (legacy method)
|
||||
|
||||
To configure cgroups in Gitaly for GitLab versions using the legacy method, add `gitaly['cgroups']` to `/etc/gitlab/gitlab.rb`. For
|
||||
example:
|
||||
For example:
|
||||
|
||||
```ruby
|
||||
# in /etc/gitlab/gitlab.rb
|
||||
gitaly['cgroups_count'] = 1000
|
||||
gitaly['cgroups_mountpoint'] = "/sys/fs/cgroup"
|
||||
gitaly['cgroups_hierarchy_root'] = "gitaly"
|
||||
gitaly['cgroups_memory_limit'] = 32212254720
|
||||
gitaly['cgroups_memory_enabled'] = true
|
||||
gitaly['cgroups_hierarchy_root'] => "gitaly"
|
||||
gitaly['cgroups_memory_bytes'] = 64424509440, # 60gb
|
||||
gitaly['cgroups_cpu_shares'] = 1024
|
||||
gitaly['cgroups_cpu_enabled'] = true
|
||||
gitaly['cgroups_repositories_count'] => 1000,
|
||||
gitaly['cgroups_repositories_memory_bytes'] => 32212254720 # 20gb
|
||||
gitaly['cgroups_repositories_cpu_shares'] => 512
|
||||
```
|
||||
|
||||
### Configure repository cgroups (legacy method)
|
||||
|
||||
To configure repository cgroups in Gitaly using the legacy method, use the following settings
|
||||
in `/etc/gitlab/gitlab.rb`:
|
||||
|
||||
- `cgroups_count` is the number of cgroups created. Each time a new
|
||||
command is spawned, Gitaly assigns it to one of these cgroups based
|
||||
on the command line arguments of the command. A circular hashing algorithm assigns
|
||||
|
@ -921,7 +905,21 @@ gitaly['cgroups_cpu_enabled'] = true
|
|||
- `cgroups_memory_enabled` enables or disables the memory limit on cgroups.
|
||||
- `cgroups_memory_bytes` is the total memory limit each cgroup imposes on the processes added to it.
|
||||
- `cgroups_cpu_enabled` enables or disables the CPU limit on cgroups.
|
||||
- `cgroups_cpu_shares` is the CPU limit each cgroup imposes on the processes added to it. The maximum is 1024 shares, which represents 100% of CPU.
|
||||
- `cgroups_cpu_shares` is the CPU limit each cgroup imposes on the processes added to it. The maximum is 1024 shares,
|
||||
which represents 100% of CPU.
|
||||
|
||||
For example:
|
||||
|
||||
```ruby
|
||||
# in /etc/gitlab/gitlab.rb
|
||||
gitaly['cgroups_count'] = 1000
|
||||
gitaly['cgroups_mountpoint'] = "/sys/fs/cgroup"
|
||||
gitaly['cgroups_hierarchy_root'] = "gitaly"
|
||||
gitaly['cgroups_memory_limit'] = 32212254720
|
||||
gitaly['cgroups_memory_enabled'] = true
|
||||
gitaly['cgroups_cpu_shares'] = 1024
|
||||
gitaly['cgroups_cpu_enabled'] = true
|
||||
```
|
||||
|
||||
### Configuring oversubscription
|
||||
|
||||
|
@ -930,16 +928,15 @@ In the previous example using the new configuration method:
|
|||
- The top level memory limit is capped at 60gb.
|
||||
- Each of the 1000 cgroups in the repositories pool is capped at 20gb.
|
||||
|
||||
This is called "oversubscription". Each cgroup in the pool has a much larger capacity than 1/1000th
|
||||
This configuration leads to "oversubscription". Each cgroup in the pool has a much larger capacity than 1/1000th
|
||||
of the top-level memory limit.
|
||||
|
||||
This strategy has two main benefits:
|
||||
|
||||
- It gives the host protection from overall memory starvation (OOM), because the top-level
|
||||
cgroup's memory limit can be set to a threshold smaller than the host's
|
||||
capacity. Processes outside of that cgroup are not at risk of OOM.
|
||||
- It gives the host protection from overall memory starvation (OOM), because the memory limit of the top-level cgroup
|
||||
can be set to a threshold smaller than the host's capacity. Processes outside of that cgroup are not at risk of OOM.
|
||||
- It allows each individual cgroup in the pool to burst up to a generous upper
|
||||
bound (in this example 20 GB) that is smaller than the parent cgroup's limit,
|
||||
bound (in this example 20 GB) that is smaller than the limit of the parent cgroup,
|
||||
but substantially larger than 1/N of the parent's limit. In this example, up
|
||||
to 3 child cgroups can concurrently burst up to their max. In general, all
|
||||
1000 cgroups would use much less than the 20 GB.
|
||||
|
|
|
@ -80,11 +80,10 @@ Gitaly Cluster does not support snapshot backups. Snapshot backups can cause iss
|
|||
out of sync with the disk storage. Because of how Praefect rebuilds the replication metadata of Gitaly disk information
|
||||
during a restore, we recommend using the [official backup and restore Rake tasks](../../raketasks/backup_restore.md).
|
||||
|
||||
If you are unable to use this method, please contact customer support for restoration help.
|
||||
The [incremental backup method](../../raketasks/backup_gitlab.md#incremental-repository-backups)
|
||||
can be used to speed up Gitaly Cluster backups.
|
||||
|
||||
We are tracking in [this issue](https://gitlab.com/gitlab-org/gitlab/-/issues/351383) improvements to the
|
||||
[official backup and restore Rake tasks](../../raketasks/backup_restore.md) to add support for incremental backups. For
|
||||
more information, see [this epic](https://gitlab.com/groups/gitlab-org/-/epics/2094).
|
||||
If you are unable to use either method, please contact customer support for restoration help.
|
||||
|
||||
### What to do if you are on Gitaly Cluster experiencing an issue or limitation
|
||||
|
||||
|
|
|
@ -170,7 +170,7 @@ Each feature flag is defined in a separate YAML file consisting of a number of f
|
|||
| `default_enabled` | yes | The default state of the feature flag. |
|
||||
| `introduced_by_url` | no | The URL to the merge request that introduced the feature flag. |
|
||||
| `rollout_issue_url` | no | The URL to the Issue covering the feature flag rollout. |
|
||||
| `milestone` | no | Milestone in which the feature was added. |
|
||||
| `milestone` | no | Milestone in which the feature flag was created. |
|
||||
| `group` | no | The [group](https://about.gitlab.com/handbook/product/categories/#devops-stages) that owns the feature flag. |
|
||||
|
||||
NOTE:
|
||||
|
|
|
@ -403,11 +403,6 @@ msgid_plural "%d tags per image name"
|
|||
msgstr[0] ""
|
||||
msgstr[1] ""
|
||||
|
||||
msgid "%d unassigned issue"
|
||||
msgid_plural "%d unassigned issues"
|
||||
msgstr[0] ""
|
||||
msgstr[1] ""
|
||||
|
||||
msgid "%d unresolved thread"
|
||||
msgid_plural "%d unresolved threads"
|
||||
msgstr[0] ""
|
||||
|
|
|
@ -248,7 +248,7 @@
|
|||
"webpack-dev-server": "4.10.0",
|
||||
"xhr-mock": "^2.5.1",
|
||||
"yarn-check-webpack-plugin": "^1.2.0",
|
||||
"yarn-deduplicate": "^5.0.0"
|
||||
"yarn-deduplicate": "^5.0.2"
|
||||
},
|
||||
"blockedDependencies": {
|
||||
"bootstrap-vue": "https://docs.gitlab.com/ee/development/fe_guide/dependencies.html#bootstrapvue"
|
||||
|
|
|
@ -8,9 +8,14 @@ module QA
|
|||
describe 'Project import' do
|
||||
let(:logger) { Runtime::Logger.logger }
|
||||
let(:differ) { RSpec::Support::Differ.new(color: true) }
|
||||
let(:gitlab_address) { QA::Runtime::Scenario.gitlab_address }
|
||||
let(:dummy_url) { "https://example.com" }
|
||||
|
||||
let(:created_by_pattern) { /\*Created by: \S+\*\n\n/ }
|
||||
let(:suggestion_pattern) { /suggestion:-\d+\+\d+/ }
|
||||
let(:gh_link_pattern) { %r{https://github.com/#{github_repo}/(issues|pull)} }
|
||||
let(:gl_link_pattern) { %r{#{gitlab_address}/#{imported_project.path_with_namespace}/-/(issues|merge_requests)} }
|
||||
let(:event_pattern) { %r{(un)?assigned( to)? @\S+|mentioned in (issue|merge request) [!#]\d+|changed title from \*\*.*\*\* to \*\*.*\*\*} } # rubocop:disable Layout/LineLength
|
||||
|
||||
let(:api_client) { Runtime::API::Client.as_admin }
|
||||
|
||||
|
@ -83,14 +88,14 @@ module QA
|
|||
let(:gh_issue_comments) do
|
||||
logger.debug("= Fetching issue comments =")
|
||||
github_client.issues_comments(github_repo).each_with_object(Hash.new { |h, k| h[k] = [] }) do |c, hash|
|
||||
hash[c.html_url.gsub(/\#\S+/, "")] << c.body # use base html url as key
|
||||
hash[c.html_url.gsub(/\#\S+/, "")] << c.body&.gsub(gh_link_pattern, dummy_url) # use base html url as key
|
||||
end
|
||||
end
|
||||
|
||||
let(:gh_pr_comments) do
|
||||
logger.debug("= Fetching pr comments =")
|
||||
github_client.pull_requests_comments(github_repo).each_with_object(Hash.new { |h, k| h[k] = [] }) do |c, hash|
|
||||
hash[c.html_url.gsub(/\#\S+/, "")] << c.body # use base html url as key
|
||||
hash[c.html_url.gsub(/\#\S+/, "")] << c.body&.gsub(gh_link_pattern, dummy_url) # use base html url as key
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -135,7 +140,7 @@ module QA
|
|||
target: {
|
||||
name: "GitLab",
|
||||
project_name: imported_project.path_with_namespace,
|
||||
address: QA::Runtime::Scenario.gitlab_address,
|
||||
address: gitlab_address,
|
||||
data: {
|
||||
branches: gl_branches.length,
|
||||
commits: gl_commits.length,
|
||||
|
@ -381,15 +386,16 @@ module QA
|
|||
end
|
||||
|
||||
logger.debug("Fetching comments for mr '#{mr[:title]}'")
|
||||
comments = resource
|
||||
.comments(auto_paginate: true, attempts: 2)
|
||||
.reject { |c| c[:system] || c[:body].match?(/^(\*\*Review:\*\*)|(\*Merged by:).*/) }
|
||||
|
||||
[mr[:iid], {
|
||||
url: mr[:web_url],
|
||||
title: mr[:title],
|
||||
body: sanitize_description(mr[:description]) || '',
|
||||
comments: resource
|
||||
.comments(auto_paginate: true, attempts: 2)
|
||||
# remove system notes
|
||||
.reject { |c| c[:system] || c[:body].match?(/^(\*\*Review:\*\*)|(\*Merged by:).*/) }
|
||||
.map { |c| sanitize_comment(c[:body]) }
|
||||
events: events(comments),
|
||||
comments: non_event_comments(comments)
|
||||
}]
|
||||
end.to_h
|
||||
end
|
||||
|
@ -412,24 +418,54 @@ module QA
|
|||
end
|
||||
|
||||
logger.debug("Fetching comments for issue '#{issue[:title]}'")
|
||||
comments = resource.comments(auto_paginate: true, attempts: 2)
|
||||
|
||||
[issue[:iid], {
|
||||
url: issue[:web_url],
|
||||
title: issue[:title],
|
||||
body: sanitize_description(issue[:description]) || '',
|
||||
comments: resource
|
||||
.comments(auto_paginate: true, attempts: 2)
|
||||
.map { |c| sanitize_comment(c[:body]) }
|
||||
events: events(comments),
|
||||
comments: non_event_comments(comments)
|
||||
}]
|
||||
end.to_h
|
||||
end
|
||||
end
|
||||
|
||||
# Remove added prefixes and legacy diff format from comments
|
||||
# Fetch comments without events
|
||||
#
|
||||
# @param [Array] comments
|
||||
# @return [Array]
|
||||
def non_event_comments(comments)
|
||||
comments
|
||||
.reject { |c| c[:body].match?(event_pattern) }
|
||||
.map { |c| sanitize_comment(c[:body]) }
|
||||
end
|
||||
|
||||
# Events
|
||||
#
|
||||
# @param [Array] comments
|
||||
# @return [Array]
|
||||
def events(comments)
|
||||
comments
|
||||
.select { |c| c[:body].match?(event_pattern) }
|
||||
.map { |c| c[:body] }
|
||||
end
|
||||
|
||||
# Normalize comments and make them directly comparable
|
||||
#
|
||||
# * remove created by prefixes
|
||||
# * unify suggestion format
|
||||
# * replace github and gitlab urls - some of the links to objects get transformed to gitlab entities, some don't,
|
||||
# update all links to example.com for now
|
||||
#
|
||||
# @param [String] body
|
||||
# @return [String]
|
||||
def sanitize_comment(body)
|
||||
body.gsub(created_by_pattern, "").gsub(suggestion_pattern, "suggestion\r")
|
||||
body
|
||||
.gsub(created_by_pattern, "")
|
||||
.gsub(suggestion_pattern, "suggestion\r")
|
||||
.gsub(gl_link_pattern, dummy_url)
|
||||
.gsub(gh_link_pattern, dummy_url)
|
||||
end
|
||||
|
||||
# Remove created by prefix from descripion
|
||||
|
|
|
@ -0,0 +1,49 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe 'Project > Settings > Packages & Registries > Container registry tag expiration policy' do
|
||||
let_it_be(:user) { create(:user) }
|
||||
let_it_be(:project, reload: true) { create(:project, namespace: user.namespace) }
|
||||
|
||||
let(:container_registry_enabled) { true }
|
||||
let(:container_registry_enabled_on_project) { ProjectFeature::ENABLED }
|
||||
|
||||
subject { visit cleanup_image_tags_project_settings_packages_and_registries_path(project) }
|
||||
|
||||
before do
|
||||
project.project_feature.update!(container_registry_access_level: container_registry_enabled_on_project)
|
||||
project.container_expiration_policy.update!(enabled: true)
|
||||
|
||||
sign_in(user)
|
||||
stub_container_registry_config(enabled: container_registry_enabled)
|
||||
end
|
||||
|
||||
context 'as owner', :js do
|
||||
it 'shows available section' do
|
||||
subject
|
||||
|
||||
expect(find('.breadcrumbs')).to have_content('Clean up image tags')
|
||||
end
|
||||
end
|
||||
|
||||
context 'when registry is disabled' do
|
||||
let(:container_registry_enabled) { false }
|
||||
|
||||
it 'does not exists' do
|
||||
subject
|
||||
|
||||
expect(page).to have_gitlab_http_status(:not_found)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when container registry is disabled on project' do
|
||||
let(:container_registry_enabled_on_project) { ProjectFeature::DISABLED }
|
||||
|
||||
it 'does not exists' do
|
||||
subject
|
||||
|
||||
expect(page).to have_gitlab_http_status(:not_found)
|
||||
end
|
||||
end
|
||||
end
|
|
@ -518,17 +518,6 @@ describe('Board Store Mutations', () => {
|
|||
|
||||
expect(state.boardItemsByListId[payload.listId]).toEqual(listState);
|
||||
});
|
||||
|
||||
it("updates the list's items count", () => {
|
||||
expect(state.boardLists['gid://gitlab/List/1'].issuesCount).toBe(1);
|
||||
|
||||
mutations.ADD_BOARD_ITEM_TO_LIST(state, {
|
||||
itemId: mockIssue2.id,
|
||||
listId: mockList.id,
|
||||
});
|
||||
|
||||
expect(state.boardLists['gid://gitlab/List/1'].issuesCount).toBe(2);
|
||||
});
|
||||
});
|
||||
|
||||
describe('REMOVE_BOARD_ITEM_FROM_LIST', () => {
|
||||
|
@ -536,8 +525,7 @@ describe('Board Store Mutations', () => {
|
|||
setBoardsListsState();
|
||||
});
|
||||
|
||||
it("removes an item from a list and updates the list's items count", () => {
|
||||
expect(state.boardLists['gid://gitlab/List/1'].issuesCount).toBe(1);
|
||||
it('removes an item from a list', () => {
|
||||
expect(state.boardItemsByListId['gid://gitlab/List/1']).toContain(mockIssue.id);
|
||||
|
||||
mutations.REMOVE_BOARD_ITEM_FROM_LIST(state, {
|
||||
|
@ -546,7 +534,6 @@ describe('Board Store Mutations', () => {
|
|||
});
|
||||
|
||||
expect(state.boardItemsByListId['gid://gitlab/List/1']).not.toContain(mockIssue.id);
|
||||
expect(state.boardLists['gid://gitlab/List/1'].issuesCount).toBe(0);
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
@ -5,12 +5,13 @@ import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
|
|||
import createMockApollo from 'helpers/mock_apollo_helper';
|
||||
import waitForPromises from 'helpers/wait_for_promises';
|
||||
import WorkItemLinksForm from '~/work_items/components/work_item_links/work_item_links_form.vue';
|
||||
import { WORK_ITEM_TYPE_IDS } from '~/work_items/constants';
|
||||
import projectWorkItemsQuery from '~/work_items/graphql/project_work_items.query.graphql';
|
||||
import projectWorkItemTypesQuery from '~/work_items/graphql/project_work_item_types.query.graphql';
|
||||
import createWorkItemMutation from '~/work_items/graphql/create_work_item.mutation.graphql';
|
||||
import updateWorkItemMutation from '~/work_items/graphql/update_work_item.mutation.graphql';
|
||||
import {
|
||||
availableWorkItemsResponse,
|
||||
projectWorkItemTypesQueryResponse,
|
||||
createWorkItemMutationResponse,
|
||||
updateWorkItemMutationResponse,
|
||||
} from '../../mock_data';
|
||||
|
@ -25,11 +26,13 @@ describe('WorkItemLinksForm', () => {
|
|||
|
||||
const createComponent = async ({
|
||||
listResponse = availableWorkItemsResponse,
|
||||
typesResponse = projectWorkItemTypesQueryResponse,
|
||||
parentConfidential = false,
|
||||
} = {}) => {
|
||||
wrapper = shallowMountExtended(WorkItemLinksForm, {
|
||||
apolloProvider: createMockApollo([
|
||||
[projectWorkItemsQuery, jest.fn().mockResolvedValue(listResponse)],
|
||||
[projectWorkItemTypesQuery, jest.fn().mockResolvedValue(typesResponse)],
|
||||
[updateWorkItemMutation, updateMutationResolver],
|
||||
[createWorkItemMutation, createMutationResolver],
|
||||
]),
|
||||
|
@ -70,7 +73,7 @@ describe('WorkItemLinksForm', () => {
|
|||
input: {
|
||||
title: 'Create task test',
|
||||
projectPath: 'project/path',
|
||||
workItemTypeId: WORK_ITEM_TYPE_IDS.TASK,
|
||||
workItemTypeId: 'gid://gitlab/WorkItems::Type/3',
|
||||
hierarchyWidget: {
|
||||
parentId: 'gid://gitlab/WorkItem/1',
|
||||
},
|
||||
|
@ -92,7 +95,7 @@ describe('WorkItemLinksForm', () => {
|
|||
input: {
|
||||
title: 'Create confidential task',
|
||||
projectPath: 'project/path',
|
||||
workItemTypeId: WORK_ITEM_TYPE_IDS.TASK,
|
||||
workItemTypeId: 'gid://gitlab/WorkItems::Type/3',
|
||||
hierarchyWidget: {
|
||||
parentId: 'gid://gitlab/WorkItem/1',
|
||||
},
|
||||
|
|
|
@ -0,0 +1,70 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe Projects::Settings::PackagesAndRegistriesController do
|
||||
let_it_be(:user) { create(:user) }
|
||||
let_it_be(:project, reload: true) { create(:project, namespace: user.namespace) }
|
||||
|
||||
let(:container_registry_enabled) { true }
|
||||
let(:container_registry_enabled_on_project) { ProjectFeature::ENABLED }
|
||||
|
||||
before do
|
||||
project.project_feature.update!(container_registry_access_level: container_registry_enabled_on_project)
|
||||
project.container_expiration_policy.update!(enabled: true)
|
||||
|
||||
stub_container_registry_config(enabled: container_registry_enabled)
|
||||
end
|
||||
|
||||
describe 'GET #cleanup_tags' do
|
||||
subject { get cleanup_image_tags_namespace_project_settings_packages_and_registries_path(user.namespace, project) }
|
||||
|
||||
context 'when user is unauthorized' do
|
||||
let_it_be(:user) { create(:user) }
|
||||
|
||||
before do
|
||||
project.add_reporter(user)
|
||||
sign_in(user)
|
||||
subject
|
||||
end
|
||||
|
||||
it 'shows 404' do
|
||||
expect(response).to have_gitlab_http_status(:not_found)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when user is authorized' do
|
||||
let(:user) { project.creator }
|
||||
|
||||
before do
|
||||
sign_in(user)
|
||||
subject
|
||||
end
|
||||
|
||||
it 'renders content' do
|
||||
expect(response).to have_gitlab_http_status(:ok)
|
||||
expect(response).to render_template(:cleanup_tags)
|
||||
end
|
||||
|
||||
context 'when registry is disabled' do
|
||||
let(:container_registry_enabled) { false }
|
||||
|
||||
it 'shows 404' do
|
||||
subject
|
||||
|
||||
expect(response).to have_gitlab_http_status(:not_found)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when container registry is disabled on project' do
|
||||
let(:container_registry_enabled_on_project) { ProjectFeature::DISABLED }
|
||||
|
||||
it 'shows 404' do
|
||||
subject
|
||||
|
||||
expect(response).to have_gitlab_http_status(:not_found)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -772,6 +772,16 @@ RSpec.describe 'project routing' do
|
|||
end
|
||||
end
|
||||
|
||||
describe Projects::Settings::PackagesAndRegistriesController, 'routing' do
|
||||
it 'to #show' do
|
||||
expect(get('/gitlab/gitlabhq/-/settings/packages_and_registries')).to route_to('projects/settings/packages_and_registries#show', namespace_id: 'gitlab', project_id: 'gitlabhq')
|
||||
end
|
||||
|
||||
it 'to #cleanup_tags' do
|
||||
expect(get('gitlab/gitlabhq/-/settings/packages_and_registries/cleanup_image_tags')).to route_to('projects/settings/packages_and_registries#cleanup_tags', namespace_id: 'gitlab', project_id: 'gitlabhq')
|
||||
end
|
||||
end
|
||||
|
||||
describe Projects::Settings::IntegrationsController, 'routing' do
|
||||
it 'to #index' do
|
||||
expect(get('/gitlab/gitlabhq/-/settings/integrations')).to route_to('projects/settings/integrations#index', namespace_id: 'gitlab', project_id: 'gitlabhq')
|
||||
|
|
26
yarn.lock
26
yarn.lock
|
@ -3549,10 +3549,10 @@ commander@^6.0.0:
|
|||
resolved "https://registry.yarnpkg.com/commander/-/commander-6.2.1.tgz#0792eb682dfbc325999bb2b84fddddba110ac73c"
|
||||
integrity sha512-U7VdrJFnJgo4xjrHpTzu0yrHPGImdsmD95ZlgYSEajAn2JKzDhDTPG9kBTefmObL2w/ngeZnilk+OV9CG3d7UA==
|
||||
|
||||
commander@^9.2.0:
|
||||
version "9.2.0"
|
||||
resolved "https://registry.yarnpkg.com/commander/-/commander-9.2.0.tgz#6e21014b2ed90d8b7c9647230d8b7a94a4a419a9"
|
||||
integrity sha512-e2i4wANQiSXgnrBlIatyHtP1odfUp0BbV5Y5nEGbxtIrStkEOAAzCUirvLBNXHLr7kwLvJl6V+4V3XV9x7Wd9w==
|
||||
commander@^9.4.0:
|
||||
version "9.4.0"
|
||||
resolved "https://registry.yarnpkg.com/commander/-/commander-9.4.0.tgz#bc4a40918fefe52e22450c111ecd6b7acce6f11c"
|
||||
integrity sha512-sRPT+umqkz90UA8M1yqYfnHlZA7fF6nSphDtxeywPZ49ysjxDQybzk13CL+mXekDRG92skbcqCLVovuCusNmFw==
|
||||
|
||||
commander@~9.0.0:
|
||||
version "9.0.0"
|
||||
|
@ -10496,7 +10496,7 @@ semver@^6.0.0, semver@^6.1.1, semver@^6.1.2, semver@^6.3.0:
|
|||
resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.0.tgz#ee0a64c8af5e8ceea67687b133761e1becbd1d3d"
|
||||
integrity sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==
|
||||
|
||||
semver@^7.3.2, semver@^7.3.4, semver@^7.3.5, semver@^7.3.6:
|
||||
semver@^7.3.2, semver@^7.3.4, semver@^7.3.5, semver@^7.3.6, semver@^7.3.7:
|
||||
version "7.3.7"
|
||||
resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.7.tgz#12c5b649afdbf9049707796e22a4028814ce523f"
|
||||
integrity sha512-QlYTucUYOews+WeEujDoEGziz4K6c47V/Bd+LjSSYcA94p+DmINdf7ncaUinThfvZyu13lN9OY1XDxt8C0Tw0g==
|
||||
|
@ -11486,7 +11486,7 @@ tslib@^1.8.1, tslib@^1.9.0, tslib@^1.9.3:
|
|||
resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00"
|
||||
integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==
|
||||
|
||||
tslib@^2, tslib@^2.1.0, tslib@^2.2.0, tslib@^2.3.0, tslib@^2.3.1, tslib@~2.4.0:
|
||||
tslib@^2, tslib@^2.1.0, tslib@^2.2.0, tslib@^2.3.0, tslib@^2.4.0, tslib@~2.4.0:
|
||||
version "2.4.0"
|
||||
resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.4.0.tgz#7cecaa7f073ce680a05847aa77be941098f36dc3"
|
||||
integrity sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ==
|
||||
|
@ -12459,15 +12459,15 @@ yarn-check-webpack-plugin@^1.2.0:
|
|||
dependencies:
|
||||
chalk "^2.4.2"
|
||||
|
||||
yarn-deduplicate@^5.0.0:
|
||||
version "5.0.0"
|
||||
resolved "https://registry.yarnpkg.com/yarn-deduplicate/-/yarn-deduplicate-5.0.0.tgz#8977b9a4b1a2fd905568c3a23507b1021fa381eb"
|
||||
integrity sha512-sYA5tqBSY3m+DtEcwfMYP1G2zWq1UtWSNg2goESqiu/JXBoBF/Qh+FuTJGGjsrisxL+5yOgq/ez1Rd+KSPwzvA==
|
||||
yarn-deduplicate@^5.0.2:
|
||||
version "5.0.2"
|
||||
resolved "https://registry.yarnpkg.com/yarn-deduplicate/-/yarn-deduplicate-5.0.2.tgz#b56484c94d8f1163a828bf20516607f89c078675"
|
||||
integrity sha512-pxKa+dM7DMQ4X2vYLKqGCUgtEoTtdMVk9gNoIsxsMSP0rOV51IWFcKHfRIcZjAPNgHTrxz46sKB4xr7Nte7jdw==
|
||||
dependencies:
|
||||
"@yarnpkg/lockfile" "^1.1.0"
|
||||
commander "^9.2.0"
|
||||
semver "^7.3.2"
|
||||
tslib "^2.3.1"
|
||||
commander "^9.4.0"
|
||||
semver "^7.3.7"
|
||||
tslib "^2.4.0"
|
||||
|
||||
yn@3.1.1:
|
||||
version "3.1.1"
|
||||
|
|
Loading…
Reference in New Issue