diff --git a/.github/workflows/build-test.yml b/.github/workflows/build-test.yml
index 4857fda3f..b6388804b 100644
--- a/.github/workflows/build-test.yml
+++ b/.github/workflows/build-test.yml
@@ -72,6 +72,20 @@ jobs:
- name: Install dependencies
run: pnpm install --frozen-lockfile
+ - name: Gradle Wrapper Validation
+ uses: gradle/wrapper-validation-action@v1.1.0
+
+ - name: Setup Java
+ uses: actions/setup-java@v4
+ with:
+ distribution: zulu
+ java-version: 17
+
+ - name: Setup Gradle
+ uses: gradle/gradle-build-action@v2
+ with:
+ gradle-home-cache-cleanup: true
+
- name: Build
run: DEFAULT_NPM_TAG=latest pnpm run build
diff --git a/packages/ide/jetbrains/.gitignore b/packages/ide/jetbrains/.gitignore
new file mode 100644
index 000000000..80b28c077
--- /dev/null
+++ b/packages/ide/jetbrains/.gitignore
@@ -0,0 +1,18 @@
+.gradle
+build/
+!gradle/wrapper/gradle-wrapper.jar
+!**/src/main/**/build/
+!**/src/test/**/build/
+
+### IntelliJ IDEA ###
+.idea/modules.xml
+.idea/jarRepositories.xml
+.idea/compiler.xml
+.idea/libraries/
+*.iws
+*.iml
+*.ipr
+out/
+!**/src/main/**/out/
+!**/src/test/**/out/
+.sign
diff --git a/packages/ide/jetbrains/.idea/.gitignore b/packages/ide/jetbrains/.idea/.gitignore
new file mode 100644
index 000000000..13566b81b
--- /dev/null
+++ b/packages/ide/jetbrains/.idea/.gitignore
@@ -0,0 +1,8 @@
+# Default ignored files
+/shelf/
+/workspace.xml
+# Editor-based HTTP Client requests
+/httpRequests/
+# Datasource local storage ignored files
+/dataSources/
+/dataSources.local.xml
diff --git a/packages/ide/jetbrains/.idea/.name b/packages/ide/jetbrains/.idea/.name
new file mode 100644
index 000000000..8aa065d41
--- /dev/null
+++ b/packages/ide/jetbrains/.idea/.name
@@ -0,0 +1 @@
+zenstack
\ No newline at end of file
diff --git a/packages/ide/jetbrains/.idea/gradle.xml b/packages/ide/jetbrains/.idea/gradle.xml
new file mode 100644
index 000000000..f9163b40e
--- /dev/null
+++ b/packages/ide/jetbrains/.idea/gradle.xml
@@ -0,0 +1,15 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/packages/ide/jetbrains/.idea/kotlinc.xml b/packages/ide/jetbrains/.idea/kotlinc.xml
new file mode 100644
index 000000000..ae3f30ae1
--- /dev/null
+++ b/packages/ide/jetbrains/.idea/kotlinc.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/packages/ide/jetbrains/.idea/misc.xml b/packages/ide/jetbrains/.idea/misc.xml
new file mode 100644
index 000000000..68bf986ec
--- /dev/null
+++ b/packages/ide/jetbrains/.idea/misc.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/packages/ide/jetbrains/.idea/vcs.xml b/packages/ide/jetbrains/.idea/vcs.xml
new file mode 100644
index 000000000..b2bdec2d7
--- /dev/null
+++ b/packages/ide/jetbrains/.idea/vcs.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/packages/ide/jetbrains/build.gradle.kts b/packages/ide/jetbrains/build.gradle.kts
new file mode 100644
index 000000000..fa0e3ed31
--- /dev/null
+++ b/packages/ide/jetbrains/build.gradle.kts
@@ -0,0 +1,68 @@
+plugins {
+ id("java")
+ id("org.jetbrains.kotlin.jvm") version "1.9.21"
+ id("org.jetbrains.intellij") version "1.16.1"
+}
+
+group = "dev.zenstack"
+version = "0.5.0"
+
+repositories {
+ mavenCentral()
+}
+
+// Configure Gradle IntelliJ Plugin
+// Read more: https://plugins.jetbrains.com/docs/intellij/tools-gradle-intellij-plugin.html
+intellij {
+ version.set("2023.3.2")
+ type.set("IU") // Target IDE Platform
+
+ plugins.set(listOf("JavaScript", "org.jetbrains.plugins.textmate"))
+}
+
+tasks {
+ // Set the JVM compatibility versions
+ withType {
+ sourceCompatibility = "17"
+ targetCompatibility = "17"
+ }
+ withType {
+ kotlinOptions.jvmTarget = "17"
+ }
+
+ prepareSandbox {
+ doLast {
+ copy {
+ from("${project.projectDir}/../../schema/bundle/language-server/main.js")
+ into("${destinationDir.path}/zenstack/language-server/")
+ }
+ copy {
+ from("${project.projectDir}/../../schema/src/res/stdlib.zmodel")
+ into("${destinationDir.path}/zenstack/res/")
+ }
+ copy {
+ from("${project.projectDir}/src/main/textMate/zmodel.tmbundle")
+ into("${destinationDir.path}/zenstack/res/zmodel.tmbundle")
+ }
+ copy {
+ from("${project.projectDir}/../../language/syntaxes/zmodel.tmLanguage")
+ into("${destinationDir.path}/zenstack/res/zmodel.tmbundle/Syntaxes/")
+ }
+ }
+ }
+
+ patchPluginXml {
+ sinceBuild.set("231")
+ untilBuild.set("241.*")
+ }
+
+ signPlugin {
+ certificateChain.set(System.getenv("CERTIFICATE_CHAIN"))
+ privateKey.set(System.getenv("PRIVATE_KEY"))
+ password.set(System.getenv("PRIVATE_KEY_PASSWORD"))
+ }
+
+ publishPlugin {
+ token.set(System.getenv("PUBLISH_TOKEN"))
+ }
+}
diff --git a/packages/ide/jetbrains/gradle.properties b/packages/ide/jetbrains/gradle.properties
new file mode 100644
index 000000000..e1c1990f8
--- /dev/null
+++ b/packages/ide/jetbrains/gradle.properties
@@ -0,0 +1,6 @@
+# Opt-out flag for bundling Kotlin standard library -> https://jb.gg/intellij-platform-kotlin-stdlib
+kotlin.stdlib.default.dependency=false
+# Enable Gradle Configuration Cache -> https://docs.gradle.org/current/userguide/configuration_cache.html
+org.gradle.configuration-cache=false
+# Enable Gradle Build Cache -> https://docs.gradle.org/current/userguide/build_cache.html
+org.gradle.caching=true
diff --git a/packages/ide/jetbrains/gradle/wrapper/gradle-wrapper.jar b/packages/ide/jetbrains/gradle/wrapper/gradle-wrapper.jar
new file mode 100644
index 000000000..249e5832f
Binary files /dev/null and b/packages/ide/jetbrains/gradle/wrapper/gradle-wrapper.jar differ
diff --git a/packages/ide/jetbrains/gradle/wrapper/gradle-wrapper.properties b/packages/ide/jetbrains/gradle/wrapper/gradle-wrapper.properties
new file mode 100644
index 000000000..a59520664
--- /dev/null
+++ b/packages/ide/jetbrains/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,5 @@
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+distributionUrl=https\://services.gradle.org/distributions/gradle-8.5-bin.zip
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
diff --git a/packages/ide/jetbrains/gradlew b/packages/ide/jetbrains/gradlew
new file mode 100755
index 000000000..1b6c78733
--- /dev/null
+++ b/packages/ide/jetbrains/gradlew
@@ -0,0 +1,234 @@
+#!/bin/sh
+
+#
+# Copyright © 2015-2021 the original authors.
+#
+# 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
+#
+# https://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.
+#
+
+##############################################################################
+#
+# Gradle start up script for POSIX generated by Gradle.
+#
+# Important for running:
+#
+# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
+# noncompliant, but you have some other compliant shell such as ksh or
+# bash, then to run this script, type that shell name before the whole
+# command line, like:
+#
+# ksh Gradle
+#
+# Busybox and similar reduced shells will NOT work, because this script
+# requires all of these POSIX shell features:
+# * functions;
+# * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
+# «${var#prefix}», «${var%suffix}», and «$( cmd )»;
+# * compound commands having a testable exit status, especially «case»;
+# * various built-in commands including «command», «set», and «ulimit».
+#
+# Important for patching:
+#
+# (2) This script targets any POSIX shell, so it avoids extensions provided
+# by Bash, Ksh, etc; in particular arrays are avoided.
+#
+# The "traditional" practice of packing multiple parameters into a
+# space-separated string is a well documented source of bugs and security
+# problems, so this is (mostly) avoided, by progressively accumulating
+# options in "$@", and eventually passing that to Java.
+#
+# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
+# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
+# see the in-line comments for details.
+#
+# There are tweaks for specific operating systems such as AIX, CygWin,
+# Darwin, MinGW, and NonStop.
+#
+# (3) This script is generated from the Groovy template
+# https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
+# within the Gradle project.
+#
+# You can find Gradle at https://github.com/gradle/gradle/.
+#
+##############################################################################
+
+# Attempt to set APP_HOME
+
+# Resolve links: $0 may be a link
+app_path=$0
+
+# Need this for daisy-chained symlinks.
+while
+ APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
+ [ -h "$app_path" ]
+do
+ ls=$( ls -ld "$app_path" )
+ link=${ls#*' -> '}
+ case $link in #(
+ /*) app_path=$link ;; #(
+ *) app_path=$APP_HOME$link ;;
+ esac
+done
+
+APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit
+
+APP_NAME="Gradle"
+APP_BASE_NAME=${0##*/}
+
+# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
+
+# Use the maximum available, or set MAX_FD != -1 to use that value.
+MAX_FD=maximum
+
+warn () {
+ echo "$*"
+} >&2
+
+die () {
+ echo
+ echo "$*"
+ echo
+ exit 1
+} >&2
+
+# OS specific support (must be 'true' or 'false').
+cygwin=false
+msys=false
+darwin=false
+nonstop=false
+case "$( uname )" in #(
+ CYGWIN* ) cygwin=true ;; #(
+ Darwin* ) darwin=true ;; #(
+ MSYS* | MINGW* ) msys=true ;; #(
+ NONSTOP* ) nonstop=true ;;
+esac
+
+CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
+
+
+# Determine the Java command to use to start the JVM.
+if [ -n "$JAVA_HOME" ] ; then
+ if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+ # IBM's JDK on AIX uses strange locations for the executables
+ JAVACMD=$JAVA_HOME/jre/sh/java
+ else
+ JAVACMD=$JAVA_HOME/bin/java
+ fi
+ if [ ! -x "$JAVACMD" ] ; then
+ die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+ fi
+else
+ JAVACMD=java
+ which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+fi
+
+# Increase the maximum file descriptors if we can.
+if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
+ case $MAX_FD in #(
+ max*)
+ MAX_FD=$( ulimit -H -n ) ||
+ warn "Could not query maximum file descriptor limit"
+ esac
+ case $MAX_FD in #(
+ '' | soft) :;; #(
+ *)
+ ulimit -n "$MAX_FD" ||
+ warn "Could not set maximum file descriptor limit to $MAX_FD"
+ esac
+fi
+
+# Collect all arguments for the java command, stacking in reverse order:
+# * args from the command line
+# * the main class name
+# * -classpath
+# * -D...appname settings
+# * --module-path (only if needed)
+# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
+
+# For Cygwin or MSYS, switch paths to Windows format before running java
+if "$cygwin" || "$msys" ; then
+ APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
+ CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
+
+ JAVACMD=$( cygpath --unix "$JAVACMD" )
+
+ # Now convert the arguments - kludge to limit ourselves to /bin/sh
+ for arg do
+ if
+ case $arg in #(
+ -*) false ;; # don't mess with options #(
+ /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
+ [ -e "$t" ] ;; #(
+ *) false ;;
+ esac
+ then
+ arg=$( cygpath --path --ignore --mixed "$arg" )
+ fi
+ # Roll the args list around exactly as many times as the number of
+ # args, so each arg winds up back in the position where it started, but
+ # possibly modified.
+ #
+ # NB: a `for` loop captures its iteration list before it begins, so
+ # changing the positional parameters here affects neither the number of
+ # iterations, nor the values presented in `arg`.
+ shift # remove old arg
+ set -- "$@" "$arg" # push replacement arg
+ done
+fi
+
+# Collect all arguments for the java command;
+# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of
+# shell script including quotes and variable substitutions, so put them in
+# double quotes to make sure that they get re-expanded; and
+# * put everything else in single quotes, so that it's not re-expanded.
+
+set -- \
+ "-Dorg.gradle.appname=$APP_BASE_NAME" \
+ -classpath "$CLASSPATH" \
+ org.gradle.wrapper.GradleWrapperMain \
+ "$@"
+
+# Use "xargs" to parse quoted args.
+#
+# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
+#
+# In Bash we could simply go:
+#
+# readarray ARGS < <( xargs -n1 <<<"$var" ) &&
+# set -- "${ARGS[@]}" "$@"
+#
+# but POSIX shell has neither arrays nor command substitution, so instead we
+# post-process each arg (as a line of input to sed) to backslash-escape any
+# character that might be a shell metacharacter, then use eval to reverse
+# that process (while maintaining the separation between arguments), and wrap
+# the whole thing up as a single "set" statement.
+#
+# This will of course break if any of these variables contains a newline or
+# an unmatched quote.
+#
+
+eval "set -- $(
+ printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
+ xargs -n1 |
+ sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
+ tr '\n' ' '
+ )" '"$@"'
+
+exec "$JAVACMD" "$@"
diff --git a/packages/ide/jetbrains/gradlew.bat b/packages/ide/jetbrains/gradlew.bat
new file mode 100644
index 000000000..ac1b06f93
--- /dev/null
+++ b/packages/ide/jetbrains/gradlew.bat
@@ -0,0 +1,89 @@
+@rem
+@rem Copyright 2015 the original author or authors.
+@rem
+@rem Licensed under the Apache License, Version 2.0 (the "License");
+@rem you may not use this file except in compliance with the License.
+@rem You may obtain a copy of the License at
+@rem
+@rem https://www.apache.org/licenses/LICENSE-2.0
+@rem
+@rem Unless required by applicable law or agreed to in writing, software
+@rem distributed under the License is distributed on an "AS IS" BASIS,
+@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+@rem See the License for the specific language governing permissions and
+@rem limitations under the License.
+@rem
+
+@if "%DEBUG%" == "" @echo off
+@rem ##########################################################################
+@rem
+@rem Gradle startup script for Windows
+@rem
+@rem ##########################################################################
+
+@rem Set local scope for the variables with windows NT shell
+if "%OS%"=="Windows_NT" setlocal
+
+set DIRNAME=%~dp0
+if "%DIRNAME%" == "" set DIRNAME=.
+set APP_BASE_NAME=%~n0
+set APP_HOME=%DIRNAME%
+
+@rem Resolve any "." and ".." in APP_HOME to make it shorter.
+for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
+
+@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
+
+@rem Find java.exe
+if defined JAVA_HOME goto findJavaFromJavaHome
+
+set JAVA_EXE=java.exe
+%JAVA_EXE% -version >NUL 2>&1
+if "%ERRORLEVEL%" == "0" goto execute
+
+echo.
+echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:findJavaFromJavaHome
+set JAVA_HOME=%JAVA_HOME:"=%
+set JAVA_EXE=%JAVA_HOME%/bin/java.exe
+
+if exist "%JAVA_EXE%" goto execute
+
+echo.
+echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:execute
+@rem Setup the command line
+
+set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
+
+
+@rem Execute Gradle
+"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
+
+:end
+@rem End local scope for the variables with windows NT shell
+if "%ERRORLEVEL%"=="0" goto mainEnd
+
+:fail
+rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
+rem the _cmd.exe /c_ return code!
+if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
+exit /b 1
+
+:mainEnd
+if "%OS%"=="Windows_NT" endlocal
+
+:omega
diff --git a/packages/ide/jetbrains/package.json b/packages/ide/jetbrains/package.json
new file mode 100644
index 000000000..a7414f0bf
--- /dev/null
+++ b/packages/ide/jetbrains/package.json
@@ -0,0 +1,17 @@
+{
+ "name": "jetbrains",
+ "version": "1.5.0",
+ "displayName": "ZenStack JetBrains IDE Plugin",
+ "description": "ZenStack JetBrains IDE plugin",
+ "homepage": "https://zenstack.dev",
+ "private": true,
+ "scripts": {
+ "build": "./gradlew buildPlugin"
+ },
+ "author": "ZenStack Team",
+ "license": "MIT",
+ "devDependencies": {
+ "zenstack": "workspace:*",
+ "@zenstackhq/language": "workspace:*"
+ }
+}
diff --git a/packages/ide/jetbrains/settings.gradle.kts b/packages/ide/jetbrains/settings.gradle.kts
new file mode 100644
index 000000000..e0ac2ff8b
--- /dev/null
+++ b/packages/ide/jetbrains/settings.gradle.kts
@@ -0,0 +1,8 @@
+pluginManagement {
+ repositories {
+ mavenCentral()
+ gradlePluginPortal()
+ }
+}
+
+rootProject.name = "zenstack"
\ No newline at end of file
diff --git a/packages/ide/jetbrains/src/main/kotlin/dev/zenstack/lang/ZModelFileType.kt b/packages/ide/jetbrains/src/main/kotlin/dev/zenstack/lang/ZModelFileType.kt
new file mode 100644
index 000000000..b0c4ada21
--- /dev/null
+++ b/packages/ide/jetbrains/src/main/kotlin/dev/zenstack/lang/ZModelFileType.kt
@@ -0,0 +1,14 @@
+package dev.zenstack.lang
+
+import com.intellij.openapi.fileTypes.LanguageFileType
+import javax.swing.Icon
+
+object ZModelFileType : LanguageFileType(ZModelLanguage) {
+ override fun getName(): String = "ZModel"
+
+ override fun getDescription(): String = "ZModel Language"
+
+ override fun getDefaultExtension(): String = "zmodel"
+
+ override fun getIcon(): Icon = ZModelIcons.ZModel
+}
\ No newline at end of file
diff --git a/packages/ide/jetbrains/src/main/kotlin/dev/zenstack/lang/ZModelIcons.kt b/packages/ide/jetbrains/src/main/kotlin/dev/zenstack/lang/ZModelIcons.kt
new file mode 100644
index 000000000..f168410d6
--- /dev/null
+++ b/packages/ide/jetbrains/src/main/kotlin/dev/zenstack/lang/ZModelIcons.kt
@@ -0,0 +1,8 @@
+package dev.zenstack.lang
+
+import com.intellij.openapi.util.IconLoader;
+
+object ZModelIcons {
+ @JvmField
+ val ZModel = IconLoader.getIcon("/icons/zmodel.png", javaClass)
+}
diff --git a/packages/ide/jetbrains/src/main/kotlin/dev/zenstack/lang/ZModelLanguage.kt b/packages/ide/jetbrains/src/main/kotlin/dev/zenstack/lang/ZModelLanguage.kt
new file mode 100644
index 000000000..cfc60749f
--- /dev/null
+++ b/packages/ide/jetbrains/src/main/kotlin/dev/zenstack/lang/ZModelLanguage.kt
@@ -0,0 +1,7 @@
+package dev.zenstack.lang
+
+import com.intellij.lang.Language
+
+object ZModelLanguage : Language("ZModel") {
+ override fun getDisplayName(): String = "ZModel"
+}
diff --git a/packages/ide/jetbrains/src/main/kotlin/dev/zenstack/lsp/ZenStackLspServerDescriptor.kt b/packages/ide/jetbrains/src/main/kotlin/dev/zenstack/lsp/ZenStackLspServerDescriptor.kt
new file mode 100644
index 000000000..3d1219574
--- /dev/null
+++ b/packages/ide/jetbrains/src/main/kotlin/dev/zenstack/lsp/ZenStackLspServerDescriptor.kt
@@ -0,0 +1,62 @@
+package dev.zenstack.lsp
+
+import com.intellij.codeInsight.completion.CompletionParameters
+import com.intellij.execution.ExecutionException
+import com.intellij.execution.configurations.GeneralCommandLine
+import com.intellij.javascript.nodejs.interpreter.NodeCommandLineConfigurator
+import com.intellij.javascript.nodejs.interpreter.NodeJsInterpreterManager
+import com.intellij.javascript.nodejs.interpreter.local.NodeJsLocalInterpreter
+import com.intellij.javascript.nodejs.interpreter.wsl.WslNodeInterpreter
+import com.intellij.lang.javascript.service.JSLanguageServiceUtil
+import com.intellij.openapi.project.Project
+import com.intellij.openapi.vfs.VirtualFile
+import com.intellij.platform.lsp.api.ProjectWideLspServerDescriptor
+import com.intellij.platform.lsp.api.customization.LspCompletionSupport
+import com.intellij.platform.lsp.api.customization.LspFormattingSupport
+import org.jetbrains.plugins.textmate.configuration.TextMateUserBundlesSettings
+import org.jetbrains.plugins.textmate.TextMateService;
+import dev.zenstack.lang.ZModelFileType
+
+class ZenStackLspServerDescriptor(project: Project) : ProjectWideLspServerDescriptor(project, "ZenStack") {
+
+ override fun isSupportedFile(file: VirtualFile) = file.fileType == ZModelFileType
+
+ override fun createCommandLine(): GeneralCommandLine {
+
+ // install TextMate bundle
+ val textMateBundle = JSLanguageServiceUtil.getPluginDirectory(javaClass, "res/zmodel.tmbundle")
+ TextMateUserBundlesSettings.instance?.addBundle(textMateBundle.path, "zmodel")
+ val textMateService = TextMateService.getInstance();
+ textMateService.reloadEnabledBundles()
+
+ // start language server
+ val interpreter = NodeJsInterpreterManager.getInstance(project).interpreter
+ if (interpreter !is NodeJsLocalInterpreter && interpreter !is WslNodeInterpreter) {
+ throw ExecutionException("Interpreter not configured")
+ }
+
+ val lsp = JSLanguageServiceUtil.getPluginDirectory(javaClass, "language-server/main.js")
+ if (lsp == null || !lsp.exists()) {
+ // broken plugin installation?
+ throw ExecutionException("Language server not found")
+ }
+
+ return GeneralCommandLine().apply {
+ withParentEnvironmentType(GeneralCommandLine.ParentEnvironmentType.CONSOLE)
+ withCharset(Charsets.UTF_8)
+ addParameter(lsp.path)
+ addParameter("--stdio")
+
+ NodeCommandLineConfigurator.find(interpreter)
+ .configure(this, NodeCommandLineConfigurator.defaultOptions(project))
+ }
+ }
+
+ override val lspGoToDefinitionSupport = true
+
+ override val lspHoverSupport = true
+
+ override val lspFormattingSupport = object : LspFormattingSupport() {
+ override fun shouldFormatThisFileExclusivelyByServer(file: VirtualFile, ideCanFormatThisFileItself: Boolean, serverExplicitlyWantsToFormatThisFile: Boolean) = file.fileType == ZModelFileType
+ }
+}
diff --git a/packages/ide/jetbrains/src/main/kotlin/dev/zenstack/lsp/ZenStackLspServerSupportProvider.kt b/packages/ide/jetbrains/src/main/kotlin/dev/zenstack/lsp/ZenStackLspServerSupportProvider.kt
new file mode 100644
index 000000000..6fb6d79db
--- /dev/null
+++ b/packages/ide/jetbrains/src/main/kotlin/dev/zenstack/lsp/ZenStackLspServerSupportProvider.kt
@@ -0,0 +1,20 @@
+package dev.zenstack.lsp
+
+import com.intellij.javascript.nodejs.interpreter.NodeJsInterpreterManager
+import com.intellij.javascript.nodejs.interpreter.local.NodeJsLocalInterpreter
+import com.intellij.javascript.nodejs.interpreter.wsl.WslNodeInterpreter
+import com.intellij.openapi.project.Project
+import com.intellij.openapi.vfs.VirtualFile
+import com.intellij.platform.lsp.api.LspServerSupportProvider
+import dev.zenstack.lang.ZModelFileType
+
+class ZenStackLspServerSupportProvider : LspServerSupportProvider {
+ override fun fileOpened(project: Project, file: VirtualFile, serverStarter: LspServerSupportProvider.LspServerStarter) {
+ if (file.fileType != ZModelFileType) return
+
+ val node = NodeJsInterpreterManager.getInstance(project).interpreter
+ if (node !is NodeJsLocalInterpreter && node !is WslNodeInterpreter) return
+
+ serverStarter.ensureServerStarted(ZenStackLspServerDescriptor(project))
+ }
+}
\ No newline at end of file
diff --git a/packages/ide/jetbrains/src/main/resources/META-INF/plugin.xml b/packages/ide/jetbrains/src/main/resources/META-INF/plugin.xml
new file mode 100644
index 000000000..3ee6bf887
--- /dev/null
+++ b/packages/ide/jetbrains/src/main/resources/META-INF/plugin.xml
@@ -0,0 +1,57 @@
+
+
+
+ dev.zenstack.zenstack
+
+
+ ZenStack Language Tools
+
+
+ ZenStack
+
+
+ ZenStack is a toolkit that simplifies the development of a web app's backend. This plugin provides code editing experiences for its ZModel schema language.
+
+ Features
+
+
+ - Syntax highlighting
+ - Go to definition
+ - Code completion
+
+ ]]>
+
+
+ com.intellij.modules.ultimate
+ JavaScript
+ org.jetbrains.plugins.textmate
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/packages/ide/jetbrains/src/main/resources/META-INF/pluginIcon.svg b/packages/ide/jetbrains/src/main/resources/META-INF/pluginIcon.svg
new file mode 100644
index 000000000..c70b27f19
--- /dev/null
+++ b/packages/ide/jetbrains/src/main/resources/META-INF/pluginIcon.svg
@@ -0,0 +1,15 @@
+
+
+
diff --git a/packages/ide/jetbrains/src/main/resources/META-INF/pluginIcon_dark.svg b/packages/ide/jetbrains/src/main/resources/META-INF/pluginIcon_dark.svg
new file mode 100644
index 000000000..6f52be69d
--- /dev/null
+++ b/packages/ide/jetbrains/src/main/resources/META-INF/pluginIcon_dark.svg
@@ -0,0 +1,15 @@
+
+
+
diff --git a/packages/ide/jetbrains/src/main/resources/icons/zmodel.png b/packages/ide/jetbrains/src/main/resources/icons/zmodel.png
new file mode 100644
index 000000000..dab4013a3
Binary files /dev/null and b/packages/ide/jetbrains/src/main/resources/icons/zmodel.png differ
diff --git a/packages/ide/jetbrains/src/main/resources/icons/zmodel_dark.png b/packages/ide/jetbrains/src/main/resources/icons/zmodel_dark.png
new file mode 100644
index 000000000..7b4b2b226
Binary files /dev/null and b/packages/ide/jetbrains/src/main/resources/icons/zmodel_dark.png differ
diff --git a/packages/ide/jetbrains/src/main/textMate/zmodel.tmbundle/info.plist b/packages/ide/jetbrains/src/main/textMate/zmodel.tmbundle/info.plist
new file mode 100644
index 000000000..9abe98fab
--- /dev/null
+++ b/packages/ide/jetbrains/src/main/textMate/zmodel.tmbundle/info.plist
@@ -0,0 +1,16 @@
+
+
+
+
+ contactEmailRot13
+ contact@zenstack.dev
+ contactName
+ ZenStack Team
+ description
+ ZModel Language
+ name
+ zmodel
+ uuid
+ 0EA94AB2-A210-4A58-9A25-CFFF59AC430B
+
+
diff --git a/packages/language/package.json b/packages/language/package.json
index fff1840c2..0977be59c 100644
--- a/packages/language/package.json
+++ b/packages/language/package.json
@@ -6,10 +6,10 @@
"homepage": "https://zenstack.dev",
"scripts": {
"clean": "rimraf dist",
- "generate": "langium generate",
+ "generate": "langium generate && npx ts-node script/generate-plist.ts",
"watch": "concurrently \"langium generate --watch\" \"tsc --watch\"",
"lint": "eslint src --ext ts",
- "build": "pnpm lint --max-warnings=0 && pnpm clean && pnpm generate && tsc && copyfiles -F ./README.md ./LICENSE ./package.json dist && pnpm pack dist --pack-destination '../../../.build'",
+ "build": "pnpm lint --max-warnings=0 && pnpm clean && pnpm generate && tsc && copyfiles -F ./README.md ./LICENSE ./package.json 'syntaxes/**/*' dist && pnpm pack dist --pack-destination '../../../.build'",
"prepublishOnly": "pnpm build"
},
"publishConfig": {
@@ -19,24 +19,27 @@
"author": "ZenStack Team",
"license": "MIT",
"devDependencies": {
- "langium-cli": "1.2.0"
+ "langium-cli": "1.2.0",
+ "plist2": "^1.1.3"
},
"dependencies": {
- "langium": "1.2.0"
+ "langium": "1.2.0"
},
"contributes": {
- "languages": [
- {
- "id": "zmodel",
- "extensions": [".zmodel"]
- }
- ],
- "grammars": [
- {
- "language": "zmodel",
- "scopeName": "source.zmodel",
- "path": "./syntaxes/zmodel.tmLanguage.json"
- }
- ]
+ "languages": [
+ {
+ "id": "zmodel",
+ "extensions": [
+ ".zmodel"
+ ]
+ }
+ ],
+ "grammars": [
+ {
+ "language": "zmodel",
+ "scopeName": "source.zmodel",
+ "path": "./syntaxes/zmodel.tmLanguage.json"
+ }
+ ]
}
}
diff --git a/packages/language/script/generate-plist.ts b/packages/language/script/generate-plist.ts
new file mode 100644
index 000000000..f987f71ed
--- /dev/null
+++ b/packages/language/script/generate-plist.ts
@@ -0,0 +1,12 @@
+// convert textmate grammar from json to plist with fixes
+
+import { json2plist } from 'plist2';
+import fs from 'fs';
+import path from 'path';
+
+const src = fs.readFileSync(path.resolve(__dirname, '../syntaxes/zmodel.tmLanguage.json'), 'utf-8');
+const json = JSON.parse(src);
+json['fileTypes'] = ['zmodel'];
+
+const plist = json2plist(JSON.stringify(json));
+fs.writeFileSync(path.resolve(__dirname, '../syntaxes/zmodel.tmLanguage'), plist, 'utf-8');
diff --git a/packages/language/syntaxes/zmodel.tmLanguage b/packages/language/syntaxes/zmodel.tmLanguage
new file mode 100644
index 000000000..bacb471c3
--- /dev/null
+++ b/packages/language/syntaxes/zmodel.tmLanguage
@@ -0,0 +1,113 @@
+
+
+
+
+ name
+ zmodel
+ scopeName
+ source.zmodel
+ fileTypes
+
+ zmodel
+
+ patterns
+
+
+ include
+ #comments
+
+
+ name
+ keyword.control.zmodel
+ match
+ \b(Any|Asc|BigInt|Boolean|Bytes|ContextType|DateTime|Decimal|Desc|FieldReference|Float|Int|Json|Null|Object|String|TransitiveFieldReference|Unsupported|abstract|attribute|datasource|enum|extends|false|function|generator|import|in|model|plugin|sort|true|view)\b
+
+
+ name
+ string.quoted.double.zmodel
+ begin
+ "
+ end
+ "
+ patterns
+
+
+ include
+ #string-character-escape
+
+
+
+
+ name
+ string.quoted.single.zmodel
+ begin
+ '
+ end
+ '
+ patterns
+
+
+ include
+ #string-character-escape
+
+
+
+
+ repository
+
+ comments
+
+ patterns
+
+
+ name
+ comment.block.zmodel
+ begin
+ /\*
+ beginCaptures
+
+ 0
+
+ name
+ punctuation.definition.comment.zmodel
+
+
+ end
+ \*/
+ endCaptures
+
+ 0
+
+ name
+ punctuation.definition.comment.zmodel
+
+
+
+
+ begin
+ //
+ beginCaptures
+
+ 1
+
+ name
+ punctuation.whitespace.comment.leading.zmodel
+
+
+ end
+ (?=$)
+ name
+ comment.line.zmodel
+
+
+
+ string-character-escape
+
+ name
+ constant.character.escape.zmodel
+ match
+ \\(x[0-9A-Fa-f]{2}|u[0-9A-Fa-f]{4}|u\{[0-9A-Fa-f]+\}|[0-2][0-7]{0,2}|3[0-6][0-7]?|37[0-7]?|[4-7][0-7]?|.|$)
+
+
+
+
\ No newline at end of file
diff --git a/packages/schema/.vscodeignore b/packages/schema/.vscodeignore
index ed687f91e..9551337ea 100644
--- a/packages/schema/.vscodeignore
+++ b/packages/schema/.vscodeignore
@@ -1,3 +1,4 @@
+.env
.vscode/**
.vscode-test/**
.gitignore
@@ -8,3 +9,5 @@ bin
build
jest.config.ts
tsconfig.json
+dist
+README-global.md
diff --git a/packages/schema/build/bundle.js b/packages/schema/build/bundle.js
index 9adab015b..537588c3e 100644
--- a/packages/schema/build/bundle.js
+++ b/packages/schema/build/bundle.js
@@ -23,19 +23,7 @@ require('esbuild')
})
.then(() => {
fs.cpSync('./src/res', 'bundle/res', { force: true, recursive: true });
- fs.cpSync('./asset', 'bundle/asset', {
- force: true,
- recursive: true,
- });
- fs.cpSync('./README.md', 'bundle/README.md', {
- force: true,
- });
- fs.cpSync('../../LICENSE', 'bundle/LICENSE', {
- force: true,
- });
- fs.cpSync('./package.json', 'bundle/package.json', {
- force: true,
- });
+ fs.cpSync('../language/syntaxes', 'bundle/syntaxes', { force: true, recursive: true });
})
.then(() => console.log(success))
.catch((err) => {
diff --git a/packages/schema/build/env-plugin.js b/packages/schema/build/env-plugin.js
deleted file mode 100644
index 99e212616..000000000
--- a/packages/schema/build/env-plugin.js
+++ /dev/null
@@ -1,60 +0,0 @@
-// from: https://github.com/rw3iss/esbuild-envfile-plugin
-
-const path = require('path');
-const fs = require('fs');
-
-const ENV = process.env.NODE_ENV || 'development';
-
-module.exports = {
- name: 'env',
-
- setup(build) {
- function _findEnvFile(dir) {
- if (!fs.existsSync(dir)) return undefined;
-
- const candidates = [`${dir}/.env.${ENV}.local`, `${dir}/.env.${ENV}`, `${dir}/.env.local`, `${dir}/.env`];
-
- for (const candidate of candidates) {
- if (fs.existsSync(candidate)) {
- console.log('Using env from:', candidate);
- return candidate;
- }
- }
-
- const next = path.resolve(dir, '../');
- if (next === dir) {
- // at root now, exit
- return undefined;
- } else {
- return _findEnvFile(next);
- }
- }
-
- build.onResolve({ filter: /^env$/ }, async (args) => {
- const envPath = _findEnvFile(args.resolveDir);
- return {
- path: args.path,
- namespace: 'env-ns',
- pluginData: {
- ...args.pluginData,
- envPath,
- },
- };
- });
-
- build.onLoad({ filter: /.*/, namespace: 'env-ns' }, async (args) => {
- // read in .env file contents and combine with regular .env:
- let config = {};
- if (args.pluginData && args.pluginData.envPath) {
- let data = await fs.promises.readFile(args.pluginData.envPath, 'utf8');
- const buf = Buffer.from(data);
- config = require('dotenv').parse(buf);
- }
-
- return {
- contents: JSON.stringify({ ...config, ...process.env }),
- loader: 'json',
- };
- });
- },
-};
diff --git a/packages/schema/package.json b/packages/schema/package.json
index a27e44032..961558b63 100644
--- a/packages/schema/package.json
+++ b/packages/schema/package.json
@@ -53,7 +53,7 @@
{
"language": "zmodel",
"scopeName": "source.zmodel",
- "path": "./syntaxes/zmodel.tmLanguage.json"
+ "path": "./bundle/syntaxes/zmodel.tmLanguage.json"
}
]
},
@@ -67,10 +67,10 @@
"scripts": {
"vscode:publish": "vsce publish --no-dependencies",
"vscode:prepublish": "pnpm bundle",
- "vscode:package": "vsce package --no-dependencies",
- "clean": "rimraf bundle dist",
+ "vscode:package": "pnpm bundle && vsce package --no-dependencies",
+ "clean": "rimraf dist",
"build": "pnpm clean && pnpm lint --max-warnings=0 && tsc && copyfiles -F \"bin/*\" dist && copyfiles ./README-global.md ./LICENSE ./package.json dist && renamer --replace \"README.md\" dist/README-global.md && copyfiles -u 1 \"src/res/*\" dist && node build/post-build.js && pnpm pack dist --pack-destination '../../../.build'",
- "bundle": "pnpm clean && pnpm lint --max-warnings=0 && node build/bundle.js --minify",
+ "bundle": "rimraf bundle && pnpm lint --max-warnings=0 && node build/bundle.js --minify",
"watch": "tsc --watch",
"lint": "eslint src tests --ext ts",
"test": "ZENSTACK_TEST=1 jest",
diff --git a/packages/schema/src/res/prism-zmodel.js b/packages/schema/src/res/prism-zmodel.js
deleted file mode 100644
index 2d9082332..000000000
--- a/packages/schema/src/res/prism-zmodel.js
+++ /dev/null
@@ -1,20 +0,0 @@
-// based on: https://github.com/prisma/docs/blob/c72eb087fcf57f3c00d153f86c549ef28b3d0f44/src/components/customMdx/prism/prism-prisma.js
-
-(function (Prism) {
- Prism.languages.zmodel = Prism.languages.extend('clike', {
- keyword: /\b(?:datasource|enum|generator|model|attribute|function|null|this)\b/,
- 'type-class-name': /(\b()\s+)[\w.\\]+/,
- });
-
- Prism.languages.javascript['class-name'][0].pattern = /(\b(?:model|datasource|enum|generator)\s+)[\w.\\]+/;
-
- Prism.languages.insertBefore('zmodel', 'function', {
- annotation: {
- pattern: /(^|[^.])@+\w+/,
- lookbehind: true,
- alias: 'punctuation',
- },
- });
-
- Prism.languages.json5 = Prism.languages.js;
-})(Prism);
diff --git a/packages/schema/syntaxes/zmodel.json b/packages/schema/syntaxes/zmodel.json
deleted file mode 100644
index 6d84ad7b6..000000000
--- a/packages/schema/syntaxes/zmodel.json
+++ /dev/null
@@ -1,55 +0,0 @@
-{
- "name": "zmodel",
- "scopeName": "source.zmodel",
- "fileTypes": [".zmodel"],
- "patterns": [
- {
- "include": "#comments"
- },
- {
- "name": "keyword.control.zmodel",
- "match": "\\b(Boolean|datasource|enum|model|String)\\b"
- },
- {
- "name": "string.quoted.double.zmodel",
- "begin": "\"",
- "end": "\""
- },
- {
- "name": "string.quoted.single.zmodel",
- "begin": "'",
- "end": "'"
- }
- ],
- "repository": {
- "comments": {
- "patterns": [
- {
- "name": "comment.block.zmodel",
- "begin": "/\\*",
- "beginCaptures": {
- "0": {
- "name": "punctuation.definition.comment.zmodel"
- }
- },
- "end": "\\*/",
- "endCaptures": {
- "0": {
- "name": "punctuation.definition.comment.zmodel"
- }
- }
- },
- {
- "begin": "//",
- "beginCaptures": {
- "1": {
- "name": "punctuation.whitespace.comment.leading.zmodel"
- }
- },
- "end": "(?=$)",
- "name": "comment.line.zmodel"
- }
- ]
- }
- }
-}
diff --git a/packages/schema/syntaxes/zmodel.tmLanguage.json b/packages/schema/syntaxes/zmodel.tmLanguage.json
deleted file mode 100644
index fb5bf9c7e..000000000
--- a/packages/schema/syntaxes/zmodel.tmLanguage.json
+++ /dev/null
@@ -1,69 +0,0 @@
-{
- "name": "zmodel",
- "scopeName": "source.zmodel",
- "fileTypes": [".zmodel"],
- "patterns": [
- {
- "include": "#comments"
- },
- {
- "name": "keyword.storage.zmodel",
- "match": "\\b(Any|Asc|BigInt|Boolean|Bytes|ContextType|DateTime|Decimal|Desc|FieldReference|Float|Int|Json|Null|Object|String|TransitiveFieldReference|Unsupported|abstract|attribute|datasource|enum|extends|false|function|generator|import|in|model|plugin|sort|true|view)\\b"
- },
- {
- "name": "string.quoted.double.zmodel",
- "begin": "\"",
- "end": "\"",
- "patterns": [
- {
- "include": "#string-character-escape"
- }
- ]
- },
- {
- "name": "string.quoted.single.zmodel",
- "begin": "'",
- "end": "'",
- "patterns": [
- {
- "include": "#string-character-escape"
- }
- ]
- }
- ],
- "repository": {
- "comments": {
- "patterns": [
- {
- "name": "comment.block.zmodel",
- "begin": "/\\*",
- "beginCaptures": {
- "0": {
- "name": "punctuation.definition.comment.zmodel"
- }
- },
- "end": "\\*/",
- "endCaptures": {
- "0": {
- "name": "punctuation.definition.comment.zmodel"
- }
- }
- },
- {
- "begin": "//",
- "beginCaptures": {
- "1": {
- "name": "punctuation.whitespace.comment.leading.zmodel"
- }
- },
- "end": "(?=$)",
- "name": "comment.line.zmodel"
- }
- ]
- },
- "string-character-escape": {
- "name": "constant.character.escape.zmodel",
- "match": "\\\\(x[0-9A-Fa-f]{2}|u[0-9A-Fa-f]{4}|u\\{[0-9A-Fa-f]+\\}|[0-2][0-7]{0,2}|3[0-6][0-7]?|37[0-7]?|[4-7][0-7]?|.|$)"
- }
- }
-}
diff --git a/packages/server/package.json b/packages/server/package.json
index 2d02ee7e5..f31242954 100644
--- a/packages/server/package.json
+++ b/packages/server/package.json
@@ -23,7 +23,7 @@
"sveltekit",
"nuxtjs"
],
- "author": "",
+ "author": "ZenStack Team",
"license": "MIT",
"dependencies": {
"@zenstackhq/runtime": "workspace:*",
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index f209cbdac..de0f8a35f 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -57,6 +57,15 @@ importers:
specifier: ^5.3.2
version: 5.3.2
+ packages/ide/jetbrains:
+ devDependencies:
+ '@zenstackhq/language':
+ specifier: workspace:*
+ version: link:../../language/dist
+ zenstack:
+ specifier: workspace:*
+ version: link:../../schema/dist
+
packages/language:
dependencies:
langium:
@@ -66,6 +75,9 @@ importers:
langium-cli:
specifier: 1.2.0
version: 1.2.0
+ plist2:
+ specifier: ^1.1.3
+ version: 1.1.3
publishDirectory: dist
packages/plugins/openapi:
@@ -11117,6 +11129,13 @@ packages:
queue-lit: 1.5.0
dev: true
+ /plist2@1.1.3:
+ resolution: {integrity: sha512-AOpoCuUdedSJYeeU2Be6UAIMZqFWno0fD43NNT64is8k5Fi2HyEylK4UAOZBmafeImnCO1FYc+u8cwwEExVztA==}
+ hasBin: true
+ dependencies:
+ yaml: 1.10.2
+ dev: true
+
/pluralize@8.0.0:
resolution: {integrity: sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA==}
engines: {node: '>=4'}
@@ -14449,6 +14468,11 @@ packages:
/yallist@4.0.0:
resolution: {integrity: sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==}
+ /yaml@1.10.2:
+ resolution: {integrity: sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==}
+ engines: {node: '>= 6'}
+ dev: true
+
/yaml@2.2.1:
resolution: {integrity: sha512-e0WHiYql7+9wr4cWMx3TVQrNwejKaEe7/rHNmQmqRjazfOP5W8PB6Jpebb5o6fIapbz9o9+2ipcaTM2ZwDI6lw==}
engines: {node: '>= 14'}
diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml
index 928f8bfc9..198ac1918 100644
--- a/pnpm-workspace.yaml
+++ b/pnpm-workspace.yaml
@@ -1,4 +1,5 @@
packages:
- 'packages/*'
- 'packages/plugins/*'
+ - 'packages/ide/*'
- 'tests/*'