create-qs 0.8.24 → 0.8.27
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/index.js +175 -2
- package/package.json +1 -1
- package/template/default/.quicksilver/manual/en-US/00-setup.md +1 -1
- package/template/default/.quicksilver/manual/zh-CN/00-setup.md +1 -1
- package/template/default/.quicksilver/manual/zh-TW/00-setup.md +1 -1
- package/template/default/AGENTS.md +23 -6
- package/template/default/pnpm-workspace.yaml +6 -6
- package/template/default/quicksilver.yaml +8 -0
- package/template/default/run/api/config/README.md +12 -0
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();
|
|
@@ -894,6 +900,133 @@ var MultiselectPrompt = class {
|
|
|
894
900
|
function multiselect(options) {
|
|
895
901
|
return new MultiselectPrompt(options).run();
|
|
896
902
|
}
|
|
903
|
+
var SelectPrompt = class {
|
|
904
|
+
rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
905
|
+
message;
|
|
906
|
+
items;
|
|
907
|
+
cursor;
|
|
908
|
+
state = "init";
|
|
909
|
+
constructor(options) {
|
|
910
|
+
if (options.items.length === 0) {
|
|
911
|
+
throw new Error("select: items must not be empty");
|
|
912
|
+
}
|
|
913
|
+
this.message = options.message;
|
|
914
|
+
this.items = options.items.map((c) => ({ ...c }));
|
|
915
|
+
const defaultIndex = options.default !== void 0 ? this.items.findIndex((it) => it.value === options.default) : -1;
|
|
916
|
+
this.cursor = defaultIndex >= 0 ? defaultIndex : 0;
|
|
917
|
+
readline.emitKeypressEvents(process.stdin, this.rl);
|
|
918
|
+
if (process.stdin.isTTY) {
|
|
919
|
+
process.stdin.setRawMode(true);
|
|
920
|
+
}
|
|
921
|
+
}
|
|
922
|
+
async run() {
|
|
923
|
+
process.stdout.write(hideCursor());
|
|
924
|
+
this.render();
|
|
925
|
+
return new Promise((resolve3, reject) => {
|
|
926
|
+
let resolved = false;
|
|
927
|
+
const cleanup = () => {
|
|
928
|
+
process.stdin.removeListener("keypress", handleKeypress);
|
|
929
|
+
process.stdin.removeListener("data", handleData);
|
|
930
|
+
};
|
|
931
|
+
const safeResolve = (value) => {
|
|
932
|
+
if (!resolved) {
|
|
933
|
+
resolved = true;
|
|
934
|
+
cleanup();
|
|
935
|
+
resolve3(value);
|
|
936
|
+
}
|
|
937
|
+
};
|
|
938
|
+
const safeReject = (reason) => {
|
|
939
|
+
if (!resolved) {
|
|
940
|
+
resolved = true;
|
|
941
|
+
cleanup();
|
|
942
|
+
reject(reason);
|
|
943
|
+
}
|
|
944
|
+
};
|
|
945
|
+
const handleKeypress = (_str, key) => {
|
|
946
|
+
if (resolved) {
|
|
947
|
+
return;
|
|
948
|
+
}
|
|
949
|
+
if (key.name === "up") {
|
|
950
|
+
this.moveCursor(-1);
|
|
951
|
+
} else if (key.name === "down") {
|
|
952
|
+
this.moveCursor(1);
|
|
953
|
+
} else if (key.name === "return") {
|
|
954
|
+
process.stdout.write(up());
|
|
955
|
+
safeResolve(this.finish());
|
|
956
|
+
} else if (key.ctrl && key.name === "c") {
|
|
957
|
+
this.state = "cancelled";
|
|
958
|
+
this.exit();
|
|
959
|
+
safeReject(new PromptCancelled());
|
|
960
|
+
}
|
|
961
|
+
this.render();
|
|
962
|
+
};
|
|
963
|
+
const handleData = (data) => {
|
|
964
|
+
if (resolved) {
|
|
965
|
+
return;
|
|
966
|
+
}
|
|
967
|
+
const str = data.toString();
|
|
968
|
+
if (str === "\n" || str === "\r" || str === "\r\n") {
|
|
969
|
+
process.stdout.write(down());
|
|
970
|
+
safeResolve(this.finish());
|
|
971
|
+
}
|
|
972
|
+
};
|
|
973
|
+
process.stdin.on("keypress", handleKeypress);
|
|
974
|
+
process.stdin.on("data", handleData);
|
|
975
|
+
});
|
|
976
|
+
}
|
|
977
|
+
render() {
|
|
978
|
+
const hint = chalk4.dim(promptTexts().selectHelp);
|
|
979
|
+
if (this.state === "init") {
|
|
980
|
+
this.state = "select";
|
|
981
|
+
console.log(chalk4.cyan("? ") + chalk4.bold(this.message) + chalk4.dim(" \u203A"));
|
|
982
|
+
console.log(" " + hint);
|
|
983
|
+
this.writeItems();
|
|
984
|
+
} else if (this.state === "select") {
|
|
985
|
+
process.stdout.write(upStart(this.items.length + 1));
|
|
986
|
+
console.log(chalk4.cyan("? ") + chalk4.bold(this.message) + chalk4.dim(" \u203A"));
|
|
987
|
+
console.log(" " + hint + clearLineFromCursor());
|
|
988
|
+
this.writeItems();
|
|
989
|
+
} else {
|
|
990
|
+
process.stdout.write(down() + upClear(this.items.length + 2));
|
|
991
|
+
console.log(this.state === "cancelled" ? cancelledLine(this.message) : chalk4.green("\u2714 ") + chalk4.bold(this.message) + chalk4.dim(" \xB7 ") + chalk4.green(this.getSelectedItem().name));
|
|
992
|
+
}
|
|
993
|
+
}
|
|
994
|
+
writeItems() {
|
|
995
|
+
this.items.forEach((item, index) => this.writeItem(item, index));
|
|
996
|
+
process.stdout.write(up());
|
|
997
|
+
}
|
|
998
|
+
writeItem(item, index) {
|
|
999
|
+
const isSelected = index === this.cursor;
|
|
1000
|
+
if (isSelected) {
|
|
1001
|
+
console.log(` ${chalk4.cyan("\u276F")} ${item.name}`);
|
|
1002
|
+
} else {
|
|
1003
|
+
console.log(` ${item.name}`);
|
|
1004
|
+
}
|
|
1005
|
+
}
|
|
1006
|
+
moveCursor(direction) {
|
|
1007
|
+
const length = this.items.length;
|
|
1008
|
+
this.cursor = (this.cursor + direction + length) % length;
|
|
1009
|
+
}
|
|
1010
|
+
getSelectedItem() {
|
|
1011
|
+
return this.items[this.cursor];
|
|
1012
|
+
}
|
|
1013
|
+
finish() {
|
|
1014
|
+
this.state = "finished";
|
|
1015
|
+
this.exit();
|
|
1016
|
+
return this.getSelectedItem().value;
|
|
1017
|
+
}
|
|
1018
|
+
exit() {
|
|
1019
|
+
this.rl.close();
|
|
1020
|
+
if (process.stdin.isTTY) {
|
|
1021
|
+
process.stdin.setRawMode(false);
|
|
1022
|
+
}
|
|
1023
|
+
process.stdin.removeAllListeners("keypress");
|
|
1024
|
+
process.stdout.write(showCursor());
|
|
1025
|
+
}
|
|
1026
|
+
};
|
|
1027
|
+
function select(options) {
|
|
1028
|
+
return new SelectPrompt(options).run();
|
|
1029
|
+
}
|
|
897
1030
|
var INDENT = " ";
|
|
898
1031
|
var MAX_COLUMNS = 100;
|
|
899
1032
|
function renderCode(line, color = chalk4.cyan) {
|
|
@@ -998,6 +1131,11 @@ var HINTS = {
|
|
|
998
1131
|
"zh-TW": "\u672C\u7522\u54C1\u76F8\u4F9D\u7684\u6A21\u7D44\uFF0C\u5F8C\u7E8C\u53EF\u518D\u884C\u65B0\u589E\u3002",
|
|
999
1132
|
"en-US": "Modules this product depends on. More can be added later."
|
|
1000
1133
|
},
|
|
1134
|
+
jsonsLocale: {
|
|
1135
|
+
"zh-CN": "\u6570\u636E\u6587\u4EF6\u4E2D\u663E\u793A\u6587\u672C\u7684\u8BED\u8A00\uFF0C\u5373\u5355\u5143\u540D\u3001\u83DC\u5355\u3001\u6587\u6848\u7B49\u7684\u4E66\u5199\u8BED\u8A00\u3002\u5B89\u88C5\u65F6\u53EF\u4EE5\u53E6\u9009\u4EA7\u54C1\u8BED\u8A00\uFF0C\u7FFB\u8BD1\u8868\u63D0\u4F9B\u5176\u5B83\u8BED\u8A00\u3002\u26A0 \u66F4\u6539\u8981\u91CD\u5199\u5168\u90E8\u6570\u636E\u6587\u4EF6\u4E0E\u7FFB\u8BD1\u8868\u3002",
|
|
1136
|
+
"zh-TW": "\u8CC7\u6599\u6A94\u4E2D\u986F\u793A\u6587\u5B57\u7684\u8A9E\u8A00\uFF0C\u5373\u55AE\u5143\u540D\u3001\u9078\u55AE\u3001\u6587\u6848\u7B49\u7684\u66F8\u5BEB\u8A9E\u8A00\u3002\u5B89\u88DD\u6642\u53EF\u4EE5\u53E6\u9078\u7522\u54C1\u8A9E\u8A00\uFF0C\u7FFB\u8B6F\u8868\u63D0\u4F9B\u5176\u4ED6\u8A9E\u8A00\u3002\u26A0 \u66F4\u6539\u8981\u91CD\u5BEB\u5168\u90E8\u8CC7\u6599\u6A94\u8207\u7FFB\u8B6F\u8868\u3002",
|
|
1137
|
+
"en-US": "The language of the display texts in data files: unit names, menus, messages. Installations may pick another product language, and translation tables supply the others. \u26A0 Changing it means rewriting every data file and translation table."
|
|
1138
|
+
},
|
|
1001
1139
|
moduleMenu: {
|
|
1002
1140
|
"zh-CN": "\u672C\u6A21\u5757\u5728\u5DE6\u4FA7\u5BFC\u822A\u4E2D\u7684\u4E00\u7EA7\u5165\u53E3\uFF0C\u4E0E\u5E73\u53F0\u5185\u7F6E\u7684\u5165\u53E3\u5E76\u5217\u3002\u540E\u7EED\u521B\u5EFA\u7684\u9875\u9762\u53EF\u6302\u8F7D\u4E8E\u5176\u4E0B\uFF0C\u540D\u79F0\u4E0E\u5B58\u5E9F\u5747\u53EF\u968F\u65F6\u8C03\u6574\u3002",
|
|
1003
1141
|
"zh-TW": "\u672C\u6A21\u7D44\u5728\u5DE6\u5074\u5C0E\u89BD\u5217\u4E2D\u7684\u4E00\u7D1A\u5165\u53E3\uFF0C\u8207\u5E73\u53F0\u5167\u5EFA\u7684\u5165\u53E3\u4E26\u5217\u3002\u5F8C\u7E8C\u5EFA\u7ACB\u7684\u9801\u9762\u53EF\u639B\u8F09\u65BC\u5176\u4E0B\uFF0C\u540D\u7A31\u8207\u5B58\u5EE2\u5747\u53EF\u96A8\u6642\u8ABF\u6574\u3002",
|
|
@@ -1033,6 +1171,7 @@ var TEXTS = {
|
|
|
1033
1171
|
"zh-TW": "\u70BA\u672C\u6A21\u7D44\u5EFA\u7ACB\u9802\u5C64\u9078\u55AE",
|
|
1034
1172
|
"en-US": "Create a top-level menu for this module"
|
|
1035
1173
|
},
|
|
1174
|
+
"label.jsonsLocale": { "zh-CN": "\u6570\u636E\u6587\u4EF6\u7684\u7F16\u5199\u8BED\u8A00", "zh-TW": "\u8CC7\u6599\u6A94\u7684\u66F8\u5BEB\u8A9E\u8A00", "en-US": "Language of data files" },
|
|
1036
1175
|
"label.moduleMenuName": { "zh-CN": "\u9876\u5C42\u83DC\u5355\u540D\u79F0", "zh-TW": "\u9802\u5C64\u9078\u55AE\u540D\u7A31", "en-US": "Top-level menu name" },
|
|
1037
1176
|
// 校验
|
|
1038
1177
|
"validate.empty": { "zh-CN": "{0}\u4E0D\u80FD\u4E3A\u7A7A", "zh-TW": "{0}\u4E0D\u80FD\u70BA\u7A7A", "en-US": "{0} must be a non-empty string" },
|
|
@@ -1211,6 +1350,17 @@ function printHint2(step) {
|
|
|
1211
1350
|
printHint(HINTS[step][language]);
|
|
1212
1351
|
}
|
|
1213
1352
|
|
|
1353
|
+
// src/collect/jsons-locale.ts
|
|
1354
|
+
var JSONS_LOCALES = ["zh-CN", "zh-TW"];
|
|
1355
|
+
var JSONS_LOCALE_NAMES = {
|
|
1356
|
+
"zh-CN": "\u7B80\u4F53\u4E2D\u6587",
|
|
1357
|
+
"zh-TW": "\u7E41\u9AD4\u4E2D\u6587"
|
|
1358
|
+
};
|
|
1359
|
+
function orderJsonsLocales(uiLanguage) {
|
|
1360
|
+
const preferred = uiLanguage === "zh-TW" ? "zh-TW" : "zh-CN";
|
|
1361
|
+
return [preferred, ...JSONS_LOCALES.filter((locale) => locale !== preferred)];
|
|
1362
|
+
}
|
|
1363
|
+
|
|
1214
1364
|
// src/collect/summary.ts
|
|
1215
1365
|
function row(step, value) {
|
|
1216
1366
|
return { label: t(`label.${step}`), value };
|
|
@@ -1229,6 +1379,7 @@ function summaryRows(options) {
|
|
|
1229
1379
|
row("apiArtifactId", options.apiArtifactId),
|
|
1230
1380
|
row("webModulePackage", options.webModulePackage),
|
|
1231
1381
|
row("platformModules", options.platformModules.join(", ")),
|
|
1382
|
+
row("jsonsLocale", JSONS_LOCALE_NAMES[options.jsonsLocale] ?? options.jsonsLocale),
|
|
1232
1383
|
{ label: t("label.moduleMenuName"), value: options.moduleMenu ?? t("summary.noMenu") }
|
|
1233
1384
|
];
|
|
1234
1385
|
}
|
|
@@ -1422,6 +1573,7 @@ var FLAGS = {
|
|
|
1422
1573
|
apiArtifactId: "--api-artifact-id",
|
|
1423
1574
|
webModulePackage: "--web-module-package",
|
|
1424
1575
|
platformModules: "--modules",
|
|
1576
|
+
jsonsLocale: "--jsons-locale",
|
|
1425
1577
|
moduleMenu: "--module-menu"
|
|
1426
1578
|
};
|
|
1427
1579
|
async function ask(args, step, options) {
|
|
@@ -1537,6 +1689,25 @@ async function collectPlatformModules(args) {
|
|
|
1537
1689
|
const codes = await multiselect({ message: t("label.platformModules"), items });
|
|
1538
1690
|
return resolvePlatformModules(codes).map((module) => module.code);
|
|
1539
1691
|
}
|
|
1692
|
+
async function collectJsonsLocale(args) {
|
|
1693
|
+
const ordered = orderJsonsLocales(language);
|
|
1694
|
+
if (args.jsonsLocale !== void 0) {
|
|
1695
|
+
const value = args.jsonsLocale.trim();
|
|
1696
|
+
if (!JSONS_LOCALES.includes(value)) {
|
|
1697
|
+
throw new UsageError(`--jsons-locale must be one of ${JSONS_LOCALES.join(", ")}, got '${args.jsonsLocale}'`);
|
|
1698
|
+
}
|
|
1699
|
+
return value;
|
|
1700
|
+
}
|
|
1701
|
+
if (args.yes) {
|
|
1702
|
+
return ordered[0];
|
|
1703
|
+
}
|
|
1704
|
+
printHint2("jsonsLocale");
|
|
1705
|
+
return select({
|
|
1706
|
+
message: t("label.jsonsLocale"),
|
|
1707
|
+
default: ordered[0],
|
|
1708
|
+
items: ordered.map((locale) => ({ name: JSONS_LOCALE_NAMES[locale], value: locale }))
|
|
1709
|
+
});
|
|
1710
|
+
}
|
|
1540
1711
|
async function collectModuleMenu(args, options) {
|
|
1541
1712
|
if (args.moduleMenu === false) {
|
|
1542
1713
|
return void 0;
|
|
@@ -1584,6 +1755,7 @@ async function collectProjectOptions(args) {
|
|
|
1584
1755
|
options.apiArtifactId = await collectApiArtifactId(args, options);
|
|
1585
1756
|
options.webModulePackage = await collectWebModulePackage(args, options);
|
|
1586
1757
|
options.platformModules = await collectPlatformModules(args);
|
|
1758
|
+
options.jsonsLocale = await collectJsonsLocale(args);
|
|
1587
1759
|
options.moduleMenu = await collectModuleMenu(args, options);
|
|
1588
1760
|
options.template = "default";
|
|
1589
1761
|
printSummary(t("summary.title"), summaryRows(options));
|
|
@@ -1804,9 +1976,10 @@ async function processFiles(options) {
|
|
|
1804
1976
|
variables.set("WEB_PACKAGE_NAME", options.webModulePackage);
|
|
1805
1977
|
variables.set("API_PACKAGE", options.apiPackage);
|
|
1806
1978
|
variables.set("API_ARTIFACT_ID", options.apiArtifactId);
|
|
1807
|
-
variables.set("QUICKSILVER_VERSION", "0.8.
|
|
1979
|
+
variables.set("QUICKSILVER_VERSION", "0.8.27");
|
|
1808
1980
|
variables.set("MODULE_ID", randomUUID());
|
|
1809
1981
|
variables.set("DB_PREFIX", toDbPrefix(options.name));
|
|
1982
|
+
variables.set("JSONS_LOCALE", options.jsonsLocale);
|
|
1810
1983
|
const menu = renderModuleMenu(options.moduleMenu, options.platformModules);
|
|
1811
1984
|
variables.set("MODULE_MENU", menu.text);
|
|
1812
1985
|
variables.set("MODULE_DEFAULT_MENU", menu.menuId ? `default_menu_id: '${menu.menuId}', ` : "");
|
|
@@ -1866,7 +2039,7 @@ if (process.argv.some((arg) => arg === "--version" || arg === "-v")) {
|
|
|
1866
2039
|
console.log(getCliVersion());
|
|
1867
2040
|
process.exit(0);
|
|
1868
2041
|
}
|
|
1869
|
-
var program = new Command().name("create-qs").description("Create a new Quicksilver application").version("0.8.
|
|
2042
|
+
var program = new Command().name("create-qs").description("Create a new Quicksilver application").version("0.8.27", "-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) => {
|
|
1870
2043
|
applyPromptTexts();
|
|
1871
2044
|
console.log(chalk4.cyan(`
|
|
1872
2045
|
${t("banner")}`));
|
package/package.json
CHANGED
|
@@ -117,7 +117,7 @@ power-crm/
|
|
|
117
117
|
├── modules/sales/ Business module, where most day-to-day development happens
|
|
118
118
|
│ ├── module.yaml Module identity: id, code, namespaces, dependencies, web package name
|
|
119
119
|
│ ├── api/ Backend (Kotlin + Spring Boot)
|
|
120
|
-
│ │ └── data/init/ ← initial data: tables.jsons (optional), units/ (one file per unit) and
|
|
120
|
+
│ │ └── data/init/ ← initial data: tables.jsons (optional), units/ (one file per unit), rows/ and views.jsons (optional)
|
|
121
121
|
│ └── web/ Frontend (Preact + TypeScript)
|
|
122
122
|
│ └── src/module.ts Registry of pages, plugins, components, styles and icons
|
|
123
123
|
├── run/
|
|
@@ -117,7 +117,7 @@ power-crm/
|
|
|
117
117
|
├── modules/sales/ 业务模块,日常开发主要在此进行
|
|
118
118
|
│ ├── module.yaml 模块标识,包括 id、code、namespaces、依赖、Web 包名
|
|
119
119
|
│ ├── api/ 后端(Kotlin + Spring Boot)
|
|
120
|
-
│ │ └── data/init/ ← 初始化数据:tables.jsons(可选)、units
|
|
120
|
+
│ │ └── data/init/ ← 初始化数据:tables.jsons(可选)、units/(每个单元一个文件)、rows/ 与 views.jsons(可选)
|
|
121
121
|
│ └── web/ 前端(Preact + TypeScript)
|
|
122
122
|
│ └── src/module.ts 页面、插件、组件、样式、图标的注册表
|
|
123
123
|
├── run/
|
|
@@ -117,7 +117,7 @@ power-crm/
|
|
|
117
117
|
├── modules/sales/ 業務模組,日常開發主要在此進行
|
|
118
118
|
│ ├── module.yaml 模組識別,包括 id、code、namespaces、相依、Web 套件名稱
|
|
119
119
|
│ ├── api/ 後端(Kotlin + Spring Boot)
|
|
120
|
-
│ │ └── data/init/ ← 初始化資料:tables.jsons(可選)、units
|
|
120
|
+
│ │ └── data/init/ ← 初始化資料:tables.jsons(可選)、units/(每個單元一個檔案)、rows/ 與 views.jsons(可選)
|
|
121
121
|
│ └── web/ 前端(Preact + TypeScript)
|
|
122
122
|
│ └── src/module.ts 頁面、外掛、元件、樣式、圖示的註冊表
|
|
123
123
|
├── run/
|
|
@@ -230,12 +230,29 @@ connect with your usual client using the host, port, user and password from that
|
|
|
230
230
|
|
|
231
231
|
The development database is kept between starts. With `auto-init.draft-sync: true`, every start runs
|
|
232
232
|
each module draft `data/upgrade/draft.jsons` that changed since the last start, and data entered by hand
|
|
233
|
-
or through the UI stays.
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
233
|
+
or through the UI stays. Data files follow four rules:
|
|
234
|
+
|
|
235
|
+
1. **`data/init/` holds the final state.** A fresh install runs only `data/init/`.
|
|
236
|
+
2. **The draft holds the rows you added or changed in `data/init/` this time.** `data/init/views.jsons`
|
|
237
|
+
is the exception: it never goes into the draft, since the platform converges the views to it on
|
|
238
|
+
every start. When a dropped or renamed column or table is used by one of those views, rewrite the
|
|
239
|
+
view's definition in `views.jsons` in the same change. A row nested in `$add` (the unit row, a field, a form item, a list field and so on)
|
|
240
|
+
goes into the draft as a standalone row carrying the id of its parent, for example a field row
|
|
241
|
+
with `unit_id`. A rename works the same way: write the new `table_name` or `column_name` in the
|
|
242
|
+
unit row or the field row, and the platform renames the table or the column.
|
|
243
|
+
3. **What `data/init/` cannot express goes into the draft only.** That is removals (take the rows out
|
|
244
|
+
of `data/init/` and write `$delete`, `@drop_column` or `@drop_table` into the draft), fixes to
|
|
245
|
+
existing data such as filling a new column, `@alter_column` to shorten a column, change a default
|
|
246
|
+
or make a column of a plain table NOT NULL, and `@rename_table`, `@rename_column` and
|
|
247
|
+
`@alter_primary_key` for plain tables.
|
|
248
|
+
4. **The platform does the rest.** That covers tables and columns, derived tables, indexes, i18n
|
|
249
|
+
views, dropping the views that depend on a changed table and rebuilding them afterwards, and the
|
|
250
|
+
references kept by name when something is renamed.
|
|
251
|
+
|
|
252
|
+
A change made only in `data/init/` reaches neither this database nor installed environments, and the
|
|
253
|
+
startup log lists such files. A mistake fails the precheck or the run, and the error says how to
|
|
254
|
+
write it instead. Changes to `data/demo/` stay out of the draft, since demo data runs on fresh
|
|
255
|
+
installs only. Imperative commands in a draft (`@sql`,
|
|
239
256
|
`$update`, `$delete` ...) run once per `$key`; to run a changed one again, give it a new `$key`.
|
|
240
257
|
`$place` is the exception: it runs again whenever its draft runs. To rebuild the database from the
|
|
241
258
|
data files under `data/init/` and `data/demo/`, add `--fresh` to the start command for one start
|
|
@@ -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.
|
|
29
|
-
"@qs-elements/web-preact": "1.0.
|
|
28
|
+
"@qs-elements/web": "1.0.165"
|
|
29
|
+
"@qs-elements/web-preact": "1.0.165"
|
|
30
30
|
quicksilver:
|
|
31
|
-
"@qs-platform/cli": "0.8.
|
|
32
|
-
"@qs-platform/preset-vite-web": "0.8.
|
|
33
|
-
"@qs-platform/web-module-core": "0.8.
|
|
34
|
-
"@qs-platform/web-module-org": "0.8.
|
|
31
|
+
"@qs-platform/cli": "0.8.27"
|
|
32
|
+
"@qs-platform/preset-vite-web": "0.8.27"
|
|
33
|
+
"@qs-platform/web-module-core": "0.8.27"
|
|
34
|
+
"@qs-platform/web-module-org": "0.8.27"
|
|
35
35
|
tooling:
|
|
36
36
|
"@types/node": "^26.4.0"
|
|
37
37
|
"sass": "^1.103.1"
|
|
@@ -7,6 +7,14 @@ names:
|
|
|
7
7
|
maven-group: {{API_PACKAGE}}
|
|
8
8
|
maven-prefix: {{PROJECT_CODE}}
|
|
9
9
|
|
|
10
|
+
i18n:
|
|
11
|
+
# Language of the display texts in data files, the same for every module of the project.
|
|
12
|
+
# Changing it later means rewriting every data file and translation table.
|
|
13
|
+
jsons-locale: {{JSONS_LOCALE}}
|
|
14
|
+
# Languages the product supports. Installations pick the product language from them.
|
|
15
|
+
# Add zh-CN, zh-TW or en-US once data/i18n.tsv of every module carries its translations.
|
|
16
|
+
locales: [{{JSONS_LOCALE}}]
|
|
17
|
+
|
|
10
18
|
packaging:
|
|
11
19
|
archive-name: {{PROJECT_CODE}}
|
|
12
20
|
fixed-modules: [{{PLATFORM_MODULE_CODES_QUOTED}}, '{{MODULE_CODE}}']
|
|
@@ -64,6 +64,18 @@ rebuild the database from init and demo data, start once with `pnpm dev --fresh`
|
|
|
64
64
|
a dialect script, as in `pnpm dev:h2 --fresh`. The option turns `drop-first` on for that start only
|
|
65
65
|
and changes no config file. Switching `draft-sync` on or off also needs one such rebuild: the two
|
|
66
66
|
modes record drafts differently, and startup refuses a database built in the other mode.
|
|
67
|
+
`drop-first` is refused outside development mode, so a production configuration that turns it on
|
|
68
|
+
fails at startup.
|
|
69
|
+
|
|
70
|
+
## Views
|
|
71
|
+
|
|
72
|
+
The views a module declares in `data/init/views.jsons` are synced on every start: missing views are
|
|
73
|
+
created, views whose definition changed are replaced, and views taken out of the file are dropped.
|
|
74
|
+
When a view fails to build, for example because its definition uses a column that no longer exists,
|
|
75
|
+
startup fails by default, and the error names the module, the view, the definition and the reason
|
|
76
|
+
given by the database. Set `quicksilver.datasource.fail-on-view-sync-error: false` to log the error
|
|
77
|
+
and start anyway. The view and the views that depend on it then stay missing until a later start
|
|
78
|
+
builds them.
|
|
67
79
|
|
|
68
80
|
## Local edits
|
|
69
81
|
|