@microteia/console 2.31.1 → 2.33.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.
@@ -22,6 +22,14 @@
22
22
  - fix(command): what was broken, and what it did to whoever hit it.
23
23
  -->
24
24
 
25
+ #### [2.33.0] — 2026-10-28
26
+
27
+ - feat(export-catalog): each screen now carries `keys` — every record key of the collection as `name`, `kind`, `fields`, `scope`, `normalize` and `sparse`, plus `ownerPluginId` when an extension declared it — and each field carries `unique: true` when it is set. The conflict `message` and the deprecated `status` stay out: they do not change which reservation a record gets. Without this, the artifact plan proposed as new, on every run, a key the office declared in the Form Design or a third-party extension installed. Always written, even empty; a snapshot taken by an older Console is read as "keys unknown", and the authoring tools say so.
28
+
29
+ #### [2.32.0] — 2026-10-27
30
+
31
+ - feat(export-catalog): each installed extension now carries `fieldPresets` — every `data.field.preset` it declares, as its key and the parts it has (`mask`, `format`, `validate`), never the templates, formatting ops or validation rules. The authoring tools check a field's `mask`, `rules.format.preset` and `rules.validate.check` against it, so a key the dependency does not declare (`cpf-cnpj` for the Brasil extension's `cpfCnpj`) is refused by `check` instead of installing a field that silently has no mask. Always written, even empty; a snapshot taken by an older Console is read as "presets unknown".
32
+
25
33
  #### [2.31.1] — 2026-10-26
26
34
 
27
35
  - docs(kernel/manifest): the optional prerequisite `portal-identity-signing` now names a third feature that signs with the functions' runtime account: a portal screen declaring `documentLinks` hands guests V4 signed URLs, and without `iam.serviceAccounts.signBlob` the guest cannot open its documents (the denial shows in the log of `getPortalDocumentUrl`). `microteia grant-signing` grants the same permission for all three.
package/README.md CHANGED
@@ -1,4 +1,4 @@
1
- # Microteia Console 2.31.1
1
+ # Microteia Console 2.33.0
2
2
 
3
3
  Installs and updates Microteia in a Firebase project you own. It has no npm dependencies of its own —
4
4
  what it needs are command-line tools that are already on the machine, listed at the bottom.
@@ -55,6 +55,29 @@ export interface CatalogFieldExport {
55
55
  ownerPluginId?: string;
56
56
  aggregation?: Record<string, unknown>;
57
57
  compute?: string;
58
+ /** O atalho de chave de um campo só — o kernel o desaçucara em `field:<key>`. */
59
+ unique?: true;
60
+ }
61
+ /**
62
+ * Uma chave da coleção (`ScreenDefinition.keys`): o que não pode se repetir. É esquema, como o campo:
63
+ * quem modela precisa saber que ela existe antes de propor outra, e o plano a propunha como nova a
64
+ * cada execução quando ela só existia na instalação.
65
+ *
66
+ * A mensagem do conflito NÃO viaja — é apresentação, e não muda qual reserva um registro recebe. O
67
+ * `status` também não: ele é ignorado pelo kernel desde que a adoção passou a ser estado dele.
68
+ */
69
+ export interface CatalogKeyExport {
70
+ name: string;
71
+ kind: string;
72
+ fields: string[];
73
+ scope?: string;
74
+ normalize?: {
75
+ trim?: boolean;
76
+ caseInsensitive?: boolean;
77
+ formatPreset?: string;
78
+ };
79
+ sparse?: boolean;
80
+ ownerPluginId?: string;
58
81
  }
59
82
  export interface CatalogScreenExport {
60
83
  key: string;
@@ -74,6 +97,11 @@ export interface CatalogScreenExport {
74
97
  parentRelation?: string;
75
98
  tagCollection?: string;
76
99
  eventPrefix?: string;
100
+ /**
101
+ * Sempre escrita, mesmo vazia, pela mesma razão de `CatalogExtensionExport.exports`: a lista vazia diz
102
+ * "esta tela não tem chave"; a chave ausente é o silêncio de um Console anterior a este campo.
103
+ */
104
+ keys: CatalogKeyExport[];
77
105
  fields: CatalogFieldExport[];
78
106
  }
79
107
  export interface CatalogListExport {
@@ -132,6 +160,15 @@ export interface CatalogExtensionDocumentTemplate {
132
160
  source: 'record' | 'payload';
133
161
  parameters: CatalogExtensionDocumentParameter[];
134
162
  }
163
+ /**
164
+ * Um preset de campo que a extensão declara (`data.field.preset`), como as PARTES que ele tem — nunca o
165
+ * template, o formato nem a regra. É contra ele que o `mask` de um campo de outra extensão é conferido:
166
+ * `mask: 'cpf-cnpj'` contra o `cpfCnpj` da Brasil instalava, e o campo ficava sem máscara.
167
+ */
168
+ export interface CatalogExtensionFieldPreset {
169
+ key: string;
170
+ slots: ('mask' | 'format' | 'validate')[];
171
+ }
135
172
  export interface CatalogExtensionExport {
136
173
  id: string;
137
174
  name: string;
@@ -146,6 +183,8 @@ export interface CatalogExtensionExport {
146
183
  navGroups: CatalogExtensionNavGroup[];
147
184
  /** Sempre escrita, mesmo vazia, pela mesma razão de `exports`. */
148
185
  documentTemplates: CatalogExtensionDocumentTemplate[];
186
+ /** Sempre escrita, mesmo vazia, pela mesma razão de `exports`. */
187
+ fieldPresets: CatalogExtensionFieldPreset[];
149
188
  }
150
189
  export interface CatalogExport {
151
190
  version: number;
@@ -165,6 +165,33 @@ const documentTemplatesOf = (extension) => {
165
165
  })
166
166
  .sort((a, b) => (a.key < b.key ? -1 : a.key > b.key ? 1 : 0));
167
167
  };
168
+ const FIELD_PRESET_POINT = 'data.field.preset';
169
+ /**
170
+ * Os presets de campo de uma cópia instalada. É a mesma leitura que `declaredFieldPresets` faz no
171
+ * kernel — chave e rótulo obrigatórios, e as partes pelo critério do editor de campo —, repetida porque
172
+ * o Console não alcança `src/`; o teste que atravessa a fronteira confere as duas.
173
+ */
174
+ const fieldPresetsOf = (extension) => {
175
+ const contributes = Array.isArray(extension.manifest?.contributes) ? extension.manifest.contributes : [];
176
+ return contributes
177
+ .map(asRecord)
178
+ .filter((one) => one?.point === FIELD_PRESET_POINT)
179
+ .flatMap((one) => {
180
+ const metadata = asRecord(one.metadata);
181
+ const key = text(metadata?.key);
182
+ if (metadata === undefined || key === undefined || text(metadata.label) === undefined)
183
+ return [];
184
+ return [{
185
+ key,
186
+ slots: [
187
+ ...(metadata.mask ? ['mask'] : []),
188
+ ...(Array.isArray(metadata.format) && metadata.format.length > 0 ? ['format'] : []),
189
+ ...(metadata.validate ? ['validate'] : []),
190
+ ],
191
+ }];
192
+ })
193
+ .sort((a, b) => (a.key < b.key ? -1 : a.key > b.key ? 1 : 0));
194
+ };
168
195
  /**
169
196
  * A mesma dedução que a aplicação faz ao ler a tela (`screenDefinitionMapper.ts`): o `origin` gravado
170
197
  * quando ele existe, senão `plugin` para quem tem dono e `design` para o que o escritório criou. Ela é
@@ -200,6 +227,38 @@ const fieldExport = (raw, index, screenOrigin) => {
200
227
  ...(text(field.ownerPluginId) === undefined ? {} : { ownerPluginId: text(field.ownerPluginId) }),
201
228
  ...(asRecord(field.aggregation) === undefined ? {} : { aggregation: asRecord(field.aggregation) }),
202
229
  ...(compute === undefined ? {} : { compute }),
230
+ ...(field.unique === true ? { unique: true } : {}),
231
+ };
232
+ };
233
+ const normalizeExport = (raw) => {
234
+ const normalize = asRecord(raw);
235
+ if (normalize === undefined)
236
+ return undefined;
237
+ const formatPreset = text(normalize.formatPreset);
238
+ const picked = {
239
+ ...(typeof normalize.trim === 'boolean' ? { trim: normalize.trim } : {}),
240
+ ...(typeof normalize.caseInsensitive === 'boolean' ? { caseInsensitive: normalize.caseInsensitive } : {}),
241
+ ...(formatPreset === undefined ? {} : { formatPreset }),
242
+ };
243
+ return Object.keys(picked).length > 0 ? picked : undefined;
244
+ };
245
+ /** A chave como o kernel a grava, sem o que é apresentação. Sem nome ou sem campo, não é chave. */
246
+ const keyExport = (raw) => {
247
+ const key = asRecord(raw);
248
+ const name = text(key?.name);
249
+ const kind = text(key?.kind);
250
+ const fields = (Array.isArray(key?.fields) ? key.fields : []).filter((one) => typeof one === 'string' && one !== '');
251
+ if (key === undefined || name === undefined || kind === undefined || fields.length === 0)
252
+ return null;
253
+ const normalize = normalizeExport(key.normalize);
254
+ return {
255
+ name,
256
+ kind,
257
+ fields,
258
+ ...(text(key.scope) === undefined ? {} : { scope: text(key.scope) }),
259
+ ...(normalize === undefined ? {} : { normalize }),
260
+ ...(typeof key.sparse === 'boolean' ? { sparse: key.sparse } : {}),
261
+ ...(text(key.ownerPluginId) === undefined ? {} : { ownerPluginId: text(key.ownerPluginId) }),
203
262
  };
204
263
  };
205
264
  const screenExport = (document) => {
@@ -220,6 +279,9 @@ const screenExport = (document) => {
220
279
  ...(text(data.parentRelation) === undefined ? {} : { parentRelation: text(data.parentRelation) }),
221
280
  ...(text(data.tagCollection) === undefined ? {} : { tagCollection: text(data.tagCollection) }),
222
281
  ...(text(data.eventPrefix) === undefined ? {} : { eventPrefix: text(data.eventPrefix) }),
282
+ keys: (Array.isArray(data.keys) ? data.keys : [])
283
+ .map(keyExport)
284
+ .filter((key) => key !== null),
223
285
  fields: fields
224
286
  .map((field, index) => fieldExport(field, index, origin))
225
287
  .filter((field) => field !== null),
@@ -263,6 +325,7 @@ export const buildCatalogExport = (input) => ({
263
325
  exports: exportedArtifacts(one),
264
326
  navGroups: navGroupsOf(one),
265
327
  documentTemplates: documentTemplatesOf(one),
328
+ fieldPresets: fieldPresetsOf(one),
266
329
  }))
267
330
  .sort((a, b) => (a.id < b.id ? -1 : a.id > b.id ? 1 : 0)),
268
331
  screens: input.screens
@@ -87,4 +87,4 @@ export declare const consoleRoot: string;
87
87
  * de código nunca chegou a quem instala. Este bloco fica sendo o que ele já era antes de crescer: a
88
88
  * política de semver da ferramenta, mais o registro do que motivou as versões até a `2.5.0`.
89
89
  */
90
- export declare const CLI_VERSION = "2.31.1";
90
+ export declare const CLI_VERSION = "2.33.0";
@@ -94,4 +94,4 @@ export const consoleRoot = resolve(here, '..', '..');
94
94
  * de código nunca chegou a quem instala. Este bloco fica sendo o que ele já era antes de crescer: a
95
95
  * política de semver da ferramenta, mais o registro do que motivou as versões até a `2.5.0`.
96
96
  */
97
- export const CLI_VERSION = '2.31.1';
97
+ export const CLI_VERSION = '2.33.0';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@microteia/console",
3
- "version": "2.31.1",
3
+ "version": "2.33.0",
4
4
  "description": "Install and update Microteia in a Firebase project you own.",
5
5
  "license": "UNLICENSED",
6
6
  "type": "module",