@mapled/cli 0.5.0 → 0.6.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.
package/README.md CHANGED
@@ -84,7 +84,9 @@ Articles (articles)
84
84
  4 breaking changes, 2 safe changes, judged for a site that reads the content. Update the site where needed, then run `mapled types generate` (it refreshes mapled/schema.json as well).
85
85
  ```
86
86
 
87
- Breaking: a collection, field or option the site may rely on disappears, a type changes, a required field becomes optional, a field becomes sensitive (it stops being delivered), a relation points elsewhere. Safe: additions, renamed labels, help texts, validation rules. `--json` for scripts and agents; `--exit-code` exits 1 when anything changed, for CI.
87
+ Breaking: a collection, field or option the site may rely on disappears, a type changes, a required field becomes optional, a field becomes sensitive (it stops being delivered), a relation points elsewhere. Safe: additions, renamed labels, help texts, validation rules — and a renamed key while its old one is still an alias (`> price renamed to “amount” — “price” still works as an alias`): the site keeps reading it, the rest of the field is compared under the new key, and `types generate` types the old key `@deprecated`. Once the alias is removed in Mapled the old key is a removed field. `--json` for scripts and agents; `--exit-code` exits 1 when anything changed, for CI.
88
+
89
+ The pin keeps formulas of computed fields and previous keys. (A pin written by 0.5.0 lost the formulas, so its hash never matched a project that has computed fields — run `schema pull` once after upgrading.)
88
90
 
89
91
  ## Scan the code into a manifest
90
92
 
@@ -153,7 +155,7 @@ Mapled doctor — Dolphin Landing
153
155
  ✓ MAPLED.md MAPLED.md matches the schema (3f9a1c2b4d5e) and manifest v3
154
156
  ✓ Environment MAPLED_KEY and MAPLED_WEBHOOK_SECRET in .env.local
155
157
  ✓ Secrets in git No env files or Mapled secrets are tracked.
156
- ✓ @mapled/next 0.6.0 (current)
158
+ ✓ @mapled/next 0.7.0 (current)
157
159
  ✓ mapled CLI 0.2.0 (current)
158
160
  ✓ Revalidation route app/api/mapled/revalidate/route.ts
159
161
  ⚠ Preview route app/api/mapled/preview/route.ts is missing — mount createPreviewHandler from "@mapled/next/server" so Preview from Mapled works.
@@ -62,6 +62,8 @@ export type SchemaIndex = Map<string, {
62
62
  displayName: string;
63
63
  type: string;
64
64
  }>;
65
+ /** previous key → current key (§33.5): a binding on it still works, through the alias */
66
+ aliases: Map<string, string>;
65
67
  }>;
66
68
  export declare function indexSchema(schema: Schema): SchemaIndex;
67
69
  /** What a well-formed manifest still gets wrong: bindings the schema
package/dist/manifest.js CHANGED
@@ -242,13 +242,16 @@ export function indexSchema(schema) {
242
242
  const index = new Map();
243
243
  for (const c of schema.collections) {
244
244
  const fields = new Map();
245
+ const aliases = new Map();
245
246
  // a computed field (§14.9) binds as a field of its result type
246
247
  const COMPUTED_AS = { number: "number", text: "short_text", boolean: "boolean", date: "date", datetime: "datetime" };
247
248
  for (const f of c.fields) {
248
249
  const type = f.type === "computed" && f.computed ? (COMPUTED_AS[f.computed.resultType] ?? f.type) : f.type;
249
250
  fields.set(f.key, { displayName: f.displayName, type });
251
+ for (const a of f.aliases ?? [])
252
+ aliases.set(a, f.key);
250
253
  }
251
- index.set(c.key, { displayName: c.displayName, kind: c.kind, fields });
254
+ index.set(c.key, { displayName: c.displayName, kind: c.kind, fields, aliases });
252
255
  }
253
256
  return index;
254
257
  }
@@ -278,7 +281,11 @@ export async function checkManifest(manifest, opts) {
278
281
  warn(`${at}.collection`, `${who}: no collection "${b.collection}" in the schema — Mapled marks it outdated.`);
279
282
  else if (b.field) {
280
283
  const field = collection.fields.get(b.field);
281
- if (!field) {
284
+ const renamedTo = collection.aliases.get(b.field);
285
+ if (!field && renamedTo) {
286
+ warn(`${at}.field`, `${who}: "${b.field}" in "${collection.displayName}" was renamed to "${renamedTo}" — it still works through an alias, and Mapled marks the binding outdated. Read "${renamedTo}", then remove the alias.`);
287
+ }
288
+ else if (!field) {
282
289
  warn(`${at}.field`, `${who}: no field "${b.field}" in "${collection.displayName}" — Mapled marks it outdated.`);
283
290
  }
284
291
  else {
package/dist/pin.d.ts CHANGED
@@ -20,7 +20,7 @@ export declare function readPin(dir: string): Promise<{
20
20
  } | null>;
21
21
  export declare function writePin(dir: string, pin: SchemaPin): Promise<string>;
22
22
  export type Severity = "breaking" | "safe";
23
- export type ChangeOp = "added" | "removed" | "changed";
23
+ export type ChangeOp = "added" | "removed" | "changed" | "renamed";
24
24
  export type SchemaChange = {
25
25
  scope: "collection" | "field" | "subfield";
26
26
  collection: string;
@@ -33,7 +33,10 @@ export type SchemaChange = {
33
33
  };
34
34
  /** What changed between two schemas, judged for a site that reads the
35
35
  content: anything that can make a read fail or a value disappear is
36
- breaking; additions and renames of labels are safe. */
36
+ breaking; additions and renames of labels are safe. A renamed key
37
+ (§33.5) is safe while the old one is still an alias of the field —
38
+ the site keeps reading it — and the rest of the field is compared
39
+ under its new key; once the alias is removed it is a removed field. */
37
40
  export declare function diffSchema(before: SchemaCollection[], after: SchemaCollection[]): SchemaChange[];
38
41
  /** The diff as terminal lines: one heading per collection, one line per
39
42
  change, the severity in the last column. */
package/dist/pin.js CHANGED
@@ -44,6 +44,11 @@ function pinField(f) {
44
44
  out.validation = f.validation;
45
45
  if (f.defaultValue !== undefined && f.defaultValue !== null)
46
46
  out.defaultValue = f.defaultValue;
47
+ // the formula is part of the schema hash: a pin without it would never match the live schema
48
+ if (f.type === "computed" && f.computed)
49
+ out.computed = { expression: f.computed.expression, resultType: f.computed.resultType };
50
+ if (f.aliases && f.aliases.length > 0)
51
+ out.aliases = [...f.aliases];
47
52
  return out;
48
53
  }
49
54
  /** The schema reduced to what the pin keeps — the live answer of
@@ -143,7 +148,10 @@ function sameJson(a, b) {
143
148
  }
144
149
  /** What changed between two schemas, judged for a site that reads the
145
150
  content: anything that can make a read fail or a value disappear is
146
- breaking; additions and renames of labels are safe. */
151
+ breaking; additions and renames of labels are safe. A renamed key
152
+ (§33.5) is safe while the old one is still an alias of the field —
153
+ the site keeps reading it — and the rest of the field is compared
154
+ under its new key; once the alias is removed it is a removed field. */
147
155
  export function diffSchema(before, after) {
148
156
  const changes = [];
149
157
  const names = new Map();
@@ -171,13 +179,21 @@ export function diffSchema(before, after) {
171
179
  col(`name ${q(b.displayName)} → ${q(a.displayName)}`, "safe");
172
180
  const afterFields = new Map(a.fields.map((f) => [f.key, f]));
173
181
  const beforeFields = new Map(b.fields.map((f) => [f.key, f]));
182
+ const renamedTo = new Set();
174
183
  for (const bf of b.fields) {
175
- const af = afterFields.get(bf.key);
184
+ let af = afterFields.get(bf.key);
176
185
  if (!af) {
177
- changes.push({ ...base, scope: "field", field: bf.key, op: "removed", detail: `removed — ${describeType(bf, names)}`, severity: "breaking" });
178
- continue;
186
+ const heir = a.fields.find((f) => !beforeFields.has(f.key) && !renamedTo.has(f.key) && f.aliases?.includes(bf.key));
187
+ if (!heir) {
188
+ changes.push({ ...base, scope: "field", field: bf.key, op: "removed", detail: `removed — ${describeType(bf, names)}`, severity: "breaking" });
189
+ continue;
190
+ }
191
+ renamedTo.add(heir.key);
192
+ changes.push({ ...base, scope: "field", field: bf.key, op: "renamed", detail: `renamed to ${q(heir.key)} — ${q(bf.key)} still works as an alias`, severity: "safe" });
193
+ af = heir;
179
194
  }
180
- const push = (op, detail, severity) => changes.push({ ...base, scope: "field", field: bf.key, op, detail, severity });
195
+ const under = af.key;
196
+ const push = (op, detail, severity) => changes.push({ ...base, scope: "field", field: under, op, detail, severity });
181
197
  diffCommon(bf, af, names, push);
182
198
  if (bf.type === "relation" && af.type === "relation" && bf.relation && af.relation) {
183
199
  if (bf.relation.target !== af.relation.target) {
@@ -209,7 +225,7 @@ export function diffSchema(before, after) {
209
225
  const beforeSubs = new Map(bf.group.fields.map((s) => [s.key, s]));
210
226
  for (const bs of bf.group.fields) {
211
227
  const as = afterSubs.get(bs.key);
212
- const sub = `${bf.key}.${bs.key}`;
228
+ const sub = `${under}.${bs.key}`;
213
229
  if (!as) {
214
230
  changes.push({ ...base, scope: "subfield", field: sub, op: "removed", detail: `removed — ${describeType(bs, names)}`, severity: "breaking" });
215
231
  continue;
@@ -222,7 +238,7 @@ export function diffSchema(before, after) {
222
238
  changes.push({
223
239
  ...base,
224
240
  scope: "subfield",
225
- field: `${bf.key}.${as.key}`,
241
+ field: `${under}.${as.key}`,
226
242
  op: "added",
227
243
  detail: `added — ${describeType(as, names)}, ${as.required ? "required" : "optional"}`,
228
244
  severity: "safe",
@@ -235,7 +251,7 @@ export function diffSchema(before, after) {
235
251
  push("changed", "default value changed", "safe");
236
252
  }
237
253
  for (const af of a.fields) {
238
- if (beforeFields.has(af.key))
254
+ if (beforeFields.has(af.key) || renamedTo.has(af.key))
239
255
  continue;
240
256
  changes.push({
241
257
  ...base,
@@ -261,7 +277,7 @@ export function diffSchema(before, after) {
261
277
  }
262
278
  return changes;
263
279
  }
264
- const GLYPH = { added: "+", removed: "-", changed: "~" };
280
+ const GLYPH = { added: "+", removed: "-", changed: "~", renamed: ">" };
265
281
  /** The diff as terminal lines: one heading per collection, one line per
266
282
  change, the severity in the last column. */
267
283
  export function formatChanges(changes, paint = (_, t) => t) {
package/dist/schema.d.ts CHANGED
@@ -37,6 +37,10 @@ export type SchemaField = {
37
37
  expression: string;
38
38
  resultType: string;
39
39
  } | null;
40
+ /** Previous keys (§33.5): the field was renamed, and Mapled serves and
41
+ accepts it under these too until they are removed. Not part of the
42
+ schema hash — a rename changes the key, which is. */
43
+ aliases?: string[];
40
44
  };
41
45
  export type SchemaCollection = {
42
46
  key: string;
package/dist/types.js CHANGED
@@ -115,14 +115,26 @@ function fieldLine(key, displayName, required, rendered) {
115
115
  function renderSubField(sub) {
116
116
  return fieldLine(sub.key, sub.displayName, sub.required, scalarType(sub.type, sub.options));
117
117
  }
118
+ /** The field's line, followed by one per previous key (§33.5): a
119
+ renamed field is still delivered under them, typed as the field, and
120
+ the editor strikes them through until the site stops reading them. */
118
121
  function renderField(field, owner, names, extras) {
122
+ const { required, rendered } = renderedField(field, owner, names, extras);
123
+ const lines = [fieldLine(field.key, field.displayName, required, rendered)];
124
+ for (const alias of field.aliases ?? []) {
125
+ lines.push(` /** @deprecated Renamed to "${comment(field.key)}" — still delivered under this key until the alias is removed in Mapled. */\n` +
126
+ ` ${prop(alias)}?: ${rendered.type} | null;`);
127
+ }
128
+ return lines.join("\n");
129
+ }
130
+ function renderedField(field, owner, names, extras) {
119
131
  if (field.type === "relation" && field.relation) {
120
132
  const target = names.get(field.relation.target) ?? field.relation.target;
121
133
  const many = field.relation.cardinality === "many";
122
- return fieldLine(field.key, field.displayName, field.required, {
123
- type: many ? "MapledRecordId[]" : "MapledRecordId",
124
- note: `relation to ${comment(target)}${many ? " (many)" : ""}`,
125
- });
134
+ return {
135
+ required: field.required,
136
+ rendered: { type: many ? "MapledRecordId[]" : "MapledRecordId", note: `relation to ${comment(target)}${many ? " (many)" : ""}` },
137
+ };
126
138
  }
127
139
  if (field.type === "group" && field.group) {
128
140
  const itemName = `${owner}${pascal(wordsOf(field.key))}${field.group.repeatable ? "Item" : ""}`;
@@ -136,15 +148,12 @@ function renderField(field, owner, names, extras) {
136
148
  const note = field.group.repeatable
137
149
  ? `group${field.group.maxItems ? `, up to ${field.group.maxItems} items` : ""}`
138
150
  : "group";
139
- return fieldLine(field.key, field.displayName, field.required, {
140
- type: field.group.repeatable ? `${itemName}[]` : itemName,
141
- note,
142
- });
151
+ return { required: field.required, rendered: { type: field.group.repeatable ? `${itemName}[]` : itemName, note } };
143
152
  }
144
153
  if (field.type === "computed" && field.computed) {
145
- return fieldLine(field.key, field.displayName, false, computedType(field.computed.resultType));
154
+ return { required: false, rendered: computedType(field.computed.resultType) };
146
155
  }
147
- return fieldLine(field.key, field.displayName, field.required, scalarType(field.type, field.options));
156
+ return { required: field.required, rendered: scalarType(field.type, field.options) };
148
157
  }
149
158
  /** Unique type names: the singular PascalCase of the key; the raw
150
159
  PascalCase when two keys meet there; a numeric suffix as a last resort. */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mapled/cli",
3
- "version": "0.5.0",
3
+ "version": "0.6.0",
4
4
  "description": "Mapled CLI — sign in, link a project, generate TypeScript types for its content and check the site's integration.",
5
5
  "license": "MIT",
6
6
  "homepage": "https://mapled.io",