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.
Files changed (2) hide show
  1. package/dist/index.js +171 -11
  2. 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 fs3 from "node:fs";
1819
- import * as path3 from "node:path";
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> <args...>").description(`Replace an element's shape with JSON. Types: ${knownTypeAliases().join(", ")}`).option("--model <id>", "Model id (defaults to .eventmodeler.json in cwd)").addHelpText("after", UPDATE_HELP).action(async (type, args, opts) => {
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((resolve2) => {
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
- resolve2(false);
4309
+ resolve4(false);
4151
4310
  }, PROMPT_TIMEOUT_MS);
4152
4311
  rl.question(question, (answer) => {
4153
4312
  clearTimeout(timeout);
4154
4313
  rl.close();
4155
- resolve2(["y", "yes"].includes(answer.trim().toLowerCase()));
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((resolve2) => {
4320
+ return new Promise((resolve4) => {
4162
4321
  const child = spawn(command, args, { stdio: "inherit" });
4163
- child.on("close", (code) => resolve2(code ?? 1));
4164
- child.on("error", () => resolve2(1));
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 = path3.dirname(__filename2);
4206
- var packageJson = JSON.parse(fs3.readFileSync(path3.join(__dirname2, "..", "package.json"), "utf-8"));
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);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "eventmodeler",
3
- "version": "0.6.10",
3
+ "version": "0.6.11",
4
4
  "description": "CLI tool for event modeling - explore, design, and generate code from your event models",
5
5
  "type": "module",
6
6
  "repository": {