eventmodeler 0.6.9 → 0.6.11
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/dist/index.js +229 -26
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1815,8 +1815,8 @@ var require_semver2 = __commonJS((exports, module) => {
|
|
|
1815
1815
|
});
|
|
1816
1816
|
|
|
1817
1817
|
// src/index.ts
|
|
1818
|
-
import * as
|
|
1819
|
-
import * as
|
|
1818
|
+
import * as fs5 from "node:fs";
|
|
1819
|
+
import * as path5 from "node:path";
|
|
1820
1820
|
import { fileURLToPath } from "node:url";
|
|
1821
1821
|
import { Command } from "commander";
|
|
1822
1822
|
|
|
@@ -2871,6 +2871,9 @@ var SCOPE_SCHEMAS = {
|
|
|
2871
2871
|
notes: NoteSchema,
|
|
2872
2872
|
flows: FlowSchema
|
|
2873
2873
|
};
|
|
2874
|
+
function isValidatedScope(scope) {
|
|
2875
|
+
return scope in SCOPE_SCHEMAS;
|
|
2876
|
+
}
|
|
2874
2877
|
function validateEntry(scope, data) {
|
|
2875
2878
|
const schema = pickSchema(scope, data);
|
|
2876
2879
|
const result = schema.safeParse(data);
|
|
@@ -3727,6 +3730,9 @@ function resolveType(alias) {
|
|
|
3727
3730
|
return;
|
|
3728
3731
|
return TYPES.find((t) => t.type === canonical);
|
|
3729
3732
|
}
|
|
3733
|
+
function resolveScope(scope2) {
|
|
3734
|
+
return TYPES.find((t) => t.scope === scope2);
|
|
3735
|
+
}
|
|
3730
3736
|
function knownTypeAliases() {
|
|
3731
3737
|
return TYPES.map((t) => t.type);
|
|
3732
3738
|
}
|
|
@@ -3824,6 +3830,10 @@ function registerCreateCommands(program) {
|
|
|
3824
3830
|
});
|
|
3825
3831
|
}
|
|
3826
3832
|
|
|
3833
|
+
// src/commands/update.ts
|
|
3834
|
+
import * as fs3 from "node:fs";
|
|
3835
|
+
import * as path3 from "node:path";
|
|
3836
|
+
|
|
3827
3837
|
// src/lib/resolve-entity.ts
|
|
3828
3838
|
function resolveEntity(doc, scope2, idKey, nameKey, name, opts = {}) {
|
|
3829
3839
|
const entries = getEntries(doc, scope2);
|
|
@@ -3864,6 +3874,78 @@ function resolveEntity(doc, scope2, idKey, nameKey, name, opts = {}) {
|
|
|
3864
3874
|
return { id: String(hit[idKey]), entry: hit };
|
|
3865
3875
|
}
|
|
3866
3876
|
|
|
3877
|
+
// src/lib/model-snapshot.ts
|
|
3878
|
+
var SNAPSHOT_FILENAME = "model.eventmodel";
|
|
3879
|
+
var SNAPSHOT_VERSION = 1;
|
|
3880
|
+
var SNAPSHOT_SCOPES = [...Object.keys(SCOPE_SCHEMAS), "scenarios"];
|
|
3881
|
+
function serializeModel(doc, modelId) {
|
|
3882
|
+
const scopes = {};
|
|
3883
|
+
for (const scope2 of SNAPSHOT_SCOPES) {
|
|
3884
|
+
const map = getScopeMap(doc, scope2);
|
|
3885
|
+
if (map.size === 0)
|
|
3886
|
+
continue;
|
|
3887
|
+
const entries = {};
|
|
3888
|
+
for (const [id, entry] of map.entries()) {
|
|
3889
|
+
entries[id] = entry.toJSON();
|
|
3890
|
+
}
|
|
3891
|
+
scopes[scope2] = entries;
|
|
3892
|
+
}
|
|
3893
|
+
return { version: SNAPSHOT_VERSION, modelId, scopes };
|
|
3894
|
+
}
|
|
3895
|
+
function applyModelSnapshot(doc, modelId, snapshot) {
|
|
3896
|
+
const issues = [];
|
|
3897
|
+
const unknownScopes = [];
|
|
3898
|
+
const writes = [];
|
|
3899
|
+
for (const [scope2, entries] of Object.entries(snapshot.scopes ?? {})) {
|
|
3900
|
+
if (!SNAPSHOT_SCOPES.includes(scope2)) {
|
|
3901
|
+
unknownScopes.push(scope2);
|
|
3902
|
+
continue;
|
|
3903
|
+
}
|
|
3904
|
+
const map = getScopeMap(doc, scope2);
|
|
3905
|
+
const validated = isValidatedScope(scope2);
|
|
3906
|
+
const meta = resolveScope(scope2);
|
|
3907
|
+
for (const [id, rawEntry] of Object.entries(entries)) {
|
|
3908
|
+
if (!rawEntry || typeof rawEntry !== "object" || Array.isArray(rawEntry)) {
|
|
3909
|
+
issues.push(`${scope2}/${id}: entry must be a JSON object`);
|
|
3910
|
+
continue;
|
|
3911
|
+
}
|
|
3912
|
+
let finalEntry;
|
|
3913
|
+
if (validated) {
|
|
3914
|
+
const res = validateEntry(scope2, rawEntry);
|
|
3915
|
+
if (!res.ok) {
|
|
3916
|
+
for (const issue of res.issues)
|
|
3917
|
+
issues.push(`${scope2}/${id}: ${issue}`);
|
|
3918
|
+
continue;
|
|
3919
|
+
}
|
|
3920
|
+
finalEntry = { ...res.data, modelId };
|
|
3921
|
+
if (meta)
|
|
3922
|
+
finalEntry[meta.idKey] = id;
|
|
3923
|
+
} else {
|
|
3924
|
+
finalEntry = { ...rawEntry };
|
|
3925
|
+
}
|
|
3926
|
+
writes.push({ scope: scope2, id, entry: finalEntry, isNew: !map.has(id) });
|
|
3927
|
+
}
|
|
3928
|
+
}
|
|
3929
|
+
if (issues.length)
|
|
3930
|
+
return { ok: false, issues };
|
|
3931
|
+
const perScope = {};
|
|
3932
|
+
doc.transact(() => {
|
|
3933
|
+
for (const w of writes) {
|
|
3934
|
+
const map = getScopeMap(doc, w.scope);
|
|
3935
|
+
map.delete(w.id);
|
|
3936
|
+
map.set(w.id, deepToY(w.entry));
|
|
3937
|
+
const counts = perScope[w.scope] ??= { created: 0, updated: 0 };
|
|
3938
|
+
if (w.isNew)
|
|
3939
|
+
counts.created++;
|
|
3940
|
+
else
|
|
3941
|
+
counts.updated++;
|
|
3942
|
+
}
|
|
3943
|
+
});
|
|
3944
|
+
const created = Object.values(perScope).reduce((a, s) => a + s.created, 0);
|
|
3945
|
+
const updated = Object.values(perScope).reduce((a, s) => a + s.updated, 0);
|
|
3946
|
+
return { ok: true, result: { created, updated, perScope, unknownScopes } };
|
|
3947
|
+
}
|
|
3948
|
+
|
|
3867
3949
|
// src/commands/update.ts
|
|
3868
3950
|
var UPDATE_HELP = `
|
|
3869
3951
|
Replace an existing element's entry with a new JSON shape. The new JSON is
|
|
@@ -3876,10 +3958,13 @@ Pattern (read-modify-write):
|
|
|
3876
3958
|
| jq '.fields += [{"name":"customerId","fieldType":"UUID","isList":false,"isGenerated":false}]')
|
|
3877
3959
|
eventmodeler update event "OrderPlaced" "$NEW"
|
|
3878
3960
|
|
|
3879
|
-
Linked-copy entries
|
|
3880
|
-
|
|
3881
|
-
|
|
3882
|
-
|
|
3961
|
+
Linked-copy entries can be repositioned/resized but never re-identified. A
|
|
3962
|
+
copy shares its original's name and fields by design, so an update to a copy
|
|
3963
|
+
writes geometry (x, y, width, height) only — any name/field keys in the
|
|
3964
|
+
payload are ignored. To change a copy's name or fields, edit the original and
|
|
3965
|
+
all copies pick up the change. Address a copy by uuid:
|
|
3966
|
+
|
|
3967
|
+
eventmodeler --id <copy-uuid> update event '{"x":120,"y":340}'
|
|
3883
3968
|
|
|
3884
3969
|
Flows have no addressable name and must be updated via uuid:
|
|
3885
3970
|
|
|
@@ -3893,9 +3978,82 @@ semantics); to preserve it, round-trip via \`show screen ... | jq\`.
|
|
|
3893
3978
|
|
|
3894
3979
|
This is a *replace*, not a patch: keys present in the previous entry but
|
|
3895
3980
|
absent in the new JSON are removed.
|
|
3981
|
+
|
|
3982
|
+
Whole-model upsert:
|
|
3983
|
+
eventmodeler update model [file]
|
|
3984
|
+
|
|
3985
|
+
Reads a snapshot file (default ${SNAPSHOT_FILENAME}; see \`eventmodeler
|
|
3986
|
+
snapshot\`) and upserts every entry by id — existing ids are replaced, new
|
|
3987
|
+
ids created, and entries absent from the file are left untouched (upsert, not
|
|
3988
|
+
sync; nothing is deleted). Validation is all-or-nothing: if any entry fails,
|
|
3989
|
+
nothing is written.
|
|
3896
3990
|
`;
|
|
3991
|
+
var GEOMETRY_KEYS = ["x", "y", "width", "height"];
|
|
3992
|
+
function extractGeometry(parsed) {
|
|
3993
|
+
if (!parsed || typeof parsed !== "object") {
|
|
3994
|
+
return { ok: false, values: {}, issues: ["payload must be a JSON object"] };
|
|
3995
|
+
}
|
|
3996
|
+
const obj = parsed;
|
|
3997
|
+
const values = {};
|
|
3998
|
+
const issues = [];
|
|
3999
|
+
for (const k of GEOMETRY_KEYS) {
|
|
4000
|
+
const v = obj[k];
|
|
4001
|
+
if (v === undefined)
|
|
4002
|
+
continue;
|
|
4003
|
+
if (typeof v !== "number" || !Number.isFinite(v)) {
|
|
4004
|
+
issues.push(`${k} must be a finite number`);
|
|
4005
|
+
continue;
|
|
4006
|
+
}
|
|
4007
|
+
if ((k === "width" || k === "height") && v <= 0) {
|
|
4008
|
+
issues.push(`${k} must be positive`);
|
|
4009
|
+
continue;
|
|
4010
|
+
}
|
|
4011
|
+
values[k] = v;
|
|
4012
|
+
}
|
|
4013
|
+
if (issues.length === 0 && Object.keys(values).length === 0) {
|
|
4014
|
+
issues.push("no geometry fields (x, y, width, height) present to update");
|
|
4015
|
+
}
|
|
4016
|
+
return { ok: issues.length === 0, values, issues };
|
|
4017
|
+
}
|
|
4018
|
+
async function runModelUpdate(args, opts) {
|
|
4019
|
+
const file = args[0] ?? SNAPSHOT_FILENAME;
|
|
4020
|
+
const filePath = path3.resolve(process.cwd(), file);
|
|
4021
|
+
if (!fs3.existsSync(filePath)) {
|
|
4022
|
+
console.error(`Snapshot file not found: ${file}. Run \`eventmodeler snapshot\` first.`);
|
|
4023
|
+
process.exit(2);
|
|
4024
|
+
}
|
|
4025
|
+
let snapshot;
|
|
4026
|
+
try {
|
|
4027
|
+
snapshot = JSON.parse(fs3.readFileSync(filePath, "utf8"));
|
|
4028
|
+
} catch (err) {
|
|
4029
|
+
console.error(`Invalid JSON in ${file}: ${err.message}`);
|
|
4030
|
+
process.exit(2);
|
|
4031
|
+
}
|
|
4032
|
+
if (!snapshot || typeof snapshot !== "object" || typeof snapshot.scopes !== "object") {
|
|
4033
|
+
console.error(`${file} is not a model snapshot (missing "scopes"). Run \`eventmodeler snapshot\` to generate one.`);
|
|
4034
|
+
process.exit(2);
|
|
4035
|
+
}
|
|
4036
|
+
const modelId = resolveModelId(opts.model);
|
|
4037
|
+
const outcome = await withDoc(modelId, (doc) => applyModelSnapshot(doc, modelId, snapshot));
|
|
4038
|
+
if (!outcome.ok) {
|
|
4039
|
+
console.error(`Model update aborted — ${outcome.issues.length} validation error(s), nothing written:`);
|
|
4040
|
+
for (const issue of outcome.issues)
|
|
4041
|
+
console.error(` - ${issue}`);
|
|
4042
|
+
process.exit(2);
|
|
4043
|
+
}
|
|
4044
|
+
const { created, updated, perScope, unknownScopes } = outcome.result;
|
|
4045
|
+
const breakdown = Object.entries(perScope).map(([scope2, c]) => `${scope2} +${c.created}/~${c.updated}`).join(", ");
|
|
4046
|
+
console.log(`Model updated — ${created} created, ${updated} updated${breakdown ? ` (${breakdown})` : ""}.`);
|
|
4047
|
+
if (unknownScopes.length) {
|
|
4048
|
+
console.error(`Ignored unknown scope(s): ${unknownScopes.join(", ")}`);
|
|
4049
|
+
}
|
|
4050
|
+
}
|
|
3897
4051
|
function registerUpdateCommands(program) {
|
|
3898
|
-
program.command("update <type>
|
|
4052
|
+
program.command("update <type> [args...]").description(`Replace an element's shape with JSON (or 'model' for whole-model upsert). Types: ${knownTypeAliases().join(", ")}`).option("--model <id>", "Model id (defaults to .eventmodeler.json in cwd)").addHelpText("after", UPDATE_HELP).action(async (type, args, opts) => {
|
|
4053
|
+
if (type === "model") {
|
|
4054
|
+
await runModelUpdate(args, opts);
|
|
4055
|
+
return;
|
|
4056
|
+
}
|
|
3899
4057
|
const meta = resolveType(type);
|
|
3900
4058
|
if (!meta) {
|
|
3901
4059
|
console.error(`Unknown type "${type}". Known: ${knownTypeAliases().join(", ")}`);
|
|
@@ -3933,32 +4091,45 @@ function registerUpdateCommands(program) {
|
|
|
3933
4091
|
if (note)
|
|
3934
4092
|
console.error(note);
|
|
3935
4093
|
}
|
|
3936
|
-
const validation = validateEntry(meta.scope, parsed);
|
|
3937
|
-
if (!validation.ok) {
|
|
3938
|
-
console.error(`Validation failed for ${meta.type}:`);
|
|
3939
|
-
for (const issue of validation.issues)
|
|
3940
|
-
console.error(` - ${issue}`);
|
|
3941
|
-
process.exit(2);
|
|
3942
|
-
}
|
|
3943
4094
|
const modelId = resolveModelId(opts.model);
|
|
3944
4095
|
await withDoc(modelId, (doc) => {
|
|
3945
4096
|
const { id, entry } = resolveEntity(doc, meta.scope, meta.idKey, meta.nameKey, name, {
|
|
3946
4097
|
idOverride,
|
|
3947
4098
|
includeLinkedCopies: Boolean(idOverride)
|
|
3948
4099
|
});
|
|
4100
|
+
const map = getScopeMap(doc, meta.scope);
|
|
3949
4101
|
if (entry.isLinkedCopy) {
|
|
3950
|
-
const
|
|
3951
|
-
|
|
4102
|
+
const geom = extractGeometry(parsed);
|
|
4103
|
+
if (!geom.ok) {
|
|
4104
|
+
console.error(`Cannot update ${meta.type} ${id} (linked copy):`);
|
|
4105
|
+
for (const issue of geom.issues)
|
|
4106
|
+
console.error(` - ${issue}`);
|
|
4107
|
+
process.exit(2);
|
|
4108
|
+
}
|
|
4109
|
+
doc.transact(() => {
|
|
4110
|
+
const e = map.get(id);
|
|
4111
|
+
if (!e)
|
|
4112
|
+
return;
|
|
4113
|
+
for (const [k, v] of Object.entries(geom.values))
|
|
4114
|
+
e.set(k, v);
|
|
4115
|
+
});
|
|
4116
|
+
console.log(`Updated ${meta.type} geometry (linked copy — name and fields stay tied to the original).`);
|
|
4117
|
+
return;
|
|
4118
|
+
}
|
|
4119
|
+
const validation = validateEntry(meta.scope, parsed);
|
|
4120
|
+
if (!validation.ok) {
|
|
4121
|
+
console.error(`Validation failed for ${meta.type}:`);
|
|
4122
|
+
for (const issue of validation.issues)
|
|
4123
|
+
console.error(` - ${issue}`);
|
|
3952
4124
|
process.exit(2);
|
|
3953
4125
|
}
|
|
3954
|
-
const map = getScopeMap(doc, meta.scope);
|
|
3955
4126
|
const finalEntry = { ...validation.data, [meta.idKey]: id, modelId };
|
|
3956
4127
|
doc.transact(() => {
|
|
3957
4128
|
map.delete(id);
|
|
3958
4129
|
map.set(id, deepToY(finalEntry));
|
|
3959
4130
|
});
|
|
4131
|
+
console.log(`Updated ${meta.type}.`);
|
|
3960
4132
|
});
|
|
3961
|
-
console.log(`Updated ${meta.type}.`);
|
|
3962
4133
|
});
|
|
3963
4134
|
}
|
|
3964
4135
|
|
|
@@ -4037,6 +4208,37 @@ function removeGroup(doc, meta, originalId) {
|
|
|
4037
4208
|
return { copies, flows };
|
|
4038
4209
|
}
|
|
4039
4210
|
|
|
4211
|
+
// src/commands/snapshot.ts
|
|
4212
|
+
import * as fs4 from "node:fs";
|
|
4213
|
+
import * as path4 from "node:path";
|
|
4214
|
+
var SNAPSHOT_HELP = `
|
|
4215
|
+
Write the entire model to a single JSON file (default ${SNAPSHOT_FILENAME})
|
|
4216
|
+
in the current directory. The file captures every scope — stickies,
|
|
4217
|
+
containers, flows and scenarios — keyed by id, so it is a faithful, editable
|
|
4218
|
+
copy of the whole model.
|
|
4219
|
+
|
|
4220
|
+
Workflow (snapshot → edit → upsert):
|
|
4221
|
+
eventmodeler snapshot # writes ${SNAPSHOT_FILENAME}
|
|
4222
|
+
$EDITOR ${SNAPSHOT_FILENAME} # diff/edit the whole model
|
|
4223
|
+
eventmodeler update model # upserts your edits back
|
|
4224
|
+
|
|
4225
|
+
The file is plain JSON. Each scope is an object of { "<id>": { ...entry } };
|
|
4226
|
+
the id is the map key, so renaming/adding entries means editing those keys.
|
|
4227
|
+
`;
|
|
4228
|
+
function registerSnapshotCommands(program) {
|
|
4229
|
+
program.command("snapshot").description(`Save the entire model as JSON to ${SNAPSHOT_FILENAME} for offline editing`).option("--model <id>", "Model id (defaults to .eventmodeler.json in cwd)").option("--out <file>", `Output file (default ${SNAPSHOT_FILENAME})`).addHelpText("after", SNAPSHOT_HELP).action(async (opts) => {
|
|
4230
|
+
const modelId = resolveModelId(opts.model);
|
|
4231
|
+
const snapshot = await withDoc(modelId, (doc) => serializeModel(doc, modelId));
|
|
4232
|
+
const outPath = path4.resolve(process.cwd(), opts.out ?? SNAPSHOT_FILENAME);
|
|
4233
|
+
fs4.writeFileSync(outPath, `${JSON.stringify(snapshot, null, 2)}
|
|
4234
|
+
`, "utf8");
|
|
4235
|
+
const scopeCount = Object.keys(snapshot.scopes).length;
|
|
4236
|
+
const total = Object.values(snapshot.scopes).reduce((a, s) => a + Object.keys(s).length, 0);
|
|
4237
|
+
const rel = path4.relative(process.cwd(), outPath) || outPath;
|
|
4238
|
+
console.log(`Wrote ${total} element(s) across ${scopeCount} scope(s) to ${rel}.`);
|
|
4239
|
+
});
|
|
4240
|
+
}
|
|
4241
|
+
|
|
4040
4242
|
// src/lib/update-check.ts
|
|
4041
4243
|
var import_semver = __toESM(require_semver2(), 1);
|
|
4042
4244
|
import { spawn } from "node:child_process";
|
|
@@ -4097,28 +4299,28 @@ async function fetchLatestVersion() {
|
|
|
4097
4299
|
}
|
|
4098
4300
|
}
|
|
4099
4301
|
function promptYesNo(question) {
|
|
4100
|
-
return new Promise((
|
|
4302
|
+
return new Promise((resolve4) => {
|
|
4101
4303
|
const rl = readline2.createInterface({
|
|
4102
4304
|
input: process.stdin,
|
|
4103
4305
|
output: process.stdout
|
|
4104
4306
|
});
|
|
4105
4307
|
const timeout = setTimeout(() => {
|
|
4106
4308
|
rl.close();
|
|
4107
|
-
|
|
4309
|
+
resolve4(false);
|
|
4108
4310
|
}, PROMPT_TIMEOUT_MS);
|
|
4109
4311
|
rl.question(question, (answer) => {
|
|
4110
4312
|
clearTimeout(timeout);
|
|
4111
4313
|
rl.close();
|
|
4112
|
-
|
|
4314
|
+
resolve4(["y", "yes"].includes(answer.trim().toLowerCase()));
|
|
4113
4315
|
});
|
|
4114
4316
|
});
|
|
4115
4317
|
}
|
|
4116
4318
|
function runUpdate() {
|
|
4117
4319
|
const [command, args] = updateCommand(detectPackageManager());
|
|
4118
|
-
return new Promise((
|
|
4320
|
+
return new Promise((resolve4) => {
|
|
4119
4321
|
const child = spawn(command, args, { stdio: "inherit" });
|
|
4120
|
-
child.on("close", (code) =>
|
|
4121
|
-
child.on("error", () =>
|
|
4322
|
+
child.on("close", (code) => resolve4(code ?? 1));
|
|
4323
|
+
child.on("error", () => resolve4(1));
|
|
4122
4324
|
});
|
|
4123
4325
|
}
|
|
4124
4326
|
async function maybeUpdateCli(packageName, currentVersion, argv) {
|
|
@@ -4159,8 +4361,8 @@ async function maybeUpdateCli(packageName, currentVersion, argv) {
|
|
|
4159
4361
|
|
|
4160
4362
|
// src/index.ts
|
|
4161
4363
|
var __filename2 = fileURLToPath(import.meta.url);
|
|
4162
|
-
var __dirname2 =
|
|
4163
|
-
var packageJson = JSON.parse(
|
|
4364
|
+
var __dirname2 = path5.dirname(__filename2);
|
|
4365
|
+
var packageJson = JSON.parse(fs5.readFileSync(path5.join(__dirname2, "..", "package.json"), "utf-8"));
|
|
4164
4366
|
var program = new Command().name("eventmodeler").version(packageJson.version).description("CLI tool for interacting with Event Model files").option("--id <uuid>", "Skip name resolution, use UUID directly").option("--no-update-check", "Skip checking npm for a newer CLI version");
|
|
4165
4367
|
setProgram(program);
|
|
4166
4368
|
registerAuthCommands(program);
|
|
@@ -4171,6 +4373,7 @@ registerSummaryCommands(program);
|
|
|
4171
4373
|
registerCreateCommands(program);
|
|
4172
4374
|
registerUpdateCommands(program);
|
|
4173
4375
|
registerRemoveCommands(program);
|
|
4376
|
+
registerSnapshotCommands(program);
|
|
4174
4377
|
await maybeUpdateCli(packageJson.name, packageJson.version, process.argv);
|
|
4175
4378
|
program.parseAsync(process.argv).catch((err) => {
|
|
4176
4379
|
console.error("Error:", err.message);
|