@microteia/console 2.8.0 → 2.18.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 (37) hide show
  1. package/CONSOLE-VERSIONS.md +97 -0
  2. package/README.md +1 -1
  3. package/lib/console/appliedFunctions.d.ts +79 -0
  4. package/lib/console/appliedFunctions.js +88 -0
  5. package/lib/console/backendResidue.d.ts +55 -0
  6. package/lib/console/backendResidue.js +53 -0
  7. package/lib/console/customFunctions.d.ts +51 -1
  8. package/lib/console/customFunctions.js +94 -1
  9. package/lib/console/deploy.js +32 -7
  10. package/lib/console/environment.d.ts +22 -6
  11. package/lib/console/environment.js +11 -0
  12. package/lib/console/exportCatalog.d.ts +124 -0
  13. package/lib/console/exportCatalog.js +264 -0
  14. package/lib/console/firestoreRest.d.ts +27 -0
  15. package/lib/console/firestoreRest.js +23 -0
  16. package/lib/console/functionGate.d.ts +198 -0
  17. package/lib/console/functionGate.js +400 -0
  18. package/lib/console/init.js +1 -0
  19. package/lib/console/installedExtensions.d.ts +37 -0
  20. package/lib/console/installedExtensions.js +37 -0
  21. package/lib/console/loginExtensions.d.ts +29 -0
  22. package/lib/console/loginExtensions.js +101 -0
  23. package/lib/console/provision.d.ts +26 -13
  24. package/lib/console/provision.js +312 -73
  25. package/lib/index.js +8 -0
  26. package/lib/kernel/backendShape.d.ts +27 -0
  27. package/lib/kernel/backendShape.js +38 -0
  28. package/lib/kernel/manifest.d.ts +16 -0
  29. package/lib/kernel/manifest.js +16 -0
  30. package/lib/shared/paths.d.ts +1 -1
  31. package/lib/shared/paths.js +1 -1
  32. package/lib/vendor/exampleSources.js +7 -3
  33. package/lib/vendor/installationDocs.js +18 -0
  34. package/lib/vendor/pack.js +3 -1
  35. package/lib/vendor/packageTemplates.d.ts +12 -1
  36. package/lib/vendor/packageTemplates.js +42 -2
  37. package/package.json +1 -1
@@ -0,0 +1,37 @@
1
+ import { decodeFields, documentId, listDocuments } from './firestoreRest.js';
2
+ export const listTenants = async (projectId) => {
3
+ const documents = await listDocuments(projectId, '/tenants');
4
+ return documents
5
+ .map((document) => {
6
+ const id = documentId(document);
7
+ const data = decodeFields(document.fields);
8
+ return { id, name: typeof data.name === 'string' && data.name !== '' ? data.name : id };
9
+ })
10
+ .sort((a, b) => (a.name < b.name ? -1 : a.name > b.name ? 1 : 0));
11
+ };
12
+ /**
13
+ * As extensões instaladas num escritório.
14
+ *
15
+ * A DESATIVADA vem junto, marcada. Quem decide o que fazer com ela é quem chamou: para o `provision`
16
+ * ela não exige backend nenhum; para o catálogo ela ainda explica de onde veio uma tela que continua
17
+ * gravada no banco.
18
+ */
19
+ export const readInstalledExtensions = async (projectId, tenantId) => {
20
+ const documents = await listDocuments(projectId, `/tenants/${tenantId}/installed_extensions`);
21
+ return documents.map((document) => {
22
+ const data = decodeFields(document.fields);
23
+ const pkg = data.package;
24
+ const manifest = pkg?.manifest;
25
+ const fallbackId = documentId(document);
26
+ return {
27
+ tenantId,
28
+ documentPath: `/tenants/${tenantId}/installed_extensions/${fallbackId}`,
29
+ id: String(manifest?.id ?? fallbackId),
30
+ name: String(manifest?.name ?? manifest?.id ?? fallbackId),
31
+ version: String(manifest?.version ?? '0.0.0'),
32
+ active: data.active !== false,
33
+ manifest,
34
+ data,
35
+ };
36
+ });
37
+ };
@@ -0,0 +1,29 @@
1
+ /**
2
+ * Este arquivo BLOQUEIA a página: é um `<script>` no `<head>`, antes do bundle. Um pacote gordo aqui
3
+ * custa em toda visita, inclusive na de quem nem vai entrar. O aviso vem antes de doer, e a recusa
4
+ * vem quando já dói para todo mundo.
5
+ */
6
+ export declare const STAMP_WARN_BYTES: number;
7
+ export declare const STAMP_MAX_BYTES: number;
8
+ export declare const STAMP_FILE = "microteia-extensions.js";
9
+ export interface LoginExtensionsResult {
10
+ /** O conteúdo do arquivo carimbado. Escrito sempre, mesmo vazio — ver `stampLoginExtensions`. */
11
+ contents: string;
12
+ /** `id@version` de cada pacote carimbado, na ordem declarada. */
13
+ stamped: string[];
14
+ /** O que quem instala precisa saber sem que o deploy pare. */
15
+ warnings: string[];
16
+ bytes: number;
17
+ }
18
+ export declare const parseExtensionPaths: (declared: string | null) => string[];
19
+ /**
20
+ * Monta o carimbo. `preLoginPoints` vem do manifesto do PACOTE do kernel instalado, e não de uma lista
21
+ * aqui: o Console é versionado separadamente, e uma cópia dela passaria a descrever o contrato de
22
+ * outra versão em silêncio. Ausente (pacote antigo), a checagem é pulada em vez de inventada.
23
+ */
24
+ export declare const buildLoginExtensions: (root: string, declared: string | null, preLoginPoints: string[] | undefined) => Promise<LoginExtensionsResult>;
25
+ /**
26
+ * Escreve o carimbo SEMPRE, inclusive vazio. Pular a escrita quando a lista está vazia deixaria o
27
+ * carimbo do deploy anterior no ar: tirar um pacote de `login.extensions` não o tiraria da tela.
28
+ */
29
+ export declare const stampPath: (install: string) => string;
@@ -0,0 +1,101 @@
1
+ import { readFile } from 'node:fs/promises';
2
+ import { join, resolve } from 'node:path';
3
+ /**
4
+ * Este arquivo BLOQUEIA a página: é um `<script>` no `<head>`, antes do bundle. Um pacote gordo aqui
5
+ * custa em toda visita, inclusive na de quem nem vai entrar. O aviso vem antes de doer, e a recusa
6
+ * vem quando já dói para todo mundo.
7
+ */
8
+ export const STAMP_WARN_BYTES = 256 * 1024;
9
+ export const STAMP_MAX_BYTES = 1024 * 1024;
10
+ export const STAMP_FILE = 'microteia-extensions.js';
11
+ export const parseExtensionPaths = (declared) => (declared ?? '')
12
+ .split(',')
13
+ .map((entry) => entry.trim())
14
+ .filter((entry) => entry !== '');
15
+ const asStampedPackage = (parsed, path) => {
16
+ const manifest = parsed?.manifest;
17
+ if (!manifest || typeof manifest !== 'object') {
18
+ throw new Error(`${path} is not an extension package: it has no "manifest".`);
19
+ }
20
+ const { id, version, contributes } = manifest;
21
+ if (typeof id !== 'string' || id === '') {
22
+ throw new Error(`${path} has no "manifest.id".`);
23
+ }
24
+ if (typeof version !== 'string' || version === '') {
25
+ throw new Error(`${path} has no "manifest.version".`);
26
+ }
27
+ if (!Array.isArray(contributes)) {
28
+ throw new Error(`${path} has no "manifest.contributes" array.`);
29
+ }
30
+ return parsed;
31
+ };
32
+ /**
33
+ * Monta o carimbo. `preLoginPoints` vem do manifesto do PACOTE do kernel instalado, e não de uma lista
34
+ * aqui: o Console é versionado separadamente, e uma cópia dela passaria a descrever o contrato de
35
+ * outra versão em silêncio. Ausente (pacote antigo), a checagem é pulada em vez de inventada.
36
+ */
37
+ export const buildLoginExtensions = async (root, declared, preLoginPoints) => {
38
+ const paths = parseExtensionPaths(declared);
39
+ const packages = [];
40
+ const stamped = [];
41
+ const warnings = [];
42
+ for (const path of paths) {
43
+ const absolute = resolve(root, path);
44
+ let parsed;
45
+ try {
46
+ parsed = JSON.parse(await readFile(absolute, 'utf8'));
47
+ }
48
+ catch (error) {
49
+ if (error.code === 'ENOENT') {
50
+ throw new Error(`login.extensions lists ${path}, which does not exist.\n`
51
+ + 'Compile the extension first ("microteia-plugin build" inside extensions/), '
52
+ + 'or remove the path from microteia.yaml.', { cause: error });
53
+ }
54
+ throw new Error(`${path} is not valid JSON.`, { cause: error });
55
+ }
56
+ const pkg = asStampedPackage(parsed, path);
57
+ if (preLoginPoints !== undefined) {
58
+ const points = pkg.manifest.contributes
59
+ .map((contribution) => contribution.point)
60
+ .filter((point) => typeof point === 'string');
61
+ const effective = points.filter((point) => preLoginPoints.includes(point));
62
+ if (effective.length === 0) {
63
+ throw new Error(`${path} (${pkg.manifest.id}) contributes nothing that works before sign-in, so stamping it `
64
+ + 'would load it with no effect.\n'
65
+ + ` Points that work before sign-in: ${preLoginPoints.join(', ')}.\n`
66
+ + ` This package contributes: ${points.length === 0 ? '(none)' : [...new Set(points)].join(', ')}.\n`
67
+ + ' To USE it inside the organization, install it from the Extensions screen instead.');
68
+ }
69
+ }
70
+ if (Array.isArray(pkg.views) && pkg.views.length > 0) {
71
+ warnings.push(`${pkg.manifest.id} ships ${pkg.views.length} view(s): stamped packages load untrusted, `
72
+ + 'so their code does not run before sign-in — only declarations apply');
73
+ }
74
+ const duplicate = packages.find((existing) => existing.manifest.id === pkg.manifest.id);
75
+ if (duplicate !== undefined) {
76
+ throw new Error(`login.extensions lists ${pkg.manifest.id} twice. The Kernel would ignore the second one, `
77
+ + 'so the file says something the installation does not do.');
78
+ }
79
+ packages.push(pkg);
80
+ stamped.push(`${pkg.manifest.id}@${pkg.manifest.version}`);
81
+ }
82
+ const contents = `// Generated by "microteia deploy". Do not edit by hand.\n`
83
+ + `window.__MICROTEIA_EXTENSIONS__ = ${JSON.stringify(packages)};\n`;
84
+ const bytes = Buffer.byteLength(contents, 'utf8');
85
+ if (bytes > STAMP_MAX_BYTES) {
86
+ throw new Error(`The stamped extensions would be ${Math.round(bytes / 1024)} KiB, over the `
87
+ + `${STAMP_MAX_BYTES / 1024} KiB limit. This file blocks the page on every visit, including `
88
+ + 'visits by people who never sign in.\n'
89
+ + ' Keep before-sign-in packages to what the sign-in screen needs, and install the rest.');
90
+ }
91
+ if (bytes > STAMP_WARN_BYTES) {
92
+ warnings.push(`the stamped extensions are ${Math.round(bytes / 1024)} KiB, and this file blocks the page on `
93
+ + 'every visit');
94
+ }
95
+ return { contents, stamped, warnings, bytes };
96
+ };
97
+ /**
98
+ * Escreve o carimbo SEMPRE, inclusive vazio. Pular a escrita quando a lista está vazia deixaria o
99
+ * carimbo do deploy anterior no ar: tirar um pacote de `login.extensions` não o tiraria da tela.
100
+ */
101
+ export const stampPath = (install) => join(install, 'hosting', STAMP_FILE);
@@ -8,6 +8,31 @@ export interface InstalledClaim extends BackendClaim {
8
8
  documentPath: string;
9
9
  applied: boolean;
10
10
  }
11
+ /**
12
+ * A varredura inteira, e não só o que ela achou.
13
+ *
14
+ * As contagens estão aqui porque é delas que sai o direito de APAGAR: a Cloud Function é do PROJETO e a
15
+ * instalação é do ESCRITÓRIO, então "extensão nenhuma exige mais" é uma afirmação sobre todos os
16
+ * escritórios do projeto, não sobre o último que foi lido. A varredura é completa ou não existe —
17
+ * `listDocuments` pagina até o fim e qualquer leitura recusada derruba o comando —, e o que faltava era
18
+ * DIZER isso a quem lê a proposta de remoção.
19
+ */
20
+ export interface InstalledSurvey {
21
+ /** Escritórios lidos. Todos os do projeto, ou nada abaixo teria acontecido. */
22
+ tenants: number;
23
+ /** Cópias de extensão lidas, ativas ou não. */
24
+ extensions: number;
25
+ claims: InstalledClaim[];
26
+ /**
27
+ * Todo id instalado e ativo no projeto, exija ele backend ou não.
28
+ *
29
+ * `claims` não serve para esta pergunta, e a diferença é a que faz a function comum ser retirada pelo
30
+ * motivo certo: uma extensão sem `manifest.backend` nunca vira reivindicação, mas ela pode muito bem
31
+ * ser uma das que a function comum serve. Decidir pela lista de reivindicações apagaria a function de
32
+ * quem está instalado.
33
+ */
34
+ installedIds: Set<string>;
35
+ }
11
36
  /**
12
37
  * O que cada escritório do projeto instalou e exige do backend.
13
38
  *
@@ -15,8 +40,7 @@ export interface InstalledClaim extends BackendClaim {
15
40
  * contra `manifest.functions` antes de qualquer deploy. Nome de function fora do pacote é recusado com
16
41
  * explicação; não há injeção possível, porque o código é sempre o do pacote.
17
42
  */
18
- export declare const readInstalledClaims: (projectId: string) => Promise<InstalledClaim[]>;
19
- export declare const parseAppliedFunctions: (value: string | null) => string[];
43
+ export declare const surveyInstallations: (projectId: string) => Promise<InstalledSurvey>;
20
44
  export interface ProvisionOptions {
21
45
  projectId: string;
22
46
  region: string;
@@ -51,17 +75,6 @@ export interface ProvisionOutcome {
51
75
  * divergiriam exatamente no ponto em que ninguém olha.
52
76
  */
53
77
  export declare const provisionBackend: (options: ProvisionOptions) => Promise<ProvisionOutcome>;
54
- /**
55
- * A lista aplicada CONVERGE para o que ainda é exigido: o que já estava e continua sendo pedido, mais
56
- * o que subiu agora, mais o órfão que não foi removido. Reescrevê-la só com o que subiu NESTA execução
57
- * a esvaziava na segunda passada — e a terceira redeployava tudo, achando que nada havia sido feito.
58
- */
59
- export declare const appliedAfterRun: (input: {
60
- alreadyApplied: string[];
61
- wanted: string[];
62
- deployed: string[];
63
- keptOrphans: string[];
64
- }) => string[];
65
78
  /**
66
79
  * A impressão digital do parâmetro só avança quando TODAS as functions que o carregam subiram. Gravar
67
80
  * antes disso é o que transformaria uma falha de deploy em silêncio permanente: o valor novo ficaria