@hamak/smart-data-dico 1.8.1 → 1.9.0
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 +145 -0
- package/backend/dist/server.mjs +130 -80
- package/bin/cli.js +1 -0
- package/frontend/dist/assets/{index-95cbb2c0.js → index-399555bb.js} +64 -64
- package/frontend/dist/index.html +1 -1
- package/package.json +2 -1
package/README.md
CHANGED
|
@@ -321,6 +321,89 @@ Set via `PROFILE` environment variable:
|
|
|
321
321
|
| **team** | Basic/JWT | Remote | Small team sharing a repo |
|
|
322
322
|
| **server** | Auth0/SSO | Remote | Organization-wide deployment |
|
|
323
323
|
|
|
324
|
+
## AI Assistance
|
|
325
|
+
|
|
326
|
+
The dictionary ships with three AI-aware surfaces, all optional. They share one set of credentials stored at `~/.dico-app/dico-app.json` (mode 0600 — never checked into git):
|
|
327
|
+
|
|
328
|
+
| Surface | What it is | Where to find it |
|
|
329
|
+
|---|---|---|
|
|
330
|
+
| **In-app AI chat panel** | Sidebar chat that grounds against the live dictionary — list packages, describe entities, generate docs, propose edits | Toolbar button **AI Assistant** in the running web app |
|
|
331
|
+
| **Slash commands** | Built-in chat shortcuts (`/list`, `/quality`, `/describe`, `/create`, `/relate`, `/export`, `/diagram`, `/help`) + your own saved prompts | Type `/` in the chat composer |
|
|
332
|
+
| **MCP server** (`dico-mcp`) | A [Model Context Protocol](https://modelcontextprotocol.io) stdio server that exposes the same operations to external clients — Claude Desktop, Cursor, Roo Code, Claude Code | `npx @hamak/smart-data-dico-mcp` or `node bin/dico-mcp.js` from source |
|
|
333
|
+
|
|
334
|
+
### Configure the in-app chat (one-time)
|
|
335
|
+
|
|
336
|
+
1. Start the app (Option A, B, or C above).
|
|
337
|
+
2. Open **Settings → AI** (or click the AI Assistant button → ⚙ icon).
|
|
338
|
+
3. Pick a **provider** (`anthropic`, `openai`, or `openai-compatible`), paste an API key, and choose a **model** (e.g. `claude-opus-4-7`, `gpt-4o`, or any model your `openai-compatible` endpoint exposes).
|
|
339
|
+
4. Save. The config writes to `~/.dico-app/dico-app.json` with mode `0600`; no env vars are required.
|
|
340
|
+
|
|
341
|
+
Alternatively, if you set `ANTHROPIC_API_KEY` or `OPENAI_API_KEY` before launching the server, the provider is auto-detected for the first session — but the **Settings dialog is the source of truth**; saving overrides env-var defaults.
|
|
342
|
+
|
|
343
|
+
Conversations and saved prompts live under `~/.dico-app/storage/` as JSON files — portable, diffable, deletable.
|
|
344
|
+
|
|
345
|
+
### Slash commands
|
|
346
|
+
|
|
347
|
+
| Command | What it does |
|
|
348
|
+
|---|---|
|
|
349
|
+
| `/help` | List all available slash commands |
|
|
350
|
+
| `/list` | List every package and its entity count |
|
|
351
|
+
| `/describe` | Describe the current entity in detail (uses page context) |
|
|
352
|
+
| `/quality` | Quality review of the current package (severity-grouped findings) |
|
|
353
|
+
| `/create` | Skeleton: create a new entity with suggested attributes |
|
|
354
|
+
| `/relate` | Skeleton: create a relationship between two entities |
|
|
355
|
+
| `/export` | Generate Markdown documentation for the current package |
|
|
356
|
+
| `/diagram` | Navigate to the organization diagram |
|
|
357
|
+
|
|
358
|
+
The `pageContext` placeholder is filled automatically from the current route (entity / package / etc.). User-saved prompts (via **Prompts** tab in the chat panel) appear in the same slash-command picker.
|
|
359
|
+
|
|
360
|
+
### MCP server (`dico-mcp`)
|
|
361
|
+
|
|
362
|
+
The MCP server reuses the same backend services as the web app — it's a different **transport** for the same operations, not a duplicate code path. You can run the web UI and the MCP server side-by-side against the same project folder.
|
|
363
|
+
|
|
364
|
+
**Tools exposed:** `listPackages`, `listEntities`, `getEntityDetails`, `createEntity`, `createRelationship`, `listStereotypes`, `listRoutes`.
|
|
365
|
+
|
|
366
|
+
**Launch (from npm):**
|
|
367
|
+
|
|
368
|
+
```bash
|
|
369
|
+
# stdio server — talks JSON-RPC over stdin/stdout
|
|
370
|
+
npx @hamak/smart-data-dico --data-dir /path/to/your/project # web UI
|
|
371
|
+
# (the MCP entrypoint is bin/dico-mcp.js inside the package)
|
|
372
|
+
node "$(npm root -g)/@hamak/smart-data-dico/bin/dico-mcp.js" --data-dir /path/to/your/project
|
|
373
|
+
```
|
|
374
|
+
|
|
375
|
+
**Register with Claude Desktop** — edit `~/Library/Application Support/Claude/claude_desktop_config.json` (macOS) or `%APPDATA%\Claude\claude_desktop_config.json` (Windows):
|
|
376
|
+
|
|
377
|
+
```json
|
|
378
|
+
{
|
|
379
|
+
"mcpServers": {
|
|
380
|
+
"smart-data-dico": {
|
|
381
|
+
"command": "npx",
|
|
382
|
+
"args": ["-y", "@hamak/smart-data-dico", "dico-mcp", "--data-dir", "/absolute/path/to/your/project"]
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
```
|
|
387
|
+
|
|
388
|
+
Or, if installed from source, point at the bin script directly:
|
|
389
|
+
|
|
390
|
+
```json
|
|
391
|
+
{
|
|
392
|
+
"command": "node",
|
|
393
|
+
"args": ["/absolute/path/to/smart-data-dico/bin/dico-mcp.js", "--data-dir", "/absolute/path/to/your/project"]
|
|
394
|
+
}
|
|
395
|
+
```
|
|
396
|
+
|
|
397
|
+
**Register with Cursor** — `.cursor/mcp.json` (project-level) or `~/.cursor/mcp.json` (global): identical shape.
|
|
398
|
+
|
|
399
|
+
**Register with Roo Code** — `.roo/mcp.json`: identical shape.
|
|
400
|
+
|
|
401
|
+
The MCP process speaks JSON-RPC on stdio and stays attached to its parent; all logging goes to stderr so it doesn't corrupt the stream. Git auto-commit honours the same `GIT_AUTO_COMMIT` env var as the web backend.
|
|
402
|
+
|
|
403
|
+
### Connect external MCP servers *to* the in-app chat
|
|
404
|
+
|
|
405
|
+
The flip side of the above: the in-app chat can also consume external MCP servers (Filesystem, GitHub, etc.). Go to **Settings → MCP** for a curated registry — pick a server, paste any required tokens, save. The chat will then surface those server's tools alongside the built-in ones.
|
|
406
|
+
|
|
324
407
|
## Technologies
|
|
325
408
|
|
|
326
409
|
| Layer | Stack |
|
|
@@ -331,3 +414,65 @@ Set via `PROFILE` environment variable:
|
|
|
331
414
|
| Visualization | Cytoscape.js (dagre + fcose layouts) |
|
|
332
415
|
| Auth | JWT + Auth0 (mock mode for dev) |
|
|
333
416
|
| Deployment | Docker (multi-stage build) |
|
|
417
|
+
|
|
418
|
+
## Documentation
|
|
419
|
+
|
|
420
|
+
| Doc | What it covers |
|
|
421
|
+
|-----|----------------|
|
|
422
|
+
| [Format reference](docs/format-reference.md) | Authoritative spec for the on-disk format — project layout, `dico.config.json`, entities, validation, constraints, relationships, stereotypes, rules, cases, actions, state machines |
|
|
423
|
+
| [User guide](docs/user-guide.md) | Task-oriented walkthrough of the app |
|
|
424
|
+
| [API reference](docs/api-reference.md) | REST endpoints (live Swagger UI at `/api-docs` when the backend is running) |
|
|
425
|
+
| [Deployment](docs/deployment.md) | Desktop vs. server modes, file layout, configuration |
|
|
426
|
+
| [Migration plan](docs/migration-plan.md) | @hamak/app-framework migration notes |
|
|
427
|
+
| [ADRs](docs/adr/) | Architecture decision records |
|
|
428
|
+
|
|
429
|
+
### Claude Code skill
|
|
430
|
+
|
|
431
|
+
The `docs/` folder doubles as a [Claude Code](https://claude.com/claude-code) skill
|
|
432
|
+
(`docs/SKILL.md` + the format reference) for authoring and validating dictionary files in
|
|
433
|
+
any project. Installing it means copying `docs/` into a skills directory as `smart-data-dico`.
|
|
434
|
+
Claude then loads it automatically whenever you work in a folder containing `dico.config.json`.
|
|
435
|
+
|
|
436
|
+
**From a local checkout of this repo:**
|
|
437
|
+
|
|
438
|
+
```bash
|
|
439
|
+
npm run install:skill # → ~/.claude/skills/smart-data-dico
|
|
440
|
+
# or into a specific project:
|
|
441
|
+
scripts/install-skill.sh /path/to/project/.claude/skills
|
|
442
|
+
```
|
|
443
|
+
|
|
444
|
+
**From another repo / machine (no checkout):** pull just `docs/` into your skills directory
|
|
445
|
+
with [`degit`](https://github.com/Rich-Harris/degit) — no auth needed:
|
|
446
|
+
|
|
447
|
+
```bash
|
|
448
|
+
# global → ~/.claude/skills/smart-data-dico
|
|
449
|
+
npx degit amah/smart-data-dico/docs ~/.claude/skills/smart-data-dico
|
|
450
|
+
```
|
|
451
|
+
|
|
452
|
+
Or, without Node, via the release tarball:
|
|
453
|
+
|
|
454
|
+
```bash
|
|
455
|
+
tmp=$(mktemp -d) && curl -fsSL https://codeload.github.com/amah/smart-data-dico/tar.gz/refs/heads/main | tar -xz -C "$tmp" \
|
|
456
|
+
&& rm -rf ~/.claude/skills/smart-data-dico \
|
|
457
|
+
&& cp -R "$tmp"/*/docs ~/.claude/skills/smart-data-dico && rm -rf "$tmp"
|
|
458
|
+
```
|
|
459
|
+
|
|
460
|
+
Swap the destination for `<project>/.claude/skills/smart-data-dico` to scope it to one project.
|
|
461
|
+
Re-run either command to update.
|
|
462
|
+
|
|
463
|
+
**Behind a proxy:** `degit` reads the lowercase `https_proxy` env var (not the CLI), while
|
|
464
|
+
`curl` takes an explicit `--proxy` flag — the curl form is usually the more reliable in
|
|
465
|
+
locked-down networks:
|
|
466
|
+
|
|
467
|
+
```bash
|
|
468
|
+
# degit
|
|
469
|
+
https_proxy=http://proxy.example.com:8080 npx degit amah/smart-data-dico/docs ~/.claude/skills/smart-data-dico
|
|
470
|
+
|
|
471
|
+
# curl
|
|
472
|
+
tmp=$(mktemp -d) && curl -fsSL --proxy http://proxy.example.com:8080 https://codeload.github.com/amah/smart-data-dico/tar.gz/refs/heads/main | tar -xz -C "$tmp" \
|
|
473
|
+
&& rm -rf ~/.claude/skills/smart-data-dico \
|
|
474
|
+
&& cp -R "$tmp"/*/docs ~/.claude/skills/smart-data-dico && rm -rf "$tmp"
|
|
475
|
+
```
|
|
476
|
+
|
|
477
|
+
If the proxy does TLS interception, point Node/curl at your CA bundle (`NODE_EXTRA_CA_CERTS=…`
|
|
478
|
+
for degit, `--cacert …` for curl).
|
package/backend/dist/server.mjs
CHANGED
|
@@ -37667,6 +37667,18 @@ function normalizeRelationshipEnds(rel) {
|
|
|
37667
37667
|
}
|
|
37668
37668
|
];
|
|
37669
37669
|
}
|
|
37670
|
+
function normalizeRelationship(rel) {
|
|
37671
|
+
const hasEnds = !!(rel.ends && rel.ends.length >= 2);
|
|
37672
|
+
const hasLegacy = !!(rel.source && rel.target);
|
|
37673
|
+
if (!hasEnds && !hasLegacy) return null;
|
|
37674
|
+
const [a, b] = normalizeRelationshipEnds(rel);
|
|
37675
|
+
return {
|
|
37676
|
+
...rel,
|
|
37677
|
+
ends: hasEnds ? rel.ends : [a, b],
|
|
37678
|
+
source: rel.source ?? { entity: a.entity, cardinality: a.cardinality, name: a.role, referenceAttributes: a.referenceAttributes },
|
|
37679
|
+
target: rel.target ?? { entity: b.entity, cardinality: b.cardinality, name: b.role, referenceAttributes: b.referenceAttributes }
|
|
37680
|
+
};
|
|
37681
|
+
}
|
|
37670
37682
|
function validateEntity(entity) {
|
|
37671
37683
|
const validator = new import_jsonschema.Validator();
|
|
37672
37684
|
const result = validator.validate(entity, entitySchema);
|
|
@@ -37721,12 +37733,13 @@ var init_EntitySchema = __esm({
|
|
|
37721
37733
|
AttributeType3["UUID"] = "uuid";
|
|
37722
37734
|
return AttributeType3;
|
|
37723
37735
|
})(AttributeType || {});
|
|
37724
|
-
Cardinality = /* @__PURE__ */ ((
|
|
37725
|
-
|
|
37726
|
-
|
|
37727
|
-
return
|
|
37736
|
+
Cardinality = /* @__PURE__ */ ((Cardinality3) => {
|
|
37737
|
+
Cardinality3["ONE"] = "one";
|
|
37738
|
+
Cardinality3["MANY"] = "many";
|
|
37739
|
+
return Cardinality3;
|
|
37728
37740
|
})(Cardinality || {});
|
|
37729
37741
|
__name(normalizeRelationshipEnds, "normalizeRelationshipEnds");
|
|
37742
|
+
__name(normalizeRelationship, "normalizeRelationship");
|
|
37730
37743
|
entitySchema = {
|
|
37731
37744
|
type: "object",
|
|
37732
37745
|
required: ["uuid", "name", "attributes"],
|
|
@@ -37891,11 +37904,27 @@ var init_StorageBackendToken = __esm({
|
|
|
37891
37904
|
STORAGE_BACKEND_TOKEN = /* @__PURE__ */ Symbol("STORAGE_BACKEND");
|
|
37892
37905
|
DICTIONARY_QUERY_TOKEN = /* @__PURE__ */ Symbol("DICTIONARY_QUERY");
|
|
37893
37906
|
StorageRegistry = class {
|
|
37907
|
+
constructor() {
|
|
37908
|
+
this.changeListeners = [];
|
|
37909
|
+
}
|
|
37894
37910
|
static {
|
|
37895
37911
|
__name(this, "StorageRegistry");
|
|
37896
37912
|
}
|
|
37913
|
+
/**
|
|
37914
|
+
* Subscribe to backend swaps/resets. Caches keyed on backend contents
|
|
37915
|
+
* (e.g. the loadPackage cache) register here so they clear when the
|
|
37916
|
+
* backend changes — chiefly so tests that install a fresh in-memory
|
|
37917
|
+
* backend per case don't read stale cached data.
|
|
37918
|
+
*/
|
|
37919
|
+
onBackendChange(fn) {
|
|
37920
|
+
this.changeListeners.push(fn);
|
|
37921
|
+
}
|
|
37922
|
+
notifyChange() {
|
|
37923
|
+
for (const fn of this.changeListeners) fn();
|
|
37924
|
+
}
|
|
37897
37925
|
setBackend(b) {
|
|
37898
37926
|
this.backend = b;
|
|
37927
|
+
this.notifyChange();
|
|
37899
37928
|
}
|
|
37900
37929
|
getBackend() {
|
|
37901
37930
|
if (!this.backend) throw new Error("STORAGE_BACKEND not registered. server.ts must call storageRegistry.setBackend() at startup.");
|
|
@@ -37911,6 +37940,7 @@ var init_StorageBackendToken = __esm({
|
|
|
37911
37940
|
reset() {
|
|
37912
37941
|
this.backend = void 0;
|
|
37913
37942
|
this.query = void 0;
|
|
37943
|
+
this.notifyChange();
|
|
37914
37944
|
}
|
|
37915
37945
|
};
|
|
37916
37946
|
storageRegistry = new StorageRegistry();
|
|
@@ -44170,6 +44200,7 @@ __export(fileOperations_exports, {
|
|
|
44170
44200
|
getPackagePath: () => getPackagePath,
|
|
44171
44201
|
getReservedPackageFiles: () => getReservedPackageFiles,
|
|
44172
44202
|
getSchemaPackagePath: () => getSchemaPackagePath,
|
|
44203
|
+
invalidatePackageCache: () => invalidatePackageCache,
|
|
44173
44204
|
listAllDictionaries: () => listAllDictionaries,
|
|
44174
44205
|
listAllEntities: () => listAllEntities,
|
|
44175
44206
|
listAllEntityRuleFiles: () => listAllEntityRuleFiles,
|
|
@@ -44507,6 +44538,8 @@ async function writeSectionsToStorage(p, sections) {
|
|
|
44507
44538
|
if (sections.cases.length > 0) payload.cases = sections.cases;
|
|
44508
44539
|
if (sections.actions.length > 0) payload.actions = sections.actions;
|
|
44509
44540
|
if (sections.stateMachines.length > 0) payload.stateMachines = sections.stateMachines;
|
|
44541
|
+
const pkgSeg = String(p).replace(/^\/+/, "").split("/")[0];
|
|
44542
|
+
if (pkgSeg) invalidatePackageCache(pkgSeg);
|
|
44510
44543
|
if (Object.keys(payload).length === 0) {
|
|
44511
44544
|
await deleteIfExists(p);
|
|
44512
44545
|
return;
|
|
@@ -44519,7 +44552,13 @@ async function listPackageYamlFilePaths(packageName) {
|
|
|
44519
44552
|
const entries = await getStorage().list(WS, dir);
|
|
44520
44553
|
return entries.filter((e) => !e.isDirectory).map((e) => e.name).filter((f) => f.endsWith(".yaml") && !RESERVED_PACKAGE_FILES.has(f)).sort().map((f) => pathOf(`${packageName}/${f}`));
|
|
44521
44554
|
}
|
|
44555
|
+
function invalidatePackageCache(packageName) {
|
|
44556
|
+
if (packageName === void 0) _packageCache.clear();
|
|
44557
|
+
else _packageCache.delete(packageName);
|
|
44558
|
+
}
|
|
44522
44559
|
async function loadPackage(packageName) {
|
|
44560
|
+
const cached2 = _packageCache.get(packageName);
|
|
44561
|
+
if (cached2 && cached2.expires > Date.now()) return cached2.model;
|
|
44523
44562
|
const files = await listPackageYamlFilePaths(packageName);
|
|
44524
44563
|
const parsed = await Promise.all(
|
|
44525
44564
|
files.map(async (p) => ({
|
|
@@ -44527,7 +44566,9 @@ async function loadPackage(packageName) {
|
|
|
44527
44566
|
sections: await parseSectionsFromStorage(p, String(p))
|
|
44528
44567
|
}))
|
|
44529
44568
|
);
|
|
44530
|
-
|
|
44569
|
+
const model = mergePackageSections(packageName, parsed);
|
|
44570
|
+
_packageCache.set(packageName, { model, expires: Date.now() + PACKAGE_CACHE_TTL_MS });
|
|
44571
|
+
return model;
|
|
44531
44572
|
}
|
|
44532
44573
|
function getSchemaPackagePath() {
|
|
44533
44574
|
return path4.join(getDataDir(), ".dico", "schemas");
|
|
@@ -44684,7 +44725,13 @@ async function readRelationshipsFile(packagePath) {
|
|
|
44684
44725
|
try {
|
|
44685
44726
|
const packageName = path4.basename(packagePath);
|
|
44686
44727
|
const pkg = await loadPackage(packageName);
|
|
44687
|
-
|
|
44728
|
+
const normalized = [];
|
|
44729
|
+
for (const rel of pkg.relationships) {
|
|
44730
|
+
const n = normalizeRelationship(rel);
|
|
44731
|
+
if (n) normalized.push(n);
|
|
44732
|
+
else logger.warn(`Skipping malformed relationship ${rel.uuid} in package '${packageName}': no ends and no source/target`);
|
|
44733
|
+
}
|
|
44734
|
+
return normalized;
|
|
44688
44735
|
} catch (error48) {
|
|
44689
44736
|
logger.error(`Error reading relationships file: ${error48}`);
|
|
44690
44737
|
return [];
|
|
@@ -45244,7 +45291,7 @@ async function deleteStateMachine(uuid3) {
|
|
|
45244
45291
|
return { ok: false };
|
|
45245
45292
|
}
|
|
45246
45293
|
}
|
|
45247
|
-
var import_yaml, WS, getDataDir, RESERVED_DIRS, RESERVED_PACKAGE_FILES, VALIDATION_FIELD_NAMES, gitServiceInstance;
|
|
45294
|
+
var import_yaml, WS, getDataDir, RESERVED_DIRS, RESERVED_PACKAGE_FILES, VALIDATION_FIELD_NAMES, gitServiceInstance, PACKAGE_CACHE_TTL_MS, _packageCache;
|
|
45248
45295
|
var init_fileOperations = __esm({
|
|
45249
45296
|
"backend/src/utils/fileOperations.ts"() {
|
|
45250
45297
|
"use strict";
|
|
@@ -45289,6 +45336,10 @@ var init_fileOperations = __esm({
|
|
|
45289
45336
|
__name(getReservedPackageFiles, "getReservedPackageFiles");
|
|
45290
45337
|
__name(writeSectionsToStorage, "writeSectionsToStorage");
|
|
45291
45338
|
__name(listPackageYamlFilePaths, "listPackageYamlFilePaths");
|
|
45339
|
+
PACKAGE_CACHE_TTL_MS = 2e3;
|
|
45340
|
+
_packageCache = /* @__PURE__ */ new Map();
|
|
45341
|
+
__name(invalidatePackageCache, "invalidatePackageCache");
|
|
45342
|
+
storageRegistry.onBackendChange(() => invalidatePackageCache());
|
|
45292
45343
|
__name(loadPackage, "loadPackage");
|
|
45293
45344
|
__name(getSchemaPackagePath, "getSchemaPackagePath");
|
|
45294
45345
|
__name(listSchemaPackageYamlFilePaths, "listSchemaPackageYamlFilePaths");
|
|
@@ -45629,21 +45680,18 @@ var init_dictionaryService = __esm({
|
|
|
45629
45680
|
try {
|
|
45630
45681
|
if (id.startsWith("microservices/")) {
|
|
45631
45682
|
const microservice = id.replace("microservices/", "");
|
|
45632
|
-
const
|
|
45683
|
+
const pkg = await loadPackage(microservice);
|
|
45633
45684
|
const entries = [];
|
|
45634
|
-
for (const
|
|
45635
|
-
const
|
|
45636
|
-
|
|
45637
|
-
|
|
45638
|
-
|
|
45639
|
-
|
|
45640
|
-
|
|
45641
|
-
|
|
45642
|
-
|
|
45643
|
-
|
|
45644
|
-
required: attr.required || false
|
|
45645
|
-
});
|
|
45646
|
-
}
|
|
45685
|
+
for (const entity of pkg.entities) {
|
|
45686
|
+
for (const attr of entity.attributes || []) {
|
|
45687
|
+
entries.push({
|
|
45688
|
+
id: `${entity.uuid || ""}_${attr.name}`,
|
|
45689
|
+
name: attr.name,
|
|
45690
|
+
description: attr.description || "",
|
|
45691
|
+
type: attr.type || "string",
|
|
45692
|
+
format: attr.validation?.format,
|
|
45693
|
+
required: attr.required || false
|
|
45694
|
+
});
|
|
45647
45695
|
}
|
|
45648
45696
|
}
|
|
45649
45697
|
return entries;
|
|
@@ -45791,9 +45839,8 @@ var init_dictionaryService = __esm({
|
|
|
45791
45839
|
const microservices = await listMicroservices();
|
|
45792
45840
|
const result = [];
|
|
45793
45841
|
for (const microservice of microservices) {
|
|
45794
|
-
const
|
|
45795
|
-
for (const
|
|
45796
|
-
const entity = await readEntityFile(microservice, entityName);
|
|
45842
|
+
const pkg = await loadPackage(microservice);
|
|
45843
|
+
for (const entity of pkg.entities) {
|
|
45797
45844
|
if (!entity) continue;
|
|
45798
45845
|
if (filters.name && !entity.name.toLowerCase().includes(filters.name.toLowerCase())) {
|
|
45799
45846
|
continue;
|
|
@@ -45882,13 +45929,8 @@ var init_dictionaryService = __esm({
|
|
|
45882
45929
|
}
|
|
45883
45930
|
}
|
|
45884
45931
|
async getServiceEntities(service) {
|
|
45885
|
-
const
|
|
45886
|
-
|
|
45887
|
-
for (const name21 of entityNames) {
|
|
45888
|
-
const entity = await readEntityFile(service, name21);
|
|
45889
|
-
if (entity) entities.push(entity);
|
|
45890
|
-
}
|
|
45891
|
-
return entities;
|
|
45932
|
+
const pkg = await loadPackage(service);
|
|
45933
|
+
return pkg.entities;
|
|
45892
45934
|
}
|
|
45893
45935
|
};
|
|
45894
45936
|
dictionaryService = new DictionaryService();
|
|
@@ -47321,29 +47363,13 @@ var init_serviceService = __esm({
|
|
|
47321
47363
|
}
|
|
47322
47364
|
}
|
|
47323
47365
|
async getServiceEntities(service) {
|
|
47324
|
-
logger.info(`Getting entities for service: ${service}`);
|
|
47325
47366
|
const startTime = process.hrtime();
|
|
47326
47367
|
try {
|
|
47327
|
-
const
|
|
47328
|
-
const entityNames = await listMicroserviceEntities(service);
|
|
47329
|
-
const listEndTime = process.hrtime(listStartTime);
|
|
47330
|
-
const listTimeMs = Number((listEndTime[0] * 1e3 + listEndTime[1] / 1e6).toFixed(2));
|
|
47331
|
-
logger.info(`Listed ${entityNames.length} entity names for service ${service} in ${listTimeMs}ms`);
|
|
47332
|
-
const entities = [];
|
|
47333
|
-
const readStartTime = process.hrtime();
|
|
47334
|
-
for (const entityName of entityNames) {
|
|
47335
|
-
const entity = await readEntityFile(service, entityName);
|
|
47336
|
-
if (entity) {
|
|
47337
|
-
entities.push(entity);
|
|
47338
|
-
}
|
|
47339
|
-
}
|
|
47340
|
-
const readEndTime = process.hrtime(readStartTime);
|
|
47341
|
-
const readTimeMs = Number((readEndTime[0] * 1e3 + readEndTime[1] / 1e6).toFixed(2));
|
|
47342
|
-
logger.info(`Read ${entities.length} entity files for service ${service} in ${readTimeMs}ms`);
|
|
47368
|
+
const pkg = await loadPackage(service);
|
|
47343
47369
|
const endTime = process.hrtime(startTime);
|
|
47344
47370
|
const totalTimeMs = Number((endTime[0] * 1e3 + endTime[1] / 1e6).toFixed(2));
|
|
47345
|
-
logger.info(`
|
|
47346
|
-
return entities;
|
|
47371
|
+
logger.info(`Got ${pkg.entities.length} entities for service ${service} in ${totalTimeMs}ms`);
|
|
47372
|
+
return pkg.entities;
|
|
47347
47373
|
} catch (error48) {
|
|
47348
47374
|
logger.error(`Error getting service entities: ${error48}`);
|
|
47349
47375
|
return [];
|
|
@@ -47970,6 +47996,17 @@ async function getGitService2() {
|
|
|
47970
47996
|
return null;
|
|
47971
47997
|
}
|
|
47972
47998
|
}
|
|
47999
|
+
function describeError(e) {
|
|
48000
|
+
if (e instanceof Error) return e.message;
|
|
48001
|
+
try {
|
|
48002
|
+
return JSON.stringify(e);
|
|
48003
|
+
} catch {
|
|
48004
|
+
return String(e);
|
|
48005
|
+
}
|
|
48006
|
+
}
|
|
48007
|
+
function isNotAGitRepo(detail) {
|
|
48008
|
+
return /not a git repo|could not find .*git repo|NotGitRepo|NOT_A_REPO|fatal: not a git/i.test(detail);
|
|
48009
|
+
}
|
|
47973
48010
|
var gitServiceInstance2, VersionService, versionService;
|
|
47974
48011
|
var init_versionService = __esm({
|
|
47975
48012
|
"backend/src/services/versionService.ts"() {
|
|
@@ -47978,6 +48015,8 @@ var init_versionService = __esm({
|
|
|
47978
48015
|
init_config();
|
|
47979
48016
|
gitServiceInstance2 = null;
|
|
47980
48017
|
__name(getGitService2, "getGitService");
|
|
48018
|
+
__name(describeError, "describeError");
|
|
48019
|
+
__name(isNotAGitRepo, "isNotAGitRepo");
|
|
47981
48020
|
VersionService = class {
|
|
47982
48021
|
static {
|
|
47983
48022
|
__name(this, "VersionService");
|
|
@@ -47996,7 +48035,12 @@ var init_versionService = __esm({
|
|
|
47996
48035
|
const ddFiles = (status.files || []).map((f) => f.path).filter((p) => p.startsWith(ddPrefix));
|
|
47997
48036
|
return { clean: ddFiles.length === 0, files: ddFiles };
|
|
47998
48037
|
} catch (error48) {
|
|
47999
|
-
|
|
48038
|
+
const detail = describeError(error48);
|
|
48039
|
+
if (isNotAGitRepo(detail)) {
|
|
48040
|
+
logger.debug(`getWorkingTreeStatus: project is not a git repository \u2014 treating as clean (${detail})`);
|
|
48041
|
+
} else {
|
|
48042
|
+
logger.warn(`getWorkingTreeStatus failed: ${detail}`);
|
|
48043
|
+
}
|
|
48000
48044
|
return { clean: true, files: [] };
|
|
48001
48045
|
}
|
|
48002
48046
|
}
|
|
@@ -99846,7 +99890,7 @@ async function auth(provider, options2) {
|
|
|
99846
99890
|
throw error48;
|
|
99847
99891
|
}
|
|
99848
99892
|
}
|
|
99849
|
-
async function authInternal(provider, { serverUrl
|
|
99893
|
+
async function authInternal(provider, { serverUrl, authorizationCode, scope, resourceMetadataUrl, fetchFn }) {
|
|
99850
99894
|
const cachedState = await provider.discoveryState?.();
|
|
99851
99895
|
let resourceMetadata;
|
|
99852
99896
|
let authorizationServerUrl;
|
|
@@ -99861,7 +99905,7 @@ async function authInternal(provider, { serverUrl: serverUrl2, authorizationCode
|
|
|
99861
99905
|
metadata = cachedState.authorizationServerMetadata ?? await discoverAuthorizationServerMetadata(authorizationServerUrl, { fetchFn });
|
|
99862
99906
|
if (!resourceMetadata) {
|
|
99863
99907
|
try {
|
|
99864
|
-
resourceMetadata = await discoverOAuthProtectedResourceMetadata(
|
|
99908
|
+
resourceMetadata = await discoverOAuthProtectedResourceMetadata(serverUrl, { resourceMetadataUrl: effectiveResourceMetadataUrl }, fetchFn);
|
|
99865
99909
|
} catch {
|
|
99866
99910
|
}
|
|
99867
99911
|
}
|
|
@@ -99874,7 +99918,7 @@ async function authInternal(provider, { serverUrl: serverUrl2, authorizationCode
|
|
|
99874
99918
|
});
|
|
99875
99919
|
}
|
|
99876
99920
|
} else {
|
|
99877
|
-
const serverInfo = await discoverOAuthServerInfo(
|
|
99921
|
+
const serverInfo = await discoverOAuthServerInfo(serverUrl, { resourceMetadataUrl: effectiveResourceMetadataUrl, fetchFn });
|
|
99878
99922
|
authorizationServerUrl = serverInfo.authorizationServerUrl;
|
|
99879
99923
|
metadata = serverInfo.authorizationServerMetadata;
|
|
99880
99924
|
resourceMetadata = serverInfo.resourceMetadata;
|
|
@@ -99885,7 +99929,7 @@ async function authInternal(provider, { serverUrl: serverUrl2, authorizationCode
|
|
|
99885
99929
|
authorizationServerMetadata: metadata
|
|
99886
99930
|
});
|
|
99887
99931
|
}
|
|
99888
|
-
const resource = await selectResourceURL(
|
|
99932
|
+
const resource = await selectResourceURL(serverUrl, provider, resourceMetadata);
|
|
99889
99933
|
const resolvedScope = scope || resourceMetadata?.scopes_supported?.join(" ") || provider.clientMetadata.scope;
|
|
99890
99934
|
let clientInformation = await Promise.resolve(provider.clientInformation());
|
|
99891
99935
|
if (!clientInformation) {
|
|
@@ -99971,8 +100015,8 @@ function isHttpsUrl(value) {
|
|
|
99971
100015
|
return false;
|
|
99972
100016
|
}
|
|
99973
100017
|
}
|
|
99974
|
-
async function selectResourceURL(
|
|
99975
|
-
const defaultResource = resourceUrlFromServerUrl(
|
|
100018
|
+
async function selectResourceURL(serverUrl, provider, resourceMetadata) {
|
|
100019
|
+
const defaultResource = resourceUrlFromServerUrl(serverUrl);
|
|
99976
100020
|
if (provider.validateResourceURL) {
|
|
99977
100021
|
return await provider.validateResourceURL(defaultResource, resourceMetadata?.resource);
|
|
99978
100022
|
}
|
|
@@ -100021,8 +100065,8 @@ function extractFieldFromWwwAuth(response, fieldName) {
|
|
|
100021
100065
|
}
|
|
100022
100066
|
return null;
|
|
100023
100067
|
}
|
|
100024
|
-
async function discoverOAuthProtectedResourceMetadata(
|
|
100025
|
-
const response = await discoverMetadataWithFallback(
|
|
100068
|
+
async function discoverOAuthProtectedResourceMetadata(serverUrl, opts, fetchFn = fetch) {
|
|
100069
|
+
const response = await discoverMetadataWithFallback(serverUrl, "oauth-protected-resource", fetchFn, {
|
|
100026
100070
|
protocolVersion: opts?.protocolVersion,
|
|
100027
100071
|
metadataUrl: opts?.resourceMetadataUrl
|
|
100028
100072
|
});
|
|
@@ -100065,8 +100109,8 @@ async function tryMetadataDiscovery(url2, protocolVersion, fetchFn = fetch) {
|
|
|
100065
100109
|
function shouldAttemptFallback(response, pathname) {
|
|
100066
100110
|
return !response || response.status >= 400 && response.status < 500 && pathname !== "/";
|
|
100067
100111
|
}
|
|
100068
|
-
async function discoverMetadataWithFallback(
|
|
100069
|
-
const issuer = new URL(
|
|
100112
|
+
async function discoverMetadataWithFallback(serverUrl, wellKnownType, fetchFn, opts) {
|
|
100113
|
+
const issuer = new URL(serverUrl);
|
|
100070
100114
|
const protocolVersion = opts?.protocolVersion ?? LATEST_PROTOCOL_VERSION;
|
|
100071
100115
|
let url2;
|
|
100072
100116
|
if (opts?.metadataUrl) {
|
|
@@ -100142,18 +100186,18 @@ async function discoverAuthorizationServerMetadata(authorizationServerUrl, { fet
|
|
|
100142
100186
|
}
|
|
100143
100187
|
return void 0;
|
|
100144
100188
|
}
|
|
100145
|
-
async function discoverOAuthServerInfo(
|
|
100189
|
+
async function discoverOAuthServerInfo(serverUrl, opts) {
|
|
100146
100190
|
let resourceMetadata;
|
|
100147
100191
|
let authorizationServerUrl;
|
|
100148
100192
|
try {
|
|
100149
|
-
resourceMetadata = await discoverOAuthProtectedResourceMetadata(
|
|
100193
|
+
resourceMetadata = await discoverOAuthProtectedResourceMetadata(serverUrl, { resourceMetadataUrl: opts?.resourceMetadataUrl }, opts?.fetchFn);
|
|
100150
100194
|
if (resourceMetadata.authorization_servers && resourceMetadata.authorization_servers.length > 0) {
|
|
100151
100195
|
authorizationServerUrl = resourceMetadata.authorization_servers[0];
|
|
100152
100196
|
}
|
|
100153
100197
|
} catch {
|
|
100154
100198
|
}
|
|
100155
100199
|
if (!authorizationServerUrl) {
|
|
100156
|
-
authorizationServerUrl = String(new URL("/",
|
|
100200
|
+
authorizationServerUrl = String(new URL("/", serverUrl));
|
|
100157
100201
|
}
|
|
100158
100202
|
const authorizationServerMetadata = await discoverAuthorizationServerMetadata(authorizationServerUrl, { fetchFn: opts?.fetchFn });
|
|
100159
100203
|
return {
|
|
@@ -148774,7 +148818,8 @@ var init_UuidIndex = __esm({
|
|
|
148774
148818
|
}
|
|
148775
148819
|
this.rebuildInFlight = true;
|
|
148776
148820
|
try {
|
|
148777
|
-
const { listPackages: listPackages2 } = await Promise.resolve().then(() => (init_fileOperations(), fileOperations_exports));
|
|
148821
|
+
const { listPackages: listPackages2, invalidatePackageCache: invalidatePackageCache2 } = await Promise.resolve().then(() => (init_fileOperations(), fileOperations_exports));
|
|
148822
|
+
invalidatePackageCache2();
|
|
148778
148823
|
const top = await listPackages2();
|
|
148779
148824
|
const allPackages = [];
|
|
148780
148825
|
for (const pkg of top) {
|
|
@@ -148899,6 +148944,8 @@ var init_UuidIndex = __esm({
|
|
|
148899
148944
|
while (this.rebuildInFlight) {
|
|
148900
148945
|
await new Promise((r) => setTimeout(r, 0));
|
|
148901
148946
|
}
|
|
148947
|
+
const { invalidatePackageCache: invalidatePackageCache2 } = await Promise.resolve().then(() => (init_fileOperations(), fileOperations_exports));
|
|
148948
|
+
invalidatePackageCache2(packageName);
|
|
148902
148949
|
const packagePrefix = `packages/${packageName}/entities/`;
|
|
148903
148950
|
const previouslyMappedUuids = /* @__PURE__ */ new Set();
|
|
148904
148951
|
for (const [uuid3, lp] of this.uuidToPath) {
|
|
@@ -156981,13 +157028,13 @@ async function resolveProjectPrefixAtRef(ref) {
|
|
|
156981
157028
|
logger.warn(`Could not resolve git repo root: ${e}`);
|
|
156982
157029
|
return null;
|
|
156983
157030
|
}
|
|
156984
|
-
const
|
|
157031
|
+
const candidates = [];
|
|
156985
157032
|
const rel = path6.relative(repoRoot, config.dataDir);
|
|
156986
157033
|
const primary = !rel || rel === "." || rel.startsWith("..") ? "" : rel.replace(/\\/g, "/") + "/";
|
|
156987
|
-
|
|
156988
|
-
if (primary !== "data-dictionaries/")
|
|
156989
|
-
if (primary !== "")
|
|
156990
|
-
for (const prefix of
|
|
157034
|
+
candidates.push(primary);
|
|
157035
|
+
if (primary !== "data-dictionaries/") candidates.push("data-dictionaries/");
|
|
157036
|
+
if (primary !== "") candidates.push("");
|
|
157037
|
+
for (const prefix of candidates) {
|
|
156991
157038
|
const marker21 = await readFileAtRef(ref, `${prefix}dico.config.json`);
|
|
156992
157039
|
if (marker21 !== null) return prefix;
|
|
156993
157040
|
}
|
|
@@ -160474,9 +160521,9 @@ var QualityService = class {
|
|
|
160474
160521
|
const services = service ? [service] : await listMicroservices();
|
|
160475
160522
|
const packages = [];
|
|
160476
160523
|
for (const svc of services) {
|
|
160477
|
-
const
|
|
160524
|
+
const pkg = await loadPackage(svc);
|
|
160478
160525
|
const entities = [];
|
|
160479
|
-
|
|
160526
|
+
const relEntityUuids = /* @__PURE__ */ new Set();
|
|
160480
160527
|
try {
|
|
160481
160528
|
const rels = await readRelationshipsFile(getPackagePath(svc));
|
|
160482
160529
|
for (const rel of rels) {
|
|
@@ -160485,9 +160532,7 @@ var QualityService = class {
|
|
|
160485
160532
|
}
|
|
160486
160533
|
} catch {
|
|
160487
160534
|
}
|
|
160488
|
-
for (const
|
|
160489
|
-
const name21 = rawName.includes("_") ? rawName.split("_").slice(1).join("_") : rawName;
|
|
160490
|
-
const entity = await readEntityFile(svc, name21);
|
|
160535
|
+
for (const entity of pkg.entities) {
|
|
160491
160536
|
if (!entity) continue;
|
|
160492
160537
|
const descFilled = !!entity.description;
|
|
160493
160538
|
const totalAttrs = entity.attributes.length;
|
|
@@ -162295,6 +162340,13 @@ async function mountFrameworkRoutes() {
|
|
|
162295
162340
|
workspaceRoots
|
|
162296
162341
|
});
|
|
162297
162342
|
enricherRegistry2.register(gitEnricher);
|
|
162343
|
+
app.use("/api/git/dictionaries/status", (_req, res, next) => {
|
|
162344
|
+
if (!fs5.existsSync(path13.join(config.dataDir, ".git"))) {
|
|
162345
|
+
res.json({ files: [], hasUncommittedChanges: false });
|
|
162346
|
+
return;
|
|
162347
|
+
}
|
|
162348
|
+
next();
|
|
162349
|
+
});
|
|
162298
162350
|
const gitRoutes = gitModule.createGitRoutes({ gitService, debug: !config.isProduction });
|
|
162299
162351
|
app.use("/api/git", gitRoutes);
|
|
162300
162352
|
app.use("/api/git", gitModule.gitErrorHandler);
|
|
@@ -162317,18 +162369,16 @@ mountFrameworkRoutes().catch((err) => {
|
|
|
162317
162369
|
logger.warn(`Failed to mount framework routes: ${err}`);
|
|
162318
162370
|
});
|
|
162319
162371
|
if (config.isProduction) {
|
|
162320
|
-
const serverUrl = eval("import.meta.url");
|
|
162321
|
-
const serverDir = path13.dirname(new URL(serverUrl).pathname);
|
|
162322
162372
|
const candidates = [
|
|
162323
|
-
|
|
162324
|
-
// npm package (
|
|
162373
|
+
process.env.SDD_FRONTEND_DIST || "",
|
|
162374
|
+
// npm package (set by bin/cli.js)
|
|
162325
162375
|
path13.join(process.cwd(), "public"),
|
|
162326
162376
|
// Docker (copied to public/)
|
|
162327
162377
|
path13.join(process.cwd(), "..", "frontend", "dist"),
|
|
162328
162378
|
// monorepo dev
|
|
162329
162379
|
path13.join(process.cwd(), "frontend", "dist")
|
|
162330
162380
|
// alt layout
|
|
162331
|
-
];
|
|
162381
|
+
].filter(Boolean);
|
|
162332
162382
|
const publicDir = candidates.find((d) => {
|
|
162333
162383
|
try {
|
|
162334
162384
|
return fs5.statSync(d).isDirectory();
|