@microteia/console 2.33.0 → 2.36.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 +16 -0
- package/README.md +3 -1
- package/lib/console/environment.d.ts +22 -0
- package/lib/console/environment.js +40 -1
- package/lib/console/versions.js +10 -1
- package/lib/kernel/channel.d.ts +2 -1
- package/lib/kernel/channel.js +7 -2
- package/lib/kernel/manifest.js +2 -2
- package/lib/shared/paths.d.ts +1 -1
- package/lib/shared/paths.js +1 -1
- package/lib/vendor/installationDocs.js +9 -4
- package/lib/vendor/pack.js +10 -2
- package/lib/vendor/packageTemplates.js +2 -0
- package/lib/vendor/publish.js +52 -11
- package/package.json +1 -1
package/CONSOLE-VERSIONS.md
CHANGED
|
@@ -22,6 +22,22 @@
|
|
|
22
22
|
- fix(command): what was broken, and what it did to whoever hit it.
|
|
23
23
|
-->
|
|
24
24
|
|
|
25
|
+
#### [2.36.0] — 2026-11-05
|
|
26
|
+
|
|
27
|
+
- fix(environment): every command that acts on an installation (`readEnvironment`) refuses when the installation's `CONSOLE-VERSIONS.md` names a Console newer than the one running. An older Console rewrites `microteia.yaml` with only the sections it knows and replaces `CONSOLE-VERSIONS.md` and `MICROTEIA.md` with its own texts — nothing failed: a forgotten copy of Console 2.6.0, left in a folder of the installation, ran `unpack` there, the `login:` block disappeared and the Console history lost thirty versions without a word. No new field marks it, on purpose: an older Console would drop the field it does not know.
|
|
28
|
+
- feat(version): `microteia --version` still answers in that case, and says which Console last updated the installation.
|
|
29
|
+
|
|
30
|
+
#### [2.35.0] — 2026-11-02
|
|
31
|
+
|
|
32
|
+
- feat(publish): the releases repository README links to the official documentation (https://doc.microteia.com), and `publish` rewrites it whenever it differs from the current text — after the catalog, warning instead of failing. It was written only when seeding an empty repository, so the README of an existing one stayed frozen at the text of its first `publish` and no change to it ever arrived. `publish --dry-run` says when it would be rewritten.
|
|
33
|
+
- docs(pack-console): the npm README of the Console links to https://doc.microteia.com.
|
|
34
|
+
|
|
35
|
+
#### [2.34.0] — 2026-11-02
|
|
36
|
+
|
|
37
|
+
- fix(publish): `withEntry` promotes `channels.stable` to the HIGHER of the current and the new version. Publishing out of order — a fix on an older line, `9.180.1` after `9.191.0` — moved `latest` back, and every `unpack --version latest` received the older version. `publish` says whether the version became `latest` or only joined the list.
|
|
38
|
+
- fix(pack): `pack --output <dir>` warns that `publish` reads only `distributions/kernel/<version>/`; the package written elsewhere was simply not found.
|
|
39
|
+
- docs(installation): a platform setting (`SELF_SERVICE_TENANTS`, `AUDIT_ARCHIVE_BUCKET`) reaches the functions with `microteia deploy --only functions`, not `provision`, which only tracks settings declared by extensions and by the installation's `functions/`. The prerequisites and `MICROTEIA.md` §3 said `provision`, which changed nothing.
|
|
40
|
+
|
|
25
41
|
#### [2.33.0] — 2026-10-28
|
|
26
42
|
|
|
27
43
|
- 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.
|
package/README.md
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
|
-
# Microteia Console 2.
|
|
1
|
+
# Microteia Console 2.36.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.
|
|
5
5
|
|
|
6
|
+
📖 **Documentation: [doc.microteia.com](https://doc.microteia.com)**
|
|
7
|
+
|
|
6
8
|
## Installing it
|
|
7
9
|
|
|
8
10
|
npm i -g @microteia/console
|
|
@@ -73,6 +73,28 @@ export interface Environment {
|
|
|
73
73
|
export declare const ENVIRONMENT_FILE = "microteia.yaml";
|
|
74
74
|
export declare const parseEnvironment: (source: string) => Environment;
|
|
75
75
|
export declare const serializeEnvironment: (environment: Environment) => string;
|
|
76
|
+
/**
|
|
77
|
+
* O Console que deixou esta instalação como ela está, quando ele é MAIS NOVO que o que está rodando.
|
|
78
|
+
*
|
|
79
|
+
* O sinal é o topo do `CONSOLE-VERSIONS.md` da raiz, que o `unpack` sobrepõe com o histórico do próprio
|
|
80
|
+
* binário — é, portanto, o último Console que trocou o kernel ali. Nenhum campo novo foi inventado para
|
|
81
|
+
* isso, e é deliberado: um Console antigo apagaria o campo que ele não conhece, que é o defeito inteiro.
|
|
82
|
+
*
|
|
83
|
+
* Sem o arquivo (instalação anterior a ele, ou apagado) não há o que comparar, e nada é recusado.
|
|
84
|
+
*/
|
|
85
|
+
export declare const newerConsoleThanThis: (root: string) => Promise<string | null>;
|
|
86
|
+
/**
|
|
87
|
+
* Lê o `microteia.yaml` — e RECUSA quando um Console mais novo já passou por aqui.
|
|
88
|
+
*
|
|
89
|
+
* Um Console antigo reescreve este arquivo só com as seções que conhece, e sobrepõe o
|
|
90
|
+
* `CONSOLE-VERSIONS.md` e o `MICROTEIA.md` com os textos dele. Nada disso falha: a configuração que ele
|
|
91
|
+
* não conhece some, e o histórico volta meses, em silêncio. Foi o que aconteceu quando uma cópia
|
|
92
|
+
* esquecida do Console, numa pasta da instalação, rodou o `unpack` — o bloco `login:` desapareceu, e o
|
|
93
|
+
* histórico do Console perdeu trinta versões.
|
|
94
|
+
*
|
|
95
|
+
* A recusa fica aqui porque este é o primeiro passo de todo comando que AGE sobre a instalação, antes de
|
|
96
|
+
* qualquer escrita. O `--version` lê o arquivo por outro caminho, e continua respondendo.
|
|
97
|
+
*/
|
|
76
98
|
export declare const readEnvironment: (path: string) => Promise<Environment>;
|
|
77
99
|
export declare const writeEnvironment: (path: string, environment: Environment) => Promise<void>;
|
|
78
100
|
export declare const slug: (value: string) => string;
|
|
@@ -1,4 +1,8 @@
|
|
|
1
1
|
import { readFile, writeFile } from 'node:fs/promises';
|
|
2
|
+
import { dirname, join } from 'node:path';
|
|
3
|
+
import { compareVersions } from '../kernel/manifest.js';
|
|
4
|
+
import { CLI_VERSION } from '../shared/paths.js';
|
|
5
|
+
import { CONSOLE_CHANGELOG, changelogVersions } from '../vendor/changelogs.js';
|
|
2
6
|
export const ENVIRONMENT_FILE = 'microteia.yaml';
|
|
3
7
|
/**
|
|
4
8
|
* O YAML aqui é um SUBCONJUNTO deliberado: mapas de dois níveis com valores escalares, que é a forma
|
|
@@ -140,9 +144,36 @@ login:
|
|
|
140
144
|
# To USE an extension inside the organization, install it from the Extensions screen instead.
|
|
141
145
|
extensions: ${quote(environment.login.extensions)}
|
|
142
146
|
`;
|
|
147
|
+
/**
|
|
148
|
+
* O Console que deixou esta instalação como ela está, quando ele é MAIS NOVO que o que está rodando.
|
|
149
|
+
*
|
|
150
|
+
* O sinal é o topo do `CONSOLE-VERSIONS.md` da raiz, que o `unpack` sobrepõe com o histórico do próprio
|
|
151
|
+
* binário — é, portanto, o último Console que trocou o kernel ali. Nenhum campo novo foi inventado para
|
|
152
|
+
* isso, e é deliberado: um Console antigo apagaria o campo que ele não conhece, que é o defeito inteiro.
|
|
153
|
+
*
|
|
154
|
+
* Sem o arquivo (instalação anterior a ele, ou apagado) não há o que comparar, e nada é recusado.
|
|
155
|
+
*/
|
|
156
|
+
export const newerConsoleThanThis = async (root) => {
|
|
157
|
+
const contents = await readFile(join(root, CONSOLE_CHANGELOG), 'utf8').catch(() => null);
|
|
158
|
+
const latest = contents === null ? undefined : changelogVersions(contents)[0];
|
|
159
|
+
return latest !== undefined && compareVersions(latest, CLI_VERSION) > 0 ? latest : null;
|
|
160
|
+
};
|
|
161
|
+
/**
|
|
162
|
+
* Lê o `microteia.yaml` — e RECUSA quando um Console mais novo já passou por aqui.
|
|
163
|
+
*
|
|
164
|
+
* Um Console antigo reescreve este arquivo só com as seções que conhece, e sobrepõe o
|
|
165
|
+
* `CONSOLE-VERSIONS.md` e o `MICROTEIA.md` com os textos dele. Nada disso falha: a configuração que ele
|
|
166
|
+
* não conhece some, e o histórico volta meses, em silêncio. Foi o que aconteceu quando uma cópia
|
|
167
|
+
* esquecida do Console, numa pasta da instalação, rodou o `unpack` — o bloco `login:` desapareceu, e o
|
|
168
|
+
* histórico do Console perdeu trinta versões.
|
|
169
|
+
*
|
|
170
|
+
* A recusa fica aqui porque este é o primeiro passo de todo comando que AGE sobre a instalação, antes de
|
|
171
|
+
* qualquer escrita. O `--version` lê o arquivo por outro caminho, e continua respondendo.
|
|
172
|
+
*/
|
|
143
173
|
export const readEnvironment = async (path) => {
|
|
174
|
+
let environment;
|
|
144
175
|
try {
|
|
145
|
-
|
|
176
|
+
environment = parseEnvironment(await readFile(path, 'utf8'));
|
|
146
177
|
}
|
|
147
178
|
catch (error) {
|
|
148
179
|
if (error.code === 'ENOENT') {
|
|
@@ -150,6 +181,14 @@ export const readEnvironment = async (path) => {
|
|
|
150
181
|
}
|
|
151
182
|
throw error;
|
|
152
183
|
}
|
|
184
|
+
const newer = await newerConsoleThanThis(dirname(path));
|
|
185
|
+
if (newer !== null) {
|
|
186
|
+
throw new Error(`This installation was last updated by Microteia Console ${newer}, and this is ${CLI_VERSION}.\n`
|
|
187
|
+
+ `An older Console rewrites ${ENVIRONMENT_FILE} without the settings it does not know, and replaces\n`
|
|
188
|
+
+ `${CONSOLE_CHANGELOG} with an older history, so it stops here. Update it with "npm i -g @microteia/console",\n`
|
|
189
|
+
+ 'and check that the "microteia" you ran is that one, not a copy left in a folder.');
|
|
190
|
+
}
|
|
191
|
+
return environment;
|
|
153
192
|
};
|
|
154
193
|
export const writeEnvironment = async (path, environment) => {
|
|
155
194
|
await writeFile(path, serializeEnvironment(environment));
|
package/lib/console/versions.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { readFile } from 'node:fs/promises';
|
|
2
2
|
import { join } from 'node:path';
|
|
3
3
|
import { CLI_VERSION } from '../shared/paths.js';
|
|
4
|
-
import { ENVIRONMENT_FILE, parseEnvironment } from './environment.js';
|
|
4
|
+
import { ENVIRONMENT_FILE, newerConsoleThanThis, parseEnvironment } from './environment.js';
|
|
5
5
|
import { installPath } from './unpack.js';
|
|
6
6
|
/**
|
|
7
7
|
* O que `microteia --version` responde.
|
|
@@ -59,6 +59,15 @@ export const versionReport = async (directory, newer) => {
|
|
|
59
59
|
lines.push('');
|
|
60
60
|
lines.push(`${inPlace} is in place and ${deployed} is what the project runs. "microteia deploy" publishes it.`);
|
|
61
61
|
}
|
|
62
|
+
/**
|
|
63
|
+
* É a pergunta que se faz quando um comando recusa por isto — e a resposta tem de estar aqui, porque
|
|
64
|
+
* o `--version` é o único que continua rodando com o Console velho.
|
|
65
|
+
*/
|
|
66
|
+
const newerConsole = await newerConsoleThanThis(directory);
|
|
67
|
+
if (newerConsole !== null) {
|
|
68
|
+
lines.push('');
|
|
69
|
+
lines.push(`Console ${newerConsole} last updated this installation. This one is older and will not act on it.`);
|
|
70
|
+
}
|
|
62
71
|
/**
|
|
63
72
|
* E o que o canal oferece, quando há canal alcançável. Vem por último porque é a única linha que
|
|
64
73
|
* depende de rede: se ela faltar, tudo acima continua sendo a resposta inteira.
|
package/lib/kernel/channel.d.ts
CHANGED
|
@@ -63,7 +63,8 @@ export declare const entryOf: (manifest: KernelManifest, urls: {
|
|
|
63
63
|
manifest: string;
|
|
64
64
|
}, publishedAt: string) => ChannelEntry;
|
|
65
65
|
/**
|
|
66
|
-
* Acrescenta a entrada e promove o canal
|
|
66
|
+
* Acrescenta a entrada e promove o canal à MAIOR versão entre a atual e a nova, RECUSANDO uma versão
|
|
67
|
+
* que já está lá.
|
|
67
68
|
*
|
|
68
69
|
* Republicar é a origem de "a mesma versão com dois conteúdos", e ela não tem conserto depois: alguém
|
|
69
70
|
* conferiu um hash contra o download de ontem e a partir dali as duas instalações discordam sobre o que
|
package/lib/kernel/channel.js
CHANGED
|
@@ -109,7 +109,8 @@ export const entryOf = (manifest, urls, publishedAt) => {
|
|
|
109
109
|
};
|
|
110
110
|
const byVersionDescending = (left, right) => compareVersions(right.version, left.version);
|
|
111
111
|
/**
|
|
112
|
-
* Acrescenta a entrada e promove o canal
|
|
112
|
+
* Acrescenta a entrada e promove o canal à MAIOR versão entre a atual e a nova, RECUSANDO uma versão
|
|
113
|
+
* que já está lá.
|
|
113
114
|
*
|
|
114
115
|
* Republicar é a origem de "a mesma versão com dois conteúdos", e ela não tem conserto depois: alguém
|
|
115
116
|
* conferiu um hash contra o download de ontem e a partir dali as duas instalações discordam sobre o que
|
|
@@ -120,9 +121,13 @@ export const withEntry = (index, entry) => {
|
|
|
120
121
|
throw new Error(`${entry.version} is already published, and a released version is never rewritten.\n`
|
|
121
122
|
+ 'Bump the version in package.json and package it again.');
|
|
122
123
|
}
|
|
124
|
+
// Publicar fora de ordem é permitido (uma correção numa linha antiga), e o canal não pode andar para
|
|
125
|
+
// trás por isso: `latest` passaria a entregar uma versão MAIS VELHA a quem atualiza.
|
|
126
|
+
const current = index.channels[STABLE_CHANNEL];
|
|
127
|
+
const stable = current !== undefined && compareVersions(current, entry.version) > 0 ? current : entry.version;
|
|
123
128
|
return {
|
|
124
129
|
schema: INDEX_SCHEMA,
|
|
125
|
-
channels: { ...index.channels, [STABLE_CHANNEL]:
|
|
130
|
+
channels: { ...index.channels, [STABLE_CHANNEL]: stable },
|
|
126
131
|
kernel: [entry, ...index.kernel].sort(byVersionDescending),
|
|
127
132
|
};
|
|
128
133
|
};
|
package/lib/kernel/manifest.js
CHANGED
|
@@ -131,7 +131,7 @@ export const MANUAL_PREREQUISITES = [
|
|
|
131
131
|
+ 'longer. Grant the functions\' runtime service account roles/storage.admin on THAT bucket only (it '
|
|
132
132
|
+ 'reads the bucket\'s retention settings and sets each day\'s retention, which the object roles '
|
|
133
133
|
+ 'do not cover), write AUDIT_ARCHIVE_BUCKET=<bucket> in .env.<projectId> '
|
|
134
|
-
+ 'and run microteia provision. What it protects against, and what it does not: a locked bucket stops '
|
|
134
|
+
+ 'and run microteia deploy --only functions (provision does not carry the platform\'s own settings). What it protects against, and what it does not: a locked bucket stops '
|
|
135
135
|
+ 'deletion and rewriting of what was already archived; the chain makes any change visible on '
|
|
136
136
|
+ 'verification. A project owner can still alter or delete an audit line in Firestore BEFORE its day '
|
|
137
137
|
+ 'is exported (the export runs hourly, about an hour after midnight UTC), turn the archive off (that '
|
|
@@ -147,7 +147,7 @@ export const MANUAL_PREREQUISITES = [
|
|
|
147
147
|
detail: 'By default every new account gets an organization of its own, with that person as the owner. In an '
|
|
148
148
|
+ 'installation shared by several organizations — each one created on purpose — that lets anyone who '
|
|
149
149
|
+ 'can create an account open one. Write SELF_SERVICE_TENANTS=false in .env.<projectId> and run '
|
|
150
|
-
+ 'microteia
|
|
150
|
+
+ 'microteia deploy --only functions: from then on a new account waits to be added to an organization, and only the '
|
|
151
151
|
+ 'installation administrator (SYSTEM) opens organizations, in Access management > Organizations, '
|
|
152
152
|
+ 'naming an owner who already has an account. The first account of the installation gets one '
|
|
153
153
|
+ 'anyway. Any value other than empty, true or false is read as false. Two-step verification is '
|
package/lib/shared/paths.d.ts
CHANGED
|
@@ -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.
|
|
90
|
+
export declare const CLI_VERSION = "2.36.0";
|
package/lib/shared/paths.js
CHANGED
|
@@ -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.
|
|
97
|
+
export const CLI_VERSION = '2.36.0';
|
|
@@ -191,10 +191,15 @@ deliberately, because you asked.
|
|
|
191
191
|
They live in \`.env.<projectId>\`, next to \`microteia.yaml\`. \`unpack\` creates the file and adds any
|
|
192
192
|
setting a new version introduces, **never overwriting what you filled in**.
|
|
193
193
|
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
194
|
+
The value is stamped into each Cloud Function at *its* deploy, so a function already running keeps the
|
|
195
|
+
previous one until it is deployed again. Which command deploys it again depends on who declares the
|
|
196
|
+
setting:
|
|
197
|
+
|
|
198
|
+
- **A setting of an extension, or of your own \`functions/\`** — run \`microteia provision\`. It notices the
|
|
199
|
+
change (by a salted digest in \`microteia.yaml\`) and requeues exactly the functions that carry it.
|
|
200
|
+
- **A setting of the platform itself** (\`SELF_SERVICE_TENANTS\`, \`AUDIT_ARCHIVE_BUCKET\`, …) — run
|
|
201
|
+
\`microteia deploy --only functions\`. \`provision\` does not track them; the deploy compares each
|
|
202
|
+
function's stamp, which includes its environment, and redeploys the ones whose value changed.
|
|
198
203
|
|
|
199
204
|
An empty line means "not configured". A feature that needs it stays off rather than failing halfway.
|
|
200
205
|
|
package/lib/vendor/pack.js
CHANGED
|
@@ -3,7 +3,7 @@ import { basename, join, relative } from 'node:path';
|
|
|
3
3
|
import { flagValue, hasFlag } from '../shared/args.js';
|
|
4
4
|
import { repoRoot } from '../shared/paths.js';
|
|
5
5
|
import { run } from '../shared/run.js';
|
|
6
|
-
import { blank, bullets, heading, info, step, success } from '../shared/ui.js';
|
|
6
|
+
import { blank, bullets, heading, info, step, success, warn } from '../shared/ui.js';
|
|
7
7
|
import { DEFAULT_FUNCTIONS_REGION, KERNEL_COMPONENTS, KERNEL_DISTRIBUTION_ID, MANUAL_PREREQUISITES, MINIMUM_CLI_VERSION, REQUIRED_NODE_VERSION, callableFunctionNames, declaredParametersIn, exportedFunctionNames, parseKernelVersion, parsePreLoginPoints, } from '../kernel/manifest.js';
|
|
8
8
|
import { digestOfFile } from '../shared/digest.js';
|
|
9
9
|
import { kernelTarballName } from '../kernel/channel.js';
|
|
@@ -140,7 +140,8 @@ export const pack = async (args) => {
|
|
|
140
140
|
}
|
|
141
141
|
const baselineFunctions = baselineFunctionsOf(functionNames, claims);
|
|
142
142
|
const baselineIndexes = baselineIndexesOf(rootIndexes.indexes, claims);
|
|
143
|
-
const
|
|
143
|
+
const defaultOutputDir = join(root, 'distributions', 'kernel', version);
|
|
144
|
+
const outputDir = flagValue(args, 'output') ?? defaultOutputDir;
|
|
144
145
|
const stageDir = join(outputDir, 'package');
|
|
145
146
|
const tarball = join(outputDir, kernelTarballName(version));
|
|
146
147
|
heading(`Packaging Microteia kernel ${version} (contract ${kernelVersion})`);
|
|
@@ -264,6 +265,13 @@ export const pack = async (args) => {
|
|
|
264
265
|
`size: ${(digest.bytes / 1_000_000).toFixed(1)} MB, sha256 ${digest.sha256.slice(0, 12)}…`,
|
|
265
266
|
]);
|
|
266
267
|
blank();
|
|
268
|
+
if (outputDir !== defaultOutputDir) {
|
|
269
|
+
// O `publish` lê só `distributions/kernel/<versão>/`: o pacote escrito em outro lugar não é achado,
|
|
270
|
+
// e sem este aviso a recusa dele parecia um pacote que nunca foi feito.
|
|
271
|
+
warn(`--output wrote it outside ${relative(root, defaultOutputDir)}/, and "microteia publish" reads only there.`);
|
|
272
|
+
info(' Copy the folder there to publish it, or install the file directly (below).');
|
|
273
|
+
blank();
|
|
274
|
+
}
|
|
267
275
|
info('Publish it with:');
|
|
268
276
|
bullets(['microteia publish --dry-run (says what would go up)', 'microteia publish']);
|
|
269
277
|
blank();
|
|
@@ -178,6 +178,8 @@ export const consoleReadme = (version) => `# Microteia Console ${version}
|
|
|
178
178
|
Installs and updates Microteia in a Firebase project you own. It has no npm dependencies of its own —
|
|
179
179
|
what it needs are command-line tools that are already on the machine, listed at the bottom.
|
|
180
180
|
|
|
181
|
+
📖 **Documentation: [doc.microteia.com](https://doc.microteia.com)**
|
|
182
|
+
|
|
181
183
|
## Installing it
|
|
182
184
|
|
|
183
185
|
npm i -g ${CONSOLE_PACKAGE_NAME}
|
package/lib/vendor/publish.js
CHANGED
|
@@ -6,7 +6,7 @@ import { CLI_VERSION, repoRoot } from '../shared/paths.js';
|
|
|
6
6
|
import { capture, captureOrThrow, run } from '../shared/run.js';
|
|
7
7
|
import { blank, bullets, heading, info, pending, step, success, warn } from '../shared/ui.js';
|
|
8
8
|
import { digestMismatch, digestOfFile } from '../shared/digest.js';
|
|
9
|
-
import { emptyIndex, entryOf, kernelTarballName, parseIndex, publishedBefore, serializeIndex, withEntry, } from '../kernel/channel.js';
|
|
9
|
+
import { emptyIndex, entryOf, kernelTarballName, parseIndex, publishedBefore, serializeIndex, withEntry, STABLE_CHANNEL, } from '../kernel/channel.js';
|
|
10
10
|
import { CONSOLE_CHANGELOG, KERNEL_CHANGELOG, changelogEntries, readChangelog } from './changelogs.js';
|
|
11
11
|
import { CONSOLE_PACKAGE_NAME } from './packageTemplates.js';
|
|
12
12
|
import { assertInsideRepository } from './repository.js';
|
|
@@ -72,16 +72,21 @@ const readPublishedIndex = async (repo) => {
|
|
|
72
72
|
const text = Buffer.from(contents.content, 'base64').toString('utf8');
|
|
73
73
|
return { index: parseIndex(text, `${repo}/${INDEX_PATH}`), sha: contents.sha, empty: false };
|
|
74
74
|
};
|
|
75
|
+
/** O caminho do README DENTRO do repositório de releases. */
|
|
76
|
+
const README_PATH = 'README.md';
|
|
75
77
|
/**
|
|
76
78
|
* O que fica na raiz do repositório de releases, para quem chegar nele sem contexto.
|
|
77
79
|
*
|
|
78
|
-
*
|
|
79
|
-
*
|
|
80
|
+
* É documento GERADO, e nenhuma pessoa escreve naquele repositório: por isso é sobreposto sempre que
|
|
81
|
+
* diverge deste texto. Escrito só ao semear, ele congelava no texto do primeiro `publish` — o link da
|
|
82
|
+
* documentação oficial nunca chegaria ao repositório que já existia.
|
|
80
83
|
*/
|
|
81
84
|
const releasesReadme = () => `# Microteia — releases
|
|
82
85
|
|
|
83
86
|
Distribution artifacts for the Microteia platform. **No source lives here**: what travels is compiled.
|
|
84
87
|
|
|
88
|
+
📖 **Documentation: [doc.microteia.com](https://doc.microteia.com)**
|
|
89
|
+
|
|
85
90
|
- \`kernel/index.json\` — the catalog the Microteia Console reads. It lists every published version with
|
|
86
91
|
its size and sha256, and the absolute URLs of the files that carry it.
|
|
87
92
|
- Each version is a release tagged \`kernel-<version>\`, carrying the package and its manifest.
|
|
@@ -94,6 +99,17 @@ Nobody downloads from here by hand:
|
|
|
94
99
|
The Console reads the catalog, verifies what it downloads against the sha256 above, and only then puts
|
|
95
100
|
it in place.
|
|
96
101
|
`;
|
|
102
|
+
const writeReadme = async (repo, sha, message) => {
|
|
103
|
+
const content = Buffer.from(releasesReadme(), 'utf8').toString('base64');
|
|
104
|
+
const args = [
|
|
105
|
+
'api', '--method', 'PUT', `repos/${repo}/contents/${README_PATH}`,
|
|
106
|
+
'-f', `message=${message}`,
|
|
107
|
+
'-f', `content=${content}`,
|
|
108
|
+
];
|
|
109
|
+
if (sha !== null)
|
|
110
|
+
args.push('-f', `sha=${sha}`);
|
|
111
|
+
await captureOrThrow('gh', args);
|
|
112
|
+
};
|
|
97
113
|
/**
|
|
98
114
|
* Dá ao repositório o primeiro commit, porque sem ele não existe release.
|
|
99
115
|
*
|
|
@@ -104,14 +120,20 @@ it in place.
|
|
|
104
120
|
*/
|
|
105
121
|
const seedRepository = async (repo) => {
|
|
106
122
|
pending(`${repo} has no commits yet — writing its README first`);
|
|
107
|
-
|
|
108
|
-
await captureOrThrow('gh', [
|
|
109
|
-
'api', '--method', 'PUT', `repos/${repo}/contents/README.md`,
|
|
110
|
-
'-f', 'message=the releases repository',
|
|
111
|
-
'-f', `content=${content}`,
|
|
112
|
-
]);
|
|
123
|
+
await writeReadme(repo, null, 'the releases repository');
|
|
113
124
|
success(`seeded ${repo}`);
|
|
114
125
|
};
|
|
126
|
+
const staleReadme = async (repo) => {
|
|
127
|
+
const result = await capture('gh', ['api', `repos/${repo}/contents/${README_PATH}`]);
|
|
128
|
+
if (result.code !== 0) {
|
|
129
|
+
if (`${result.stderr}${result.stdout}`.includes('HTTP 404'))
|
|
130
|
+
return { sha: null };
|
|
131
|
+
throw new Error(`Could not read ${README_PATH} from ${repo}.\n${result.stderr.trim() || result.stdout.trim()}`);
|
|
132
|
+
}
|
|
133
|
+
const contents = JSON.parse(result.stdout);
|
|
134
|
+
const text = Buffer.from(contents.content, 'base64').toString('utf8');
|
|
135
|
+
return text === releasesReadme() ? null : { sha: contents.sha };
|
|
136
|
+
};
|
|
115
137
|
const writePublishedIndex = async (repo, index, sha, version) => {
|
|
116
138
|
const content = Buffer.from(serializeIndex(index), 'utf8').toString('base64');
|
|
117
139
|
const args = [
|
|
@@ -178,6 +200,7 @@ const publishKernel = async (root, version, repo, dryRun) => {
|
|
|
178
200
|
const directory = join(root, 'distributions', 'kernel', version);
|
|
179
201
|
const manifestPath = join(directory, 'manifest.json');
|
|
180
202
|
const published = await readPublishedIndex(repo);
|
|
203
|
+
const readme = published.empty ? null : await staleReadme(repo);
|
|
181
204
|
/**
|
|
182
205
|
* O corpo do release cobre o INTERVALO, e não só a versão que está saindo. Um pacote leva tudo o que
|
|
183
206
|
* veio antes dele; um release que só cita a própria entrada esconde as versões que foram empacotadas e
|
|
@@ -190,13 +213,19 @@ const publishKernel = async (root, version, repo, dryRun) => {
|
|
|
190
213
|
manifest: assetUrl(repo, version, 'manifest.json'),
|
|
191
214
|
}, new Date().toISOString()));
|
|
192
215
|
success(`kernel ${version} verified: ${manifest.tarball?.bytes} bytes, sha256 ${manifest.tarball?.sha256.slice(0, 12)}…`);
|
|
193
|
-
|
|
216
|
+
const promotes = next.channels[STABLE_CHANNEL] === version;
|
|
217
|
+
info(promotes
|
|
218
|
+
? ` tag ${tagOf(version)} in ${repo}, and ${INDEX_PATH} promoting it to "latest"`
|
|
219
|
+
: ` tag ${tagOf(version)} in ${repo}, and ${INDEX_PATH} listing it — "latest" stays at `
|
|
220
|
+
+ `${next.channels[STABLE_CHANNEL]}, which is newer`);
|
|
194
221
|
info(notes.versions.length === 1
|
|
195
222
|
? ` release notes: the ${version} entry of ${KERNEL_CHANGELOG}`
|
|
196
223
|
: ` release notes: ${notes.versions.join(', ')} — every entry since ${since ?? 'the first one'}`);
|
|
197
224
|
if (dryRun) {
|
|
198
225
|
if (published.empty)
|
|
199
226
|
info(` ${repo} has no commits, so it would be seeded with a README first`);
|
|
227
|
+
if (readme !== null)
|
|
228
|
+
info(` ${README_PATH} in ${repo} differs from the current text, so it would be rewritten`);
|
|
200
229
|
info(` the catalog would go from ${published.index.kernel.length} to ${next.kernel.length} version(s)`);
|
|
201
230
|
return;
|
|
202
231
|
}
|
|
@@ -231,7 +260,19 @@ const publishKernel = async (root, version, repo, dryRun) => {
|
|
|
231
260
|
success(`assets published for ${version}`);
|
|
232
261
|
// O catálogo por último, sempre. Ver a nota de ordem no topo deste módulo.
|
|
233
262
|
await writePublishedIndex(repo, next, published.sha, version);
|
|
234
|
-
success(
|
|
263
|
+
success(promotes
|
|
264
|
+
? `${INDEX_PATH} updated — "microteia unpack --version latest" now takes ${version}`
|
|
265
|
+
: `${INDEX_PATH} updated — "microteia unpack --version ${version}" takes it; "latest" stays at `
|
|
266
|
+
+ `${next.channels[STABLE_CHANNEL]}`);
|
|
267
|
+
/**
|
|
268
|
+
* Depois do catálogo, e sem derrubar a publicação: o README não é o que alguém instala, e uma
|
|
269
|
+
* falha nele não pode desfazer — nem fazer parecer desfeito — um release que já está no ar.
|
|
270
|
+
*/
|
|
271
|
+
if (readme !== null) {
|
|
272
|
+
await writeReadme(repo, readme.sha, `README for kernel ${version}`)
|
|
273
|
+
.then(() => success(`${README_PATH} in ${repo} rewritten`))
|
|
274
|
+
.catch((error) => warn(`${README_PATH} in ${repo} could not be rewritten: ${error instanceof Error ? error.message : String(error)}`));
|
|
275
|
+
}
|
|
235
276
|
}
|
|
236
277
|
finally {
|
|
237
278
|
await rm(notesRoot, { recursive: true, force: true });
|