@microteia/console 2.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.
- package/CONSOLE-VERSIONS.md +85 -0
- package/README.md +64 -0
- package/lib/console/backendEnv.d.ts +52 -0
- package/lib/console/backendEnv.js +118 -0
- package/lib/console/backendIndexes.d.ts +40 -0
- package/lib/console/backendIndexes.js +178 -0
- package/lib/console/backendParameters.d.ts +48 -0
- package/lib/console/backendParameters.js +100 -0
- package/lib/console/callableAccess.d.ts +39 -0
- package/lib/console/callableAccess.js +61 -0
- package/lib/console/channel.d.ts +49 -0
- package/lib/console/channel.js +145 -0
- package/lib/console/connect.d.ts +2 -0
- package/lib/console/connect.js +74 -0
- package/lib/console/customFunctions.d.ts +120 -0
- package/lib/console/customFunctions.js +298 -0
- package/lib/console/deploy.d.ts +2 -0
- package/lib/console/deploy.js +261 -0
- package/lib/console/deployFunctions.d.ts +45 -0
- package/lib/console/deployFunctions.js +204 -0
- package/lib/console/environment.d.ts +62 -0
- package/lib/console/environment.js +151 -0
- package/lib/console/firebaseCli.d.ts +15 -0
- package/lib/console/firebaseCli.js +90 -0
- package/lib/console/firestoreRest.d.ts +39 -0
- package/lib/console/firestoreRest.js +131 -0
- package/lib/console/hostingCheck.d.ts +29 -0
- package/lib/console/hostingCheck.js +44 -0
- package/lib/console/init.d.ts +2 -0
- package/lib/console/init.js +60 -0
- package/lib/console/pluginWorkspace.d.ts +48 -0
- package/lib/console/pluginWorkspace.js +148 -0
- package/lib/console/provision.d.ts +70 -0
- package/lib/console/provision.js +478 -0
- package/lib/console/regionOrphans.d.ts +43 -0
- package/lib/console/regionOrphans.js +99 -0
- package/lib/console/runtimeConfigShape.d.ts +25 -0
- package/lib/console/runtimeConfigShape.js +35 -0
- package/lib/console/unpack.d.ts +9 -0
- package/lib/console/unpack.js +333 -0
- package/lib/console/versions.d.ts +10 -0
- package/lib/console/versions.js +72 -0
- package/lib/console/workspace.d.ts +19 -0
- package/lib/console/workspace.js +33 -0
- package/lib/index.d.ts +2 -0
- package/lib/index.js +136 -0
- package/lib/kernel/backendShape.d.ts +74 -0
- package/lib/kernel/backendShape.js +91 -0
- package/lib/kernel/channel.d.ts +79 -0
- package/lib/kernel/channel.js +157 -0
- package/lib/kernel/manifest.d.ts +138 -0
- package/lib/kernel/manifest.js +145 -0
- package/lib/shared/args.d.ts +14 -0
- package/lib/shared/args.js +44 -0
- package/lib/shared/digest.d.ts +30 -0
- package/lib/shared/digest.js +34 -0
- package/lib/shared/paths.d.ts +90 -0
- package/lib/shared/paths.js +97 -0
- package/lib/shared/run.d.ts +28 -0
- package/lib/shared/run.js +49 -0
- package/lib/shared/ui.d.ts +23 -0
- package/lib/shared/ui.js +43 -0
- package/lib/vendor/authoringGuide.d.ts +19 -0
- package/lib/vendor/authoringGuide.js +284 -0
- package/lib/vendor/bundleAudit.d.ts +19 -0
- package/lib/vendor/bundleAudit.js +95 -0
- package/lib/vendor/changelogs.d.ts +62 -0
- package/lib/vendor/changelogs.js +131 -0
- package/lib/vendor/exampleSources.d.ts +29 -0
- package/lib/vendor/exampleSources.js +126 -0
- package/lib/vendor/installationDocs.d.ts +35 -0
- package/lib/vendor/installationDocs.js +243 -0
- package/lib/vendor/pack.d.ts +2 -0
- package/lib/vendor/pack.js +276 -0
- package/lib/vendor/packConsole.d.ts +13 -0
- package/lib/vendor/packConsole.js +63 -0
- package/lib/vendor/packageTemplates.d.ts +66 -0
- package/lib/vendor/packageTemplates.js +200 -0
- package/lib/vendor/pluginWorkspaceTemplates.d.ts +19 -0
- package/lib/vendor/pluginWorkspaceTemplates.js +29 -0
- package/lib/vendor/publish.d.ts +2 -0
- package/lib/vendor/publish.js +277 -0
- package/lib/vendor/repository.d.ts +9 -0
- package/lib/vendor/repository.js +26 -0
- package/package.json +21 -0
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A forma de `manifest.backend` redeclarada do lado do Console, pelo mesmo motivo de
|
|
3
|
+
* `console/runtimeConfigShape.ts`: o Console é publicado sem o `src/` do kernel, então não pode
|
|
4
|
+
* importar o contrato. `backendShape.test.ts` amarra as duas pontas — separadas em silêncio, o Console
|
|
5
|
+
* gravaria um fingerprint que o kernel nunca reconhece, e a extensão ficaria pendente para sempre.
|
|
6
|
+
*/
|
|
7
|
+
export interface FirestoreIndexField {
|
|
8
|
+
fieldPath: string;
|
|
9
|
+
order?: 'ASCENDING' | 'DESCENDING';
|
|
10
|
+
arrayConfig?: 'CONTAINS';
|
|
11
|
+
}
|
|
12
|
+
export interface FirestoreIndexSpec {
|
|
13
|
+
collectionGroup: string;
|
|
14
|
+
queryScope: 'COLLECTION' | 'COLLECTION_GROUP';
|
|
15
|
+
fields: FirestoreIndexField[];
|
|
16
|
+
}
|
|
17
|
+
export interface BackendPrerequisite {
|
|
18
|
+
id: string;
|
|
19
|
+
title: string;
|
|
20
|
+
detail: string;
|
|
21
|
+
}
|
|
22
|
+
export interface PluginBackendRequirement {
|
|
23
|
+
functions?: string[];
|
|
24
|
+
indexes?: FirestoreIndexSpec[];
|
|
25
|
+
parameters?: string[];
|
|
26
|
+
prerequisites?: BackendPrerequisite[];
|
|
27
|
+
}
|
|
28
|
+
/** Uma exigência com o dono dela — é o dono que o relatório nomeia, e o dono que fica pendente. */
|
|
29
|
+
export interface BackendClaim {
|
|
30
|
+
pluginId: string;
|
|
31
|
+
name: string;
|
|
32
|
+
version: string;
|
|
33
|
+
backend: PluginBackendRequirement;
|
|
34
|
+
}
|
|
35
|
+
/** Cópia byte a byte de `domain/schema/plugin/backendRequirement.ts`. Ver o teste de paridade. */
|
|
36
|
+
export declare const backendFingerprint: (backend?: PluginBackendRequirement) => string;
|
|
37
|
+
export declare const hasBackendRequirement: (backend?: PluginBackendRequirement) => boolean;
|
|
38
|
+
/** Chave de identidade de um índice, para deduplicar e para casar reivindicação com a raiz. */
|
|
39
|
+
export declare const indexKey: (index: FirestoreIndexSpec) => string;
|
|
40
|
+
export declare const claimedFunctions: (claims: BackendClaim[]) => string[];
|
|
41
|
+
export declare const claimedIndexes: (claims: BackendClaim[]) => FirestoreIndexSpec[];
|
|
42
|
+
export declare const claimedParameters: (claims: BackendClaim[]) => string[];
|
|
43
|
+
/**
|
|
44
|
+
* O que o `deploy` sobe sempre: tudo o que foi empacotado menos o que alguma extensão reivindica.
|
|
45
|
+
* A ordem de `index.ts` é preservada — é a ordem em que o deploy uma-a-uma acontece.
|
|
46
|
+
*/
|
|
47
|
+
export declare const baselineFunctionsOf: (all: string[], claims: BackendClaim[]) => string[];
|
|
48
|
+
export declare const baselineIndexesOf: (rootIndexes: FirestoreIndexSpec[], claims: BackendClaim[]) => FirestoreIndexSpec[];
|
|
49
|
+
/**
|
|
50
|
+
* Reivindicar function que não foi empacotada, ou a mesma function por duas extensões.
|
|
51
|
+
*
|
|
52
|
+
* A primeira deixaria o deploy nomeando uma function inexistente; a segunda tornaria a remoção por
|
|
53
|
+
* `--prune` indecidível — ninguém sabe de quem é. Vale nos dois lados: no `pack`, contra os templates;
|
|
54
|
+
* no `provision`, contra o que veio do banco do escritório, que é dado e não código.
|
|
55
|
+
*/
|
|
56
|
+
export declare const functionClaimProblems: (claims: BackendClaim[], packagedFunctions: string[]) => string[];
|
|
57
|
+
/**
|
|
58
|
+
* Índice reivindicado que não existe na raiz. É conferência de EMPACOTAMENTO, não de provisionamento:
|
|
59
|
+
* a raiz é o que roda em `microteia-dev` e `curatino`, então uma declaração que não esteja lá deixaria
|
|
60
|
+
* de criar o índice justamente nos nossos ambientes. Do lado de quem instala a pergunta não faz
|
|
61
|
+
* sentido — lá o índice reivindicado é, por construção, o que o `pack` tirou da baseline.
|
|
62
|
+
*/
|
|
63
|
+
export declare const indexDeclarationProblems: (claims: BackendClaim[], rootIndexes: FirestoreIndexSpec[]) => string[];
|
|
64
|
+
/** As duas conferências juntas — o que o `pack` exige antes de escrever qualquer coisa. */
|
|
65
|
+
export declare const claimProblems: (claims: BackendClaim[], packagedFunctions: string[], rootIndexes: FirestoreIndexSpec[]) => string[];
|
|
66
|
+
/** As exigências das extensões de primeira parte, lidas dos `extension-templates/*.json` gerados. */
|
|
67
|
+
export declare const claimsFromTemplates: (templates: {
|
|
68
|
+
manifest?: {
|
|
69
|
+
id?: string;
|
|
70
|
+
name?: string;
|
|
71
|
+
version?: string;
|
|
72
|
+
backend?: PluginBackendRequirement;
|
|
73
|
+
};
|
|
74
|
+
}[]) => BackendClaim[];
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
const canonicalIndex = (index) => JSON.stringify([
|
|
2
|
+
index.collectionGroup,
|
|
3
|
+
index.queryScope,
|
|
4
|
+
index.fields.map((field) => [field.fieldPath, field.order ?? '', field.arrayConfig ?? '']),
|
|
5
|
+
]);
|
|
6
|
+
/** Cópia byte a byte de `domain/schema/plugin/backendRequirement.ts`. Ver o teste de paridade. */
|
|
7
|
+
export const backendFingerprint = (backend) => JSON.stringify({
|
|
8
|
+
functions: [...(backend?.functions ?? [])].sort(),
|
|
9
|
+
indexes: [...(backend?.indexes ?? [])].map(canonicalIndex).sort(),
|
|
10
|
+
parameters: [...(backend?.parameters ?? [])].sort(),
|
|
11
|
+
});
|
|
12
|
+
export const hasBackendRequirement = (backend) => (backend?.functions?.length ?? 0) > 0
|
|
13
|
+
|| (backend?.indexes?.length ?? 0) > 0
|
|
14
|
+
|| (backend?.parameters?.length ?? 0) > 0;
|
|
15
|
+
/** Chave de identidade de um índice, para deduplicar e para casar reivindicação com a raiz. */
|
|
16
|
+
export const indexKey = canonicalIndex;
|
|
17
|
+
export const claimedFunctions = (claims) => [...new Set(claims.flatMap((claim) => claim.backend.functions ?? []))].sort();
|
|
18
|
+
export const claimedIndexes = (claims) => {
|
|
19
|
+
const seen = new Map();
|
|
20
|
+
for (const claim of claims) {
|
|
21
|
+
for (const index of claim.backend.indexes ?? [])
|
|
22
|
+
seen.set(indexKey(index), index);
|
|
23
|
+
}
|
|
24
|
+
return [...seen.values()];
|
|
25
|
+
};
|
|
26
|
+
export const claimedParameters = (claims) => [...new Set(claims.flatMap((claim) => claim.backend.parameters ?? []))].sort();
|
|
27
|
+
/**
|
|
28
|
+
* O que o `deploy` sobe sempre: tudo o que foi empacotado menos o que alguma extensão reivindica.
|
|
29
|
+
* A ordem de `index.ts` é preservada — é a ordem em que o deploy uma-a-uma acontece.
|
|
30
|
+
*/
|
|
31
|
+
export const baselineFunctionsOf = (all, claims) => {
|
|
32
|
+
const claimed = new Set(claimedFunctions(claims));
|
|
33
|
+
return all.filter((name) => !claimed.has(name));
|
|
34
|
+
};
|
|
35
|
+
export const baselineIndexesOf = (rootIndexes, claims) => {
|
|
36
|
+
const claimed = new Set(claimedIndexes(claims).map(indexKey));
|
|
37
|
+
return rootIndexes.filter((index) => !claimed.has(indexKey(index)));
|
|
38
|
+
};
|
|
39
|
+
/**
|
|
40
|
+
* Reivindicar function que não foi empacotada, ou a mesma function por duas extensões.
|
|
41
|
+
*
|
|
42
|
+
* A primeira deixaria o deploy nomeando uma function inexistente; a segunda tornaria a remoção por
|
|
43
|
+
* `--prune` indecidível — ninguém sabe de quem é. Vale nos dois lados: no `pack`, contra os templates;
|
|
44
|
+
* no `provision`, contra o que veio do banco do escritório, que é dado e não código.
|
|
45
|
+
*/
|
|
46
|
+
export const functionClaimProblems = (claims, packagedFunctions) => {
|
|
47
|
+
const problems = [];
|
|
48
|
+
const packaged = new Set(packagedFunctions);
|
|
49
|
+
const owner = new Map();
|
|
50
|
+
for (const claim of claims) {
|
|
51
|
+
for (const name of claim.backend.functions ?? []) {
|
|
52
|
+
if (!packaged.has(name)) {
|
|
53
|
+
problems.push(`${claim.pluginId} claims function "${name}", which is not available to deploy`);
|
|
54
|
+
}
|
|
55
|
+
const previous = owner.get(name);
|
|
56
|
+
if (previous !== undefined && previous !== claim.pluginId) {
|
|
57
|
+
problems.push(`function "${name}" is claimed by both ${previous} and ${claim.pluginId}`);
|
|
58
|
+
}
|
|
59
|
+
owner.set(name, claim.pluginId);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
return problems;
|
|
63
|
+
};
|
|
64
|
+
/**
|
|
65
|
+
* Índice reivindicado que não existe na raiz. É conferência de EMPACOTAMENTO, não de provisionamento:
|
|
66
|
+
* a raiz é o que roda em `microteia-dev` e `curatino`, então uma declaração que não esteja lá deixaria
|
|
67
|
+
* de criar o índice justamente nos nossos ambientes. Do lado de quem instala a pergunta não faz
|
|
68
|
+
* sentido — lá o índice reivindicado é, por construção, o que o `pack` tirou da baseline.
|
|
69
|
+
*/
|
|
70
|
+
export const indexDeclarationProblems = (claims, rootIndexes) => {
|
|
71
|
+
const rootKeys = new Set(rootIndexes.map(indexKey));
|
|
72
|
+
return claims.flatMap((claim) => (claim.backend.indexes ?? [])
|
|
73
|
+
.filter((index) => !rootKeys.has(indexKey(index)))
|
|
74
|
+
.map((index) => `${claim.pluginId} claims an index on "${index.collectionGroup}" `
|
|
75
|
+
+ `(${index.fields.map((field) => field.fieldPath).join(', ')}) that firestore.indexes.json does not declare`));
|
|
76
|
+
};
|
|
77
|
+
/** As duas conferências juntas — o que o `pack` exige antes de escrever qualquer coisa. */
|
|
78
|
+
export const claimProblems = (claims, packagedFunctions, rootIndexes) => [
|
|
79
|
+
...functionClaimProblems(claims, packagedFunctions),
|
|
80
|
+
...indexDeclarationProblems(claims, rootIndexes),
|
|
81
|
+
];
|
|
82
|
+
/** As exigências das extensões de primeira parte, lidas dos `extension-templates/*.json` gerados. */
|
|
83
|
+
export const claimsFromTemplates = (templates) => templates
|
|
84
|
+
.filter((template) => hasBackendRequirement(template.manifest?.backend))
|
|
85
|
+
.map((template) => ({
|
|
86
|
+
pluginId: String(template.manifest?.id),
|
|
87
|
+
name: String(template.manifest?.name ?? template.manifest?.id),
|
|
88
|
+
version: String(template.manifest?.version ?? '0.0.0'),
|
|
89
|
+
backend: template.manifest?.backend,
|
|
90
|
+
}))
|
|
91
|
+
.sort((a, b) => (a.pluginId < b.pluginId ? -1 : a.pluginId > b.pluginId ? 1 : 0));
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import type { KernelManifest } from './manifest.js';
|
|
2
|
+
/**
|
|
3
|
+
* O catálogo de versões publicadas — a forma do `index.json`, e as regras de quem escreve nele e de quem
|
|
4
|
+
* o lê. Fica em `kernel/` pela mesma razão que o `manifest.ts`: os DOIS lados dependem dele. O `publish`
|
|
5
|
+
* acrescenta uma entrada, o `unpack` resolve uma versão a partir dela, e uma discordância entre os dois
|
|
6
|
+
* seria descoberta na máquina de quem instala.
|
|
7
|
+
*
|
|
8
|
+
* O índice é o único ponto de acoplamento com onde os arquivos estão hospedados. As URLs são DADO dentro
|
|
9
|
+
* dele, absolutas, e nada no Console monta uma: trocar de host — ou passar a emitir URL assinada por
|
|
10
|
+
* escritório — reescreve o índice e não muda comando nenhum que alguém digita. Era o que precisava ser
|
|
11
|
+
* comprado agora, porque o host escolhido não tem caminho próprio para isso.
|
|
12
|
+
*/
|
|
13
|
+
export declare const INDEX_SCHEMA = 1;
|
|
14
|
+
/**
|
|
15
|
+
* O canal padrão, e por ora o único. Ele existe como nome — e não como "a maior versão da lista" —
|
|
16
|
+
* porque publicar não é promover: uma versão pode estar no índice, com URL e hash, sem ser a que uma
|
|
17
|
+
* instalação recebe ao pedir `latest`.
|
|
18
|
+
*/
|
|
19
|
+
export declare const STABLE_CHANNEL = "stable";
|
|
20
|
+
export declare const LATEST = "latest";
|
|
21
|
+
/**
|
|
22
|
+
* O nome do arquivo, montado do número e não lido da URL. Quem baixa não deriva nome de caminho que veio
|
|
23
|
+
* pela rede — e quem empacota usa o mesmo daqui, para que as duas pontas não possam divergir.
|
|
24
|
+
*/
|
|
25
|
+
export declare const kernelTarballName: (version: string) => string;
|
|
26
|
+
export interface ChannelEntry {
|
|
27
|
+
version: string;
|
|
28
|
+
kernelVersion: string;
|
|
29
|
+
minimumCliVersion: string;
|
|
30
|
+
publishedAt: string;
|
|
31
|
+
bytes: number;
|
|
32
|
+
sha256: string;
|
|
33
|
+
/** URL absoluta do tarball. */
|
|
34
|
+
tarball: string;
|
|
35
|
+
/** URL absoluta da cópia externa do manifesto, para ler pré-requisitos sem baixar 7 MB. */
|
|
36
|
+
manifest: string;
|
|
37
|
+
}
|
|
38
|
+
export interface ChannelIndex {
|
|
39
|
+
schema: number;
|
|
40
|
+
channels: Record<string, string>;
|
|
41
|
+
kernel: ChannelEntry[];
|
|
42
|
+
}
|
|
43
|
+
export declare const emptyIndex: () => ChannelIndex;
|
|
44
|
+
/**
|
|
45
|
+
* Lê o catálogo RECUSANDO o que não entende, em vez de seguir com campos ausentes.
|
|
46
|
+
*
|
|
47
|
+
* O que ele guarda não é a boa formação do JSON: é a impossibilidade de instalar algo cuja integridade
|
|
48
|
+
* não pode ser conferida. Uma entrada sem `sha256` passaria por uma verificação que não compara nada, e
|
|
49
|
+
* essa é exatamente a forma de o hash não servir para nada.
|
|
50
|
+
*
|
|
51
|
+
* `schema` maior que o desta ferramenta é recusa com o conserto na mão: quem publicou já sabia de algo
|
|
52
|
+
* que este Console não sabe.
|
|
53
|
+
*/
|
|
54
|
+
export declare const parseIndex: (text: string, source: string) => ChannelIndex;
|
|
55
|
+
export declare const serializeIndex: (index: ChannelIndex) => string;
|
|
56
|
+
/**
|
|
57
|
+
* A entrada que descreve um pacote, montada do manifesto EXTERNO — o único que carrega o tamanho e o
|
|
58
|
+
* hash do tarball. Um manifesto sem eles é o de dentro do tarball, ou o de um pacote anterior ao canal,
|
|
59
|
+
* e nos dois casos publicar seria publicar algo que ninguém consegue verificar.
|
|
60
|
+
*/
|
|
61
|
+
export declare const entryOf: (manifest: KernelManifest, urls: {
|
|
62
|
+
tarball: string;
|
|
63
|
+
manifest: string;
|
|
64
|
+
}, publishedAt: string) => ChannelEntry;
|
|
65
|
+
/**
|
|
66
|
+
* Acrescenta a entrada e promove o canal, RECUSANDO uma versão que já está lá.
|
|
67
|
+
*
|
|
68
|
+
* Republicar é a origem de "a mesma versão com dois conteúdos", e ela não tem conserto depois: alguém
|
|
69
|
+
* conferiu um hash contra o download de ontem e a partir dali as duas instalações discordam sobre o que
|
|
70
|
+
* `9.8.2` significa. Um número novo custa um minuto.
|
|
71
|
+
*/
|
|
72
|
+
export declare const withEntry: (index: ChannelIndex, entry: ChannelEntry) => ChannelIndex;
|
|
73
|
+
/**
|
|
74
|
+
* Resolve o que foi pedido — `latest` ou um número — nomeando o que existe quando não acha.
|
|
75
|
+
*
|
|
76
|
+
* A recusa lista as versões porque a alternativa é a pessoa tentar de novo com outro palpite. Cinco
|
|
77
|
+
* bastam: quem pediu uma versão exata e errou quase sempre errou no patch.
|
|
78
|
+
*/
|
|
79
|
+
export declare const resolveVersion: (index: ChannelIndex, requested: string) => ChannelEntry;
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
import { compareVersions } from './manifest.js';
|
|
2
|
+
/**
|
|
3
|
+
* O catálogo de versões publicadas — a forma do `index.json`, e as regras de quem escreve nele e de quem
|
|
4
|
+
* o lê. Fica em `kernel/` pela mesma razão que o `manifest.ts`: os DOIS lados dependem dele. O `publish`
|
|
5
|
+
* acrescenta uma entrada, o `unpack` resolve uma versão a partir dela, e uma discordância entre os dois
|
|
6
|
+
* seria descoberta na máquina de quem instala.
|
|
7
|
+
*
|
|
8
|
+
* O índice é o único ponto de acoplamento com onde os arquivos estão hospedados. As URLs são DADO dentro
|
|
9
|
+
* dele, absolutas, e nada no Console monta uma: trocar de host — ou passar a emitir URL assinada por
|
|
10
|
+
* escritório — reescreve o índice e não muda comando nenhum que alguém digita. Era o que precisava ser
|
|
11
|
+
* comprado agora, porque o host escolhido não tem caminho próprio para isso.
|
|
12
|
+
*/
|
|
13
|
+
export const INDEX_SCHEMA = 1;
|
|
14
|
+
/**
|
|
15
|
+
* O canal padrão, e por ora o único. Ele existe como nome — e não como "a maior versão da lista" —
|
|
16
|
+
* porque publicar não é promover: uma versão pode estar no índice, com URL e hash, sem ser a que uma
|
|
17
|
+
* instalação recebe ao pedir `latest`.
|
|
18
|
+
*/
|
|
19
|
+
export const STABLE_CHANNEL = 'stable';
|
|
20
|
+
export const LATEST = 'latest';
|
|
21
|
+
/**
|
|
22
|
+
* O nome do arquivo, montado do número e não lido da URL. Quem baixa não deriva nome de caminho que veio
|
|
23
|
+
* pela rede — e quem empacota usa o mesmo daqui, para que as duas pontas não possam divergir.
|
|
24
|
+
*/
|
|
25
|
+
export const kernelTarballName = (version) => `microteia-kernel-${version}.tgz`;
|
|
26
|
+
export const emptyIndex = () => ({
|
|
27
|
+
schema: INDEX_SCHEMA,
|
|
28
|
+
channels: {},
|
|
29
|
+
kernel: [],
|
|
30
|
+
});
|
|
31
|
+
const isRecord = (value) => typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
32
|
+
const REQUIRED_TEXT = ['version', 'kernelVersion', 'minimumCliVersion', 'publishedAt', 'tarball', 'manifest'];
|
|
33
|
+
const entryProblems = (value, where) => {
|
|
34
|
+
if (!isRecord(value))
|
|
35
|
+
return [`${where} is not an object.`];
|
|
36
|
+
const problems = REQUIRED_TEXT
|
|
37
|
+
.filter((field) => typeof value[field] !== 'string' || value[field] === '')
|
|
38
|
+
.map((field) => `${where} has no ${field}.`);
|
|
39
|
+
for (const field of ['bytes', 'sha256']) {
|
|
40
|
+
const actual = value[field];
|
|
41
|
+
const ok = field === 'bytes' ? typeof actual === 'number' && actual > 0 : typeof actual === 'string';
|
|
42
|
+
if (!ok)
|
|
43
|
+
problems.push(`${where} has no ${field}, so what it points at could not be verified.`);
|
|
44
|
+
}
|
|
45
|
+
return problems;
|
|
46
|
+
};
|
|
47
|
+
/**
|
|
48
|
+
* Lê o catálogo RECUSANDO o que não entende, em vez de seguir com campos ausentes.
|
|
49
|
+
*
|
|
50
|
+
* O que ele guarda não é a boa formação do JSON: é a impossibilidade de instalar algo cuja integridade
|
|
51
|
+
* não pode ser conferida. Uma entrada sem `sha256` passaria por uma verificação que não compara nada, e
|
|
52
|
+
* essa é exatamente a forma de o hash não servir para nada.
|
|
53
|
+
*
|
|
54
|
+
* `schema` maior que o desta ferramenta é recusa com o conserto na mão: quem publicou já sabia de algo
|
|
55
|
+
* que este Console não sabe.
|
|
56
|
+
*/
|
|
57
|
+
export const parseIndex = (text, source) => {
|
|
58
|
+
let value;
|
|
59
|
+
try {
|
|
60
|
+
value = JSON.parse(text);
|
|
61
|
+
}
|
|
62
|
+
catch {
|
|
63
|
+
throw new Error(`${source} is not valid JSON, so there is no catalog to read.`);
|
|
64
|
+
}
|
|
65
|
+
if (!isRecord(value))
|
|
66
|
+
throw new Error(`${source} does not contain a catalog.`);
|
|
67
|
+
const schema = value.schema;
|
|
68
|
+
if (typeof schema !== 'number')
|
|
69
|
+
throw new Error(`${source} does not say which schema it uses.`);
|
|
70
|
+
if (schema > INDEX_SCHEMA) {
|
|
71
|
+
throw new Error(`${source} uses catalog schema ${schema} and this Console understands ${INDEX_SCHEMA}.\n`
|
|
72
|
+
+ 'Update it with "npm i -g @microteia/console".');
|
|
73
|
+
}
|
|
74
|
+
const kernel = value.kernel;
|
|
75
|
+
if (!Array.isArray(kernel))
|
|
76
|
+
throw new Error(`${source} lists no kernel versions.`);
|
|
77
|
+
const problems = kernel.flatMap((entry, index) => entryProblems(entry, `${source} entry ${index}`));
|
|
78
|
+
if (problems.length > 0) {
|
|
79
|
+
throw new Error(`The catalog cannot be trusted:\n${problems.map((problem) => ` ${problem}`).join('\n')}`);
|
|
80
|
+
}
|
|
81
|
+
const channels = isRecord(value.channels) ? value.channels : {};
|
|
82
|
+
return {
|
|
83
|
+
schema,
|
|
84
|
+
channels: Object.fromEntries(Object.entries(channels).filter(([, version]) => typeof version === 'string')),
|
|
85
|
+
kernel: kernel,
|
|
86
|
+
};
|
|
87
|
+
};
|
|
88
|
+
export const serializeIndex = (index) => `${JSON.stringify(index, null, 2)}\n`;
|
|
89
|
+
/**
|
|
90
|
+
* A entrada que descreve um pacote, montada do manifesto EXTERNO — o único que carrega o tamanho e o
|
|
91
|
+
* hash do tarball. Um manifesto sem eles é o de dentro do tarball, ou o de um pacote anterior ao canal,
|
|
92
|
+
* e nos dois casos publicar seria publicar algo que ninguém consegue verificar.
|
|
93
|
+
*/
|
|
94
|
+
export const entryOf = (manifest, urls, publishedAt) => {
|
|
95
|
+
if (manifest.tarball === undefined) {
|
|
96
|
+
throw new Error('This manifest carries no tarball digest, so the version could not be verified after download.\n'
|
|
97
|
+
+ 'Rebuild the package with "npm run pack:kernel".');
|
|
98
|
+
}
|
|
99
|
+
return {
|
|
100
|
+
version: manifest.version,
|
|
101
|
+
kernelVersion: manifest.kernelVersion,
|
|
102
|
+
minimumCliVersion: manifest.minimumCliVersion,
|
|
103
|
+
publishedAt,
|
|
104
|
+
bytes: manifest.tarball.bytes,
|
|
105
|
+
sha256: manifest.tarball.sha256,
|
|
106
|
+
tarball: urls.tarball,
|
|
107
|
+
manifest: urls.manifest,
|
|
108
|
+
};
|
|
109
|
+
};
|
|
110
|
+
const byVersionDescending = (left, right) => compareVersions(right.version, left.version);
|
|
111
|
+
/**
|
|
112
|
+
* Acrescenta a entrada e promove o canal, RECUSANDO uma versão que já está lá.
|
|
113
|
+
*
|
|
114
|
+
* Republicar é a origem de "a mesma versão com dois conteúdos", e ela não tem conserto depois: alguém
|
|
115
|
+
* conferiu um hash contra o download de ontem e a partir dali as duas instalações discordam sobre o que
|
|
116
|
+
* `9.8.2` significa. Um número novo custa um minuto.
|
|
117
|
+
*/
|
|
118
|
+
export const withEntry = (index, entry) => {
|
|
119
|
+
if (index.kernel.some((existing) => existing.version === entry.version)) {
|
|
120
|
+
throw new Error(`${entry.version} is already published, and a released version is never rewritten.\n`
|
|
121
|
+
+ 'Bump the version in package.json and package it again.');
|
|
122
|
+
}
|
|
123
|
+
return {
|
|
124
|
+
schema: INDEX_SCHEMA,
|
|
125
|
+
channels: { ...index.channels, [STABLE_CHANNEL]: entry.version },
|
|
126
|
+
kernel: [entry, ...index.kernel].sort(byVersionDescending),
|
|
127
|
+
};
|
|
128
|
+
};
|
|
129
|
+
/**
|
|
130
|
+
* Resolve o que foi pedido — `latest` ou um número — nomeando o que existe quando não acha.
|
|
131
|
+
*
|
|
132
|
+
* A recusa lista as versões porque a alternativa é a pessoa tentar de novo com outro palpite. Cinco
|
|
133
|
+
* bastam: quem pediu uma versão exata e errou quase sempre errou no patch.
|
|
134
|
+
*/
|
|
135
|
+
export const resolveVersion = (index, requested) => {
|
|
136
|
+
const available = [...index.kernel].sort(byVersionDescending);
|
|
137
|
+
if (requested === LATEST) {
|
|
138
|
+
const promoted = index.channels[STABLE_CHANNEL];
|
|
139
|
+
if (promoted === undefined) {
|
|
140
|
+
throw new Error('The catalog promotes no version to "latest" yet.');
|
|
141
|
+
}
|
|
142
|
+
const entry = available.find((candidate) => candidate.version === promoted);
|
|
143
|
+
if (entry === undefined) {
|
|
144
|
+
throw new Error(`The catalog promotes ${promoted} to "latest", and lists no such version.`);
|
|
145
|
+
}
|
|
146
|
+
return entry;
|
|
147
|
+
}
|
|
148
|
+
const entry = available.find((candidate) => candidate.version === requested);
|
|
149
|
+
if (entry === undefined) {
|
|
150
|
+
const names = available.slice(0, 5).map((candidate) => candidate.version);
|
|
151
|
+
throw new Error(`The catalog has no kernel ${requested}.\n`
|
|
152
|
+
+ (names.length === 0
|
|
153
|
+
? 'It lists no versions at all.'
|
|
154
|
+
: `The most recent are ${names.join(', ')} — or use --version latest.`));
|
|
155
|
+
}
|
|
156
|
+
return entry;
|
|
157
|
+
};
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
import type { FirestoreIndexSpec } from './backendShape.js';
|
|
2
|
+
export interface KernelManifestComponent {
|
|
3
|
+
id: string;
|
|
4
|
+
path: string;
|
|
5
|
+
}
|
|
6
|
+
export interface KernelPrerequisite {
|
|
7
|
+
id: string;
|
|
8
|
+
title: string;
|
|
9
|
+
detail: string;
|
|
10
|
+
optional: boolean;
|
|
11
|
+
}
|
|
12
|
+
export interface BackendParameter {
|
|
13
|
+
name: string;
|
|
14
|
+
/** Sem `default` no `defineString`, o firebase-functions EXIGE valor e recusa o deploy sem ele. */
|
|
15
|
+
required: boolean;
|
|
16
|
+
}
|
|
17
|
+
export interface KernelManifest {
|
|
18
|
+
id: string;
|
|
19
|
+
name: string;
|
|
20
|
+
version: string;
|
|
21
|
+
kernelVersion: string;
|
|
22
|
+
minimumCliVersion: string;
|
|
23
|
+
packagedAt: string;
|
|
24
|
+
components: KernelManifestComponent[];
|
|
25
|
+
requires: {
|
|
26
|
+
functionsRegion: string;
|
|
27
|
+
nodeVersion: string;
|
|
28
|
+
};
|
|
29
|
+
/**
|
|
30
|
+
* TODAS as functions que existem no pacote. Continua sendo a lista completa porque é a fronteira de
|
|
31
|
+
* validação: uma extensão só pode reivindicar o que foi empacotado.
|
|
32
|
+
*/
|
|
33
|
+
functions: string[];
|
|
34
|
+
/**
|
|
35
|
+
* As que o `deploy` sobe SEMPRE. O que sobra é reivindicado por extensão (`manifest.backend`) e só
|
|
36
|
+
* sobe quando ela está instalada — empacotar não é deployar.
|
|
37
|
+
*/
|
|
38
|
+
baselineFunctions: string[];
|
|
39
|
+
/**
|
|
40
|
+
* Os índices compostos que o pacote já traz. O que uma extensão reivindica saiu daqui, e é esta
|
|
41
|
+
* lista que dá ao `provision` a referência exata do que é do kernel — sem ela, distinguir índice do
|
|
42
|
+
* kernel de índice de extensão desinstalada exigiria adivinhar.
|
|
43
|
+
*/
|
|
44
|
+
baselineIndexes: FirestoreIndexSpec[];
|
|
45
|
+
/** Ausente nos pacotes anteriores a 7.51.0, que não conheciam a verificação de alcance. */
|
|
46
|
+
callables?: string[];
|
|
47
|
+
backendParameters: BackendParameter[];
|
|
48
|
+
manualPrerequisites: KernelPrerequisite[];
|
|
49
|
+
/**
|
|
50
|
+
* O SDK de autoria que este pacote carrega, quando carrega. Está aqui para que o `plugin-init`
|
|
51
|
+
* saiba a versão e ache o tarball sem desempacotar nada duas vezes — e para que um pacote
|
|
52
|
+
* ANTERIOR ao SDK seja reconhecido pela ausência, em vez de gerar um `package.json` apontando
|
|
53
|
+
* para um arquivo que não existe.
|
|
54
|
+
*/
|
|
55
|
+
sdk?: KernelSdk;
|
|
56
|
+
/**
|
|
57
|
+
* O tamanho e a impressão digital do tarball, presentes SÓ na cópia externa do manifesto — a que fica
|
|
58
|
+
* ao lado do `.tgz` e é publicada com ele. A cópia de dentro do tarball não pode carregá-los: ela
|
|
59
|
+
* entra no arquivo cujo hash seria descrito, e nenhum dos dois números existiria ainda.
|
|
60
|
+
*
|
|
61
|
+
* É a única diferença entre as duas cópias, e é o que permite ao `unpack` conferir o que baixou.
|
|
62
|
+
* Ausente nos pacotes anteriores ao canal de distribuição, e ausente também quando o pacote chegou
|
|
63
|
+
* por caminho de arquivo — ali a conferência não tem contra o que ser feita.
|
|
64
|
+
*/
|
|
65
|
+
tarball?: KernelTarball;
|
|
66
|
+
}
|
|
67
|
+
export interface KernelTarball {
|
|
68
|
+
/** O nome do arquivo, não um caminho: onde ele fica é decisão de quem publica. */
|
|
69
|
+
name: string;
|
|
70
|
+
bytes: number;
|
|
71
|
+
sha256: string;
|
|
72
|
+
}
|
|
73
|
+
export interface KernelSdk {
|
|
74
|
+
version: string;
|
|
75
|
+
/** O contrato Kernel↔plugins contra o qual o autor escreve. */
|
|
76
|
+
kernelVersion: string;
|
|
77
|
+
/** Caminho do tarball dentro do pacote, SEM número de versão — ver `pluginWorkspaceTemplates`. */
|
|
78
|
+
path: string;
|
|
79
|
+
}
|
|
80
|
+
export declare const KERNEL_DISTRIBUTION_ID = "com.microteia.kernel";
|
|
81
|
+
/**
|
|
82
|
+
* O Console mínimo que consegue instalar um pacote montado hoje. Sobe junto com uma mudança que o
|
|
83
|
+
* pacote PASSA A DEPENDER.
|
|
84
|
+
*
|
|
85
|
+
* Em `2.0.0`: o arquivo de configuração de backend na raiz da instalação. Um Console `1.0.0`
|
|
86
|
+
* desempacotaria este pacote e escreveria a credencial em `.microteia/install/functions/`, que o upgrade
|
|
87
|
+
* seguinte apaga — recusar é mais barato que isso.
|
|
88
|
+
*
|
|
89
|
+
* Em `2.1.0`: as Cloud Functions do ESCRITÓRIO. O pacote carrega o SDK cujo `create function` gera uma
|
|
90
|
+
* extensão que declara a própria function em `manifest.backend.functions`, e um Console anterior monta
|
|
91
|
+
* essa function dentro do pacote e nunca a deploya — a extensão fica "Backend pendente" para sempre,
|
|
92
|
+
* com a mensagem errada ("this kernel package does not contain"), apontando para o lugar errado. Foi
|
|
93
|
+
* exatamente o que aconteceu quando um `microteia` de PATH, linkado a um Console antigo, provisionou uma
|
|
94
|
+
* instalação montada por um Console novo: nada no relato dizia que o problema era a ferramenta.
|
|
95
|
+
*/
|
|
96
|
+
export declare const MINIMUM_CLI_VERSION = "2.1.0";
|
|
97
|
+
export declare const DEFAULT_FUNCTIONS_REGION = "southamerica-east1";
|
|
98
|
+
/**
|
|
99
|
+
* A região padrão do Google, que NÃO é a nossa. Uma instalação fora dela chama as callables por um
|
|
100
|
+
* host próprio da região, e é lá que a permissão de invocação tem de existir — daí o aviso no
|
|
101
|
+
* `connect`.
|
|
102
|
+
*/
|
|
103
|
+
export declare const GOOGLE_DEFAULT_REGION = "us-central1";
|
|
104
|
+
export declare const REQUIRED_NODE_VERSION = "22";
|
|
105
|
+
export declare const KERNEL_COMPONENTS: KernelManifestComponent[];
|
|
106
|
+
/**
|
|
107
|
+
* As armadilhas de projeto novo, que até aqui viviam só como prosa no ARCHITECTURE.md (seção "Ao
|
|
108
|
+
* criar um ambiente do zero"). Elas viajam NO MANIFESTO porque quem instala não tem o ARCHITECTURE.md
|
|
109
|
+
* — e porque cada uma delas já custou um diagnóstico errado: a falta do App Engine aparece como um
|
|
110
|
+
* 404 de autenticação de service agent, que manda procurar no lugar errado.
|
|
111
|
+
*/
|
|
112
|
+
export declare const MANUAL_PREREQUISITES: KernelPrerequisite[];
|
|
113
|
+
/**
|
|
114
|
+
* Os nomes das Cloud Functions saem de `functions/src/index.ts`, a mesma fonte e o mesmo parse que
|
|
115
|
+
* `scripts/deployLists.test.ts` já usa para garantir que nenhuma function fique fora dos grupos de
|
|
116
|
+
* deploy. Reaproveitar a fonte guardada evita criar uma terceira lista para desatualizar.
|
|
117
|
+
*/
|
|
118
|
+
export declare const exportedFunctionNames: (indexSource: string) => string[];
|
|
119
|
+
/**
|
|
120
|
+
* As callables são as functions que o NAVEGADOR chama. Elas se distinguem das de gatilho por uma
|
|
121
|
+
* razão prática: só elas podem ser verificadas de fora, e só nelas a permissão de invocação pública
|
|
122
|
+
* importa. Sem ela a aplicação abre e toda chamada ao servidor falha — parecendo erro de CORS, que
|
|
123
|
+
* manda procurar no lugar errado.
|
|
124
|
+
*/
|
|
125
|
+
export declare const callableFunctionNames: (source: string) => string[];
|
|
126
|
+
/**
|
|
127
|
+
* Os parâmetros que o backend declara com `defineString`. O `firebase-functions` os valida
|
|
128
|
+
* GLOBALMENTE, e não por function: um parâmetro sem valor e sem default recusa o deploy de TODAS as
|
|
129
|
+
* functions, inclusive as que não têm relação nenhuma com ele. Foi assim que um `GOOGLE_GMAIL_TOPIC`
|
|
130
|
+
* ausente derrubou o deploy de `callAIProxy` numa instalação nova.
|
|
131
|
+
*
|
|
132
|
+
* Por isso a lista é DERIVADA do código, e não copiada de um `.env.example` — que já estava
|
|
133
|
+
* desatualizado quando esta função foi escrita.
|
|
134
|
+
*/
|
|
135
|
+
export declare const declaredParametersIn: (source: string) => BackendParameter[];
|
|
136
|
+
/** Comparação de versão suficiente para `minimumCliVersion`: três números, sem pré-lançamento. */
|
|
137
|
+
export declare const compareVersions: (left: string, right: string) => number;
|
|
138
|
+
export declare const parseKernelVersion: (source: string) => string;
|