@bobbykim/manguito-cms-cli 0.6.2 → 0.7.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.
Files changed (2) hide show
  1. package/dist/index.js +127 -66
  2. package/package.json +3 -3
package/dist/index.js CHANGED
@@ -43958,6 +43958,16 @@ function loadEnvFile(filePath) {
43958
43958
  config2({ path: filePath });
43959
43959
  }
43960
43960
 
43961
+ // src/utils/project-version-model.ts
43962
+ init_esm_shims();
43963
+ function loadProjectVersionModel(schema, registry2) {
43964
+ const snapshots = loadVersionSnapshots(schema, registry2);
43965
+ if (!snapshots.ok) return snapshots;
43966
+ const model = computeVersionModel({ current: registry2, snapshots: snapshots.value });
43967
+ if (!model.ok) return model;
43968
+ return { ok: true, value: { snapshots: snapshots.value, model: model.value } };
43969
+ }
43970
+
43961
43971
  // src/commands/build.ts
43962
43972
  function registerBuild(program2) {
43963
43973
  program2.command("build").description("Run codegen and compile project to dist/").option("--env <path>", "path to .env file to load").action(async (options) => {
@@ -44018,6 +44028,11 @@ async function runBuild(options, deps) {
44018
44028
  parsedRoutesDef,
44019
44029
  parsedRoles
44020
44030
  );
44031
+ const versions = loadProjectVersionModel(resolveSchemaConfig(cwd, config3), registry2);
44032
+ if (!versions.ok) {
44033
+ printValidationErrors(versions.errors, "Version model errors", "manguito build");
44034
+ process.exit(1);
44035
+ }
44021
44036
  printSuccess("Config loaded");
44022
44037
  const contentCount = parsedSchemas.filter((s) => s.schema_type === "content-type").length;
44023
44038
  const paragraphCount = parsedSchemas.filter((s) => s.schema_type === "paragraph-type").length;
@@ -44033,19 +44048,8 @@ async function runBuild(options, deps) {
44033
44048
  await generateSchemaRegistry(registry2, generatedDir);
44034
44049
  await generateRoutes2(registry2, generatedDir);
44035
44050
  await generateForms(registry2, join9(generatedDir, "forms"));
44036
- const schema = resolveSchemaConfig(cwd, config3);
44037
- const snapshots = loadVersionSnapshots(schema, registry2);
44038
- if (!snapshots.ok) {
44039
- printValidationErrors(snapshots.errors, "Snapshot errors", "manguito build");
44040
- process.exit(1);
44041
- }
44042
- const versionModel = computeVersionModel({ current: registry2, snapshots: snapshots.value });
44043
- if (!versionModel.ok) {
44044
- printValidationErrors(versionModel.errors, "Version model errors", "manguito build");
44045
- process.exit(1);
44046
- }
44047
- await generateVersionModel(versionModel.value, generatedDir);
44048
- printSuccess(`Version model baked (live: ${versionModel.value.live.join(", ")})`);
44051
+ await generateVersionModel(versions.value.model, generatedDir);
44052
+ printSuccess(`Version model baked (live: ${versions.value.model.live.join(", ")})`);
44049
44053
  const programmaticDir = resolve4(cwd, config3.programmatic.dir);
44050
44054
  const resolverFiles = existsSync3(programmaticDir) ? walkFiles(programmaticDir).filter((f) => /\.(ts|mjs|js)$/.test(f)) : [];
44051
44055
  await generateProgrammaticRegistry(resolverFiles, generatedDir);
@@ -50741,18 +50745,12 @@ async function runDev(options, deps) {
50741
50745
  await generateRoutes2(registry2, manguitoDir);
50742
50746
  await generateForms(registry2, join13(manguitoDir, "forms"));
50743
50747
  await generateNav(registry2, manguitoDir);
50744
- const schemaConfig = resolveSchemaConfig(cwd, config3);
50745
- const snapshotsResult = loadVersionSnapshots(schemaConfig, registry2);
50746
- if (!snapshotsResult.ok) {
50747
- printValidationErrors(snapshotsResult.errors, "Version snapshot errors", "manguito version:diff");
50748
- process.exit(1);
50749
- }
50750
- const versionModelResult = computeVersionModel({ current: registry2, snapshots: snapshotsResult.value });
50751
- if (!versionModelResult.ok) {
50752
- printValidationErrors(versionModelResult.errors, "Version model errors", "manguito version:diff");
50748
+ const versionsResult = loadProjectVersionModel(resolveSchemaConfig(cwd, config3), registry2);
50749
+ if (!versionsResult.ok) {
50750
+ printValidationErrors(versionsResult.errors, "Version model errors", "manguito validate");
50753
50751
  process.exit(1);
50754
50752
  }
50755
- const versionModel = reduceVersionModel(versionModelResult.value);
50753
+ const versionModel = reduceVersionModel(versionsResult.value.model);
50756
50754
  const resolverMap = await loadProgrammaticResolvers(cwd, config3.programmatic.dir);
50757
50755
  const adapter = createCmsApp({
50758
50756
  name: config3.name,
@@ -50891,20 +50889,13 @@ async function onSchemaFileChange(args) {
50891
50889
  await generateRoutes2(registry2, manguitoDir);
50892
50890
  await generateForms(registry2, join13(manguitoDir, "forms"));
50893
50891
  await generateNav(registry2, manguitoDir);
50894
- const schemaConfig = resolveSchemaConfig(cwd, config3);
50895
- const snapshotsResult = loadVersionSnapshots(schemaConfig, registry2);
50896
- if (!snapshotsResult.ok) {
50897
- printValidationErrors(snapshotsResult.errors, "Version snapshot errors", "manguito version:diff");
50892
+ const versionsResult = loadProjectVersionModel(resolveSchemaConfig(cwd, config3), registry2);
50893
+ if (!versionsResult.ok) {
50894
+ printValidationErrors(versionsResult.errors, "Version model errors", "manguito validate");
50898
50895
  process.stderr.write("\u26A0 Changes not applied.\n");
50899
50896
  return;
50900
50897
  }
50901
- const versionModelResult = computeVersionModel({ current: registry2, snapshots: snapshotsResult.value });
50902
- if (!versionModelResult.ok) {
50903
- printValidationErrors(versionModelResult.errors, "Version model errors", "manguito version:diff");
50904
- process.stderr.write("\u26A0 Changes not applied.\n");
50905
- return;
50906
- }
50907
- const versionModel = reduceVersionModel(versionModelResult.value);
50898
+ const versionModel = reduceVersionModel(versionsResult.value.model);
50908
50899
  try {
50909
50900
  await runDevMigration(drizzleConfigPath);
50910
50901
  } catch (err) {
@@ -51362,10 +51353,16 @@ async function runValidate(options, deps) {
51362
51353
  parsedRoutesDef = routesParseResult.value;
51363
51354
  }
51364
51355
  }
51356
+ let liveVersions = null;
51365
51357
  if (allErrors.length === 0 && parsedRoles !== null && parsedRoutesDef !== null) {
51366
51358
  const registry2 = buildSchemaRegistry(parsedSchemas, parsedRoutesDef, parsedRoles);
51367
51359
  const crossRefErrors = validateCrossReferences(registry2, config3.api.media?.max_file_size);
51368
51360
  allErrors.push(...crossRefErrors);
51361
+ if (crossRefErrors.length === 0) {
51362
+ const versions = loadProjectVersionModel(resolveSchemaConfig(cwd, config3), registry2);
51363
+ if (versions.ok) liveVersions = versions.value.model.live;
51364
+ else allErrors.push(...versions.errors);
51365
+ }
51369
51366
  }
51370
51367
  if (allErrors.length > 0) {
51371
51368
  printValidationErrors(allErrors, "Validation errors", "manguito validate");
@@ -51380,6 +51377,7 @@ async function runValidate(options, deps) {
51380
51377
  );
51381
51378
  printSuccess("roles.json valid");
51382
51379
  printSuccess("routes.json valid");
51380
+ if (liveVersions !== null) printSuccess(`Versions valid (live: ${liveVersions.join(", ")})`);
51383
51381
  process.stdout.write("\nNo errors found.\n");
51384
51382
  }
51385
51383
 
@@ -51693,7 +51691,7 @@ function formatField(change) {
51693
51691
  }
51694
51692
  }
51695
51693
  function formatSchemaChange(change) {
51696
- const header = change.from === null ? `Working schema \u2014 nothing has been cut yet, so ${change.to} would be the first version` : `Working schema vs ${change.from} (highest snapshot) \u2014 would become ${change.to}`;
51694
+ const header = change.from === null ? `Working schema \u2014 nothing has been created yet, so ${change.to} would be the first version` : `Working schema vs ${change.from} (highest snapshot) \u2014 would become ${change.to}`;
51697
51695
  if (change.identical) {
51698
51696
  return `${header}
51699
51697
 
@@ -51707,6 +51705,38 @@ No column added, renamed, tombstoned or restored.`;
51707
51705
  });
51708
51706
  return [header, "", ...blocks].join("\n");
51709
51707
  }
51708
+ function driftSummary(change) {
51709
+ const counts = { renamed: 0, tombstoned: 0, added: 0, restored: 0 };
51710
+ for (const type of change.types) {
51711
+ for (const field of type.fields) counts[field.kind]++;
51712
+ }
51713
+ const parts = [];
51714
+ if (counts.renamed > 0) parts.push(`${counts.renamed} renamed`);
51715
+ if (counts.tombstoned > 0) parts.push(`${counts.tombstoned} removed`);
51716
+ if (counts.added > 0) parts.push(`${counts.added} added`);
51717
+ if (counts.restored > 0) parts.push(`${counts.restored} restored`);
51718
+ if (parts.length > 0) return `${parts.join(", ")} since`;
51719
+ if (change.identical) return "identical to current";
51720
+ const newTypes = change.types.filter((t) => t.status === "added").length;
51721
+ return `${newTypes} new type${newTypes === 1 ? "" : "s"} since`;
51722
+ }
51723
+ function formatVersionList(input2) {
51724
+ const { prefix, current, older } = input2;
51725
+ if (older.length === 0) {
51726
+ return `No versions created yet. Your working schema is ${current}, served at ${prefix}/${current} and ${prefix}.
51727
+ Run \`manguito version:create\` before making a breaking change.`;
51728
+ }
51729
+ const rows = [
51730
+ ...older.map((o) => [o.version, `${prefix}/${o.version}`, driftSummary(o.change)]),
51731
+ [current, `${prefix}/${current}`, `current \u2014 also ${prefix}`]
51732
+ ];
51733
+ const nameWidth = Math.max(...rows.map((r) => r[0].length));
51734
+ const pathWidth = Math.max(...rows.map((r) => r[1].length));
51735
+ return [
51736
+ `Live versions (${rows.length}):`,
51737
+ ...rows.map(([name, url2, note]) => ` ${name.padEnd(nameWidth)} ${url2.padEnd(pathWidth)} ${note}`)
51738
+ ].join("\n");
51739
+ }
51710
51740
 
51711
51741
  // src/commands/version-fs.ts
51712
51742
  init_esm_shims();
@@ -51764,17 +51794,19 @@ async function loadVersionContext(options, deps, command) {
51764
51794
  const config3 = await resolveConfig(deps.cwd);
51765
51795
  const schema = resolveSchemaConfig(deps.cwd, config3);
51766
51796
  const registry2 = loadWorkingRegistry(deps.cwd, config3, command);
51767
- const snapshots = loadVersionSnapshots(schema, registry2);
51768
- if (!snapshots.ok) {
51769
- printValidationErrors(snapshots.errors, "Snapshot errors", command);
51797
+ const versions = loadProjectVersionModel(schema, registry2);
51798
+ if (!versions.ok) {
51799
+ printValidationErrors(versions.errors, "Version model errors", command);
51770
51800
  process.exit(1);
51771
51801
  }
51772
- const model = computeVersionModel({ current: registry2, snapshots: snapshots.value });
51773
- if (!model.ok) {
51774
- printValidationErrors(model.errors, "Version model errors", command);
51775
- process.exit(1);
51776
- }
51777
- return { schema, registry: registry2, snapshots: snapshots.value, model: model.value };
51802
+ return {
51803
+ schema,
51804
+ registry: registry2,
51805
+ snapshots: versions.value.snapshots,
51806
+ model: versions.value.model,
51807
+ // Same default `dev` uses: the prefix the served routes actually carry.
51808
+ apiPrefix: config3.api.prefix ?? "/api"
51809
+ };
51778
51810
  }
51779
51811
  function orphanedTombstoneErrors(input2) {
51780
51812
  const remaining = input2.snapshots.filter((s) => s.version !== input2.retiring);
@@ -51782,14 +51814,22 @@ function orphanedTombstoneErrors(input2) {
51782
51814
  if (model.ok) return [];
51783
51815
  return model.errors.filter((e) => e.code === "ORPHANED_TOMBSTONE");
51784
51816
  }
51817
+ var VERSION_CUT_DEPRECATION = "`version:cut` is deprecated \u2014 use `version:create`. It will be removed in a future release.\n";
51785
51818
  function registerVersion(program2) {
51786
- program2.command("version:diff").description("Show what cutting a new version would freeze").option("--env <path>", "path to .env file to load").action(async (options) => {
51819
+ program2.command("version:diff").description("Show what creating a new version would freeze").option("--env <path>", "path to .env file to load").action(async (options) => {
51787
51820
  await runVersionDiff(options, { cwd: process.cwd() });
51788
51821
  });
51789
- program2.command("version:cut").description("Freeze the working schema as a new version").option("--env <path>", "path to .env file to load").option("--yes", "skip the confirmation prompt").action(async (options) => {
51790
- await runVersionCut(options, { cwd: process.cwd(), prompt: createPromptAdapter() });
51822
+ program2.command("version:create").description("Create a version: freeze the working schema so it keeps being served unchanged").option("--env <path>", "path to .env file to load").option("--yes", "skip the confirmation prompt").action(async (options) => {
51823
+ await runVersionCreate(options, { cwd: process.cwd(), prompt: createPromptAdapter() });
51791
51824
  });
51792
- program2.command("version:retire <version>").description("Stop serving a cut version and delete its snapshot").option("--env <path>", "path to .env file to load").option("--yes", "skip the confirmation prompt").action(async (version3, options) => {
51825
+ program2.command("version:list").description("List the versions being served and how far each is behind the working schema").option("--env <path>", "path to .env file to load").action(async (options) => {
51826
+ await runVersionList(options, { cwd: process.cwd() });
51827
+ });
51828
+ program2.command("version:cut", { hidden: true }).option("--env <path>", "path to .env file to load").option("--yes", "skip the confirmation prompt").action(async (options) => {
51829
+ process.stderr.write(VERSION_CUT_DEPRECATION);
51830
+ await runVersionCreate(options, { cwd: process.cwd(), prompt: createPromptAdapter() });
51831
+ });
51832
+ program2.command("version:retire <version>").description("Stop serving a created version and delete its snapshot").option("--env <path>", "path to .env file to load").option("--yes", "skip the confirmation prompt").action(async (version3, options) => {
51793
51833
  await runVersionRetire(version3, options, { cwd: process.cwd(), prompt: createPromptAdapter() });
51794
51834
  });
51795
51835
  }
@@ -51805,15 +51845,15 @@ async function runVersionDiff(options, deps) {
51805
51845
  if (change.identical) {
51806
51846
  process.stdout.write(
51807
51847
  `
51808
- Nothing to cut \u2014 no column was added, renamed, tombstoned or restored, so ${ctx.model.current} would expose the same contract.
51848
+ Nothing to create \u2014 no column was added, renamed, tombstoned or restored, so ${ctx.model.current} would expose the same contract.
51809
51849
  `
51810
51850
  );
51811
51851
  return;
51812
51852
  }
51813
- printSuccess(`Cutting now would create ${ctx.schema.base_path}/versions/${ctx.model.current}/`);
51853
+ printSuccess(`Creating a version now would write ${ctx.schema.base_path}/versions/${ctx.model.current}/`);
51814
51854
  }
51815
- async function runVersionCut(options, deps) {
51816
- const ctx = await loadVersionContext(options, deps, "manguito version:cut");
51855
+ async function runVersionCreate(options, deps) {
51856
+ const ctx = await loadVersionContext(options, deps, "manguito version:create");
51817
51857
  const from = highestSnapshot(ctx.snapshots);
51818
51858
  const version3 = ctx.model.current;
51819
51859
  const change = describeSchemaChange({
@@ -51822,8 +51862,8 @@ async function runVersionCut(options, deps) {
51822
51862
  });
51823
51863
  if (change.identical) {
51824
51864
  printGuidedError(
51825
- `No column was added, renamed, tombstoned or restored since ${from?.version ?? "the last cut"} \u2014 cutting ${version3} would freeze an identical contract.`,
51826
- "Changes to paragraph, programmatic, many-to-many and enum definitions are not versioned and need no new version. A live version commits you to retaining every column it exposes, so cut only when a column actually changed \u2014 or run `manguito version:retire <version>` if you meant to shrink the live set."
51865
+ `No column was added, renamed, tombstoned or restored since ${from?.version ?? "the working schema began"} \u2014 creating ${version3} would freeze an identical contract.`,
51866
+ "Changes to paragraph, programmatic, many-to-many and enum definitions are not versioned and need no new version. A live version commits you to retaining every column it exposes, so create one only when a column actually changed \u2014 or run `manguito version:retire <version>` if you meant to shrink the live set."
51827
51867
  );
51828
51868
  process.exit(1);
51829
51869
  }
@@ -51832,22 +51872,32 @@ async function runVersionCut(options, deps) {
51832
51872
  if (fs6.existsSync(target)) {
51833
51873
  printGuidedError(
51834
51874
  `${target} already exists.`,
51835
- "A snapshot directory is never overwritten. Remove or rename it, then run version:cut again."
51875
+ "A snapshot directory is never overwritten. Remove or rename it, then run version:create again."
51836
51876
  );
51837
51877
  process.exit(1);
51838
51878
  }
51839
51879
  process.stdout.write(`${formatSchemaChange(change)}
51840
51880
 
51841
51881
  `);
51842
- const live = [...ctx.model.live.filter((v2) => v2 !== version3), version3].join(" ");
51882
+ const next = `v${Number.parseInt(version3.slice(1), 10) + 1}`;
51883
+ const liveAfter = [...ctx.model.live.filter((v2) => v2 !== version3), version3, next];
51884
+ if (from === null) {
51885
+ const prefix = ctx.apiPrefix;
51886
+ process.stdout.write(
51887
+ `This creates ${version3} from your working schema. ${prefix}/${version3} keeps serving it
51888
+ unchanged; your working schema becomes ${next}, served at ${prefix}/${next} and ${prefix}.
51889
+
51890
+ `
51891
+ );
51892
+ }
51843
51893
  process.stdout.write(
51844
- `After cutting, ${live} are live. Every column those versions expose must stay in the
51845
- schema \u2014 as a live field or a tombstone \u2014 until you retire them.
51894
+ `After creating ${version3}, these versions are live: ${liveAfter.join(" ")}. Every column they
51895
+ expose must stay in the schema \u2014 as a live field or a tombstone \u2014 until you retire them.
51846
51896
 
51847
51897
  `
51848
51898
  );
51849
51899
  if (options.yes !== true) {
51850
- const ok = await deps.prompt.confirm(`Freeze the working schema as ${version3}?`);
51900
+ const ok = await deps.prompt.confirm(`Create ${version3} from the working schema?`);
51851
51901
  if (!ok) {
51852
51902
  process.stdout.write("Cancelled. Nothing was written.\n");
51853
51903
  return;
@@ -51862,8 +51912,19 @@ schema \u2014 as a live field or a tombstone \u2014 until you retire them.
51862
51912
  );
51863
51913
  process.exit(1);
51864
51914
  }
51865
- printSuccess(`Froze the working schema as ${version3} at ${target}`);
51866
- process.stdout.write(`Live: ${live}. Working schema is now v${Number.parseInt(version3.slice(1), 10) + 1}.
51915
+ printSuccess(`Created ${version3} at ${target}`);
51916
+ process.stdout.write(`Live: ${liveAfter.join(" ")}. Working schema is now ${next}.
51917
+ `);
51918
+ }
51919
+ async function runVersionList(options, deps) {
51920
+ const ctx = await loadVersionContext(options, deps, "manguito version:list");
51921
+ const current = ctx.model.current;
51922
+ const versionNumber3 = (v2) => Number.parseInt(v2.slice(1), 10);
51923
+ const older = [...ctx.snapshots].sort((a2, b2) => versionNumber3(a2.version) - versionNumber3(b2.version)).map((s) => ({
51924
+ version: s.version,
51925
+ change: describeSchemaChange({ from: s, to: { version: current, registry: ctx.registry } })
51926
+ }));
51927
+ process.stdout.write(`${formatVersionList({ prefix: ctx.apiPrefix, current, older })}
51867
51928
  `);
51868
51929
  }
51869
51930
  async function runVersionRetire(version3, options, deps) {
@@ -51875,8 +51936,8 @@ async function runVersionRetire(version3, options, deps) {
51875
51936
  if (!ctx.snapshots.some((s) => s.version === version3)) {
51876
51937
  const existing = ctx.snapshots.map((s) => s.version).join(", ");
51877
51938
  printGuidedError(
51878
- `${version3} is not a cut version.`,
51879
- existing === "" ? "No versions have been cut yet \u2014 run `manguito version:cut` first." : `Cut versions: ${existing}.`
51939
+ `${version3} is not a created version.`,
51940
+ existing === "" ? "No versions have been created yet \u2014 run `manguito version:create` first." : `Created versions: ${existing}.`
51880
51941
  );
51881
51942
  process.exit(1);
51882
51943
  }
@@ -51884,8 +51945,8 @@ async function runVersionRetire(version3, options, deps) {
51884
51945
  if (highest !== null && highest.version === version3) {
51885
51946
  const target = path6.join(ctx.schema.base_path, "versions", version3);
51886
51947
  printGuidedError(
51887
- `${version3} is the newest cut version and cannot be retired.`,
51888
- `The working schema is ${ctx.model.current} because ${version3} is the highest snapshot \u2014 retiring it would renumber the working schema back onto ${version3}, and anyone pinned to ${version3} would get a different contract. Cutting a version with a real contract change makes ${version3} retirable. If ${version3} was cut in error, delete ${target} by hand \u2014 safe only while nothing yet consumes ${version3}, since deleting the newest snapshot renumbers the working schema back onto it.`
51948
+ `${version3} is the newest created version and cannot be retired.`,
51949
+ `The working schema is ${ctx.model.current} because ${version3} is the highest snapshot \u2014 retiring it would renumber the working schema back onto ${version3}, and anyone pinned to ${version3} would get a different contract. Creating a newer version with a real contract change makes ${version3} retirable. If ${version3} was created in error, delete ${target} by hand \u2014 safe only while nothing yet consumes ${version3}, since deleting the newest snapshot renumbers the working schema back onto it.`
51889
51950
  );
51890
51951
  process.exit(1);
51891
51952
  }
@@ -51911,7 +51972,7 @@ longer exposed by any live version. You must then delete these fields:
51911
51972
  `);
51912
51973
  }
51913
51974
  process.stdout.write(
51914
- "Until you do, `manguito validate` will report ORPHANED_TOMBSTONE. Deleting them\nshrinks the union and lets the next migration DROP those columns.\n\n"
51975
+ "Until you do, `manguito validate` and `manguito build` will fail. Deleting them\nshrinks the union and lets the next migration DROP those columns.\n\n"
51915
51976
  );
51916
51977
  }
51917
51978
  if (options.yes !== true) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bobbykim/manguito-cms-cli",
3
- "version": "0.6.2",
3
+ "version": "0.7.0",
4
4
  "license": "MIT",
5
5
  "author": "Bobby Kim <bobby.sihun.kim@gmail.com>",
6
6
  "homepage": "https://github.com/bobbykim89/manguito-cms",
@@ -30,9 +30,9 @@
30
30
  "typescript": "^6.0.3",
31
31
  "vite": "^8.1.3",
32
32
  "@bobbykim/manguito-cms-admin": "0.4.3",
33
- "@bobbykim/manguito-cms-core": "0.6.0",
34
33
  "@bobbykim/manguito-cms-api": "0.7.0",
35
- "@bobbykim/manguito-cms-db": "0.1.6"
34
+ "@bobbykim/manguito-cms-db": "0.1.6",
35
+ "@bobbykim/manguito-cms-core": "0.6.0"
36
36
  },
37
37
  "devDependencies": {
38
38
  "@types/node": "^25.9.5",