mirror of
https://github.com/nejimakibird/model-weave.git
synced 2026-07-22 06:51:24 +00:00
fix: merge app process domain sources for rendering
This commit is contained in:
parent
fb1477a0b1
commit
da7f0acb54
10 changed files with 557 additions and 38 deletions
|
|
@ -641,13 +641,15 @@ Behavior:
|
|||
|
||||
* If local `## Domains` is absent, `Steps.domain` still groups Business Flow steps, but Model Weave does not warn about unknown domain values unless `## Domain Sources` is present.
|
||||
* If local `## Domains` is present, `Steps.domain` is resolved by local Domain `id`.
|
||||
* Resolved local Domains are rendered as nested Business Flow groups using `Domains.parent`.
|
||||
* If `## Domain Sources` is also present, external Domains are loaded first and local `## Domains` override external definitions for the same `id`.
|
||||
* Resolved Domains are rendered as nested Business Flow groups using `Domains.parent`.
|
||||
* Domain `name` is used as the visible group label. If `name` is empty, `id` is used.
|
||||
* Domains that have no direct steps are still rendered when they are ancestors of a Domain that has steps.
|
||||
* Duplicate local Domain ids produce diagnostics.
|
||||
* Local Domain `parent` values must reference another local Domain id.
|
||||
* Local Domain `parent` values must reference another resolved Domain id. With `## Domain Sources`, this may be an imported external Domain id.
|
||||
* If a local Domain has an unknown parent, it is rendered as a root-level group and the parent diagnostic remains visible.
|
||||
* When a Color Scheme is active, resolved local Domain groups use `target=domain` and `kind=<Domains.kind>` for group colors.
|
||||
* If a local Domain overrides an external Domain `name`, `kind`, or `parent`, Model Weave reports a warning and uses the local value.
|
||||
* When a Color Scheme is active, resolved Domain groups use `target=domain` and `kind=<Domains.kind>` for group colors.
|
||||
* If `Steps.domain` is present but not found, Model Weave does not fall back to `Steps.lane`.
|
||||
* `Steps.lane` remains legacy-compatible layout-only placement and is only used when `Steps.domain` is empty.
|
||||
|
||||
|
|
@ -670,10 +672,10 @@ Behavior:
|
|||
* If `## Domain Sources` is present, sources are loaded in table order and merged using the same Domain Source behavior as `domain_diagram` where practical.
|
||||
* If a source cannot be resolved, or resolves to a non-`domains` file, Model Weave reports a warning.
|
||||
* If `Steps.domain` does not match any merged Domain id, Model Weave reports a warning.
|
||||
* Domain Sources currently validate placement metadata, but Business Flow hierarchy rendering and Domain group coloring are driven by local `## Domains`.
|
||||
* If local `## Domains` is also present, local rows are applied after external sources and win for the same Domain `id`.
|
||||
* The merged Domain set drives Business Flow hierarchy rendering and Domain group coloring.
|
||||
* If `Steps.domain` is present but unresolved, Model Weave does not fall back to `Steps.lane`.
|
||||
* `Steps.lane` remains legacy-compatible layout-only placement and is only used when `Steps.domain` is empty.
|
||||
* Domain group coloring is applied only for resolved local `## Domains`. External Domain Source group coloring is planned for a later step.
|
||||
* Business Flow step node colors continue to use `target=app_process` and `Steps.kind`.
|
||||
|
||||
#### Steps as the default flow
|
||||
|
|
|
|||
|
|
@ -641,13 +641,15 @@ Business Flow preview には table-based steps を使います。
|
|||
|
||||
* ローカル `## Domains` がない場合でも、`Steps.domain` は Business Flow のStep groupingに使われます。ただし `## Domain Sources` がない限り、不明なdomain値の警告は出しません。
|
||||
* ローカル `## Domains` がある場合、`Steps.domain` はローカルDomain `id` で解決されます。
|
||||
* 解決されたローカルDomainは、`Domains.parent` を使って入れ子のBusiness Flow groupとして描画されます。
|
||||
* `## Domain Sources` もある場合、外部Domainsを先に読み込み、同じ `id` の定義はローカル `## Domains` が上書きします。
|
||||
* 解決されたDomainは、`Domains.parent` を使って入れ子のBusiness Flow groupとして描画されます。
|
||||
* 表示されるgroup labelにはDomain `name` が使われます。`name` が空の場合は `id` が使われます。
|
||||
* 直接Stepを持たないDomainでも、Stepを持つ子Domainの祖先であれば描画されます。
|
||||
* 重複したローカルDomain idは診断になります。
|
||||
* ローカルDomainの `parent` は、別のローカルDomain idを参照する必要があります。
|
||||
* ローカルDomainの `parent` は、解決済みDomain idを参照する必要があります。`## Domain Sources` がある場合は、importされた外部Domain idも参照できます。
|
||||
* ローカルDomainの `parent` が不明な場合、そのDomainはroot-level groupとして描画され、parent診断は表示され続けます。
|
||||
* Color Scheme が有効な場合、解決されたローカルDomain group は `target=domain` と `kind=<Domains.kind>` を使って色付けされます。
|
||||
* ローカルDomainが外部Domainの `name`, `kind`, `parent` を上書きする場合、警告を出し、ローカル値を使います。
|
||||
* Color Scheme が有効な場合、解決されたDomain group は `target=domain` と `kind=<Domains.kind>` を使って色付けされます。
|
||||
* `Steps.domain` が存在して解決できない場合でも、`Steps.lane` へはfallbackしません。
|
||||
* `Steps.lane` は legacy-compatible な layout-only 配置であり、`Steps.domain` が空の場合だけ使われます。
|
||||
|
||||
|
|
@ -670,10 +672,10 @@ Business Flow preview には table-based steps を使います。
|
|||
* `## Domain Sources` がある場合、sourceはテーブル順に読み込まれ、可能な範囲で `domain_diagram` と同じDomain Source動作でマージされます。
|
||||
* sourceが解決できない場合、または `domains` 以外のファイルを指す場合、警告が出ます。
|
||||
* `Steps.domain` がマージ済みDomain idに一致しない場合、警告が出ます。
|
||||
* Domain Sources は配置metadataの検証に使われますが、Business Flow hierarchy rendering と Domain group coloring はローカル `## Domains` によって行われます。
|
||||
* ローカル `## Domains` もある場合、ローカル行は外部sourceの後に適用され、同じDomain `id` ではローカル定義が優先されます。
|
||||
* マージ済みDomain setが Business Flow hierarchy rendering と Domain group coloring に使われます。
|
||||
* `Steps.domain` が存在して解決できない場合でも、`Steps.lane` へはfallbackしません。
|
||||
* `Steps.lane` は legacy-compatible な layout-only 配置であり、`Steps.domain` が空の場合だけ使われます。
|
||||
* Domain group coloring は解決されたローカル `## Domains` にのみ適用されます。外部 Domain Source に基づく group coloring は後続stepです。
|
||||
* Business Flow のStep node色は引き続き `target=app_process` と `Steps.kind` を使います。
|
||||
|
||||
#### Stepsを基本フローとして使う
|
||||
|
|
|
|||
111
main.js
111
main.js
|
|
@ -249,6 +249,9 @@ function formatAppProcessUnknownDomainMessage(stepId, domainId) {
|
|||
function formatAppProcessUnknownLocalDomainMessage(stepId, domainId) {
|
||||
return `app_process Step "${stepId}" references unknown local Domain "${domainId}".`;
|
||||
}
|
||||
function formatAppProcessLocalDomainFieldOverrideMessage(id, field) {
|
||||
return `app_process local Domain "${id}" overrides external Domain ${field}.`;
|
||||
}
|
||||
|
||||
// src/core/reference-resolver.ts
|
||||
function normalizeReferenceTarget(reference) {
|
||||
|
|
@ -824,6 +827,7 @@ function createMergeWarning(path2, message, severity) {
|
|||
}
|
||||
|
||||
// src/core/app-process-domain-resolver.ts
|
||||
var APP_PROCESS_DOMAIN_CONFLICT_FIELDS = ["parent", "kind", "name"];
|
||||
function resolveAppProcessDomainPlacement(process, index) {
|
||||
const localDomains = process.domains ?? [];
|
||||
const hasLocalDomains = localDomains.length > 0;
|
||||
|
|
@ -843,13 +847,21 @@ function resolveAppProcessDomainPlacement(process, index) {
|
|||
conflicts: [],
|
||||
warnings: []
|
||||
};
|
||||
const domainsById = new Map(localDomains.map((domain) => [domain.id, domain]));
|
||||
for (const domain of resolvedSources.domains) {
|
||||
if (!domainsById.has(domain.id)) {
|
||||
domainsById.set(domain.id, domain);
|
||||
}
|
||||
}
|
||||
const warnings = [...resolvedSources.warnings];
|
||||
const mergeResult = mergeAppProcessDomains(
|
||||
resolvedSources.domains,
|
||||
localDomains,
|
||||
process.path
|
||||
);
|
||||
const domainsById = new Map(mergeResult.domains.map((domain) => [domain.id, domain]));
|
||||
const warnings = [
|
||||
...resolvedSources.warnings,
|
||||
...mergeResult.warnings,
|
||||
...validateMergedAppProcessDomainParents(process.path, mergeResult.domains)
|
||||
];
|
||||
const conflicts = [
|
||||
...resolvedSources.conflicts,
|
||||
...mergeResult.conflicts
|
||||
];
|
||||
const placements = (process.steps ?? []).map((step) => {
|
||||
const domainId = step.domain?.trim() ?? "";
|
||||
if (!domainId) {
|
||||
|
|
@ -859,7 +871,7 @@ function resolveAppProcessDomainPlacement(process, index) {
|
|||
if (!domain) {
|
||||
warnings.push({
|
||||
code: "unresolved-reference",
|
||||
message: hasLocalDomains ? formatAppProcessUnknownLocalDomainMessage(step.id, domainId) : formatAppProcessUnknownDomainMessage(step.id, domainId),
|
||||
message: hasLocalDomains && process.domainSources.length === 0 ? formatAppProcessUnknownLocalDomainMessage(step.id, domainId) : formatAppProcessUnknownDomainMessage(step.id, domainId),
|
||||
severity: "warning",
|
||||
path: process.path,
|
||||
field: "Steps.domain",
|
||||
|
|
@ -879,13 +891,81 @@ function resolveAppProcessDomainPlacement(process, index) {
|
|||
);
|
||||
return {
|
||||
process,
|
||||
domains: [...domainsById.values()],
|
||||
domains: mergeResult.domains,
|
||||
sourceSummaries: resolvedSources.sourceSummaries,
|
||||
conflicts: resolvedSources.conflicts,
|
||||
conflicts,
|
||||
placements,
|
||||
warnings
|
||||
};
|
||||
}
|
||||
function validateMergedAppProcessDomainParents(processPath, domains) {
|
||||
const domainIds = new Set(domains.map((domain) => domain.id));
|
||||
const warnings = [];
|
||||
for (const domain of domains) {
|
||||
if (!domain.parent || domain.parent === domain.id || domainIds.has(domain.parent)) {
|
||||
continue;
|
||||
}
|
||||
warnings.push({
|
||||
code: "unresolved-reference",
|
||||
message: formatDomainParentUnknownMessage(domain.parent),
|
||||
severity: "warning",
|
||||
path: processPath,
|
||||
field: "Domains.parent",
|
||||
context: { rowIndex: domain.rowIndex + 1 }
|
||||
});
|
||||
}
|
||||
return warnings;
|
||||
}
|
||||
function mergeAppProcessDomains(externalDomains, localDomains, processPath) {
|
||||
const domainsById = /* @__PURE__ */ new Map();
|
||||
const order = [];
|
||||
const conflicts = [];
|
||||
const warnings = [];
|
||||
for (const domain of externalDomains) {
|
||||
if (!domainsById.has(domain.id)) {
|
||||
order.push(domain.id);
|
||||
}
|
||||
domainsById.set(domain.id, { ...domain });
|
||||
}
|
||||
for (const localDomain of localDomains) {
|
||||
const externalDomain = domainsById.get(localDomain.id);
|
||||
if (!externalDomain) {
|
||||
order.push(localDomain.id);
|
||||
domainsById.set(localDomain.id, { ...localDomain });
|
||||
continue;
|
||||
}
|
||||
for (const field of APP_PROCESS_DOMAIN_CONFLICT_FIELDS) {
|
||||
const externalValue = externalDomain[field]?.trim() ?? "";
|
||||
const localValue = localDomain[field]?.trim() ?? "";
|
||||
if (externalValue === localValue) {
|
||||
continue;
|
||||
}
|
||||
conflicts.push({
|
||||
domainId: localDomain.id,
|
||||
field,
|
||||
earlierSourcePath: "Domain Sources",
|
||||
laterSourcePath: processPath,
|
||||
earlierValue: externalValue,
|
||||
laterValue: localValue,
|
||||
effectiveSourcePath: processPath,
|
||||
severity: "warning"
|
||||
});
|
||||
warnings.push({
|
||||
code: "invalid-structure",
|
||||
message: formatAppProcessLocalDomainFieldOverrideMessage(localDomain.id, field),
|
||||
severity: "warning",
|
||||
path: processPath,
|
||||
field: "Domains"
|
||||
});
|
||||
}
|
||||
domainsById.set(localDomain.id, { ...localDomain });
|
||||
}
|
||||
return {
|
||||
domains: order.map((id) => domainsById.get(id)).filter((domain) => Boolean(domain)),
|
||||
conflicts,
|
||||
warnings
|
||||
};
|
||||
}
|
||||
|
||||
// src/core/color-scheme.ts
|
||||
var BUILT_IN_COLOR_ENTRIES = [
|
||||
|
|
@ -2204,6 +2284,7 @@ function localizeDiagnosticMessage(message, language) {
|
|||
[/^Step "([^"]+)" has both domain and lane\. domain is used and lane is ignored\.$/, (_match, step) => `Step "${step}" \u306B\u306F domain \u3068 lane \u306E\u4E21\u65B9\u304C\u3042\u308A\u307E\u3059\u3002domain \u304C\u4F7F\u308F\u308C\u3001lane \u306F\u7121\u8996\u3055\u308C\u307E\u3059\u3002`],
|
||||
[/^app_process Step "([^"]+)" references unknown Domain "([^"]+)"\.$/, (_match, step, domain) => `app_process Step "${step}" \u304C\u672A\u5B9A\u7FA9\u306E Domain "${domain}" \u3092\u53C2\u7167\u3057\u3066\u3044\u307E\u3059\u3002`],
|
||||
[/^app_process Step "([^"]+)" references unknown local Domain "([^"]+)"\.$/, (_match, step, domain) => `app_process Step "${step}" \u304C\u672A\u5B9A\u7FA9\u306E\u30ED\u30FC\u30AB\u30EB Domain "${domain}" \u3092\u53C2\u7167\u3057\u3066\u3044\u307E\u3059\u3002`],
|
||||
[/^app_process local Domain "([^"]+)" overrides external Domain (name|kind|parent)\.$/, (_match, domain, field) => `app_process \u30ED\u30FC\u30AB\u30EB Domain "${domain}" \u304C\u5916\u90E8 Domain \u306E ${field} \u3092\u4E0A\u66F8\u304D\u3057\u3066\u3044\u307E\u3059\u3002`],
|
||||
[/^(.+) "([^"]+)" could not be resolved\. Check the ID or file name\.$/, (_match, target, value) => `${target} "${value}" \u306E\u53C2\u7167\u5148\u304C\u898B\u3064\u304B\u308A\u307E\u305B\u3093\u3002ID\u307E\u305F\u306F\u30D5\u30A1\u30A4\u30EB\u540D\u3092\u78BA\u8A8D\u3057\u3066\u304F\u3060\u3055\u3044\u3002`],
|
||||
[/^unresolved (.+) "([^"]+)"$/, (_match, target, value) => `${target} "${value}" \u306E\u53C2\u7167\u5148\u304C\u898B\u3064\u304B\u308A\u307E\u305B\u3093\u3002ID\u307E\u305F\u306F\u30D5\u30A1\u30A4\u30EB\u540D\u3092\u78BA\u8A8D\u3057\u3066\u304F\u3060\u3055\u3044\u3002`],
|
||||
[/^unresolved member ref: (.+) in (.+)$/, (_match, member, owner) => `member ref "${member}" \u304C "${owner}" \u5185\u3067\u898B\u3064\u304B\u308A\u307E\u305B\u3093\u3002`],
|
||||
|
|
@ -9938,7 +10019,7 @@ function parseDomainEntries(lines, path2) {
|
|||
});
|
||||
return { rows, warnings };
|
||||
}
|
||||
function validateDomainEntries(path2, domains) {
|
||||
function validateDomainEntries(path2, domains, options = {}) {
|
||||
const warnings = [];
|
||||
const domainIds = new Set(domains.map((domain) => domain.id));
|
||||
for (const domain of domains) {
|
||||
|
|
@ -9956,7 +10037,7 @@ function validateDomainEntries(path2, domains) {
|
|||
});
|
||||
continue;
|
||||
}
|
||||
if (!domainIds.has(domain.parent)) {
|
||||
if (!domainIds.has(domain.parent) && !options.skipUnknownParents) {
|
||||
warnings.push({
|
||||
code: "unresolved-reference",
|
||||
message: formatDomainParentUnknownMessage(domain.parent),
|
||||
|
|
@ -10780,7 +10861,9 @@ function parseAppProcessFile(markdown, path2) {
|
|||
...triggersTable.warnings,
|
||||
...transitionsTable.warnings,
|
||||
...domainsTable.warnings,
|
||||
...validateDomainEntries(path2, domainsTable.rows),
|
||||
...validateDomainEntries(path2, domainsTable.rows, {
|
||||
skipUnknownParents: domainSourcesTable.rows.length > 0
|
||||
}),
|
||||
...stepsTable.warnings,
|
||||
...flowsTable.warnings,
|
||||
...domainSourcesTable.warnings
|
||||
|
|
@ -21294,7 +21377,7 @@ var ModelWeavePlugin = class extends import_obsidian8.Plugin {
|
|||
steps: model.steps ?? [],
|
||||
flows: model.flows ?? [],
|
||||
hasExplicitFlows: Boolean(model.hasExplicitFlows),
|
||||
domains: model.domains
|
||||
domains: domainPlacement.domains.length > 0 ? domainPlacement.domains : model.domains
|
||||
} : void 0,
|
||||
colorScheme: colorSchemeResult.colorScheme,
|
||||
warnings: diagnostics,
|
||||
|
|
|
|||
|
|
@ -1,4 +1,6 @@
|
|||
import {
|
||||
formatDomainParentUnknownMessage,
|
||||
formatAppProcessLocalDomainFieldOverrideMessage,
|
||||
formatAppProcessUnknownDomainMessage,
|
||||
formatAppProcessUnknownLocalDomainMessage
|
||||
} from "./domain-diagnostics";
|
||||
|
|
@ -6,10 +8,14 @@ import { resolveDomainSources } from "./domain-diagram-resolver";
|
|||
import type { ModelingVaultIndex } from "./vault-index";
|
||||
import type {
|
||||
AppProcessModel,
|
||||
DomainEntry,
|
||||
DomainMergeConflict,
|
||||
ResolvedAppProcessDomainPlacement,
|
||||
ValidationWarning
|
||||
} from "../types/models";
|
||||
|
||||
const APP_PROCESS_DOMAIN_CONFLICT_FIELDS = ["parent", "kind", "name"] as const;
|
||||
|
||||
export function resolveAppProcessDomainPlacement(
|
||||
process: AppProcessModel,
|
||||
index: ModelingVaultIndex
|
||||
|
|
@ -35,13 +41,21 @@ export function resolveAppProcessDomainPlacement(
|
|||
conflicts: [],
|
||||
warnings: []
|
||||
};
|
||||
const domainsById = new Map(localDomains.map((domain) => [domain.id, domain]));
|
||||
for (const domain of resolvedSources.domains) {
|
||||
if (!domainsById.has(domain.id)) {
|
||||
domainsById.set(domain.id, domain);
|
||||
}
|
||||
}
|
||||
const warnings: ValidationWarning[] = [...resolvedSources.warnings];
|
||||
const mergeResult = mergeAppProcessDomains(
|
||||
resolvedSources.domains,
|
||||
localDomains,
|
||||
process.path
|
||||
);
|
||||
const domainsById = new Map(mergeResult.domains.map((domain) => [domain.id, domain]));
|
||||
const warnings: ValidationWarning[] = [
|
||||
...resolvedSources.warnings,
|
||||
...mergeResult.warnings,
|
||||
...validateMergedAppProcessDomainParents(process.path, mergeResult.domains)
|
||||
];
|
||||
const conflicts = [
|
||||
...resolvedSources.conflicts,
|
||||
...mergeResult.conflicts
|
||||
];
|
||||
const placements = (process.steps ?? [])
|
||||
.map((step) => {
|
||||
const domainId = step.domain?.trim() ?? "";
|
||||
|
|
@ -53,7 +67,7 @@ export function resolveAppProcessDomainPlacement(
|
|||
if (!domain) {
|
||||
warnings.push({
|
||||
code: "unresolved-reference",
|
||||
message: hasLocalDomains
|
||||
message: hasLocalDomains && process.domainSources.length === 0
|
||||
? formatAppProcessUnknownLocalDomainMessage(step.id, domainId)
|
||||
: formatAppProcessUnknownDomainMessage(step.id, domainId),
|
||||
severity: "warning",
|
||||
|
|
@ -78,10 +92,102 @@ export function resolveAppProcessDomainPlacement(
|
|||
|
||||
return {
|
||||
process,
|
||||
domains: [...domainsById.values()],
|
||||
domains: mergeResult.domains,
|
||||
sourceSummaries: resolvedSources.sourceSummaries,
|
||||
conflicts: resolvedSources.conflicts,
|
||||
conflicts,
|
||||
placements,
|
||||
warnings
|
||||
};
|
||||
}
|
||||
|
||||
function validateMergedAppProcessDomainParents(
|
||||
processPath: string,
|
||||
domains: DomainEntry[]
|
||||
): ValidationWarning[] {
|
||||
const domainIds = new Set(domains.map((domain) => domain.id));
|
||||
const warnings: ValidationWarning[] = [];
|
||||
|
||||
for (const domain of domains) {
|
||||
if (!domain.parent || domain.parent === domain.id || domainIds.has(domain.parent)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
warnings.push({
|
||||
code: "unresolved-reference",
|
||||
message: formatDomainParentUnknownMessage(domain.parent),
|
||||
severity: "warning",
|
||||
path: processPath,
|
||||
field: "Domains.parent",
|
||||
context: { rowIndex: domain.rowIndex + 1 }
|
||||
});
|
||||
}
|
||||
|
||||
return warnings;
|
||||
}
|
||||
|
||||
function mergeAppProcessDomains(
|
||||
externalDomains: DomainEntry[],
|
||||
localDomains: DomainEntry[],
|
||||
processPath: string
|
||||
): {
|
||||
domains: DomainEntry[];
|
||||
conflicts: DomainMergeConflict[];
|
||||
warnings: ValidationWarning[];
|
||||
} {
|
||||
const domainsById = new Map<string, DomainEntry>();
|
||||
const order: string[] = [];
|
||||
const conflicts: DomainMergeConflict[] = [];
|
||||
const warnings: ValidationWarning[] = [];
|
||||
|
||||
for (const domain of externalDomains) {
|
||||
if (!domainsById.has(domain.id)) {
|
||||
order.push(domain.id);
|
||||
}
|
||||
domainsById.set(domain.id, { ...domain });
|
||||
}
|
||||
|
||||
for (const localDomain of localDomains) {
|
||||
const externalDomain = domainsById.get(localDomain.id);
|
||||
if (!externalDomain) {
|
||||
order.push(localDomain.id);
|
||||
domainsById.set(localDomain.id, { ...localDomain });
|
||||
continue;
|
||||
}
|
||||
|
||||
for (const field of APP_PROCESS_DOMAIN_CONFLICT_FIELDS) {
|
||||
const externalValue = externalDomain[field]?.trim() ?? "";
|
||||
const localValue = localDomain[field]?.trim() ?? "";
|
||||
if (externalValue === localValue) {
|
||||
continue;
|
||||
}
|
||||
|
||||
conflicts.push({
|
||||
domainId: localDomain.id,
|
||||
field,
|
||||
earlierSourcePath: "Domain Sources",
|
||||
laterSourcePath: processPath,
|
||||
earlierValue: externalValue,
|
||||
laterValue: localValue,
|
||||
effectiveSourcePath: processPath,
|
||||
severity: "warning"
|
||||
});
|
||||
warnings.push({
|
||||
code: "invalid-structure",
|
||||
message: formatAppProcessLocalDomainFieldOverrideMessage(localDomain.id, field),
|
||||
severity: "warning",
|
||||
path: processPath,
|
||||
field: "Domains"
|
||||
});
|
||||
}
|
||||
|
||||
domainsById.set(localDomain.id, { ...localDomain });
|
||||
}
|
||||
|
||||
return {
|
||||
domains: order
|
||||
.map((id) => domainsById.get(id))
|
||||
.filter((domain): domain is DomainEntry => Boolean(domain)),
|
||||
conflicts,
|
||||
warnings
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1204,6 +1204,7 @@ export function localizeDiagnosticMessage(message: string, language?: string): s
|
|||
[/^Step "([^"]+)" has both domain and lane\. domain is used and lane is ignored\.$/, (_match, step) => `Step "${step}" には domain と lane の両方があります。domain が使われ、lane は無視されます。`],
|
||||
[/^app_process Step "([^"]+)" references unknown Domain "([^"]+)"\.$/, (_match, step, domain) => `app_process Step "${step}" が未定義の Domain "${domain}" を参照しています。`],
|
||||
[/^app_process Step "([^"]+)" references unknown local Domain "([^"]+)"\.$/, (_match, step, domain) => `app_process Step "${step}" が未定義のローカル Domain "${domain}" を参照しています。`],
|
||||
[/^app_process local Domain "([^"]+)" overrides external Domain (name|kind|parent)\.$/, (_match, domain, field) => `app_process ローカル Domain "${domain}" が外部 Domain の ${field} を上書きしています。`],
|
||||
[/^(.+) "([^"]+)" could not be resolved\. Check the ID or file name\.$/, (_match, target, value) => `${target} "${value}" の参照先が見つかりません。IDまたはファイル名を確認してください。`],
|
||||
[/^unresolved (.+) "([^"]+)"$/, (_match, target, value) => `${target} "${value}" の参照先が見つかりません。IDまたはファイル名を確認してください。`],
|
||||
[/^unresolved member ref: (.+) in (.+)$/, (_match, member, owner) => `member ref "${member}" が "${owner}" 内で見つかりません。`],
|
||||
|
|
|
|||
|
|
@ -109,3 +109,10 @@ export function formatAppProcessUnknownLocalDomainMessage(
|
|||
): string {
|
||||
return `app_process Step "${stepId}" references unknown local Domain "${domainId}".`;
|
||||
}
|
||||
|
||||
export function formatAppProcessLocalDomainFieldOverrideMessage(
|
||||
id: string,
|
||||
field: "name" | "kind" | "parent"
|
||||
): string {
|
||||
return `app_process local Domain "${id}" overrides external Domain ${field}.`;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1332,7 +1332,9 @@ export default class ModelWeavePlugin extends Plugin {
|
|||
steps: model.steps ?? [],
|
||||
flows: model.flows ?? [],
|
||||
hasExplicitFlows: Boolean(model.hasExplicitFlows),
|
||||
domains: model.domains
|
||||
domains: domainPlacement.domains.length > 0
|
||||
? domainPlacement.domains
|
||||
: model.domains
|
||||
}
|
||||
: undefined,
|
||||
colorScheme: colorSchemeResult.colorScheme,
|
||||
|
|
|
|||
|
|
@ -107,7 +107,9 @@ export function parseAppProcessFile(
|
|||
...triggersTable.warnings,
|
||||
...transitionsTable.warnings,
|
||||
...domainsTable.warnings,
|
||||
...validateDomainEntries(path, domainsTable.rows),
|
||||
...validateDomainEntries(path, domainsTable.rows, {
|
||||
skipUnknownParents: domainSourcesTable.rows.length > 0
|
||||
}),
|
||||
...stepsTable.warnings,
|
||||
...flowsTable.warnings,
|
||||
...domainSourcesTable.warnings
|
||||
|
|
|
|||
|
|
@ -123,7 +123,8 @@ export function parseDomainEntries(
|
|||
|
||||
export function validateDomainEntries(
|
||||
path: string,
|
||||
domains: DomainEntry[]
|
||||
domains: DomainEntry[],
|
||||
options: { skipUnknownParents?: boolean } = {}
|
||||
): ValidationWarning[] {
|
||||
const warnings: ValidationWarning[] = [];
|
||||
const domainIds = new Set(domains.map((domain) => domain.id));
|
||||
|
|
@ -145,7 +146,7 @@ export function validateDomainEntries(
|
|||
continue;
|
||||
}
|
||||
|
||||
if (!domainIds.has(domain.parent)) {
|
||||
if (!domainIds.has(domain.parent) && !options.skipUnknownParents) {
|
||||
warnings.push({
|
||||
code: "unresolved-reference",
|
||||
message: formatDomainParentUnknownMessage(domain.parent),
|
||||
|
|
|
|||
|
|
@ -699,9 +699,322 @@ test("app_process Domain Sources resolve known step domains", () => {
|
|||
assert.equal(resolved.placements[0].status, "resolved");
|
||||
assert.equal(resolved.placements[0].domain.name, "Fulfillment");
|
||||
assert.equal(resolved.placements[0].domain.kind, "business_domain");
|
||||
assert.equal(resolved.domains[0].id, "fulfillment");
|
||||
assert.equal(resolved.warnings.length, 0);
|
||||
});
|
||||
|
||||
test("app_process Domain Sources render merged hierarchy", () => {
|
||||
const { index, model } = buildIndexWithProcess(
|
||||
processMarkdown(
|
||||
domainStepsHeader,
|
||||
[
|
||||
"| receive | office | Receive order | process | | | | | | external child |",
|
||||
"| open | wms | Open WMS | screen | | | | | | external grandchild |"
|
||||
].join("\n"),
|
||||
[
|
||||
"## Domain Sources",
|
||||
"",
|
||||
"| ref |",
|
||||
"|---|",
|
||||
"| [[DOMAINS-COMPANY]] |"
|
||||
].join("\n")
|
||||
),
|
||||
[
|
||||
{
|
||||
path: "DOMAINS-COMPANY.md",
|
||||
content: domainsMarkdown([
|
||||
"| warehouse | Warehouse | operations | | Warehouse area |",
|
||||
"| office | Office | organization | warehouse | Office area |",
|
||||
"| wms | WMS | system | office | Warehouse management system |"
|
||||
].join("\n"))
|
||||
}
|
||||
]
|
||||
);
|
||||
|
||||
const resolved = resolveAppProcessDomainPlacement(model, index);
|
||||
const source = buildAppProcessBusinessFlowMermaidSource({
|
||||
title: model.name,
|
||||
hasExplicitFlows: false,
|
||||
domains: resolved.domains,
|
||||
steps: model.steps,
|
||||
flows: []
|
||||
});
|
||||
|
||||
assert.equal(resolved.warnings.length, 0);
|
||||
assert.match(source, /subgraph domain_warehouse\["Warehouse"\]/);
|
||||
assert.match(source, /subgraph domain_office\["Office"\]/);
|
||||
assert.match(source, /subgraph domain_wms\["WMS"\]/);
|
||||
assert.match(source, /S1\["Receive order"\]/);
|
||||
assert.match(source, /S2\[\/"Open WMS"\/\]/);
|
||||
});
|
||||
|
||||
test("app_process mixed local and external Domain Sources render full Business Flow hierarchy", () => {
|
||||
const processContent = `---
|
||||
type: app_process
|
||||
id: PROC-DOMAIN-PLACEMENT
|
||||
name: Domain Placement
|
||||
---
|
||||
|
||||
# Domain Placement
|
||||
|
||||
## Domain Sources
|
||||
|
||||
| ref |
|
||||
|---|
|
||||
| [[DOMAINS-COMPANY]] |
|
||||
|
||||
${localDomainsSection([
|
||||
"| office | 事務所 | organization | warehouse | local definition override |",
|
||||
"| qa | 検品 | operations | floor | local child of external floor |"
|
||||
].join("\n"))}
|
||||
|
||||
## Steps
|
||||
|
||||
${domainStepsHeader}
|
||||
| start | office | 在庫照会開始 | start | | | | | | |
|
||||
| open | wms | 在庫照会画面を開く | screen | | | | | | |
|
||||
| search | wms | 在庫検索 | input | | | | | | |
|
||||
| judge | office | 在庫有無を判定 | decision | | | | | | |
|
||||
| available | floor | 在庫ありの場合受注 | subflow | | | | | | |
|
||||
| inspect | qa | 検品対象を確認 | process | | | | | | local Domainsで追加した領域 |
|
||||
| unavailable | external | 在庫なしの場合は連絡 | subflow | | | | | | |
|
||||
| end | | 終了 | end | | | | | | |
|
||||
|
||||
## Flows
|
||||
|
||||
| from | to | condition | label | notes |
|
||||
|---|---|---|---|---|
|
||||
| start | open | | | |
|
||||
| open | search | | | |
|
||||
| search | judge | | | |
|
||||
| judge | available | | 在庫あり | |
|
||||
| judge | unavailable | | 在庫なし | |
|
||||
| available | inspect | | | |
|
||||
| inspect | end | | | |
|
||||
| unavailable | end | | | |
|
||||
`;
|
||||
const parsed = parseAppProcessFile(processContent, "PROC-DOMAIN-PLACEMENT.md");
|
||||
assert.ok(parsed.file);
|
||||
assert.equal(
|
||||
parsed.warnings.some((warning) =>
|
||||
warning.message === 'Domain parent "floor" is not defined.'
|
||||
),
|
||||
false
|
||||
);
|
||||
|
||||
const { index, model } = buildIndexWithProcess(
|
||||
processContent,
|
||||
[
|
||||
{
|
||||
path: "DOMAINS-COMPANY.md",
|
||||
content: domainsMarkdown([
|
||||
"| warehouse | 倉庫 | operations | | |",
|
||||
"| office | 事務所 | organization | warehouse | |",
|
||||
"| wms | WMS | system | office | |",
|
||||
"| floor | フロア | location | warehouse | |",
|
||||
"| external | 外部 | external | | |"
|
||||
].join("\n"))
|
||||
}
|
||||
]
|
||||
);
|
||||
const resolved = resolveAppProcessDomainPlacement(model, index);
|
||||
const scheme = {
|
||||
id: "test",
|
||||
name: "Test",
|
||||
entries: [
|
||||
{ target: "domain", kind: "operations", fill: "#111111", stroke: "#222222", text: "#ffffff", rowIndex: 0 },
|
||||
{ target: "domain", kind: "organization", fill: "#333333", stroke: "#444444", text: "#eeeeee", rowIndex: 1 },
|
||||
{ target: "domain", kind: "system", fill: "#555555", stroke: "#666666", text: "#ffffff", rowIndex: 2 },
|
||||
{ target: "domain", kind: "location", fill: "#777777", stroke: "#888888", text: "#ffffff", rowIndex: 3 },
|
||||
{ target: "domain", kind: "external", fill: "#999999", stroke: "#aaaaaa", text: "#111111", rowIndex: 4 },
|
||||
{ target: "app_process", kind: "process", fill: "#e8f5e9", stroke: "#388e3c", text: "#111111", rowIndex: 5 },
|
||||
{ kind: "default", fill: "#f5f5f5", stroke: "#9e9e9e", text: "#111111", rowIndex: 6 }
|
||||
],
|
||||
defaultStyle: {
|
||||
fill: "#f5f5f5",
|
||||
stroke: "#9e9e9e",
|
||||
text: "#111111"
|
||||
}
|
||||
};
|
||||
const source = buildAppProcessBusinessFlowMermaidSource({
|
||||
title: model.name,
|
||||
hasExplicitFlows: Boolean(model.hasExplicitFlows),
|
||||
domains: resolved.domains,
|
||||
steps: model.steps,
|
||||
flows: model.flows
|
||||
}, scheme);
|
||||
|
||||
assert.equal(
|
||||
resolved.warnings.some((warning) =>
|
||||
warning.message === 'Domain parent "floor" is not defined.'
|
||||
),
|
||||
false
|
||||
);
|
||||
assert.equal(resolved.placements.filter((placement) => placement.status === "resolved").length, 7);
|
||||
assert.match(source, /subgraph domain_warehouse\["倉庫"\]/);
|
||||
assert.match(source, /subgraph domain_office\["事務所"\]/);
|
||||
assert.match(source, /subgraph domain_wms\["WMS"\]/);
|
||||
assert.match(source, /subgraph domain_floor\["フロア"\]/);
|
||||
assert.match(source, /subgraph domain_qa\["検品"\]/);
|
||||
assert.match(source, /subgraph domain_external\["外部"\]/);
|
||||
assert.ok(source.indexOf('subgraph domain_wms["WMS"]') > source.indexOf('subgraph domain_office["事務所"]'));
|
||||
assert.ok(source.indexOf('subgraph domain_qa["検品"]') > source.indexOf('subgraph domain_floor["フロア"]'));
|
||||
assert.match(source, /style domain_warehouse fill:#111111,stroke:#222222,color:#ffffff/);
|
||||
assert.match(source, /style domain_wms fill:#555555,stroke:#666666,color:#ffffff/);
|
||||
assert.match(source, /style domain_floor fill:#777777,stroke:#888888,color:#ffffff/);
|
||||
assert.match(source, /style domain_external fill:#999999,stroke:#aaaaaa,color:#111111/);
|
||||
assert.match(source, /classDef kind_app_process_process fill:#e8f5e9,stroke:#388e3c,color:#111111/);
|
||||
assert.match(source, /S4 -->\|"在庫あり"\| S5/);
|
||||
assert.match(source, /S4 -->\|"在庫なし"\| S7/);
|
||||
});
|
||||
|
||||
test("app_process Domain group Color Scheme uses merged external Domain kind", () => {
|
||||
const { index, model } = buildIndexWithProcess(
|
||||
processMarkdown(
|
||||
domainStepsHeader,
|
||||
"| receive | fulfillment | Receive order | process | | | | | | external color |",
|
||||
[
|
||||
"## Domain Sources",
|
||||
"",
|
||||
"| ref |",
|
||||
"|---|",
|
||||
"| [[DOMAINS-COMPANY]] |"
|
||||
].join("\n")
|
||||
),
|
||||
[
|
||||
{
|
||||
path: "DOMAINS-COMPANY.md",
|
||||
content: domainsMarkdown(
|
||||
"| fulfillment | Fulfillment | business_domain | | Fulfillment process area |"
|
||||
)
|
||||
}
|
||||
]
|
||||
);
|
||||
const resolved = resolveAppProcessDomainPlacement(model, index);
|
||||
const scheme = {
|
||||
id: "test",
|
||||
name: "Test",
|
||||
entries: [
|
||||
{ target: "domain", kind: "business_domain", fill: "#123456", stroke: "#234567", text: "#ffffff", rowIndex: 0 },
|
||||
{ target: "app_process", kind: "process", fill: "#e8f5e9", stroke: "#388e3c", text: "#111111", rowIndex: 1 },
|
||||
{ kind: "default", fill: "#f5f5f5", stroke: "#9e9e9e", text: "#111111", rowIndex: 2 }
|
||||
],
|
||||
defaultStyle: {
|
||||
fill: "#f5f5f5",
|
||||
stroke: "#9e9e9e",
|
||||
text: "#111111"
|
||||
}
|
||||
};
|
||||
const source = buildAppProcessBusinessFlowMermaidSource({
|
||||
title: model.name,
|
||||
hasExplicitFlows: false,
|
||||
domains: resolved.domains,
|
||||
steps: model.steps,
|
||||
flows: []
|
||||
}, scheme);
|
||||
|
||||
assert.match(source, /style domain_fulfillment fill:#123456,stroke:#234567,color:#ffffff/);
|
||||
assert.match(source, /classDef kind_app_process_process fill:#e8f5e9,stroke:#388e3c,color:#111111/);
|
||||
});
|
||||
|
||||
test("app_process local Domains override external source definitions", () => {
|
||||
const { index, model } = buildIndexWithProcess(
|
||||
processMarkdown(
|
||||
domainStepsHeader,
|
||||
"| receive | fulfillment | Receive order | process | | | | | | local override |",
|
||||
[
|
||||
"## Domain Sources",
|
||||
"",
|
||||
"| ref |",
|
||||
"|---|",
|
||||
"| [[DOMAINS-COMPANY]] |",
|
||||
"",
|
||||
localDomainsSection(
|
||||
"| fulfillment | Local Fulfillment | local_kind | local_parent | Local definition |"
|
||||
)
|
||||
].join("\n")
|
||||
),
|
||||
[
|
||||
{
|
||||
path: "DOMAINS-COMPANY.md",
|
||||
content: domainsMarkdown(
|
||||
"| fulfillment | External Fulfillment | external_kind | external_parent | External definition |"
|
||||
)
|
||||
}
|
||||
]
|
||||
);
|
||||
|
||||
const resolved = resolveAppProcessDomainPlacement(model, index);
|
||||
const domain = resolved.placements[0].domain;
|
||||
const messages = resolved.warnings.map((warning) => warning.message);
|
||||
|
||||
assert.equal(domain.name, "Local Fulfillment");
|
||||
assert.equal(domain.kind, "local_kind");
|
||||
assert.equal(domain.parent, "local_parent");
|
||||
assert.ok(messages.includes('app_process local Domain "fulfillment" overrides external Domain name.'));
|
||||
assert.ok(messages.includes('app_process local Domain "fulfillment" overrides external Domain kind.'));
|
||||
assert.ok(messages.includes('app_process local Domain "fulfillment" overrides external Domain parent.'));
|
||||
assert.equal(resolved.conflicts.length, 3);
|
||||
assert.equal(
|
||||
localizeDiagnosticMessage(messages[0], "ja"),
|
||||
'app_process ローカル Domain "fulfillment" が外部 Domain の parent を上書きしています。'
|
||||
);
|
||||
});
|
||||
|
||||
test("app_process local override controls rendered Domain color", () => {
|
||||
const { index, model } = buildIndexWithProcess(
|
||||
processMarkdown(
|
||||
domainStepsHeader,
|
||||
"| receive | fulfillment | Receive order | process | | | | | | local color override |",
|
||||
[
|
||||
"## Domain Sources",
|
||||
"",
|
||||
"| ref |",
|
||||
"|---|",
|
||||
"| [[DOMAINS-COMPANY]] |",
|
||||
"",
|
||||
localDomainsSection(
|
||||
"| fulfillment | Local Fulfillment | local_kind | | Local definition |"
|
||||
)
|
||||
].join("\n")
|
||||
),
|
||||
[
|
||||
{
|
||||
path: "DOMAINS-COMPANY.md",
|
||||
content: domainsMarkdown(
|
||||
"| fulfillment | External Fulfillment | external_kind | | External definition |"
|
||||
)
|
||||
}
|
||||
]
|
||||
);
|
||||
const resolved = resolveAppProcessDomainPlacement(model, index);
|
||||
const scheme = {
|
||||
id: "test",
|
||||
name: "Test",
|
||||
entries: [
|
||||
{ target: "domain", kind: "external_kind", fill: "#000000", stroke: "#000000", text: "#ffffff", rowIndex: 0 },
|
||||
{ target: "domain", kind: "local_kind", fill: "#abcdef", stroke: "#123456", text: "#111111", rowIndex: 1 },
|
||||
{ kind: "default", fill: "#f5f5f5", stroke: "#9e9e9e", text: "#111111", rowIndex: 2 }
|
||||
],
|
||||
defaultStyle: {
|
||||
fill: "#f5f5f5",
|
||||
stroke: "#9e9e9e",
|
||||
text: "#111111"
|
||||
}
|
||||
};
|
||||
const source = buildAppProcessBusinessFlowMermaidSource({
|
||||
title: model.name,
|
||||
hasExplicitFlows: false,
|
||||
domains: resolved.domains,
|
||||
steps: model.steps,
|
||||
flows: []
|
||||
}, scheme);
|
||||
|
||||
assert.match(source, /subgraph domain_fulfillment\["Local Fulfillment"\]/);
|
||||
assert.match(source, /style domain_fulfillment fill:#abcdef,stroke:#123456,color:#111111/);
|
||||
assert.doesNotMatch(source, /fill:#000000,stroke:#000000,color:#ffffff/);
|
||||
});
|
||||
|
||||
test("app_process unknown step domain warns when Domain Sources are present", () => {
|
||||
const { index, model } = buildIndexWithProcess(
|
||||
processMarkdown(
|
||||
|
|
|
|||
Loading…
Reference in a new issue