diff --git a/deployment/src/main/java/io/quarkiverse/quinoa/deployment/ForwardedDevProcessor.java b/deployment/src/main/java/io/quarkiverse/quinoa/deployment/ForwardedDevProcessor.java index 3578a216..d1acb345 100644 --- a/deployment/src/main/java/io/quarkiverse/quinoa/deployment/ForwardedDevProcessor.java +++ b/deployment/src/main/java/io/quarkiverse/quinoa/deployment/ForwardedDevProcessor.java @@ -53,6 +53,7 @@ public class ForwardedDevProcessor { private static final Logger LOG = Logger.getLogger(ForwardedDevProcessor.class); private static final Predicate PROCESS_THREAD_PREDICATE = thread -> thread.getName() .matches("Process (stdout|stderr) streamer"); + private static final String DEV_SERVICE_NAME = "quinoa-dev-server"; private static volatile DevServicesResultBuildItem.RunningDevService devService; @BuildStep(onlyIf = IsDevelopment.class) @@ -71,8 +72,10 @@ public ForwardedDevServerBuildItem prepareDevService( final QuinoaDirectoryBuildItem quinoaDirectoryBuildItem = quinoaDir.get(); QuinoaConfig oldConfig = liveReload.getContextObject(QuinoaConfig.class); liveReload.setContextObject(QuinoaConfig.class, quinoaConfig); - final String devServerHost = quinoaConfig.devServer.host; + final String configuredDevServerHost = quinoaConfig.devServer.host; final String devServerCommand = quinoaDirectoryBuildItem.getDevServerCommand(); + final PackageManager packageManager = quinoaDir.get().getPackageManager(); + final String checkPath = quinoaConfig.devServer.checkPath.orElse(null); if (devService != null) { boolean shouldShutdownTheBroker = !quinoaConfig.equals(oldConfig); if (!shouldShutdownTheBroker) { @@ -82,7 +85,10 @@ public ForwardedDevServerBuildItem prepareDevService( } LOG.debug("Quinoa config did not change; no need to restart."); devServices.produce(devService.toBuildItem()); - return new ForwardedDevServerBuildItem(devServerHost, quinoaDirectoryBuildItem.getDevServerPort().getAsInt()); + final int devServerPort = quinoaDirectoryBuildItem.getDevServerPort().getAsInt(); + final String resolvedDevServerHost = PackageManager.isDevServerUp(configuredDevServerHost, devServerPort, + checkPath); + return new ForwardedDevServerBuildItem(resolvedDevServerHost, devServerPort); } shutdownDevService(); } @@ -101,11 +107,11 @@ public ForwardedDevServerBuildItem prepareDevService( return null; } - PackageManager packageManager = quinoaDir.get().getPackageManager(); final int devServerPort = quinoaDirectoryBuildItem.getDevServerPort().getAsInt(); - final String checkPath = quinoaConfig.devServer.checkPath.orElse(null); if (!quinoaConfig.devServer.managed) { - final String hostAddress = PackageManager.isDevServerUp(devServerHost, devServerPort, checkPath); + // No need to start the dev-service it is not managed by Quinoa + // We just check that it is up + final String hostAddress = PackageManager.isDevServerUp(configuredDevServerHost, devServerPort, checkPath); if (hostAddress != null) { return new ForwardedDevServerBuildItem(hostAddress, devServerPort); } else { @@ -127,7 +133,8 @@ public ForwardedDevServerBuildItem prepareDevService( } final long start = Instant.now().toEpochMilli(); - final PackageManager.DevServer devServer = packageManager.dev(devServerCommand, devServerHost, devServerPort, + final PackageManager.DevServer devServer = packageManager.dev(devServerCommand, configuredDevServerHost, + devServerPort, checkPath, checkTimeout); dev.set(devServer.process()); compressor.close(); @@ -141,17 +148,10 @@ public ForwardedDevServerBuildItem prepareDevService( logOutputFilter.close(); packageManager.stopDev(dev.get()); }; - Map devServerConfigMap = new LinkedHashMap<>(); - devServerConfigMap.put("quarkus.quinoa.dev-server.host", quinoaConfig.devServer.host); - devServerConfigMap.put("quarkus.quinoa.dev-server.port", Integer.toString(devServerPort)); - devServerConfigMap.put("quarkus.quinoa.dev-server.check-timeout", - Integer.toString(quinoaConfig.devServer.checkTimeout)); - devServerConfigMap.put("quarkus.quinoa.dev-server.check-path", quinoaConfig.devServer.checkPath.orElse("")); - devServerConfigMap.put("quarkus.quinoa.dev-server.managed", Boolean.toString(quinoaConfig.devServer.managed)); - devServerConfigMap.put("quarkus.quinoa.dev-server.logs", Boolean.toString(quinoaConfig.devServer.logs)); - devServerConfigMap.put("quarkus.quinoa.dev-server.websocket", Boolean.toString(quinoaConfig.devServer.websocket)); + Map devServerConfigMap = createDevServiceMapForDevUI(quinoaConfig, devServer.hostAddress(), + devServerPort, checkPath); devService = new DevServicesResultBuildItem.RunningDevService( - "quinoa-dev-server", null, onClose, devServerConfigMap); + DEV_SERVICE_NAME, null, onClose, devServerConfigMap); devServices.produce(devService.toBuildItem()); return new ForwardedDevServerBuildItem(devServer.hostAddress(), devServerPort); } catch (Throwable t) { @@ -161,6 +161,20 @@ public ForwardedDevServerBuildItem prepareDevService( } } + private static Map createDevServiceMapForDevUI(QuinoaConfig quinoaConfig, String devServerHost, + int devServerPort, String checkPath) { + Map devServerConfigMap = new LinkedHashMap<>(); + devServerConfigMap.put("quarkus.quinoa.dev-server.host", devServerHost); + devServerConfigMap.put("quarkus.quinoa.dev-server.port", Integer.toString(devServerPort)); + devServerConfigMap.put("quarkus.quinoa.dev-server.check-timeout", + Integer.toString(quinoaConfig.devServer.checkTimeout)); + devServerConfigMap.put("quarkus.quinoa.dev-server.check-path", checkPath); + devServerConfigMap.put("quarkus.quinoa.dev-server.managed", Boolean.toString(quinoaConfig.devServer.managed)); + devServerConfigMap.put("quarkus.quinoa.dev-server.logs", Boolean.toString(quinoaConfig.devServer.logs)); + devServerConfigMap.put("quarkus.quinoa.dev-server.websocket", Boolean.toString(quinoaConfig.devServer.websocket)); + return devServerConfigMap; + } + @BuildStep(onlyIf = IsDevelopment.class) @Record(RUNTIME_INIT) public void runtimeInit( diff --git a/docs/modules/ROOT/pages/includes/attributes.adoc b/docs/modules/ROOT/pages/includes/attributes.adoc index b18984c0..642c3acf 100644 --- a/docs/modules/ROOT/pages/includes/attributes.adoc +++ b/docs/modules/ROOT/pages/includes/attributes.adoc @@ -1,5 +1,5 @@ -:quarkus-version: 3.1.0.Final -:quarkus-quinoa-version: 2.0.8 +:quarkus-version: 3.3.0 +:quarkus-quinoa-version: 2.1.0 :maven-version: 3.8.1+ :quarkus-org-url: https://github.com/quarkusio diff --git a/docs/modules/ROOT/pages/includes/quarkus-quinoa.adoc b/docs/modules/ROOT/pages/includes/quarkus-quinoa.adoc index b01dbbee..ea094b0f 100644 --- a/docs/modules/ROOT/pages/includes/quarkus-quinoa.adoc +++ b/docs/modules/ROOT/pages/includes/quarkus-quinoa.adoc @@ -12,6 +12,7 @@ h|Default a|icon:lock[title=Fixed at build time] [[quarkus-quinoa_quarkus.quinoa]]`link:#quarkus-quinoa_quarkus.quinoa[quarkus.quinoa]` + [.description] -- Indicate if the extension should be enabled. Default is true if the Web UI directory exists and dev and prod mode. Default is false in test mode (to avoid building the Web UI during backend tests). @@ -28,6 +29,7 @@ endif::add-copy-button-to-env-var[] a|icon:lock[title=Fixed at build time] [[quarkus-quinoa_quarkus.quinoa.just-build]]`link:#quarkus-quinoa_quarkus.quinoa.just-build[quarkus.quinoa.just-build]` + [.description] -- Indicate if Quinoa should just do the build part. If true, Quinoa will NOT serve the Web UI built resources. This is handy when the output of the build is used to be served via something else (nginx, cdn, ...) Quinoa put the built files in 'target/quinoa-build' (or 'build/quinoa-build with Gradle). Default is false. @@ -44,6 +46,7 @@ endif::add-copy-button-to-env-var[] a|icon:lock[title=Fixed at build time] [[quarkus-quinoa_quarkus.quinoa.ui-dir]]`link:#quarkus-quinoa_quarkus.quinoa.ui-dir[quarkus.quinoa.ui-dir]` + [.description] -- Path to the Web UI (NodeJS) root directory. If not set $++{++project.root++}++/src/main/webui/ will be used. otherwise the path will be considered relative to the project root. @@ -60,6 +63,7 @@ endif::add-copy-button-to-env-var[] a|icon:lock[title=Fixed at build time] [[quarkus-quinoa_quarkus.quinoa.build-dir]]`link:#quarkus-quinoa_quarkus.quinoa.build-dir[quarkus.quinoa.build-dir]` + [.description] -- This the Web UI internal build system (webpack, ...) output directory. After the build, Quinoa will take the files from this directory, move them to 'target/quinoa-build' (or build/quinoa-build with Gradle) and serve them at runtime. The path is relative to the Web UI path. If not set "build/" will be used @@ -76,6 +80,7 @@ endif::add-copy-button-to-env-var[] a|icon:lock[title=Fixed at build time] [[quarkus-quinoa_quarkus.quinoa.package-manager]]`link:#quarkus-quinoa_quarkus.quinoa.package-manager[quarkus.quinoa.package-manager]` + [.description] -- Name of the package manager binary. If not set, it will be auto-detected depending on the lockfile falling back to "npm". Only npm, pnpm and yarn are supported for the moment. @@ -92,6 +97,7 @@ endif::add-copy-button-to-env-var[] a|icon:lock[title=Fixed at build time] [[quarkus-quinoa_quarkus.quinoa.package-manager-install]]`link:#quarkus-quinoa_quarkus.quinoa.package-manager-install[quarkus.quinoa.package-manager-install]` + [.description] -- Enable Package Manager Installation. This will override "package-manager" config. Set "quarkus.quinoa.package-manager-command.prepend-binary=true" when using with custom commands @@ -108,6 +114,7 @@ endif::add-copy-button-to-env-var[] a|icon:lock[title=Fixed at build time] [[quarkus-quinoa_quarkus.quinoa.package-manager-install.install-dir]]`link:#quarkus-quinoa_quarkus.quinoa.package-manager-install.install-dir[quarkus.quinoa.package-manager-install.install-dir]` + [.description] -- The directory where NodeJS should be installed, it will be installed in a node/ sub-directory. Default is $++{++project.root++}++/.quinoa @@ -124,6 +131,7 @@ endif::add-copy-button-to-env-var[] a|icon:lock[title=Fixed at build time] [[quarkus-quinoa_quarkus.quinoa.package-manager-install.node-version]]`link:#quarkus-quinoa_quarkus.quinoa.package-manager-install.node-version[quarkus.quinoa.package-manager-install.node-version]` + [.description] -- The NodeJS Version to install locally to the project. Required when package-manager-install is enabled. @@ -140,6 +148,7 @@ endif::add-copy-button-to-env-var[] a|icon:lock[title=Fixed at build time] [[quarkus-quinoa_quarkus.quinoa.package-manager-install.node-download-root]]`link:#quarkus-quinoa_quarkus.quinoa.package-manager-install.node-download-root[quarkus.quinoa.package-manager-install.node-download-root]` + [.description] -- Where to download NodeJS from. @@ -156,6 +165,7 @@ endif::add-copy-button-to-env-var[] a|icon:lock[title=Fixed at build time] [[quarkus-quinoa_quarkus.quinoa.package-manager-install.npm-version]]`link:#quarkus-quinoa_quarkus.quinoa.package-manager-install.npm-version[quarkus.quinoa.package-manager-install.npm-version]` + [.description] -- The NPM version to install. By default, the version is provided by NodeJS. @@ -172,6 +182,7 @@ endif::add-copy-button-to-env-var[] a|icon:lock[title=Fixed at build time] [[quarkus-quinoa_quarkus.quinoa.package-manager-install.npm-download-root]]`link:#quarkus-quinoa_quarkus.quinoa.package-manager-install.npm-download-root[quarkus.quinoa.package-manager-install.npm-download-root]` + [.description] -- Where to download NPM from. @@ -188,6 +199,7 @@ endif::add-copy-button-to-env-var[] a|icon:lock[title=Fixed at build time] [[quarkus-quinoa_quarkus.quinoa.package-manager-install.pnpm-version]]`link:#quarkus-quinoa_quarkus.quinoa.package-manager-install.pnpm-version[quarkus.quinoa.package-manager-install.pnpm-version]` + [.description] -- The PNPM version to install. If the version is set and NPM and YARN are not set, then this version will attempt to be downloaded. @@ -204,6 +216,7 @@ endif::add-copy-button-to-env-var[] a|icon:lock[title=Fixed at build time] [[quarkus-quinoa_quarkus.quinoa.package-manager-install.pnpm-download-root]]`link:#quarkus-quinoa_quarkus.quinoa.package-manager-install.pnpm-download-root[quarkus.quinoa.package-manager-install.pnpm-download-root]` + [.description] -- Where to download PNPM from. @@ -220,6 +233,7 @@ endif::add-copy-button-to-env-var[] a|icon:lock[title=Fixed at build time] [[quarkus-quinoa_quarkus.quinoa.package-manager-install.yarn-version]]`link:#quarkus-quinoa_quarkus.quinoa.package-manager-install.yarn-version[quarkus.quinoa.package-manager-install.yarn-version]` + [.description] -- The YARN version to install. If the version is set and NPM Version is not set, then this version will attempt to be downloaded. @@ -236,6 +250,7 @@ endif::add-copy-button-to-env-var[] a|icon:lock[title=Fixed at build time] [[quarkus-quinoa_quarkus.quinoa.package-manager-install.yarn-download-root]]`link:#quarkus-quinoa_quarkus.quinoa.package-manager-install.yarn-download-root[quarkus.quinoa.package-manager-install.yarn-download-root]` + [.description] -- Where to download YARN from. @@ -252,6 +267,7 @@ endif::add-copy-button-to-env-var[] a|icon:lock[title=Fixed at build time] [[quarkus-quinoa_quarkus.quinoa.package-manager-command.prepend-binary]]`link:#quarkus-quinoa_quarkus.quinoa.package-manager-command.prepend-binary[quarkus.quinoa.package-manager-command.prepend-binary]` + [.description] -- If true, the package manager binary will be prepended by Quinoa (Only configure the arguments in the different commands as the binary will be prepended). e.g. «quarkus.quinoa.package-manager-command.install=ci --cache $CACHE_DIR/.npm --prefer-offline» Else, the command should also contain the binary. @@ -268,6 +284,7 @@ endif::add-copy-button-to-env-var[] a|icon:lock[title=Fixed at build time] [[quarkus-quinoa_quarkus.quinoa.package-manager-command.install]]`link:#quarkus-quinoa_quarkus.quinoa.package-manager-command.install[quarkus.quinoa.package-manager-command.install]` + [.description] -- Custom command for installing all dependencies. e.g. «npm ci --cache $CACHE_DIR/.npm --prefer-offline» @@ -284,6 +301,7 @@ endif::add-copy-button-to-env-var[] a|icon:lock[title=Fixed at build time] [[quarkus-quinoa_quarkus.quinoa.package-manager-command.build]]`link:#quarkus-quinoa_quarkus.quinoa.package-manager-command.build[quarkus.quinoa.package-manager-command.build]` + [.description] -- Custom command for building the application. @@ -300,6 +318,7 @@ endif::add-copy-button-to-env-var[] a|icon:lock[title=Fixed at build time] [[quarkus-quinoa_quarkus.quinoa.package-manager-command.test]]`link:#quarkus-quinoa_quarkus.quinoa.package-manager-command.test[quarkus.quinoa.package-manager-command.test]` + [.description] -- Custom command for running tests for the application. @@ -316,6 +335,7 @@ endif::add-copy-button-to-env-var[] a|icon:lock[title=Fixed at build time] [[quarkus-quinoa_quarkus.quinoa.package-manager-command.dev]]`link:#quarkus-quinoa_quarkus.quinoa.package-manager-command.dev[quarkus.quinoa.package-manager-command.dev]` + [.description] -- Custom command for starting the application in development mode. @@ -332,6 +352,7 @@ endif::add-copy-button-to-env-var[] a|icon:lock[title=Fixed at build time] [[quarkus-quinoa_quarkus.quinoa.index-page]]`link:#quarkus-quinoa_quarkus.quinoa.index-page[quarkus.quinoa.index-page]` + [.description] -- Name of the index page. If not set, "index.html" will be used. @@ -348,6 +369,7 @@ endif::add-copy-button-to-env-var[] a|icon:lock[title=Fixed at build time] [[quarkus-quinoa_quarkus.quinoa.run-tests]]`link:#quarkus-quinoa_quarkus.quinoa.run-tests[quarkus.quinoa.run-tests]` + [.description] -- Indicate if the Web UI should also be tested during the build phase (i.e: npm test). To be used in a `io.quarkus.test.junit.QuarkusTestProfile` to have Web UI test running during a `io.quarkus.test.junit.QuarkusTest` Default is false. @@ -364,6 +386,7 @@ endif::add-copy-button-to-env-var[] a|icon:lock[title=Fixed at build time] [[quarkus-quinoa_quarkus.quinoa.frozen-lockfile]]`link:#quarkus-quinoa_quarkus.quinoa.frozen-lockfile[quarkus.quinoa.frozen-lockfile]` + [.description] -- Install the packages using a frozen lockfile. Don’t generate a lockfile and fail if an update is needed (useful in CI). If not set it is true if environment CI=true, else it is false. @@ -380,6 +403,7 @@ endif::add-copy-button-to-env-var[] a|icon:lock[title=Fixed at build time] [[quarkus-quinoa_quarkus.quinoa.force-install]]`link:#quarkus-quinoa_quarkus.quinoa.force-install[quarkus.quinoa.force-install]` + [.description] -- Force install packages before building. If not set, it will install packages only if the node_modules directory is absent or when the package.json is modified in dev-mode. @@ -396,6 +420,7 @@ endif::add-copy-button-to-env-var[] a|icon:lock[title=Fixed at build time] [[quarkus-quinoa_quarkus.quinoa.enable-spa-routing]]`link:#quarkus-quinoa_quarkus.quinoa.enable-spa-routing[quarkus.quinoa.enable-spa-routing]` + [.description] -- Enable SPA (Single Page Application) routing, all relevant requests will be re-routed to the "index.html". Currently, for technical reasons, the Quinoa SPA routing configuration won't work with RESTEasy Classic. If not set, it is disabled. @@ -412,6 +437,7 @@ endif::add-copy-button-to-env-var[] a|icon:lock[title=Fixed at build time] [[quarkus-quinoa_quarkus.quinoa.ignored-path-prefixes]]`link:#quarkus-quinoa_quarkus.quinoa.ignored-path-prefixes[quarkus.quinoa.ignored-path-prefixes]` + [.description] -- List of path prefixes to be ignored by Quinoa. If not set, "quarkus.resteasy-reactive.path", "quarkus.resteasy.path" and "quarkus.http.non-application-root-path" will be ignored. @@ -428,6 +454,7 @@ endif::add-copy-button-to-env-var[] a|icon:lock[title=Fixed at build time] [[quarkus-quinoa_quarkus.quinoa.dev-server]]`link:#quarkus-quinoa_quarkus.quinoa.dev-server[quarkus.quinoa.dev-server]` + [.description] -- Enable external dev server (live coding). The "dev-server.port" config is required to communicate with the dev server. If not set the default is true. @@ -444,6 +471,7 @@ endif::add-copy-button-to-env-var[] a|icon:lock[title=Fixed at build time] [[quarkus-quinoa_quarkus.quinoa.dev-server.managed]]`link:#quarkus-quinoa_quarkus.quinoa.dev-server.managed[quarkus.quinoa.dev-server.managed]` + [.description] -- When set to true, Quinoa will manage the Web UI dev server When set to false, the Web UI dev server have to be started before running Quarkus dev @@ -460,6 +488,7 @@ endif::add-copy-button-to-env-var[] a|icon:lock[title=Fixed at build time] [[quarkus-quinoa_quarkus.quinoa.dev-server.port]]`link:#quarkus-quinoa_quarkus.quinoa.dev-server.port[quarkus.quinoa.dev-server.port]` + [.description] -- Port of the server to forward requests to. The dev server process (i.e npm start) is managed like a dev service by Quarkus. If the external server responds with a 404, it is ignored by Quinoa and processed like any other backend request. @@ -476,6 +505,7 @@ endif::add-copy-button-to-env-var[] a|icon:lock[title=Fixed at build time] [[quarkus-quinoa_quarkus.quinoa.dev-server.host]]`link:#quarkus-quinoa_quarkus.quinoa.dev-server.host[quarkus.quinoa.dev-server.host]` + [.description] -- Host of the server to forward requests to. "localhost" is the default @@ -492,6 +522,7 @@ endif::add-copy-button-to-env-var[] a|icon:lock[title=Fixed at build time] [[quarkus-quinoa_quarkus.quinoa.dev-server.check-path]]`link:#quarkus-quinoa_quarkus.quinoa.dev-server.check-path[quarkus.quinoa.dev-server.check-path]` + [.description] -- After start, Quinoa wait for the external dev server. by sending GET requests to this path waiting for a 200 status. If not set the default is "/". If empty string "", Quinoa will not check if the dev server is up. @@ -508,6 +539,7 @@ endif::add-copy-button-to-env-var[] a|icon:lock[title=Fixed at build time] [[quarkus-quinoa_quarkus.quinoa.dev-server.websocket]]`link:#quarkus-quinoa_quarkus.quinoa.dev-server.websocket[quarkus.quinoa.dev-server.websocket]` + [.description] -- By default, Quinoa will handle request upgrade to websocket and act as proxy with the dev server. If set to false, Quinoa will pass websocket upgrade request to the next Vert.x route handler. @@ -524,6 +556,7 @@ endif::add-copy-button-to-env-var[] a|icon:lock[title=Fixed at build time] [[quarkus-quinoa_quarkus.quinoa.dev-server.check-timeout]]`link:#quarkus-quinoa_quarkus.quinoa.dev-server.check-timeout[quarkus.quinoa.dev-server.check-timeout]` + [.description] -- Timeout in ms for the dev server to be up and running. If not set the default is ~30000ms. @@ -540,6 +573,7 @@ endif::add-copy-button-to-env-var[] a|icon:lock[title=Fixed at build time] [[quarkus-quinoa_quarkus.quinoa.dev-server.logs]]`link:#quarkus-quinoa_quarkus.quinoa.dev-server.logs[quarkus.quinoa.dev-server.logs]` + [.description] -- Enable external dev server live coding logs. This is not enabled by default because most dev servers display compilation errors directly in the browser. False if not set. @@ -556,6 +590,7 @@ endif::add-copy-button-to-env-var[] a|icon:lock[title=Fixed at build time] [[quarkus-quinoa_quarkus.quinoa.package-manager-command.install-env-install-env]]`link:#quarkus-quinoa_quarkus.quinoa.package-manager-command.install-env-install-env[quarkus.quinoa.package-manager-command.install-env]` + [.description] -- ifdef::add-copy-button-to-env-var[] @@ -570,6 +605,7 @@ endif::add-copy-button-to-env-var[] a|icon:lock[title=Fixed at build time] [[quarkus-quinoa_quarkus.quinoa.package-manager-command.build-env-build-env]]`link:#quarkus-quinoa_quarkus.quinoa.package-manager-command.build-env-build-env[quarkus.quinoa.package-manager-command.build-env]` + [.description] -- ifdef::add-copy-button-to-env-var[] @@ -584,6 +620,7 @@ endif::add-copy-button-to-env-var[] a|icon:lock[title=Fixed at build time] [[quarkus-quinoa_quarkus.quinoa.package-manager-command.test-env-test-env]]`link:#quarkus-quinoa_quarkus.quinoa.package-manager-command.test-env-test-env[quarkus.quinoa.package-manager-command.test-env]` + [.description] -- ifdef::add-copy-button-to-env-var[] @@ -598,6 +635,7 @@ endif::add-copy-button-to-env-var[] a|icon:lock[title=Fixed at build time] [[quarkus-quinoa_quarkus.quinoa.package-manager-command.dev-env-dev-env]]`link:#quarkus-quinoa_quarkus.quinoa.package-manager-command.dev-env-dev-env[quarkus.quinoa.package-manager-command.dev-env]` + [.description] -- ifdef::add-copy-button-to-env-var[]