create-qs 0.8.26 → 0.8.29

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -6,9 +6,11 @@
6
6
  用一条命令创建全栈 Quicksilver 项目。
7
7
 
8
8
  ```bash
9
- npm create qs
9
+ npm create qs@latest
10
10
  ```
11
11
 
12
+ 命令中的 `@latest` 不能省略。省略时,如果本机全局安装过 `create-qs`,或者当前目录所属的项目依赖了它,npm 会直接运行已有的那一份,不检查 registry 上是否有更新的版本。
13
+
12
14
  这一条命令请使用 npm,不要使用 pnpm。自 pnpm 11 起,`pnpm create` 会跳过发布不足 24 小时的版本(`minimumReleaseAge`),且不给出任何提示,当天发布的脚手架会被静默替换为较早的版本。npm 默认没有这一限制。项目创建完成后,其余操作一律使用 pnpm。
13
15
 
14
16
  脚手架发布在公网 npmjs 上,未做任何配置的机器即可直接运行。它会询问项目及其第一个模块的若干问题,然后生成一个可以直接运行的全栈项目,包括 Kotlin/Spring Boot 后端、Preact/TypeScript 前端、Gradle Wrapper,以及文件型 H2 开发数据库。
@@ -50,9 +52,11 @@ pnpm dev
50
52
  用一個指令建立全端 Quicksilver 專案。
51
53
 
52
54
  ```bash
53
- npm create qs
55
+ npm create qs@latest
54
56
  ```
55
57
 
58
+ 指令中的 `@latest` 不可省略。省略時,如果本機全域安裝過 `create-qs`,或者目前目錄所屬的專案相依於它,npm 會直接執行已有的那一份,不檢查 registry 上是否有更新的版本。
59
+
56
60
  這一個指令請使用 npm,不要使用 pnpm。自 pnpm 11 起,`pnpm create` 會略過發布不足 24 小時的版本(`minimumReleaseAge`),且不會給出任何提示,當天發布的鷹架會被默默替換為較早的版本。npm 預設沒有這項限制。專案建立完成後,其餘操作一律使用 pnpm。
57
61
 
58
62
  鷹架發布在公開的 npmjs 上,未做任何設定的電腦即可直接執行。它會詢問專案及其第一個模組的幾個問題,然後產生一個可以直接執行的全端專案,包括 Kotlin/Spring Boot 後端、Preact/TypeScript 前端、Gradle Wrapper,以及檔案型 H2 開發資料庫。
@@ -93,9 +97,12 @@ pnpm dev
93
97
  Create a full-stack Quicksilver project with a single command.
94
98
 
95
99
  ```bash
96
- npm create qs
100
+ npm create qs@latest
97
101
  ```
98
102
 
103
+ Keep the `@latest`. Without it, npm runs any `create-qs` already installed globally or in the
104
+ project that contains the current directory, and does not check the registry for a newer version.
105
+
99
106
  Use npm for this one command, not pnpm. Since pnpm 11, `pnpm create` skips any version published
100
107
  less than 24 hours ago (`minimumReleaseAge`) and does not report it, so a scaffolder released the
101
108
  same day is silently replaced by an older one. npm applies no such delay by default. Everything
package/index.js CHANGED
@@ -646,6 +646,12 @@ async function inputBoolean(options, initial) {
646
646
  }
647
647
  var { Input } = enquirer;
648
648
  var TextPrompt = class extends Input {
649
+ async submit() {
650
+ if (this.options.cancelOnEmpty && String(this.value ?? "").trim() === "") {
651
+ return this.cancel();
652
+ }
653
+ return super.submit();
654
+ }
649
655
  async close() {
650
656
  try {
651
657
  await super.close();
@@ -1970,7 +1976,7 @@ async function processFiles(options) {
1970
1976
  variables.set("WEB_PACKAGE_NAME", options.webModulePackage);
1971
1977
  variables.set("API_PACKAGE", options.apiPackage);
1972
1978
  variables.set("API_ARTIFACT_ID", options.apiArtifactId);
1973
- variables.set("QUICKSILVER_VERSION", "0.8.26");
1979
+ variables.set("QUICKSILVER_VERSION", "0.8.29");
1974
1980
  variables.set("MODULE_ID", randomUUID());
1975
1981
  variables.set("DB_PREFIX", toDbPrefix(options.name));
1976
1982
  variables.set("JSONS_LOCALE", options.jsonsLocale);
@@ -2033,7 +2039,7 @@ if (process.argv.some((arg) => arg === "--version" || arg === "-v")) {
2033
2039
  console.log(getCliVersion());
2034
2040
  process.exit(0);
2035
2041
  }
2036
- var program = new Command().name("create-qs").description("Create a new Quicksilver application").version("0.8.26", "-v, --version").argument("[project-name]", "Name of the project").option("-c, --module-code <code>", "Full module code (e.g., power_crm.sales)").option("-d, --module-directory <directory>", "Module directory name (e.g., sales)").option("-n, --module-namespace <code>", "Prefix of unit/table, defaults to the initials of the module code (e.g., pcs)").option("-p, --api-package <package>", "JVM package for Kotlin files (e.g., com.company.crm)").option("-a, --api-artifact-id <artifactId>", "Module artifact ID in repository (e.g., power-crm-module-sales)").option("-w, --web-module-package <package>", "Web module package name (e.g., @powercrm/module-sales-web)").option("-m, --modules <codes>", "Modules to depend on, comma separated (e.g., quicksilver.org)").option("--module-menu <name>", "Create the module's top-level menu in the left nav, with this label").option("--no-module-menu", "Do not create a top-level menu").option("--jsons-locale <locale>", "Language of the display texts in data files: zh-CN or zh-TW. Changing it later means rewriting every data file").option("-y, --yes", "Skip prompts and use defaults").action(async (projectName, options) => {
2042
+ var program = new Command().name("create-qs").description("Create a new Quicksilver application").version("0.8.29", "-v, --version").argument("[project-name]", "Name of the project").option("-c, --module-code <code>", "Full module code (e.g., power_crm.sales)").option("-d, --module-directory <directory>", "Module directory name (e.g., sales)").option("-n, --module-namespace <code>", "Prefix of unit/table, defaults to the initials of the module code (e.g., pcs)").option("-p, --api-package <package>", "JVM package for Kotlin files (e.g., com.company.crm)").option("-a, --api-artifact-id <artifactId>", "Module artifact ID in repository (e.g., power-crm-module-sales)").option("-w, --web-module-package <package>", "Web module package name (e.g., @powercrm/module-sales-web)").option("-m, --modules <codes>", "Modules to depend on, comma separated (e.g., quicksilver.org)").option("--module-menu <name>", "Create the module's top-level menu in the left nav, with this label").option("--no-module-menu", "Do not create a top-level menu").option("--jsons-locale <locale>", "Language of the display texts in data files: zh-CN or zh-TW. Changing it later means rewriting every data file").option("-y, --yes", "Skip prompts and use defaults").action(async (projectName, options) => {
2037
2043
  applyPromptTexts();
2038
2044
  console.log(chalk4.cyan(`
2039
2045
  ${t("banner")}`));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-qs",
3
- "version": "0.8.26",
3
+ "version": "0.8.29",
4
4
  "description": "Create a full-stack Quicksilver project with a single command",
5
5
  "type": "module",
6
6
  "bin": {
@@ -12,7 +12,7 @@ This chapter is committed with the project. The other chapters of the manual app
12
12
  | Node.js | **`>=22.13.0`** | The minimum version pnpm 11 requires |
13
13
  | pnpm | **11** | pnpm is required, npm and yarn are not supported. Workspaces, catalogs and `allowBuilds` all depend on pnpm.
14
14
  | Gradle | No installation needed | The project ships the Gradle Wrapper (`./gradlew`), which downloads Gradle on first run |
15
- | Database | No installation needed | A file-based H2 database is used by default. To use another database, see [01 Getting Started](01-getting-started.md#switching-databases) |
15
+ | Database | No installation needed | A file-based H2 database is used by default. To use another database, see [01 Getting Started](01-getting-started.md#development-instances) |
16
16
 
17
17
  ## Basic setup
18
18
 
@@ -145,7 +145,7 @@ Keep the following two points in mind:
145
145
 
146
146
  ## Next steps
147
147
 
148
- After dependencies are installed, the other chapters of the manual appear in this directory. Start with the [table of contents](README.md), then read [01 Getting Started](01-getting-started.md), which explains the options chosen when creating a project, the common commands, and how to switch databases.
148
+ After dependencies are installed, the other chapters of the manual appear in this directory. Start with the [table of contents](README.md), then read [01 Getting Started](01-getting-started.md), which explains the options chosen when creating a project, the common commands, and the development instances with their databases.
149
149
 
150
150
  On first use, run `pnpm qs sources` once. It downloads the platform's API documentation, data files and the frontend `.d.ts` files into `.quicksilver/generated/`, which later chapters of the manual refer to in many places.
151
151
 
@@ -12,7 +12,7 @@
12
12
  | Node.js | **`>=22.13.0`** | pnpm 11 要求的最低版本 |
13
13
  | pnpm | **11** | 必须使用 pnpm,不支持 npm 与 yarn。workspace、catalog、`allowBuilds` 均依赖 pnpm。
14
14
  | Gradle | 无需安装 | 项目自带 Gradle Wrapper(`./gradlew`),首次运行时自动下载 |
15
- | 数据库 | 无需安装 | 默认使用文件型 H2 数据库。使用其他数据库见 [01 快速入门](01-getting-started.md#切换数据库) |
15
+ | 数据库 | 无需安装 | 默认使用文件型 H2 数据库。使用其他数据库见 [01 快速入门](01-getting-started.md#开发实例) |
16
16
 
17
17
  ## 基础设置
18
18
 
@@ -145,7 +145,7 @@ power-crm/
145
145
 
146
146
  ## 下一步
147
147
 
148
- 安装依赖后,本目录下会出现手册的其余章节。建议先阅读[手册目录](README.md),再阅读 [01 快速入门](01-getting-started.md),其中介绍创建项目时各选项的含义、常用命令以及如何切换数据库。
148
+ 安装依赖后,本目录下会出现手册的其余章节。建议先阅读[手册目录](README.md),再阅读 [01 快速入门](01-getting-started.md),其中介绍创建项目时各选项的含义、常用命令以及开发实例与数据库。
149
149
 
150
150
  首次使用时建议执行一次 `pnpm qs sources`,将平台的接口文档、数据文件与前端 `.d.ts` 下载到 `.quicksilver/generated/`,手册后续多处会引用其中的内容。
151
151
 
@@ -12,7 +12,7 @@
12
12
  | Node.js | **`>=22.13.0`** | pnpm 11 要求的最低版本 |
13
13
  | pnpm | **11** | 必須使用 pnpm,不支援 npm 與 yarn。workspace、catalog、`allowBuilds` 均依賴 pnpm。
14
14
  | Gradle | 無需安裝 | 專案內建 Gradle Wrapper(`./gradlew`),首次執行時自動下載 |
15
- | 資料庫 | 無需安裝 | 預設使用檔案型 H2 資料庫。使用其他資料庫請參閱 [01 快速入門](01-getting-started.md#切換資料庫) |
15
+ | 資料庫 | 無需安裝 | 預設使用檔案型 H2 資料庫。使用其他資料庫請參閱 [01 快速入門](01-getting-started.md#開發實例) |
16
16
 
17
17
  ## 基本設定
18
18
 
@@ -145,7 +145,7 @@ power-crm/
145
145
 
146
146
  ## 下一步
147
147
 
148
- 安裝相依套件後,本目錄下會出現手冊的其餘章節。建議先閱讀[手冊目錄](README.md),再閱讀 [01 快速入門](01-getting-started.md),其中介紹建立專案時各選項的含義、常用指令以及如何切換資料庫。
148
+ 安裝相依套件後,本目錄下會出現手冊的其餘章節。建議先閱讀[手冊目錄](README.md),再閱讀 [01 快速入門](01-getting-started.md),其中介紹建立專案時各選項的含義、常用指令以及開發實例與資料庫。
149
149
 
150
150
  首次使用時建議執行一次 `pnpm qs sources`,將平台的 API 文件、資料檔與前端 `.d.ts` 下載到 `.quicksilver/generated/`,手冊後續多處會引用其中的內容。
151
151
 
@@ -103,9 +103,10 @@ can be started; you rarely touch it.
103
103
  Project scripts (see `scripts` in `package.json`; the database dialect scripts live there too):
104
104
 
105
105
  ```bash
106
- pnpm dev # Full dev environment (API + web), ports 6286 / 6288
107
- pnpm dev:h2 # A second, isolated instance (own database and Atomikos directory)
108
- pnpm dev:fresh # A separate instance (ports 6280 / 6281) whose own database is rebuilt from init and demo data at every start
106
+ pnpm dev # Full dev environment (API + web), ports 6286 / 6288 in the main checkout
107
+ pnpm dev:ai # The instance for AI agents, with ports and a database of its own
108
+ pnpm dev:fresh # An instance whose own database is rebuilt from init and demo data at every start
109
+ pnpm qs dev --list # Every instance of this workspace, its ports and whether it is running
109
110
  pnpm build # Full build (backend + frontend)
110
111
  pnpm test:api # Backend tests
111
112
  pnpm lint # ESLint (pnpm lint:fix to autofix)
@@ -173,32 +174,43 @@ you are unsure what a target would actually do.
173
174
 
174
175
  ## Development servers and ports
175
176
 
176
- **An AI agent starts `pnpm dev:h2`, never `pnpm dev`.**
177
-
178
- `pnpm dev` (web 6286 / api 6288) belongs to the person you are working with. It may well be running
179
- already, and it is backed by the H2 file database `run/api/local/data/h2/dev`, which keeps
180
- their data between starts: with `auto-init.draft-sync: true`, every start runs the upgrade drafts that
181
- changed since the last one. Starting or restarting it yourself interrupts them and runs your
182
- half-finished drafts against their data, so taking it over does not just steal a port.
183
-
184
- `pnpm dev:h2` (6210 / 6211) is the isolated instance: its own database (`h2`), its own log
185
- directory and its own Atomikos directory, so it can run side by side with theirs. The other dialect
186
- scripts (`dev:postgresql` 6220/6221, `dev:mariadb` 6230/6231, `dev:mssql` 6240/6241, `dev:oracle`
187
- 6250/6251) are equally isolated, but they need a real server: fill in the placeholders in
188
- `run/api/config/application-<dialect>.yaml` first. `pnpm dev:fresh` (6280 / 6281) is isolated in the
189
- same way, on the H2 database `fresh`, which it drops and rebuilds from init and demo data at every start.
190
-
191
- Two more rules:
192
-
193
- - **Port already taken?** Do not prefix the pnpm script — `pnpm dev:h2` sets `PORT` and `API_PORT`
194
- itself, and a command-prefix assignment inside the script wins over the one you exported. Call the
195
- CLI directly instead: `PORT=9001 API_PORT=9002 SPRING_PROFILES_ACTIVE=dev,h2 pnpm qs dev`. Pick
196
- something in 9000–9999; if it is still taken, Vite fails outright — pick another.
197
- - **Stop the server you started.** Especially after Playwright: leaving it running holds the port
198
- and the database file.
177
+ **An AI agent starts `pnpm dev:ai`, never `pnpm dev` or `pnpm dev:fresh`.**
178
+
179
+ Every workspace, the main checkout or a git worktree, runs its own development instances side by
180
+ side, each with its own ports and its own database. `pnpm dev` and `pnpm dev:fresh` belong to the
181
+ person you are working with, in every workspace. They may well be running already, and the dev
182
+ database keeps their data between starts: with `auto-init.draft-sync: true`, every start runs the
183
+ upgrade drafts that changed since the last one. Starting or restarting it yourself interrupts them
184
+ and runs your half-finished drafts against their data, so taking it over does not just steal a port.
185
+
186
+ `pnpm dev:ai` is the instance for you: ports and a database of its own, side by side with theirs.
187
+ `pnpm qs dev --list` shows every instance of the workspace with its ports and whether it is running.
188
+ To check a database dialect, start its instance, such as `pnpm dev:postgresql`, after filling in the
189
+ placeholders in `run/api/config/application-<dialect>.yaml`.
190
+
191
+ Rules:
192
+
193
+ - **`dev:ai` ports already taken?** Another session in this workspace is using it. Do not stop that
194
+ process: start `pnpm dev:ai2` instead, and ask the person you work with when both are taken. Point
195
+ the test commands at the instance you started: `pnpm qs test verify --instance ai2`,
196
+ `pnpm qs test unit-smoke <unit> --instance ai2`.
197
+ - **Add `--fresh` the first time you start `dev:ai` or `dev:ai2` in a session**, as in
198
+ `pnpm dev:ai --fresh`, and leave it out on later restarts. The database belongs to this workspace
199
+ and instance, but one workspace is used by several sessions in turn, so it may still hold what an
200
+ earlier session left. Restarts within the session go without `--fresh`, so that your own draft
201
+ changes pass through draft sync as they will for others.
202
+ - **Treat the databases of the dialect instances as scratch.** The person you work with and you
203
+ share them, anyone may rebuild them at any time, so keep nothing there you need. Add `--fresh`
204
+ the first time you start a dialect instance in a session, as in `pnpm dev:postgresql --fresh`,
205
+ and again after changing its database by hand. One exception: to check a DDL command in a draft
206
+ (a column type change, a rename), the database has to exist from before the change, and the start
207
+ after editing the draft goes without `--fresh`. A fresh install runs init and skips the drafts,
208
+ so the command would never run.
199
209
  - **Rebuild when the database no longer matches the branch.** After switching branches, or when
200
210
  earlier experiments left the database in a state you cannot explain, start once with
201
- `pnpm dev:h2 --fresh`. Draft sync never undoes a change that was taken out of a draft.
211
+ `pnpm dev:ai --fresh`. Draft sync never undoes a change that was taken out of a draft.
212
+ - **Stop the server you started.** Especially after Playwright: leaving it running holds the ports
213
+ and the database.
202
214
 
203
215
  ## Signing in locally
204
216
 
@@ -220,13 +232,16 @@ platform's own specs and for yours under `test/e2e/scenarios/`, which start out
220
232
  ## Looking at the development data
221
233
 
222
234
  The profile file is the source of truth for where the data actually lives. Read
223
- `run/api/config/application-dev.yaml` (or `application-<dialect>.yaml` for whichever profile you
224
- started) and take the datasource from there; note that paths in it are relative to `run/api`, which
225
- is the working directory of the API process.
235
+ `run/api/config/application-dev.yaml` (or `application-<dialect>.yaml` for whichever instance you
236
+ started) and take the datasource from there. Paths in it are relative to `run/api`, which is the
237
+ working directory of the API process. The database name then gets the suffix of the instance, which
238
+ `pnpm qs dev --list` shows: in a git worktree and for instances such as `ai`, the name in the file is
239
+ followed by `_worktree<slot>` and `_ai`.
226
240
 
227
- For the default H2 profiles that means a file — `run/api/local/data/h2/dev` for `pnpm dev`,
228
- `h2` for `pnpm dev:h2` — which you can open with any H2 client. For the other dialects,
229
- connect with your usual client using the host, port, user and password from that same file.
241
+ For the default H2 profiles that means a file, `run/api/local/data/h2/dev` for `pnpm dev` in the main
242
+ checkout and `dev_ai` next to it for `pnpm dev:ai`, which you can open with any H2 client. For the
243
+ other dialects, connect with your usual client using the host, port, user and password from that
244
+ same file.
230
245
 
231
246
  The development database is kept between starts. With `auto-init.draft-sync: true`, every start runs
232
247
  each module draft `data/upgrade/draft.jsons` that changed since the last start, and data entered by hand
@@ -256,7 +271,7 @@ installs only. Imperative commands in a draft (`@sql`,
256
271
  `$update`, `$delete` ...) run once per `$key`; to run a changed one again, give it a new `$key`.
257
272
  `$place` is the exception: it runs again whenever its draft runs. To rebuild the database from the
258
273
  data files under `data/init/` and `data/demo/`, add `--fresh` to the start command for one start
259
- (`pnpm dev:h2 --fresh`). It changes no config file, and the next start without it keeps the data again.
274
+ (`pnpm dev:ai --fresh`). It changes no config file, and the next start without it keeps the data again.
260
275
 
261
276
  ## Development skills
262
277
 
@@ -10,15 +10,17 @@
10
10
  "scripts": {
11
11
  "// --- start dev servers -------------------------------------------": "",
12
12
  "dev": "quicksilver dev",
13
- "dev:fresh": "PORT=6280 API_PORT=6281 SPRING_PROFILES_ACTIVE=dev,fresh quicksilver dev --fresh",
14
- "dev:h2": "PORT=6210 API_PORT=6211 SPRING_PROFILES_ACTIVE=dev,h2 quicksilver dev",
15
- "dev:postgresql": "PORT=6220 API_PORT=6221 SPRING_PROFILES_ACTIVE=dev,postgresql quicksilver dev",
16
- "dev:mariadb": "PORT=6230 API_PORT=6231 SPRING_PROFILES_ACTIVE=dev,mariadb quicksilver dev",
17
- "dev:mssql": "PORT=6240 API_PORT=6241 SPRING_PROFILES_ACTIVE=dev,mssql quicksilver dev",
18
- "dev:oracle": "PORT=6250 API_PORT=6251 SPRING_PROFILES_ACTIVE=dev,oracle quicksilver dev",
13
+ "dev:fresh": "quicksilver dev --instance fresh",
14
+ "dev:ai": "quicksilver dev --instance ai",
15
+ "dev:ai2": "quicksilver dev --instance ai2",
16
+ "dev:h2": "quicksilver dev --instance h2",
17
+ "dev:postgresql": "quicksilver dev --instance postgresql",
18
+ "dev:mariadb": "quicksilver dev --instance mariadb",
19
+ "dev:mssql": "quicksilver dev --instance mssql",
20
+ "dev:oracle": "quicksilver dev --instance oracle",
19
21
  "dev:debug": "quicksilver dev --debug",
20
- "dev:api": "quicksilver gradle :run:bootRun",
21
- "dev:web": "pnpm -C run/web dev",
22
+ "dev:api": "quicksilver dev api",
23
+ "dev:web": "quicksilver dev web",
22
24
  "// --- build subprojects -------------------------------------------": "",
23
25
  "build": "pnpm build:api && pnpm build:web",
24
26
  "build:api": "quicksilver gradle build",
@@ -25,13 +25,13 @@ catalogs:
25
25
  client:
26
26
  "@qs-charts/adapter-preact": "0.2.11"
27
27
  "@qs-charts/core": "0.2.11"
28
- "@qs-elements/web": "1.0.163"
29
- "@qs-elements/web-preact": "1.0.163"
28
+ "@qs-elements/web": "1.0.165"
29
+ "@qs-elements/web-preact": "1.0.165"
30
30
  quicksilver:
31
- "@qs-platform/cli": "0.8.26"
32
- "@qs-platform/preset-vite-web": "0.8.26"
33
- "@qs-platform/web-module-core": "0.8.26"
34
- "@qs-platform/web-module-org": "0.8.26"
31
+ "@qs-platform/cli": "0.8.29"
32
+ "@qs-platform/preset-vite-web": "0.8.29"
33
+ "@qs-platform/web-module-core": "0.8.29"
34
+ "@qs-platform/web-module-org": "0.8.29"
35
35
  tooling:
36
36
  "@types/node": "^26.4.0"
37
37
  "sass": "^1.103.1"
@@ -19,3 +19,30 @@ packaging:
19
19
  archive-name: {{PROJECT_CODE}}
20
20
  fixed-modules: [{{PLATFORM_MODULE_CODES_QUOTED}}, '{{MODULE_CODE}}']
21
21
  visible-modules:
22
+
23
+ # Development instances. `pnpm dev:<name>` runs `quicksilver dev --instance <name>`. The dev instance
24
+ # is required, `pnpm dev` starts it. port-offset gives the last two digits of the ports: web listens on
25
+ # the base port plus it, the API on that plus 2, and plus 1 and plus 3 are reserved. profile adds
26
+ # run/api/config/application-<profile>.yaml on top of application-dev.yaml. db-suffix is appended to
27
+ # the database name. rebuild: always rebuilds the database from init and demo data at every start. The
28
+ # developer manual (01-getting-started.md) describes every field, the Oracle user each instance logs in
29
+ # as, and the base ports to choose when several projects run on one machine.
30
+ dev:
31
+ # The main workspace listens on this plus the port offset: 6286 for dev.
32
+ main-base-port: 6200
33
+ # A git worktree with slot n listens on this plus n * 100 plus the port offset: 7186 for dev in
34
+ # worktree 1.
35
+ worktree-base-port: 7000
36
+ instances:
37
+ # 1x to 5x: one instance per database
38
+ h2: { port-offset: 10, profile: h2 }
39
+ postgresql: { port-offset: 20, profile: postgresql }
40
+ mariadb: { port-offset: 30, profile: mariadb }
41
+ mssql: { port-offset: 40, profile: mssql }
42
+ oracle: { port-offset: 50, profile: oracle }
43
+ # 8x: the developer
44
+ fresh: { port-offset: 80, db-suffix: fresh, rebuild: always }
45
+ dev: { port-offset: 86 }
46
+ # 9x: AI agents
47
+ ai: { port-offset: 92, db-suffix: ai }
48
+ ai2: { port-offset: 96, db-suffix: ai2 }
@@ -9,34 +9,75 @@ startup; nothing in this directory ships to your users.
9
9
  |---|---|
10
10
  | `application.yaml` | Base configuration: ports, CORS, cache, upload, Atomikos. **Declares no datasources** — those live in the profile files below |
11
11
  | `application-dev.yaml` | The `dev` profile, active by default. Datasources for local development, plus `auto-init` (with `draft-sync` on) and `fail-on-unregistered-data-source` |
12
- | `application-<dialect>.yaml` | One overlay per database dialect (`h2`, `postgresql`, `mariadb`, `mssql`, `oracle`). Each carries its own datasources, log directory and Atomikos log directory, so several can run side by side |
12
+ | `application-<dialect>.yaml` | One overlay per database dialect (`h2`, `postgresql`, `mariadb`, `mssql`, `oracle`), each with its own datasources. `pnpm dev:<dialect>` starts the instance that uses it |
13
13
  | `application-test.yaml` | Used by `modules/*/api` tests. In-memory H2, wiped on every run |
14
14
  | `logback.xml` / `logback-test.xml` | Logging |
15
15
 
16
16
  ## Choosing a database
17
17
 
18
- `pnpm dev` runs on a file-based H2 database with no setup. To use another dialect, run its script
19
- — `pnpm dev:postgresql`, `pnpm dev:mariadb`, and so on — which sets
20
- `SPRING_PROFILES_ACTIVE=dev,<dialect>` and its own ports.
18
+ `pnpm dev` runs on a file-based H2 database with no setup. To use another dialect, run its script,
19
+ such as `pnpm dev:postgresql` or `pnpm dev:mariadb`. Each script starts the development instance of
20
+ that name, `quicksilver dev --instance <dialect>`, which puts the dialect's profile on top of `dev`
21
+ and gives the instance ports and a database of its own. `pnpm qs dev --list` shows every instance.
21
22
 
22
- The H2 profiles ship with working credentials (`sa` / `sa`) — H2 creates the database on first
23
+ The H2 profiles ship with working credentials (`sa` / `sa`): H2 creates the database file on first
23
24
  connect, so there is nothing to change. In every other dialect file the user and `PASSWORD` are
24
25
  placeholders you must replace.
25
26
 
26
- **Do not drop the `dev,` prefix.** Without it `application-dev.yaml` is not loaded, and since
27
- `application.yaml` declares no datasources, startup fails with `Missing required datasource code`.
28
- That is deliberate: a missing datasource list must fail loudly rather than fall back to some
29
- default database.
30
-
31
- Create the databases before use — the server-based dialects only. Each of those files names its
32
- database after this project — a project called `power-crm` gets `power_crm_dev` — so several
33
- Quicksilver products can share one database server without colliding. Two files sit outside that
34
- rule: Oracle, whose "database" is a PDB with a name fixed by the container image (`FREEPDB1`), and
35
- the H2 profiles, whose databases are plain files under this project's own `local/data/h2/`
36
- (`dev` for `pnpm dev`, `h2` for `pnpm dev:h2`) and so have nothing to collide with. The
37
- datasource list is replaced as a whole, so when you change one entry, keep the other one listed
27
+ The profile always comes from the instance. `quicksilver dev` refuses to start when
28
+ `SPRING_PROFILES_ACTIVE` is set, because a profile from elsewhere would not match the database the
29
+ instance is meant to use.
30
+
31
+ Each server-based dialect file names its database after this project, so several Quicksilver
32
+ products can share one database server without colliding: a project called `power-crm` gets
33
+ `power_crm_matrix`. When you point `application-dev.yaml` at a server, name that database
34
+ `power_crm_dev`, so that `pnpm dev` and the dialect instances never share one. Two files sit outside
35
+ that rule. Oracle's "database" is a PDB with a name fixed by the container image (`FREEPDB1`), so
36
+ `application-dev.yaml` and `application-oracle.yaml` are kept apart by user instead, see the next
37
+ section. The H2 profiles keep their databases as plain files under this project's own
38
+ `local/data/h2/` (`dev` for `pnpm dev`, `h2` for `pnpm dev:h2`) and so have nothing to collide with.
39
+ The datasource list is replaced as a whole, so when you change one entry, keep the other one listed
38
40
  as well.
39
41
 
42
+ ## Instances, worktrees and databases
43
+
44
+ Every workspace, the main checkout or a git worktree, runs its own instances side by side: `dev`,
45
+ `fresh`, `ai`, `ai2` and one per dialect. The instance decides the ports and the database. In the
46
+ main checkout `pnpm dev` uses web 6286 / API 6288 and the database configured here. A worktree gets
47
+ a slot number the first time an instance starts in it, and its ports move to 7n86 / 7n88 for slot n.
48
+ The base ports 6200 and 7000 are `main-base-port` and `worktree-base-port` in `quicksilver.yaml`.
49
+
50
+ The database name gets a suffix in a worktree (`worktree<slot>`) and for instances with a
51
+ `db-suffix` (`fresh`, `ai`, `ai2`), joined with underscores: `power_crm_dev_worktree1_ai`, or for H2
52
+ the file `dev_worktree1_ai`. In development mode a missing database is created at startup: the log
53
+ names it in a WARN line, and when the account may not create databases, startup fails with the
54
+ statement to run by hand. When a suffixed database is created next to an existing configured
55
+ database, MySQL, MariaDB and SQL Server copy its character set and collation. The first start of each
56
+ instance in a new worktree rebuilds its database from init and demo data, and so does every start of
57
+ `fresh`.
58
+
59
+ An Oracle database is a PDB and cannot be created per instance, so the suffix goes on the user
60
+ instead: the instance logs in as `<configured user>_<suffix>` with the configured URL and password,
61
+ for example `pnpm dev:oracle` in worktree 1 as `system_worktree1`. In Oracle a user is its own
62
+ schema, so each instance keeps its tables apart. In development mode a missing user is created at startup with the configured account:
63
+ the log names it in a WARN line. The account needs the DBA role, or the system privileges
64
+ `create profile`, `create user`, `drop user`, `grant any privilege` and `grant any role`. Without
65
+ them startup fails with every statement a DBA has to run. The configured user must be an unquoted
66
+ name that does not start with `C##`. Such a name belongs to a common user of the CDB, while the
67
+ derived user is a local user created in the PDB, and local user names cannot start with `C##`.
68
+
69
+ When you point `application-dev.yaml` at Oracle, give it a user other than the one in
70
+ `application-oracle.yaml`, for example `power_crm_dev` created for development. With the same user,
71
+ `pnpm dev` and `pnpm dev:oracle` share one schema in the main checkout, where rebuilding either with
72
+ `--fresh` wipes the data of the other. In a worktree they derive one user, and the second of the two
73
+ to start for the first time wipes the data of the other. No user is created when no suffix applies,
74
+ so create this one beforehand. Besides `create session`, `create table`, `create view`,
75
+ `select_catalog_role` and a tablespace quota, it needs the privileges above, which the instances
76
+ whose database name gets a suffix use to create their derived users.
77
+
78
+ To change the instance table, or the base ports when several projects run on one machine, see the
79
+ developer manual (`.quicksilver/manual`, "Getting started").
80
+
40
81
  ## H2 URLs
41
82
 
42
83
  The two H2 parameters below are not optional. Both failures are quiet and show up far from the
@@ -52,9 +93,11 @@ configuration, so keep them when you edit an H2 URL:
52
93
  ## MySQL
53
94
 
54
95
  MySQL works, but **you have to supply the driver**: Connector/J is GPLv2, so it is not bundled.
55
- Add `runtimeOnly("com.mysql:mysql-connector-j")` to `run/api/build.gradle.kts`, then add a
56
- `application-mysql.yaml` overlay and a `dev:mysql` script of your own, modelled on the other
57
- dialects. Every other dialect listed above ships with its driver.
96
+ Add `runtimeOnly("com.mysql:mysql-connector-j")` to `run/api/build.gradle.kts`, then add an
97
+ `application-mysql.yaml` overlay modelled on the other dialects, a `mysql` instance in the
98
+ `dev.instances` table of `quicksilver.yaml`, and a `dev:mysql` script running
99
+ `quicksilver dev --instance mysql`. Give the new instance a port offset at least 4 away from every
100
+ other one, for example 70. Every other dialect listed above ships with its driver.
58
101
 
59
102
  ## The dev database
60
103
 
@@ -1,12 +1,17 @@
1
1
  #
2
2
  # Overlay for the `dev` profile. Activated by default via `spring.profiles.active` in
3
- # application.yaml, and by every `pnpm dev:*` script.
3
+ # application.yaml, and by `quicksilver dev` for every instance.
4
4
  #
5
5
  spring:
6
6
  application.name: DEV
7
7
 
8
8
  quicksilver:
9
9
  datasource:
10
+ # `quicksilver dev` gives every instance in every workspace a database of its own, derived from
11
+ # the URLs below. In a git worktree, and for instances with a db-suffix such as fresh and ai, it
12
+ # appends a suffix to the database name: dev_worktree1, dev_fresh, dev_ai. For H2 the suffix goes
13
+ # on the file name. A missing database is created at startup in development mode, also when you
14
+ # point dev at a server-based database here.
10
15
  # Keep the quotes around passwords: special characters (# : @ ...) then paste in as is.
11
16
  items:
12
17
  - code: metadata
@@ -1,11 +1,8 @@
1
1
  #
2
2
  # Overlay for the `h2` profile: only the keys that differ from application-dev.yaml,
3
- # everything else is inherited key by key. Activate with SPRING_PROFILES_ACTIVE=dev,h2
4
- # (or run `pnpm dev:h2`).
3
+ # everything else is inherited key by key. `pnpm dev:h2` starts the `h2` instance with
4
+ # it. The log and Atomikos directories are passed by `quicksilver dev` for each instance.
5
5
  #
6
- logging:
7
- file.path: local/log/application/h2
8
-
9
6
  quicksilver:
10
7
  datasource:
11
8
  # Keep the quotes around passwords: special characters (# : @ ...) then paste in as is.
@@ -18,5 +15,3 @@ quicksilver:
18
15
  url: "jdbc:h2:./local/data/h2/h2;DB_CLOSE_ON_EXIT=FALSE;DATABASE_TO_UPPER=false;MODE=MySQL"
19
16
  user: sa
20
17
  password: "sa"
21
- atomikos:
22
- log_base_dir: local/log/atomikos/h2
@@ -1,22 +1,17 @@
1
1
  #
2
2
  # Overlay for the `mariadb` profile: only the keys that differ from application-dev.yaml,
3
- # everything else is inherited key by key. Activate with SPRING_PROFILES_ACTIVE=dev,mariadb
4
- # (or run `pnpm dev:mariadb`).
3
+ # everything else is inherited key by key. `pnpm dev:mariadb` starts the `mariadb` instance with
4
+ # it. The log and Atomikos directories are passed by `quicksilver dev` for each instance.
5
5
  #
6
- logging:
7
- file.path: local/log/application/mariadb
8
-
9
6
  quicksilver:
10
7
  datasource:
11
8
  # Keep the quotes around passwords: special characters (# : @ ...) then paste in as is.
12
9
  items:
13
10
  - code: metadata
14
- url: "jdbc:mariadb://localhost:3308/{{DB_PREFIX}}_dev?characterEncoding=utf8mb4"
11
+ url: "jdbc:mariadb://localhost:3308/{{DB_PREFIX}}_matrix?characterEncoding=utf8mb4"
15
12
  user: root
16
13
  password: "PASSWORD"
17
14
  - code: business
18
- url: "jdbc:mariadb://localhost:3308/{{DB_PREFIX}}_dev?characterEncoding=utf8mb4"
15
+ url: "jdbc:mariadb://localhost:3308/{{DB_PREFIX}}_matrix?characterEncoding=utf8mb4"
19
16
  user: root
20
17
  password: "PASSWORD"
21
- atomikos:
22
- log_base_dir: local/log/atomikos/mariadb
@@ -1,22 +1,17 @@
1
1
  #
2
2
  # Overlay for the `mssql` profile: only the keys that differ from application-dev.yaml,
3
- # everything else is inherited key by key. Activate with SPRING_PROFILES_ACTIVE=dev,mssql
4
- # (or run `pnpm dev:mssql`).
3
+ # everything else is inherited key by key. `pnpm dev:mssql` starts the `mssql` instance with
4
+ # it. The log and Atomikos directories are passed by `quicksilver dev` for each instance.
5
5
  #
6
- logging:
7
- file.path: local/log/application/mssql
8
-
9
6
  quicksilver:
10
7
  datasource:
11
8
  # Keep the quotes around passwords: special characters (# : @ ...) then paste in as is.
12
9
  items:
13
10
  - code: metadata
14
- url: "jdbc:sqlserver://localhost:1433;DatabaseName={{DB_PREFIX}}_dev;trustServerCertificate=true"
11
+ url: "jdbc:sqlserver://localhost:1433;DatabaseName={{DB_PREFIX}}_matrix;trustServerCertificate=true"
15
12
  user: sa
16
13
  password: "PASSWORD"
17
14
  - code: business
18
- url: "jdbc:sqlserver://localhost:1433;DatabaseName={{DB_PREFIX}}_dev;trustServerCertificate=true"
15
+ url: "jdbc:sqlserver://localhost:1433;DatabaseName={{DB_PREFIX}}_matrix;trustServerCertificate=true"
19
16
  user: sa
20
17
  password: "PASSWORD"
21
- atomikos:
22
- log_base_dir: local/log/atomikos/mssql
@@ -1,11 +1,8 @@
1
1
  #
2
2
  # Overlay for the `oracle` profile: only the keys that differ from application-dev.yaml,
3
- # everything else is inherited key by key. Activate with SPRING_PROFILES_ACTIVE=dev,oracle
4
- # (or run `pnpm dev:oracle`).
3
+ # everything else is inherited key by key. `pnpm dev:oracle` starts the `oracle` instance with
4
+ # it. The log and Atomikos directories are passed by `quicksilver dev` for each instance.
5
5
  #
6
- logging:
7
- file.path: local/log/application/oracle
8
-
9
6
  quicksilver:
10
7
  datasource:
11
8
  # Keep the quotes around passwords: special characters (# : @ ...) then paste in as is.
@@ -18,5 +15,3 @@ quicksilver:
18
15
  url: "jdbc:oracle:thin:@//localhost:1521/FREEPDB1"
19
16
  user: system
20
17
  password: "PASSWORD"
21
- atomikos:
22
- log_base_dir: local/log/atomikos/oracle
@@ -1,22 +1,17 @@
1
1
  #
2
2
  # Overlay for the `postgresql` profile: only the keys that differ from application-dev.yaml,
3
- # everything else is inherited key by key. Activate with SPRING_PROFILES_ACTIVE=dev,postgresql
4
- # (or run `pnpm dev:postgresql`).
3
+ # everything else is inherited key by key. `pnpm dev:postgresql` starts the `postgresql` instance with
4
+ # it. The log and Atomikos directories are passed by `quicksilver dev` for each instance.
5
5
  #
6
- logging:
7
- file.path: local/log/application/postgresql
8
-
9
6
  quicksilver:
10
7
  datasource:
11
8
  # Keep the quotes around passwords: special characters (# : @ ...) then paste in as is.
12
9
  items:
13
10
  - code: metadata
14
- url: "jdbc:postgresql://127.0.0.1/{{DB_PREFIX}}_dev?stringtype=unspecified"
11
+ url: "jdbc:postgresql://127.0.0.1/{{DB_PREFIX}}_matrix?stringtype=unspecified"
15
12
  user: postgres
16
13
  password: "PASSWORD"
17
14
  - code: business
18
- url: "jdbc:postgresql://127.0.0.1/{{DB_PREFIX}}_dev?stringtype=unspecified"
15
+ url: "jdbc:postgresql://127.0.0.1/{{DB_PREFIX}}_matrix?stringtype=unspecified"
19
16
  user: postgres
20
17
  password: "PASSWORD"
21
- atomikos:
22
- log_base_dir: local/log/atomikos/postgresql
@@ -1,23 +0,0 @@
1
- #
2
- # Overlay for the `fresh` profile used by `pnpm dev:fresh`: only the keys that differ from
3
- # application-dev.yaml, everything else is inherited key by key. It has a database of its own, which
4
- # the script drops and rebuilds from init and demo data at every start, so the `pnpm dev` database
5
- # is never touched.
6
- #
7
- logging:
8
- file.path: local/log/application/fresh
9
-
10
- quicksilver:
11
- datasource:
12
- # Keep the quotes around passwords: special characters (# : @ ...) then paste in as is.
13
- items:
14
- - code: metadata
15
- url: "jdbc:h2:./local/data/h2/fresh;DB_CLOSE_ON_EXIT=FALSE;DATABASE_TO_UPPER=false;MODE=MySQL"
16
- user: sa
17
- password: "sa"
18
- - code: business
19
- url: "jdbc:h2:./local/data/h2/fresh;DB_CLOSE_ON_EXIT=FALSE;DATABASE_TO_UPPER=false;MODE=MySQL"
20
- user: sa
21
- password: "sa"
22
- atomikos:
23
- log_base_dir: local/log/atomikos/fresh