eventmodeler 0.6.10 → 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 +171 -11
- 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
|
|
@@ -3896,6 +3978,15 @@ semantics); to preserve it, round-trip via \`show screen ... | jq\`.
|
|
|
3896
3978
|
|
|
3897
3979
|
This is a *replace*, not a patch: keys present in the previous entry but
|
|
3898
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.
|
|
3899
3990
|
`;
|
|
3900
3991
|
var GEOMETRY_KEYS = ["x", "y", "width", "height"];
|
|
3901
3992
|
function extractGeometry(parsed) {
|
|
@@ -3924,8 +4015,45 @@ function extractGeometry(parsed) {
|
|
|
3924
4015
|
}
|
|
3925
4016
|
return { ok: issues.length === 0, values, issues };
|
|
3926
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
|
+
}
|
|
3927
4051
|
function registerUpdateCommands(program) {
|
|
3928
|
-
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
|
+
}
|
|
3929
4057
|
const meta = resolveType(type);
|
|
3930
4058
|
if (!meta) {
|
|
3931
4059
|
console.error(`Unknown type "${type}". Known: ${knownTypeAliases().join(", ")}`);
|
|
@@ -4080,6 +4208,37 @@ function removeGroup(doc, meta, originalId) {
|
|
|
4080
4208
|
return { copies, flows };
|
|
4081
4209
|
}
|
|
4082
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
|
+
|
|
4083
4242
|
// src/lib/update-check.ts
|
|
4084
4243
|
var import_semver = __toESM(require_semver2(), 1);
|
|
4085
4244
|
import { spawn } from "node:child_process";
|
|
@@ -4140,28 +4299,28 @@ async function fetchLatestVersion() {
|
|
|
4140
4299
|
}
|
|
4141
4300
|
}
|
|
4142
4301
|
function promptYesNo(question) {
|
|
4143
|
-
return new Promise((
|
|
4302
|
+
return new Promise((resolve4) => {
|
|
4144
4303
|
const rl = readline2.createInterface({
|
|
4145
4304
|
input: process.stdin,
|
|
4146
4305
|
output: process.stdout
|
|
4147
4306
|
});
|
|
4148
4307
|
const timeout = setTimeout(() => {
|
|
4149
4308
|
rl.close();
|
|
4150
|
-
|
|
4309
|
+
resolve4(false);
|
|
4151
4310
|
}, PROMPT_TIMEOUT_MS);
|
|
4152
4311
|
rl.question(question, (answer) => {
|
|
4153
4312
|
clearTimeout(timeout);
|
|
4154
4313
|
rl.close();
|
|
4155
|
-
|
|
4314
|
+
resolve4(["y", "yes"].includes(answer.trim().toLowerCase()));
|
|
4156
4315
|
});
|
|
4157
4316
|
});
|
|
4158
4317
|
}
|
|
4159
4318
|
function runUpdate() {
|
|
4160
4319
|
const [command, args] = updateCommand(detectPackageManager());
|
|
4161
|
-
return new Promise((
|
|
4320
|
+
return new Promise((resolve4) => {
|
|
4162
4321
|
const child = spawn(command, args, { stdio: "inherit" });
|
|
4163
|
-
child.on("close", (code) =>
|
|
4164
|
-
child.on("error", () =>
|
|
4322
|
+
child.on("close", (code) => resolve4(code ?? 1));
|
|
4323
|
+
child.on("error", () => resolve4(1));
|
|
4165
4324
|
});
|
|
4166
4325
|
}
|
|
4167
4326
|
async function maybeUpdateCli(packageName, currentVersion, argv) {
|
|
@@ -4202,8 +4361,8 @@ async function maybeUpdateCli(packageName, currentVersion, argv) {
|
|
|
4202
4361
|
|
|
4203
4362
|
// src/index.ts
|
|
4204
4363
|
var __filename2 = fileURLToPath(import.meta.url);
|
|
4205
|
-
var __dirname2 =
|
|
4206
|
-
var packageJson = JSON.parse(
|
|
4364
|
+
var __dirname2 = path5.dirname(__filename2);
|
|
4365
|
+
var packageJson = JSON.parse(fs5.readFileSync(path5.join(__dirname2, "..", "package.json"), "utf-8"));
|
|
4207
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");
|
|
4208
4367
|
setProgram(program);
|
|
4209
4368
|
registerAuthCommands(program);
|
|
@@ -4214,6 +4373,7 @@ registerSummaryCommands(program);
|
|
|
4214
4373
|
registerCreateCommands(program);
|
|
4215
4374
|
registerUpdateCommands(program);
|
|
4216
4375
|
registerRemoveCommands(program);
|
|
4376
|
+
registerSnapshotCommands(program);
|
|
4217
4377
|
await maybeUpdateCli(packageJson.name, packageJson.version, process.argv);
|
|
4218
4378
|
program.parseAsync(process.argv).catch((err) => {
|
|
4219
4379
|
console.error("Error:", err.message);
|