@hamak/smart-data-dico 1.9.1 → 1.9.3
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/backend/dist/server.mjs +212 -93
- package/backend/dist/validate.mjs +203 -3
- package/bin/cli.js +69 -38
- package/frontend/dist/assets/index-591046eb.js +543 -0
- package/frontend/dist/assets/{index-95df1406.css → index-af8abe92.css} +1 -1
- package/frontend/dist/index.html +2 -2
- package/package.json +1 -1
- package/frontend/dist/assets/index-399555bb.js +0 -543
|
@@ -10082,6 +10082,65 @@ function validateDerivedTypes(types) {
|
|
|
10082
10082
|
}
|
|
10083
10083
|
__name(validateDerivedTypes, "validateDerivedTypes");
|
|
10084
10084
|
|
|
10085
|
+
// backend/src/models/ormVocabulary.ts
|
|
10086
|
+
var ORM_VOCABULARY = {
|
|
10087
|
+
entity: [
|
|
10088
|
+
{ key: "orm.package", kind: "string", label: "Java package", mapsTo: "class package (FQN)" },
|
|
10089
|
+
{ key: "orm.className", kind: "string", label: "Class name", mapsTo: "class name override" },
|
|
10090
|
+
{ key: "orm.embeddable", kind: "flag", label: "Embeddable", mapsTo: "@Embeddable" },
|
|
10091
|
+
{ key: "orm.mappedSuperclass", kind: "flag", label: "Mapped superclass", mapsTo: "@MappedSuperclass" },
|
|
10092
|
+
{ key: "orm.extends", kind: "entityRef", label: "Extends (supertype)", mapsTo: "inheritance \u2014 extends parent entity" },
|
|
10093
|
+
{ key: "orm.inheritanceStrategy", kind: "enum", values: ["SINGLE_TABLE", "JOINED", "TABLE_PER_CLASS"], label: "Inheritance strategy", mapsTo: "@Inheritance (on root)" },
|
|
10094
|
+
{ key: "orm.discriminatorColumn", kind: "string", label: "Discriminator column", mapsTo: "@DiscriminatorColumn (root)" },
|
|
10095
|
+
{ key: "orm.discriminatorValue", kind: "string", label: "Discriminator value", mapsTo: "@DiscriminatorValue (subclass)" },
|
|
10096
|
+
{ key: "orm.idClass", kind: "string", label: "Id class", mapsTo: "@IdClass (composite key)" },
|
|
10097
|
+
{ key: "orm.embeddedId", kind: "string", label: "Embedded id", mapsTo: "@EmbeddedId (composite key)" }
|
|
10098
|
+
],
|
|
10099
|
+
attribute: [
|
|
10100
|
+
{ key: "orm.javaType", kind: "string", label: "Java type", mapsTo: "field Java type override" },
|
|
10101
|
+
{ key: "orm.generatedValue", kind: "enum", values: ["IDENTITY", "SEQUENCE", "TABLE", "UUID", "AUTO", "NONE"], label: "Generated value", mapsTo: "@GeneratedValue" },
|
|
10102
|
+
{ key: "orm.sequenceName", kind: "string", label: "Sequence name", mapsTo: "@SequenceGenerator(name)" },
|
|
10103
|
+
{ key: "orm.allocationSize", kind: "int", label: "Allocation size", mapsTo: "@SequenceGenerator(allocationSize)" },
|
|
10104
|
+
{ key: "orm.enumerated", kind: "enum", values: ["STRING", "ORDINAL"], label: "Enumerated", mapsTo: "@Enumerated (enum attrs)" },
|
|
10105
|
+
{ key: "orm.enumType", kind: "string", label: "Enum type", mapsTo: "generated Java enum class" },
|
|
10106
|
+
{ key: "orm.version", kind: "flag", label: "Version", mapsTo: "@Version" },
|
|
10107
|
+
{ key: "orm.transient", kind: "flag", label: "Transient", mapsTo: "@Transient" },
|
|
10108
|
+
{ key: "orm.lob", kind: "flag", label: "Lob", mapsTo: "@Lob" },
|
|
10109
|
+
{ key: "orm.temporal", kind: "enum", values: ["DATE", "TIME", "TIMESTAMP"], label: "Temporal", mapsTo: "@Temporal" },
|
|
10110
|
+
{ key: "orm.converter", kind: "string", label: "Converter", mapsTo: "@Convert(converter)" },
|
|
10111
|
+
{ key: "orm.elementCollection", kind: "flag", label: "Element collection", mapsTo: "@ElementCollection" },
|
|
10112
|
+
{ key: "orm.embedded", kind: "flag", label: "Embedded", mapsTo: "@Embedded" }
|
|
10113
|
+
],
|
|
10114
|
+
relationship: [
|
|
10115
|
+
{ key: "orm.fetch", kind: "enum", values: ["LAZY", "EAGER"], label: "Fetch", mapsTo: "fetch type" },
|
|
10116
|
+
{ key: "orm.cascade", kind: "enumList", values: ["ALL", "PERSIST", "MERGE", "REMOVE", "REFRESH", "DETACH"], label: "Cascade", mapsTo: "cascade types" },
|
|
10117
|
+
{ key: "orm.orphanRemoval", kind: "flag", label: "Orphan removal", mapsTo: "orphanRemoval" },
|
|
10118
|
+
{ key: "orm.optional", kind: "flag", label: "Optional", mapsTo: "optional" },
|
|
10119
|
+
{ key: "orm.mappedBy", kind: "string", label: "Mapped by", mapsTo: "mappedBy (inverse side)" },
|
|
10120
|
+
{ key: "orm.owningEnd", kind: "string", label: "Owning end", mapsTo: "owning side role" },
|
|
10121
|
+
{ key: "orm.joinTable", kind: "string", label: "Join table", mapsTo: "@JoinTable(name) for many-to-many" },
|
|
10122
|
+
{ key: "orm.joinColumns", kind: "string", label: "Join columns", mapsTo: "@JoinTable joinColumns" },
|
|
10123
|
+
{ key: "orm.inverseJoinColumns", kind: "string", label: "Inverse join columns", mapsTo: "@JoinTable inverseJoinColumns" }
|
|
10124
|
+
]
|
|
10125
|
+
};
|
|
10126
|
+
var ORM_KEYS = {
|
|
10127
|
+
entity: new Set(ORM_VOCABULARY.entity.map((d) => d.key)),
|
|
10128
|
+
attribute: new Set(ORM_VOCABULARY.attribute.map((d) => d.key)),
|
|
10129
|
+
relationship: new Set(ORM_VOCABULARY.relationship.map((d) => d.key))
|
|
10130
|
+
};
|
|
10131
|
+
var allDefs = [
|
|
10132
|
+
...ORM_VOCABULARY.entity,
|
|
10133
|
+
...ORM_VOCABULARY.attribute,
|
|
10134
|
+
...ORM_VOCABULARY.relationship
|
|
10135
|
+
];
|
|
10136
|
+
var ORM_ENUM_VALUES = Object.fromEntries(
|
|
10137
|
+
allDefs.filter((d) => d.kind === "enum" && d.values).map((d) => [d.key, d.values])
|
|
10138
|
+
);
|
|
10139
|
+
var ORM_CASCADE_VALUES = allDefs.find((d) => d.key === "orm.cascade")?.values ?? [];
|
|
10140
|
+
var ORM_FLAG_KEYS = new Set(
|
|
10141
|
+
allDefs.filter((d) => d.kind === "flag").map((d) => d.key)
|
|
10142
|
+
);
|
|
10143
|
+
|
|
10085
10144
|
// backend/src/scripts/validateDico.ts
|
|
10086
10145
|
var WS2 = wsId("dictionaries");
|
|
10087
10146
|
var Report = class {
|
|
@@ -10237,7 +10296,137 @@ function collectAttributes(attrs, acc) {
|
|
|
10237
10296
|
}
|
|
10238
10297
|
}
|
|
10239
10298
|
__name(collectAttributes, "collectAttributes");
|
|
10240
|
-
function
|
|
10299
|
+
function checkOrmMetadata(scope, metadata, ownerDesc, label, identifier, report, ctx) {
|
|
10300
|
+
const entries = (metadata || []).filter((m) => m.name.startsWith("orm."));
|
|
10301
|
+
if (entries.length === 0) return;
|
|
10302
|
+
const byName = new Map(entries.map((e) => [e.name, e.value]));
|
|
10303
|
+
for (const m of entries) {
|
|
10304
|
+
const key = m.name;
|
|
10305
|
+
if (!ORM_KEYS[scope].has(key)) {
|
|
10306
|
+
report.warn("orm.unknownKey", `Unknown or misplaced ORM key '${key}' on ${ownerDesc} (not a known ${scope}-level orm.* key)`, label, identifier);
|
|
10307
|
+
continue;
|
|
10308
|
+
}
|
|
10309
|
+
const v = m.value;
|
|
10310
|
+
if (key === "orm.cascade") {
|
|
10311
|
+
const tokens = Array.isArray(v) ? v.map(String) : String(v).split(",").map((s) => s.trim()).filter(Boolean);
|
|
10312
|
+
for (const tok of tokens) {
|
|
10313
|
+
if (!ORM_CASCADE_VALUES.includes(tok)) {
|
|
10314
|
+
report.error("orm.value", `ORM '${key}' on ${ownerDesc}: '${tok}' is not a valid cascade type (${ORM_CASCADE_VALUES.join("|")})`, label, identifier);
|
|
10315
|
+
}
|
|
10316
|
+
}
|
|
10317
|
+
} else if (ORM_ENUM_VALUES[key]) {
|
|
10318
|
+
if (!ORM_ENUM_VALUES[key].includes(String(v))) {
|
|
10319
|
+
report.error("orm.value", `ORM '${key}' on ${ownerDesc}: '${v}' is not one of ${ORM_ENUM_VALUES[key].join("|")}`, label, identifier);
|
|
10320
|
+
}
|
|
10321
|
+
} else if (ORM_FLAG_KEYS.has(key) && typeof v !== "boolean") {
|
|
10322
|
+
report.warn("orm.flag", `ORM flag '${key}' on ${ownerDesc} should be a boolean, got '${v}'`, label, identifier);
|
|
10323
|
+
}
|
|
10324
|
+
}
|
|
10325
|
+
if (scope === "entity" && byName.has("orm.extends")) {
|
|
10326
|
+
const ref = String(byName.get("orm.extends"));
|
|
10327
|
+
if (ref && !ctx.globalEntityUuids.has(ref) && !ctx.globalEntityNames.has(ref)) {
|
|
10328
|
+
report.error("orm.reference", `ORM 'orm.extends' on ${ownerDesc} references '${ref}', which is not an entity in the project`, label, identifier);
|
|
10329
|
+
}
|
|
10330
|
+
}
|
|
10331
|
+
if (scope === "attribute" && byName.get("orm.version") === true && byName.get("orm.transient") === true) {
|
|
10332
|
+
report.error("orm.conflict", `${ownerDesc} marks both orm.version and orm.transient`, label, identifier);
|
|
10333
|
+
}
|
|
10334
|
+
if (scope === "entity" && byName.get("orm.embeddable") === true && byName.has("orm.extends")) {
|
|
10335
|
+
report.error("orm.conflict", `${ownerDesc} is orm.embeddable but also declares orm.extends (an @Embeddable cannot extend an @Entity)`, label, identifier);
|
|
10336
|
+
}
|
|
10337
|
+
if (scope === "attribute" && byName.has("orm.enumerated") && ctx.attrType && ctx.attrType !== "enum") {
|
|
10338
|
+
report.warn("orm.enumerated", `orm.enumerated on ${ownerDesc} but the attribute type is '${ctx.attrType}', not 'enum'`, label, identifier);
|
|
10339
|
+
}
|
|
10340
|
+
}
|
|
10341
|
+
__name(checkOrmMetadata, "checkOrmMetadata");
|
|
10342
|
+
function checkOrmInheritance(models, report) {
|
|
10343
|
+
const byUuid = /* @__PURE__ */ new Map();
|
|
10344
|
+
const byName = /* @__PURE__ */ new Map();
|
|
10345
|
+
for (const { pkg, model } of models) {
|
|
10346
|
+
const fileOf = model.ownership;
|
|
10347
|
+
for (const e of model.entities) {
|
|
10348
|
+
const label = fileOf.entityByName.get(e.name) || fileOf.entityByUuid.get(e.uuid) || pkg;
|
|
10349
|
+
const node = { entity: e, label };
|
|
10350
|
+
if (e.uuid) byUuid.set(e.uuid, node);
|
|
10351
|
+
if (e.name) byName.set(e.name, node);
|
|
10352
|
+
}
|
|
10353
|
+
}
|
|
10354
|
+
const meta = /* @__PURE__ */ __name((e, key) => (e.metadata || []).find((m) => m.name === key)?.value, "meta");
|
|
10355
|
+
const idOf = /* @__PURE__ */ __name((e) => e.uuid || e.name, "idOf");
|
|
10356
|
+
const parentOf = /* @__PURE__ */ __name((e) => {
|
|
10357
|
+
const ref = meta(e, "orm.extends");
|
|
10358
|
+
if (ref === void 0 || ref === null || ref === "") return void 0;
|
|
10359
|
+
const s = String(ref);
|
|
10360
|
+
return byUuid.get(s) || byName.get(s);
|
|
10361
|
+
}, "parentOf");
|
|
10362
|
+
const reportedCycle = /* @__PURE__ */ new Set();
|
|
10363
|
+
for (const node of /* @__PURE__ */ new Set([...byUuid.values(), ...byName.values()])) {
|
|
10364
|
+
const e = node.entity;
|
|
10365
|
+
if (meta(e, "orm.extends") === void 0) continue;
|
|
10366
|
+
const path4 = /* @__PURE__ */ new Set([idOf(e)]);
|
|
10367
|
+
let cur = e;
|
|
10368
|
+
let steps = 0;
|
|
10369
|
+
while (cur && steps++ < 100) {
|
|
10370
|
+
const p = parentOf(cur);
|
|
10371
|
+
if (!p) break;
|
|
10372
|
+
const pid = idOf(p.entity);
|
|
10373
|
+
if (path4.has(pid)) {
|
|
10374
|
+
if (!reportedCycle.has(pid)) {
|
|
10375
|
+
reportedCycle.add(pid);
|
|
10376
|
+
report.error(
|
|
10377
|
+
"orm.inheritanceCycle",
|
|
10378
|
+
`ORM inheritance cycle via orm.extends through entity '${p.entity.name}'`,
|
|
10379
|
+
p.label,
|
|
10380
|
+
p.entity.uuid
|
|
10381
|
+
);
|
|
10382
|
+
}
|
|
10383
|
+
break;
|
|
10384
|
+
}
|
|
10385
|
+
path4.add(pid);
|
|
10386
|
+
cur = p.entity;
|
|
10387
|
+
}
|
|
10388
|
+
if (meta(e, "orm.inheritanceStrategy") !== void 0) {
|
|
10389
|
+
report.warn(
|
|
10390
|
+
"orm.inheritance",
|
|
10391
|
+
`Entity '${e.name}' declares both orm.extends and orm.inheritanceStrategy \u2014 the strategy belongs on the root supertype`,
|
|
10392
|
+
node.label,
|
|
10393
|
+
e.uuid
|
|
10394
|
+
);
|
|
10395
|
+
}
|
|
10396
|
+
const parent = parentOf(e);
|
|
10397
|
+
if (parent && meta(parent.entity, "orm.embeddable") === true) {
|
|
10398
|
+
report.error(
|
|
10399
|
+
"orm.conflict",
|
|
10400
|
+
`Entity '${e.name}' orm.extends '${parent.entity.name}', which is orm.embeddable (an @Embeddable cannot be a superclass)`,
|
|
10401
|
+
node.label,
|
|
10402
|
+
e.uuid
|
|
10403
|
+
);
|
|
10404
|
+
}
|
|
10405
|
+
if (meta(e, "orm.discriminatorValue") !== void 0) {
|
|
10406
|
+
let hasDisc = false;
|
|
10407
|
+
let a = parentOf(e);
|
|
10408
|
+
const guard = /* @__PURE__ */ new Set([idOf(e)]);
|
|
10409
|
+
while (a && !guard.has(idOf(a.entity))) {
|
|
10410
|
+
guard.add(idOf(a.entity));
|
|
10411
|
+
if (meta(a.entity, "orm.discriminatorColumn") !== void 0) {
|
|
10412
|
+
hasDisc = true;
|
|
10413
|
+
break;
|
|
10414
|
+
}
|
|
10415
|
+
a = parentOf(a.entity);
|
|
10416
|
+
}
|
|
10417
|
+
if (!hasDisc) {
|
|
10418
|
+
report.warn(
|
|
10419
|
+
"orm.inheritance",
|
|
10420
|
+
`Entity '${e.name}' sets orm.discriminatorValue but no ancestor declares orm.discriminatorColumn`,
|
|
10421
|
+
node.label,
|
|
10422
|
+
e.uuid
|
|
10423
|
+
);
|
|
10424
|
+
}
|
|
10425
|
+
}
|
|
10426
|
+
}
|
|
10427
|
+
}
|
|
10428
|
+
__name(checkOrmInheritance, "checkOrmInheritance");
|
|
10429
|
+
function checkPackageModel(pkg, model, globalEntityUuids, globalEntityNames, globalAttrUuids, report) {
|
|
10241
10430
|
const fileOf = model.ownership;
|
|
10242
10431
|
for (const entity of model.entities) {
|
|
10243
10432
|
const label = fileOf.entityByName.get(entity.name) || fileOf.entityByUuid.get(entity.uuid) || pkg;
|
|
@@ -10271,6 +10460,11 @@ function checkPackageModel(pkg, model, globalEntityUuids, globalAttrUuids, repor
|
|
|
10271
10460
|
globalAttrUuids.set(attr.uuid, `${label} (entity '${entity.name}', attribute '${attr.name}')`);
|
|
10272
10461
|
}
|
|
10273
10462
|
}
|
|
10463
|
+
const ormCtx = { globalEntityUuids, globalEntityNames };
|
|
10464
|
+
checkOrmMetadata("entity", entity.metadata, `entity '${entity.name}'`, label, entity.uuid, report, ormCtx);
|
|
10465
|
+
for (const attr of allAttrs) {
|
|
10466
|
+
checkOrmMetadata("attribute", attr.metadata, `attribute '${attr.name}' on entity '${entity.name}'`, label, attr.uuid, report, { ...ormCtx, attrType: String(attr.type) });
|
|
10467
|
+
}
|
|
10274
10468
|
}
|
|
10275
10469
|
for (const rel of model.relationships) {
|
|
10276
10470
|
const label = fileOf.relationshipByUuid.get(rel.uuid) || pkg;
|
|
@@ -10303,6 +10497,7 @@ function checkPackageModel(pkg, model, globalEntityUuids, globalAttrUuids, repor
|
|
|
10303
10497
|
);
|
|
10304
10498
|
}
|
|
10305
10499
|
}
|
|
10500
|
+
checkOrmMetadata("relationship", rel.metadata, `relationship '${rel.uuid}'`, label, rel.uuid, report, { globalEntityUuids, globalEntityNames });
|
|
10306
10501
|
}
|
|
10307
10502
|
const allRules = [
|
|
10308
10503
|
...model.rules,
|
|
@@ -10520,12 +10715,17 @@ async function validateProject(root, report) {
|
|
|
10520
10715
|
report.warn("loader.unavailable", `App loader cross-check failed: ${e.message}`, root);
|
|
10521
10716
|
}
|
|
10522
10717
|
const globalEntityUuids = /* @__PURE__ */ new Set();
|
|
10718
|
+
const globalEntityNames = /* @__PURE__ */ new Set();
|
|
10523
10719
|
for (const { model } of models) {
|
|
10524
|
-
for (const e of model.entities)
|
|
10720
|
+
for (const e of model.entities) {
|
|
10721
|
+
if (e.uuid) globalEntityUuids.add(e.uuid);
|
|
10722
|
+
if (e.name) globalEntityNames.add(e.name);
|
|
10723
|
+
}
|
|
10525
10724
|
}
|
|
10526
10725
|
for (const { pkg, model } of models) {
|
|
10527
|
-
checkPackageModel(pkg, model, globalEntityUuids, globalAttrUuids, report);
|
|
10726
|
+
checkPackageModel(pkg, model, globalEntityUuids, globalEntityNames, globalAttrUuids, report);
|
|
10528
10727
|
}
|
|
10728
|
+
checkOrmInheritance(models, report);
|
|
10529
10729
|
}
|
|
10530
10730
|
__name(validateProject, "validateProject");
|
|
10531
10731
|
async function main() {
|
package/bin/cli.js
CHANGED
|
@@ -2,7 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
import { fileURLToPath } from 'url';
|
|
4
4
|
import { dirname, join, resolve } from 'node:path';
|
|
5
|
-
import { existsSync, mkdirSync, cpSync, writeFileSync } from 'fs';
|
|
5
|
+
import { existsSync, mkdirSync, cpSync, writeFileSync, readFileSync } from 'fs';
|
|
6
|
+
import { homedir } from 'node:os';
|
|
6
7
|
import { spawn, spawnSync } from 'child_process';
|
|
7
8
|
|
|
8
9
|
const __filename = fileURLToPath(import.meta.url);
|
|
@@ -140,56 +141,86 @@ if (existsSync(bundledServer)) {
|
|
|
140
141
|
|
|
141
142
|
const frontendDist = join(PKG_ROOT, 'frontend', 'dist');
|
|
142
143
|
|
|
143
|
-
|
|
144
|
+
// In-app "Open project" can't hot-swap the boot-time data dir, so the server
|
|
145
|
+
// persists the new project here and exits with RESTART_EXIT_CODE; we respawn
|
|
146
|
+
// it with DATA_DIR set to that path. SDD_MANAGED=1 tells the server it's safe
|
|
147
|
+
// to use this restart path.
|
|
148
|
+
const RESTART_EXIT_CODE = 75;
|
|
149
|
+
const ACTIVE_PROJECT_FILE = join(homedir(), '.dico-app', 'active-project');
|
|
150
|
+
|
|
151
|
+
let currentChild = null;
|
|
152
|
+
let browserOpened = false;
|
|
153
|
+
|
|
154
|
+
function startServer(dir) {
|
|
155
|
+
console.log(`
|
|
144
156
|
Smart Data Dictionary
|
|
145
157
|
|
|
146
158
|
Port: ${port}
|
|
147
|
-
Data: ${
|
|
159
|
+
Data: ${dir}
|
|
148
160
|
Profile: ${process.env.PROFILE || 'local'}
|
|
149
161
|
Frontend: ${existsSync(frontendDist) ? 'bundled' : 'dev (use frontend dev server on :3000)'}
|
|
150
162
|
`);
|
|
151
163
|
|
|
152
|
-
const child = spawn(bin, binArgs, {
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
if (
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
console.log(`Open ${url} in your browser`);
|
|
164
|
+
const child = spawn(bin, binArgs, {
|
|
165
|
+
cwd: PKG_ROOT,
|
|
166
|
+
env: {
|
|
167
|
+
...process.env,
|
|
168
|
+
PORT: port,
|
|
169
|
+
NODE_ENV: 'production',
|
|
170
|
+
PROFILE: process.env.PROFILE || 'local',
|
|
171
|
+
DATA_DIR: dir,
|
|
172
|
+
SDD_FRONTEND_DIST: frontendDist,
|
|
173
|
+
SDD_MANAGED: '1',
|
|
174
|
+
},
|
|
175
|
+
stdio: 'inherit',
|
|
176
|
+
});
|
|
177
|
+
currentChild = child;
|
|
178
|
+
|
|
179
|
+
child.on('error', (err) => {
|
|
180
|
+
console.error('Failed to start server:', err.message);
|
|
181
|
+
process.exit(1);
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
child.on('exit', (code) => {
|
|
185
|
+
if (code === RESTART_EXIT_CODE) {
|
|
186
|
+
// Project switch requested — read the new dir and respawn.
|
|
187
|
+
let nextDir = dir;
|
|
188
|
+
try {
|
|
189
|
+
const persisted = readFileSync(ACTIVE_PROJECT_FILE, 'utf-8').trim();
|
|
190
|
+
if (persisted) nextDir = persisted;
|
|
191
|
+
} catch { /* keep current dir */ }
|
|
192
|
+
console.log(`\nSwitching project → ${nextDir}\n`);
|
|
193
|
+
startServer(nextDir);
|
|
194
|
+
return;
|
|
184
195
|
}
|
|
185
|
-
|
|
196
|
+
process.exit(code || 0);
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
// Open the browser once, on the first start only (not on project-switch restarts).
|
|
200
|
+
if (!flags.noOpen && !browserOpened) {
|
|
201
|
+
browserOpened = true;
|
|
202
|
+
setTimeout(async () => {
|
|
203
|
+
const url = `http://localhost:${port}`;
|
|
204
|
+
console.log(`Opening ${url} ...`);
|
|
205
|
+
try {
|
|
206
|
+
const { exec } = await import('child_process');
|
|
207
|
+
const cmd = process.platform === 'darwin' ? 'open' :
|
|
208
|
+
process.platform === 'win32' ? 'start' : 'xdg-open';
|
|
209
|
+
exec(`${cmd} ${url}`);
|
|
210
|
+
} catch {
|
|
211
|
+
console.log(`Open ${url} in your browser`);
|
|
212
|
+
}
|
|
213
|
+
}, 3000);
|
|
214
|
+
}
|
|
186
215
|
}
|
|
187
216
|
|
|
217
|
+
startServer(dataDir);
|
|
218
|
+
|
|
188
219
|
process.on('SIGINT', () => {
|
|
189
220
|
console.log('\nShutting down...');
|
|
190
|
-
|
|
221
|
+
if (currentChild) currentChild.kill('SIGINT');
|
|
191
222
|
});
|
|
192
223
|
|
|
193
224
|
process.on('SIGTERM', () => {
|
|
194
|
-
|
|
225
|
+
if (currentChild) currentChild.kill('SIGTERM');
|
|
195
226
|
});
|