mirror of https://github.com/harness/drone.git
feat: [CDE-563]: add support for intellij IDE (#3165)
* feat: [CDE-552}: add ide url scheme consts * feat: [CDE-552}: define shell interpreter for script * feat: [CDE-552}: add intellij port * feat: [CDE-552}: fix lint * feat: [CDE-552}: add intellij integration * feat: [CDE-552}: add intellij typeBT-10437
parent
2ced78f102
commit
4f739d5127
|
@ -117,17 +117,13 @@ func (i infraProvisioner) paramsForProvisioningTypeExisting(
|
|||
func (i infraProvisioner) getGitspaceScheme(ideType enum.IDEType, gitspaceSchemeFromMetadata string) (string, error) {
|
||||
switch ideType {
|
||||
case enum.IDETypeVSCodeWeb:
|
||||
{
|
||||
return gitspaceSchemeFromMetadata, nil
|
||||
}
|
||||
return gitspaceSchemeFromMetadata, nil
|
||||
case enum.IDETypeVSCode:
|
||||
{
|
||||
return "ssh", nil
|
||||
}
|
||||
return "ssh", nil
|
||||
case enum.IDETypeIntellij:
|
||||
return gitspaceSchemeFromMetadata, nil
|
||||
default:
|
||||
{
|
||||
return "", fmt.Errorf("unknown ideType %s", ideType)
|
||||
}
|
||||
return "", fmt.Errorf("unknown ideType %s", ideType)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -74,6 +74,9 @@ func InstallTools(
|
|||
return err
|
||||
}
|
||||
return nil
|
||||
case enum.IDETypeIntellij:
|
||||
// not installing any tools for intellij
|
||||
return nil
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -0,0 +1,45 @@
|
|||
// Copyright 2023 Harness, Inc.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package ide
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/harness/gitness/types/enum"
|
||||
)
|
||||
|
||||
type Factory struct {
|
||||
ides map[enum.IDEType]IDE
|
||||
}
|
||||
|
||||
func NewFactory(vscode *VSCode, vscodeWeb *VSCodeWeb) Factory {
|
||||
ides := make(map[enum.IDEType]IDE)
|
||||
ides[enum.IDETypeVSCode] = vscode
|
||||
ides[enum.IDETypeVSCodeWeb] = vscodeWeb
|
||||
return Factory{ides: ides}
|
||||
}
|
||||
|
||||
func NewFactoryWithIDEs(ides map[enum.IDEType]IDE) Factory {
|
||||
return Factory{ides: ides}
|
||||
}
|
||||
|
||||
func (f *Factory) GetIDE(ideType enum.IDEType) (IDE, error) {
|
||||
val, exist := f.ides[ideType]
|
||||
if !exist {
|
||||
return nil, fmt.Errorf("unsupported IDE type: %s", ideType)
|
||||
}
|
||||
|
||||
return val, nil
|
||||
}
|
|
@ -32,8 +32,11 @@ import (
|
|||
|
||||
var _ IDE = (*VSCode)(nil)
|
||||
|
||||
const templateSetupSSHServer string = "setup_ssh_server.sh"
|
||||
const templateRunSSHServer string = "run_ssh_server.sh"
|
||||
const (
|
||||
templateSetupSSHServer string = "setup_ssh_server.sh"
|
||||
templateRunSSHServer string = "run_ssh_server.sh"
|
||||
templateSetupVSCodeExtensions string = "setup_vscode_extensions.sh"
|
||||
)
|
||||
|
||||
type VSCodeConfig struct {
|
||||
Port int
|
||||
|
@ -53,6 +56,30 @@ func (v *VSCode) Setup(
|
|||
exec *devcontainer.Exec,
|
||||
args map[gitspaceTypes.IDEArg]interface{},
|
||||
gitspaceLogger gitspaceTypes.GitspaceLogger,
|
||||
) error {
|
||||
gitspaceLogger.Info("Installing ssh-server inside container")
|
||||
err := v.setupSSHServer(ctx, exec, gitspaceLogger)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to setup SSH server: %w", err)
|
||||
}
|
||||
gitspaceLogger.Info("Successfully installed ssh-server")
|
||||
|
||||
gitspaceLogger.Info("Installing vs-code extensions inside container")
|
||||
gitspaceLogger.Info("IDE setup output...")
|
||||
err = v.setupVSCodeExtensions(ctx, exec, args, gitspaceLogger)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to setup vs code extensions: %w", err)
|
||||
}
|
||||
gitspaceLogger.Info("Successfully installed vs-code extensions")
|
||||
gitspaceLogger.Info("Successfully set up IDE inside container")
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (v *VSCode) setupSSHServer(
|
||||
ctx context.Context,
|
||||
exec *devcontainer.Exec,
|
||||
gitspaceLogger gitspaceTypes.GitspaceLogger,
|
||||
) error {
|
||||
osInfoScript := common.GetOSInfoScript()
|
||||
payload := template.SetupSSHServerPayload{
|
||||
|
@ -60,24 +87,49 @@ func (v *VSCode) Setup(
|
|||
AccessType: exec.AccessType,
|
||||
OSInfoScript: osInfoScript,
|
||||
}
|
||||
if err := v.updateVSCodeSetupPayload(args, gitspaceLogger, &payload); err != nil {
|
||||
return err
|
||||
}
|
||||
sshServerScript, err := template.GenerateScriptFromTemplate(
|
||||
templateSetupSSHServer, &payload)
|
||||
if err != nil {
|
||||
return fmt.Errorf(
|
||||
"failed to generate scipt to setup ssh server from template %s: %w", templateSetupSSHServer, err)
|
||||
}
|
||||
|
||||
gitspaceLogger.Info("Installing ssh-server inside container")
|
||||
gitspaceLogger.Info("IDE setup output...")
|
||||
err = common.ExecuteCommandInHomeDirAndLog(ctx, exec, sshServerScript, true, gitspaceLogger, false)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to setup SSH serverr: %w", err)
|
||||
}
|
||||
gitspaceLogger.Info("Successfully installed ssh-server")
|
||||
gitspaceLogger.Info("Successfully set up IDE inside container")
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (v *VSCode) setupVSCodeExtensions(
|
||||
ctx context.Context,
|
||||
exec *devcontainer.Exec,
|
||||
args map[gitspaceTypes.IDEArg]interface{},
|
||||
gitspaceLogger gitspaceTypes.GitspaceLogger,
|
||||
) error {
|
||||
payload := template.SetupVSCodeExtensionsPayload{
|
||||
Username: exec.RemoteUser,
|
||||
}
|
||||
if err := v.updateVSCodeSetupPayload(args, gitspaceLogger, &payload); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
vscodeExtensionsScript, err := template.GenerateScriptFromTemplate(
|
||||
templateSetupVSCodeExtensions, &payload)
|
||||
if err != nil {
|
||||
return fmt.Errorf(
|
||||
"failed to generate scipt to setup vscode extensions from template %s: %w",
|
||||
templateSetupVSCodeExtensions,
|
||||
err,
|
||||
)
|
||||
}
|
||||
|
||||
err = common.ExecuteCommandInHomeDirAndLog(ctx, exec, vscodeExtensionsScript,
|
||||
true, gitspaceLogger, false)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to setup vs-code extensions: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@ -122,7 +174,7 @@ func (v *VSCode) Type() enum.IDEType {
|
|||
func (v *VSCode) updateVSCodeSetupPayload(
|
||||
args map[gitspaceTypes.IDEArg]interface{},
|
||||
gitspaceLogger gitspaceTypes.GitspaceLogger,
|
||||
payload *template.SetupSSHServerPayload,
|
||||
payload *template.SetupVSCodeExtensionsPayload,
|
||||
) error {
|
||||
if args == nil {
|
||||
return nil
|
||||
|
@ -141,7 +193,7 @@ func (v *VSCode) updateVSCodeSetupPayload(
|
|||
func (v *VSCode) handleVSCodeCustomization(
|
||||
args map[gitspaceTypes.IDEArg]interface{},
|
||||
gitspaceLogger gitspaceTypes.GitspaceLogger,
|
||||
payload *template.SetupSSHServerPayload,
|
||||
payload *template.SetupVSCodeExtensionsPayload,
|
||||
) error {
|
||||
customization, exists := args[gitspaceTypes.VSCodeCustomizationArg]
|
||||
if !exists {
|
||||
|
@ -171,7 +223,7 @@ func (v *VSCode) handleVSCodeCustomization(
|
|||
|
||||
func (v *VSCode) handleRepoName(
|
||||
args map[gitspaceTypes.IDEArg]interface{},
|
||||
payload *template.SetupSSHServerPayload,
|
||||
payload *template.SetupVSCodeExtensionsPayload,
|
||||
) error {
|
||||
repoName, exists := args[gitspaceTypes.IDERepoNameArg]
|
||||
if !exists {
|
||||
|
|
|
@ -21,6 +21,7 @@ import (
|
|||
var WireSet = wire.NewSet(
|
||||
ProvideVSCodeWebService,
|
||||
ProvideVSCodeService,
|
||||
ProvideIDEFactory,
|
||||
)
|
||||
|
||||
func ProvideVSCodeWebService(config *VSCodeWebConfig) *VSCodeWeb {
|
||||
|
@ -30,3 +31,7 @@ func ProvideVSCodeWebService(config *VSCodeWebConfig) *VSCodeWeb {
|
|||
func ProvideVSCodeService(config *VSCodeConfig) *VSCode {
|
||||
return NewVsCodeService(config)
|
||||
}
|
||||
|
||||
func ProvideIDEFactory(vscode *VSCode, vscodeWeb *VSCodeWeb) Factory {
|
||||
return NewFactory(vscode, vscodeWeb)
|
||||
}
|
||||
|
|
|
@ -21,6 +21,11 @@ import (
|
|||
"github.com/harness/gitness/types/enum"
|
||||
)
|
||||
|
||||
const (
|
||||
vscodeURLScheme = "vscode-remote"
|
||||
intellijURLScheme = "jetbrains-gateway"
|
||||
)
|
||||
|
||||
type Orchestrator interface {
|
||||
// TriggerStartGitspace fetches the infra resources configured for the gitspace and triggers the infra provisioning.
|
||||
TriggerStartGitspace(ctx context.Context, gitspaceConfig types.GitspaceConfig) *types.GitspaceError
|
||||
|
|
|
@ -74,7 +74,7 @@ func (o orchestrator) ResumeStartGitspace(
|
|||
}
|
||||
gitspaceInstance.AccessKey = &resolvedSecret.SecretValue
|
||||
|
||||
ideSvc, err := o.getIDEService(gitspaceConfig)
|
||||
ideSvc, err := o.ideFactory.GetIDE(gitspaceConfig.IDE)
|
||||
if err != nil {
|
||||
return *gitspaceInstance, &types.GitspaceError{
|
||||
Error: err,
|
||||
|
@ -207,16 +207,17 @@ func generateIDEURL(
|
|||
|
||||
relativeRepoPath := strings.TrimPrefix(startResponse.AbsoluteRepoPath, "/")
|
||||
|
||||
if gitspaceConfig.IDE == enum.IDETypeVSCodeWeb {
|
||||
switch gitspaceConfig.IDE {
|
||||
case enum.IDETypeVSCodeWeb:
|
||||
ideURL = url.URL{
|
||||
Scheme: scheme,
|
||||
Host: host + ":" + forwardedPort,
|
||||
RawQuery: filepath.Join("folder=", relativeRepoPath),
|
||||
}
|
||||
} else if gitspaceConfig.IDE == enum.IDETypeVSCode {
|
||||
case enum.IDETypeVSCode:
|
||||
// TODO: the following userID is hard coded and should be changed.
|
||||
ideURL = url.URL{
|
||||
Scheme: "vscode-remote",
|
||||
Scheme: vscodeURLScheme,
|
||||
Host: "", // Empty since we include the host and port in the path
|
||||
Path: fmt.Sprintf(
|
||||
"ssh-remote+%s@%s:%s",
|
||||
|
@ -225,7 +226,24 @@ func generateIDEURL(
|
|||
filepath.Join(forwardedPort, relativeRepoPath),
|
||||
),
|
||||
}
|
||||
case enum.IDETypeIntellij:
|
||||
idePath := relativeRepoPath + "/.cache"
|
||||
ideURL = url.URL{
|
||||
Scheme: intellijURLScheme,
|
||||
Host: "", // Empty since we include the host and port in the path
|
||||
Path: "connect",
|
||||
Fragment: fmt.Sprintf("idePath=%s&projectPath=%s&host=%s&port=%s&user=%s&type=%s&deploy=%s",
|
||||
idePath,
|
||||
relativeRepoPath,
|
||||
host,
|
||||
forwardedPort,
|
||||
startResponse.RemoteUser,
|
||||
"ssh",
|
||||
"false",
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
ideURLString := ideURL.String()
|
||||
return ideURLString
|
||||
}
|
||||
|
|
|
@ -49,8 +49,7 @@ type orchestrator struct {
|
|||
containerOrchestrator container.Orchestrator
|
||||
eventReporter *events.Reporter
|
||||
config *Config
|
||||
vsCodeService *ide.VSCode
|
||||
vsCodeWebService *ide.VSCodeWeb
|
||||
ideFactory ide.Factory
|
||||
secretResolverFactory *secret.ResolverFactory
|
||||
}
|
||||
|
||||
|
@ -64,8 +63,7 @@ func NewOrchestrator(
|
|||
containerOrchestrator container.Orchestrator,
|
||||
eventReporter *events.Reporter,
|
||||
config *Config,
|
||||
vsCodeService *ide.VSCode,
|
||||
vsCodeWebService *ide.VSCodeWeb,
|
||||
ideFactory ide.Factory,
|
||||
secretResolverFactory *secret.ResolverFactory,
|
||||
) Orchestrator {
|
||||
return orchestrator{
|
||||
|
@ -76,8 +74,7 @@ func NewOrchestrator(
|
|||
containerOrchestrator: containerOrchestrator,
|
||||
eventReporter: eventReporter,
|
||||
config: config,
|
||||
vsCodeService: vsCodeService,
|
||||
vsCodeWebService: vsCodeWebService,
|
||||
ideFactory: ideFactory,
|
||||
secretResolverFactory: secretResolverFactory,
|
||||
}
|
||||
}
|
||||
|
@ -344,27 +341,12 @@ func (o orchestrator) emitGitspaceEvent(
|
|||
})
|
||||
}
|
||||
|
||||
func (o orchestrator) getIDEService(gitspaceConfig types.GitspaceConfig) (ide.IDE, error) {
|
||||
var ideService ide.IDE
|
||||
|
||||
switch gitspaceConfig.IDE {
|
||||
case enum.IDETypeVSCode:
|
||||
ideService = o.vsCodeService
|
||||
case enum.IDETypeVSCodeWeb:
|
||||
ideService = o.vsCodeWebService
|
||||
default:
|
||||
return nil, fmt.Errorf("unsupported IDE: %s", gitspaceConfig.IDE)
|
||||
}
|
||||
|
||||
return ideService, nil
|
||||
}
|
||||
|
||||
func (o orchestrator) getPortsRequiredForGitspace(
|
||||
gitspaceConfig types.GitspaceConfig,
|
||||
devcontainerConfig types.DevcontainerConfig,
|
||||
) ([]types.GitspacePort, error) {
|
||||
// TODO: What if the required ports in the config have deviated from when the last instance was created?
|
||||
resolvedIDE, err := o.getIDEService(gitspaceConfig)
|
||||
resolvedIDE, err := o.ideFactory.GetIDE(gitspaceConfig.IDE)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to get IDE service while checking required Gitspace ports: %w", err)
|
||||
}
|
||||
|
|
|
@ -73,8 +73,12 @@ type SetupSSHServerPayload struct {
|
|||
Username string
|
||||
AccessType enum.GitspaceAccessType
|
||||
OSInfoScript string
|
||||
Extensions string
|
||||
RepoName string
|
||||
}
|
||||
|
||||
type SetupVSCodeExtensionsPayload struct {
|
||||
Username string
|
||||
Extensions string
|
||||
RepoName string
|
||||
}
|
||||
|
||||
type RunSSHServerPayload struct {
|
||||
|
|
|
@ -41,7 +41,6 @@ fi
|
|||
|
||||
username={{ .Username }}
|
||||
accessType={{ .AccessType }}
|
||||
repoName="{{ .RepoName }}"
|
||||
|
||||
# Configure SSH to allow this user
|
||||
config_file='/etc/ssh/sshd_config'
|
||||
|
@ -81,28 +80,3 @@ fi
|
|||
|
||||
mkdir -p /var/run/sshd
|
||||
|
||||
# Create .vscode/extensions.json for the current user
|
||||
USER_HOME=$(eval echo ~$username)
|
||||
VSCODE_REMOTE_DIR="$USER_HOME/$repoName/.vscode"
|
||||
EXTENSIONS_FILE="$VSCODE_REMOTE_DIR/extensions.json"
|
||||
|
||||
# Create .vscode directory with correct ownership
|
||||
if [ ! -d "$VSCODE_REMOTE_DIR" ]; then
|
||||
echo "Creating directory: $VSCODE_REMOTE_DIR"
|
||||
mkdir -p "$VSCODE_REMOTE_DIR"
|
||||
chown "$username:$username" "$VSCODE_REMOTE_DIR"
|
||||
fi
|
||||
|
||||
# Create extensions.json file with correct ownership
|
||||
if [ ! -f "$EXTENSIONS_FILE" ]; then
|
||||
echo "Creating extensions.json file for user $username"
|
||||
cat <<EOF > "$EXTENSIONS_FILE"
|
||||
{
|
||||
"recommendations": {{ .Extensions }}
|
||||
}
|
||||
EOF
|
||||
chmod 644 "$EXTENSIONS_FILE"
|
||||
chown "$username:$username" "$EXTENSIONS_FILE"
|
||||
else
|
||||
echo "extensions.json already exists for user $username"
|
||||
fi
|
||||
|
|
|
@ -0,0 +1,30 @@
|
|||
#!/bin/sh
|
||||
|
||||
username={{ .Username }}
|
||||
repoName="{{ .RepoName }}"
|
||||
|
||||
# Create .vscode/extensions.json for the current user
|
||||
USER_HOME=$(eval echo ~$username)
|
||||
VSCODE_REMOTE_DIR="$USER_HOME/$repoName/.vscode"
|
||||
EXTENSIONS_FILE="$VSCODE_REMOTE_DIR/extensions.json"
|
||||
|
||||
# Create .vscode directory with correct ownership
|
||||
if [ ! -d "$VSCODE_REMOTE_DIR" ]; then
|
||||
echo "Creating directory: $VSCODE_REMOTE_DIR"
|
||||
mkdir -p "$VSCODE_REMOTE_DIR"
|
||||
chown "$username:$username" "$VSCODE_REMOTE_DIR"
|
||||
fi
|
||||
|
||||
# Create extensions.json file with correct ownership
|
||||
if [ ! -f "$EXTENSIONS_FILE" ]; then
|
||||
echo "Creating extensions.json file for user $username"
|
||||
cat <<EOF > "$EXTENSIONS_FILE"
|
||||
{
|
||||
"recommendations": {{ .Extensions }}
|
||||
}
|
||||
EOF
|
||||
chmod 644 "$EXTENSIONS_FILE"
|
||||
chown "$username:$username" "$EXTENSIONS_FILE"
|
||||
else
|
||||
echo "extensions.json already exists for user $username"
|
||||
fi
|
|
@ -40,8 +40,7 @@ func ProvideOrchestrator(
|
|||
containerOrchestrator container.Orchestrator,
|
||||
reporter *events.Reporter,
|
||||
config *Config,
|
||||
vsCodeService *ide.VSCode,
|
||||
vsCodeWebService *ide.VSCodeWeb,
|
||||
ideFactory ide.Factory,
|
||||
secretResolverFactory *secret.ResolverFactory,
|
||||
) Orchestrator {
|
||||
return NewOrchestrator(
|
||||
|
@ -52,8 +51,7 @@ func ProvideOrchestrator(
|
|||
containerOrchestrator,
|
||||
reporter,
|
||||
config,
|
||||
vsCodeService,
|
||||
vsCodeWebService,
|
||||
ideFactory,
|
||||
secretResolverFactory,
|
||||
)
|
||||
}
|
||||
|
|
|
@ -333,9 +333,10 @@ func initSystem(ctx context.Context, config *types.Config) (*server.System, erro
|
|||
vsCode := ide.ProvideVSCodeService(vsCodeConfig)
|
||||
vsCodeWebConfig := server.ProvideIDEVSCodeWebConfig(config)
|
||||
vsCodeWeb := ide.ProvideVSCodeWebService(vsCodeWebConfig)
|
||||
ideFactory := ide.ProvideIDEFactory(vsCode, vsCodeWeb)
|
||||
passwordResolver := secret.ProvidePasswordResolver()
|
||||
resolverFactory := secret.ProvideResolverFactory(passwordResolver)
|
||||
orchestratorOrchestrator := orchestrator.ProvideOrchestrator(scmSCM, platformConnector, infraProviderResourceStore, infraProvisioner, containerOrchestrator, eventsReporter, orchestratorConfig, vsCode, vsCodeWeb, resolverFactory)
|
||||
orchestratorOrchestrator := orchestrator.ProvideOrchestrator(scmSCM, platformConnector, infraProviderResourceStore, infraProvisioner, containerOrchestrator, eventsReporter, orchestratorConfig, ideFactory, resolverFactory)
|
||||
gitspaceService := gitspace.ProvideGitspace(transactor, gitspaceConfigStore, gitspaceInstanceStore, eventsReporter, gitspaceEventStore, spaceStore, infraproviderService, orchestratorOrchestrator, scmSCM, config)
|
||||
spaceController := space.ProvideController(config, transactor, provider, streamer, spaceIdentifier, authorizer, spacePathStore, pipelineStore, secretStore, connectorStore, templateStore, spaceStore, repoStore, principalStore, repoController, membershipStore, listService, repository, exporterRepository, resourceLimiter, publicaccessService, auditService, gitspaceService, labelService, instrumentService, executionStore, rulesService)
|
||||
reporter3, err := events5.ProvideReporter(eventsSystem)
|
||||
|
|
|
@ -411,6 +411,11 @@ type Config struct {
|
|||
// Port is the port on which the SSH server for VSCode will be accessible.
|
||||
Port int `envconfig:"GITNESS_IDE_VSCODE_PORT" default:"8088"`
|
||||
}
|
||||
|
||||
Intellij struct {
|
||||
// Port is the port on which the SSH server for Intellij will be accessible.
|
||||
Port int `envconfig:"GITNESS_IDE_INTELLIJ_PORT" default:"8090"`
|
||||
}
|
||||
}
|
||||
|
||||
Gitspace struct {
|
||||
|
|
|
@ -18,9 +18,10 @@ type IDEType string
|
|||
|
||||
func (IDEType) Enum() []interface{} { return toInterfaceSlice(ideTypes) }
|
||||
|
||||
var ideTypes = []IDEType{IDETypeVSCode, IDETypeVSCodeWeb}
|
||||
var ideTypes = []IDEType{IDETypeVSCode, IDETypeVSCodeWeb, IDETypeIntellij}
|
||||
|
||||
const (
|
||||
IDETypeVSCode IDEType = "vs_code"
|
||||
IDETypeVSCodeWeb IDEType = "vs_code_web"
|
||||
IDETypeIntellij IDEType = "intellij"
|
||||
)
|
||||
|
|
Loading…
Reference in New Issue