@microteia/console 2.21.0 → 2.23.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,32 @@
22
22
  - fix(command): what was broken, and what it did to whoever hit it.
23
23
  -->
24
24
 
25
+ #### [2.23.0] — 2026-10-07
26
+
27
+ - fix(provision): `microteia provision` run on its own now probes the deployed stamps itself instead of concluding that nothing changed. It could only compare stamps with the map `deploy` hands it, and running alone — the path of anyone who has just swapped kernels with `unpack` — there was no map: `outdatedByStamp` drew no conclusion, the function stayed out of the queue, and the record said "applied" because it genuinely was running, from the previous package. An office upgraded this way kept serving an extension's old Cloud Function with nothing anywhere saying so; the symptom surfaced in the client, not in the Console. The queue is now everything the installed extensions claim, and `deployFunctionsThatChanged` — the same probe `deploy` uses — decides what actually goes up: one reference function per stamp, and the rest left alone when it comes back unchanged. What the probe skips counts as satisfied, or a complete installation would stay "pending" forever.
28
+
29
+ #### [2.22.1] — 2026-09-19
30
+
31
+ - fix(cli/publish): the release notes now carry every changelog entry since the last PUBLISHED version,
32
+ not only the one being packaged. A version can be packaged and never published — the catalog is what
33
+ says what went up, and the package being published contains all of them. Publishing 9.66.0 over a
34
+ catalog whose newest entry was 9.64.0 created a release that carries 9.65.0 and did not quote a line
35
+ of it, and a release is immutable: no later release goes back to tell what those versions changed.
36
+ The cut now starts at `publishedBefore(index, version)`, so it happens after the catalog is read
37
+ rather than while the package is; the first publish, which has no interval, still cuts its own entry
38
+ alone. The command prints which versions the notes carry, in the dry run too.
39
+
40
+ #### [2.22.0] — 2026-09-10
41
+
42
+ - feat(deploy): the reachability check now covers HTTP endpoints, not only callables.
43
+ `callableFunctionNames` matches `onRequest` as well, because the list means "what the outside world
44
+ calls" and an HTTP endpoint depends on public invocation even more than a callable does — a blocked
45
+ invoker grant answers 403 with no CORS header, and the client reports a connection error. Without
46
+ this, the platform's first `onRequest` would have gone up unverified.
47
+ - fix(deploy): the `functions-invoker` prerequisite no longer promises that every reachable function
48
+ rejects a caller with no signed-in user. That is true of a callable and false of an endpoint that
49
+ authenticates by token, and the sentence was about to be read by whoever installs the agent access.
50
+
25
51
  #### [2.21.0] — 2026-09-09
26
52
 
27
53
  - fix(cli/pack): reading-only examples are left out of the first-party backend accounting.
package/README.md CHANGED
@@ -1,4 +1,4 @@
1
- # Microteia Console 2.21.0
1
+ # Microteia Console 2.23.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.
@@ -10,6 +10,7 @@ import { changedParameters, functionsDeclaringParameters, parameterDigests, pars
10
10
  import { ENVIRONMENT_FILE, readEnvironment, writeEnvironment } from './environment.js';
11
11
  import { FIREBASE_BIN } from './firebaseCli.js';
12
12
  import { deployFunctionsOneByOne, reportFailures, reportNotices } from './deployFunctions.js';
13
+ import { deployFunctionsThatChanged } from './functionsUpToDate.js';
13
14
  import { clearRegionOrphans, listDeployedFunctions } from './regionOrphans.js';
14
15
  import { outdatedByStamp } from './functionsUpToDate.js';
15
16
  import { checkCallables, invokerGrantCommand } from './callableAccess.js';
@@ -363,7 +364,28 @@ export const provisionBackend = async (options) => {
363
364
  info(' Cloud Function at ITS deploy, so a function already running keeps the previous one.');
364
365
  blank();
365
366
  }
366
- const queued = wantedFunctions.filter((name) => options.all === true || !appliedNames.has(name) || staleFunctions.has(name));
367
+ /**
368
+ * De onde vem a conclusão sobre o que mudou — e são DOIS caminhos, porque as duas situações são
369
+ * diferentes de verdade.
370
+ *
371
+ * Vindo do `deploy`, o carimbo de agora já foi observado: as functions do kernel acabaram de subir,
372
+ * e as de extensão estão no MESMO codebase, então `outdatedByStamp` decide sem sondar nada.
373
+ *
374
+ * Rodando SOZINHO — que é o caminho de quem acabou de trocar de kernel por `unpack` — não há mapa
375
+ * nenhum, e era aqui que estava o defeito: `outdatedByStamp` não tinha com o que comparar, não
376
+ * tirava conclusão, e a function de extensão cujo CÓDIGO mudou ficava de fora da fila para sempre.
377
+ * O registro dizia "aplicada", porque ela de fato estava no ar — só que da versão anterior. Nada
378
+ * avisava, e o sintoma aparecia no cliente, não aqui.
379
+ *
380
+ * Então a fila passa a ser TUDO o que as extensões pedem, e quem decide o que realmente sobe é
381
+ * `deployFunctionsThatChanged` — a mesma sondagem do `deploy`, pela mesma função: deploya uma
382
+ * referência por carimbo e, se o carimbo voltar igual, deixa as outras em paz. Custa uma function
383
+ * por execução; não custar nada era o que fazia a conta ficar errada.
384
+ */
385
+ const probing = options.refreshedStamps === undefined;
386
+ const queued = probing
387
+ ? wantedFunctions
388
+ : wantedFunctions.filter((name) => options.all === true || !appliedNames.has(name) || staleFunctions.has(name));
367
389
  /**
368
390
  * A recusa do portão, cobrada de quem está na fila — e só dela.
369
391
  *
@@ -382,6 +404,8 @@ export const provisionBackend = async (options) => {
382
404
  info(' they are the only ones held back. Fix the guard and run "microteia provision" again.');
383
405
  outcome.failed.push(...refused);
384
406
  }
407
+ /** O que já estava no ar com ESTE código, e por isso não subiu. Conta como satisfeito. */
408
+ let skippedNow = [];
385
409
  if (toDeploy.length === 0) {
386
410
  // Anunciar "as 0 functions já estão deployadas" seria o preço de ter tirado o desvio, e não é
387
411
  // preciso pagá-lo: quando não há exigência nenhuma, quem já falou foi a linha lá em cima.
@@ -405,17 +429,27 @@ export const provisionBackend = async (options) => {
405
429
  // A extensão que mudou de região deixa a cópia antiga para trás igual ao kernel, e ali o efeito
406
430
  // é o mesmo: deploy recusado em toda tentativa, e gatilho disparando em duas regiões.
407
431
  await clearRegionOrphans(projectId, toDeploy, region, options.prune, await projectFunctions());
408
- info(` deploying ${toDeploy.length} function(s), one at a time…`);
409
- const report = await deployFunctionsOneByOne(toDeploy, {
410
- projectId, cwd: install, delaySeconds, force: false,
411
- });
432
+ const deployOptions = { projectId, cwd: install, delaySeconds, force: false };
433
+ if (!probing)
434
+ info(` deploying ${toDeploy.length} function(s), one at a time…`);
435
+ const report = probing
436
+ ? await deployFunctionsThatChanged(toDeploy, deployOptions, {
437
+ deployed: await projectFunctions(),
438
+ region,
439
+ all: options.all === true,
440
+ })
441
+ : { ...await deployFunctionsOneByOne(toDeploy, deployOptions), skipped: [] };
442
+ skippedNow = report.skipped;
412
443
  outcome.deployed = report.deployed;
413
444
  outcome.failed.push(...report.failed.map((entry) => entry.name));
414
445
  reportFailures(report.failed);
415
446
  reportNotices(report.notices);
416
447
  await checkNewCallables([...(manifest.callables ?? []), ...office.callables], report.deployed, projectId, region);
417
448
  }
418
- const deployedNow = new Set([...appliedNames, ...outcome.deployed]);
449
+ // O PULADO entra junto: ele não subiu agora porque o projeto já roda exatamente este código, que é
450
+ // o mesmo que estar satisfeito. Deixá-lo de fora manteria a extensão "pendente" para sempre numa
451
+ // instalação em que não falta nada.
452
+ const deployedNow = new Set([...appliedNames, ...outcome.deployed, ...skippedNow]);
419
453
  for (const claim of valid) {
420
454
  const satisfied = (claim.backend.functions ?? []).every((name) => deployedNow.has(name));
421
455
  if (satisfied) {
@@ -70,6 +70,17 @@ export declare const entryOf: (manifest: KernelManifest, urls: {
70
70
  * `9.8.2` significa. Um número novo custa um minuto.
71
71
  */
72
72
  export declare const withEntry: (index: ChannelIndex, entry: ChannelEntry) => ChannelIndex;
73
+ /**
74
+ * A última versão publicada ABAIXO desta — o começo do intervalo que o release novo cobre.
75
+ *
76
+ * É "abaixo desta" e não "o topo do catálogo" porque publicar fora de ordem é possível, e o que o
77
+ * release precisa descrever é o que ele acrescenta a quem já tinha a anterior. Quem já tinha a anterior
78
+ * está na maior versão MENOR que esta.
79
+ *
80
+ * Nada publicado ainda é `undefined`, e não a menor versão do histórico: o arquivo de histórico começa
81
+ * antes do catálogo, e a primeira publicação não descreve o que veio antes dela.
82
+ */
83
+ export declare const publishedBefore: (index: ChannelIndex, version: string) => string | undefined;
73
84
  /**
74
85
  * Resolve o que foi pedido — `latest` ou um número — nomeando o que existe quando não acha.
75
86
  *
@@ -126,6 +126,20 @@ export const withEntry = (index, entry) => {
126
126
  kernel: [entry, ...index.kernel].sort(byVersionDescending),
127
127
  };
128
128
  };
129
+ /**
130
+ * A última versão publicada ABAIXO desta — o começo do intervalo que o release novo cobre.
131
+ *
132
+ * É "abaixo desta" e não "o topo do catálogo" porque publicar fora de ordem é possível, e o que o
133
+ * release precisa descrever é o que ele acrescenta a quem já tinha a anterior. Quem já tinha a anterior
134
+ * está na maior versão MENOR que esta.
135
+ *
136
+ * Nada publicado ainda é `undefined`, e não a menor versão do histórico: o arquivo de histórico começa
137
+ * antes do catálogo, e a primeira publicação não descreve o que veio antes dela.
138
+ */
139
+ export const publishedBefore = (index, version) => [...index.kernel]
140
+ .sort(byVersionDescending)
141
+ .find((entry) => compareVersions(entry.version, version) < 0)
142
+ ?.version;
129
143
  /**
130
144
  * Resolve o que foi pedido — `latest` ou um número — nomeando o que existe quando não acha.
131
145
  *
@@ -127,10 +127,20 @@ export declare const MANUAL_PREREQUISITES: KernelPrerequisite[];
127
127
  */
128
128
  export declare const exportedFunctionNames: (indexSource: string) => string[];
129
129
  /**
130
- * As callables são as functions que o NAVEGADOR chama. Elas se distinguem das de gatilho por uma
131
- * razão prática: só elas podem ser verificadas de fora, e só nelas a permissão de invocação pública
132
- * importa. Sem ela a aplicação abre e toda chamada ao servidor falha — parecendo erro de CORS, que
133
- * manda procurar no lugar errado.
130
+ * As functions que o mundo de FORA chama — e é isso que a lista significa, não o construtor delas.
131
+ *
132
+ * Elas se distinguem das de gatilho por uma razão prática: só elas podem ser verificadas de fora, e
133
+ * só nelas a permissão de invocação pública importa. Sem ela a aplicação abre e toda chamada ao
134
+ * servidor falha — parecendo erro de CORS, que manda procurar no lugar errado.
135
+ *
136
+ * `onRequest` entra pela MESMA razão, e não por generosidade: um endpoint HTTP é ainda mais
137
+ * dependente da invocação pública que uma callable, porque quem o consome pode não ser um navegador
138
+ * nosso — é o caso do endpoint MCP, que um cliente de IA chama. O preflight de `callableAccess`
139
+ * serve os dois sem mudar uma linha: ele pergunta `OPTIONS` e confere o cabeçalho de CORS, que um
140
+ * `onRequest` com `cors: true` responde igual.
141
+ *
142
+ * O que NÃO se confunde com isto é o portão de instalação (`functionGate`): lá a distinção entre
143
+ * `onCall` e `onRequest` importa muito, porque uma tem escritório no chamador e a outra não.
134
144
  */
135
145
  export declare const callableFunctionNames: (source: string) => string[];
136
146
  /**
@@ -86,7 +86,9 @@ export const MANUAL_PREREQUISITES = [
86
86
  + 'CORS error. The fix is one command per function, and the deploy prints the exact list: gcloud '
87
87
  + 'run services add-iam-policy-binding <function-name-in-lowercase> --region=<region> '
88
88
  + '--project=<projectId> --member=allUsers --role=roles/run.invoker. Unauthenticated invocation '
89
- + 'is not open access: every callable still rejects callers with no signed-in user.',
89
+ + 'is not open access: reaching a function is not the same as being allowed to use it. A callable '
90
+ + 'rejects any caller with no signed-in user, and an HTTP endpoint authenticates every request '
91
+ + 'with the credential it was given and refuses everything else.',
90
92
  optional: true,
91
93
  },
92
94
  {
@@ -107,12 +109,23 @@ export const exportedFunctionNames = (indexSource) => [...indexSource.matchAll(/
107
109
  .map((name) => name.trim())
108
110
  .filter((name) => name !== '');
109
111
  /**
110
- * As callables são as functions que o NAVEGADOR chama. Elas se distinguem das de gatilho por uma
111
- * razão prática: só elas podem ser verificadas de fora, e só nelas a permissão de invocação pública
112
- * importa. Sem ela a aplicação abre e toda chamada ao servidor falha — parecendo erro de CORS, que
113
- * manda procurar no lugar errado.
112
+ * As functions que o mundo de FORA chama — e é isso que a lista significa, não o construtor delas.
113
+ *
114
+ * Elas se distinguem das de gatilho por uma razão prática: só elas podem ser verificadas de fora, e
115
+ * só nelas a permissão de invocação pública importa. Sem ela a aplicação abre e toda chamada ao
116
+ * servidor falha — parecendo erro de CORS, que manda procurar no lugar errado.
117
+ *
118
+ * `onRequest` entra pela MESMA razão, e não por generosidade: um endpoint HTTP é ainda mais
119
+ * dependente da invocação pública que uma callable, porque quem o consome pode não ser um navegador
120
+ * nosso — é o caso do endpoint MCP, que um cliente de IA chama. O preflight de `callableAccess`
121
+ * serve os dois sem mudar uma linha: ele pergunta `OPTIONS` e confere o cabeçalho de CORS, que um
122
+ * `onRequest` com `cors: true` responde igual.
123
+ *
124
+ * O que NÃO se confunde com isto é o portão de instalação (`functionGate`): lá a distinção entre
125
+ * `onCall` e `onRequest` importa muito, porque uma tem escritório no chamador e a outra não.
114
126
  */
115
- export const callableFunctionNames = (source) => [...source.matchAll(/export\s+const\s+([a-zA-Z0-9_]+)\s*=\s*onCall\b/g)].map((match) => match[1]);
127
+ export const callableFunctionNames = (source) => [...source.matchAll(/export\s+const\s+([a-zA-Z0-9_]+)\s*=\s*(?:onCall|onRequest)\b/g)]
128
+ .map((match) => match[1]);
116
129
  /**
117
130
  * Os parâmetros que o backend declara com `defineString`. O `firebase-functions` os valida
118
131
  * GLOBALMENTE, e não por function: um parâmetro sem valor e sem default recusa o deploy de TODAS as
@@ -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.21.0";
90
+ export declare const CLI_VERSION = "2.23.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.21.0';
97
+ export const CLI_VERSION = '2.23.0';
@@ -38,16 +38,28 @@ export declare const changelogProblems: (file: string, contents: string, version
38
38
  */
39
39
  export declare const readChangelog: (directory: string, file: string, version: string) => Promise<string>;
40
40
  /**
41
- * A entrada de UMA versão, para ser o corpo do release publicado.
41
+ * As entradas que o release publicado carrega: a da versão que está saindo, e a de TODA versão entre ela
42
+ * e a última já publicada.
42
43
  *
43
44
  * O texto do release não é escrito à mão em lugar nenhum: ele é o mesmo parágrafo que já foi conferido
44
45
  * pelo `pack`, recortado daqui. Dois textos sobre a mesma versão divergem — e o que divergiria é
45
46
  * justamente o que fica visível para quem procura o que mudou.
46
47
  *
47
- * Recorta do cabeçalho da versão até o próximo cabeçalho, e não até o fim: o que vem depois é o
48
- * histórico anterior, e ele já está publicado nos releases dele.
48
+ * O recorte de uma entrada só perdia o intervalo. Publicar a 9.66.0 quando a última publicada é a
49
+ * 9.64.0 — porque a 9.65.0 foi empacotada e não subiu — entrega um release que CONTÉM a 9.65.0 e não
50
+ * menciona uma linha dela: para quem procura o que mudou, aquelas alterações nunca existiram, e não há
51
+ * release posterior que as traga de volta. O release descreve o que ele leva, e ele leva tudo desde a
52
+ * última publicação.
53
+ *
54
+ * Sem `since` é a primeira publicação, e aí o recorte é só da própria versão: o histórico do arquivo é
55
+ * anterior ao catálogo, e despejá-lo inteiro não seria descrever o que subiu.
49
56
  */
50
- export declare const changelogEntry: (contents: string, version: string) => string;
57
+ export interface ChangelogNotes {
58
+ body: string;
59
+ /** As versões recortadas, da mais recente para a mais antiga — o que o release diz que cobre. */
60
+ versions: string[];
61
+ }
62
+ export declare const changelogEntries: (contents: string, version: string, since?: string) => ChangelogNotes;
51
63
  export interface ChangelogRefresh {
52
64
  written: string[];
53
65
  missing: string[];
@@ -83,26 +83,25 @@ export const readChangelog = async (directory, file, version) => {
83
83
  }
84
84
  return contents;
85
85
  };
86
- /**
87
- * A entrada de UMA versão, para ser o corpo do release publicado.
88
- *
89
- * O texto do release não é escrito à mão em lugar nenhum: ele é o mesmo parágrafo que já foi conferido
90
- * pelo `pack`, recortado daqui. Dois textos sobre a mesma versão divergem — e o que divergiria é
91
- * justamente o que fica visível para quem procura o que mudou.
92
- *
93
- * Recorta do cabeçalho da versão até o próximo cabeçalho, e não até o fim: o que vem depois é o
94
- * histórico anterior, e ele já está publicado nos releases dele.
95
- */
96
- export const changelogEntry = (contents, version) => {
86
+ export const changelogEntries = (contents, version, since) => {
97
87
  const body = withoutTemplates(contents);
98
88
  const headings = [...body.matchAll(ENTRY)];
99
- const index = headings.findIndex((match) => match[1] === version);
100
- if (index === -1) {
89
+ const first = headings.findIndex((match) => match[1] === version);
90
+ if (first === -1) {
101
91
  throw new Error(`The changelog has no entry for ${version}.`);
102
92
  }
103
- const start = headings[index].index;
104
- const end = index + 1 < headings.length ? headings[index + 1].index : body.length;
105
- return `${body.slice(start, end).trim()}\n`;
93
+ let last = first;
94
+ while (since !== undefined
95
+ && last + 1 < headings.length
96
+ && compareVersions(headings[last + 1][1], since) > 0) {
97
+ last += 1;
98
+ }
99
+ const start = headings[first].index;
100
+ const end = last + 1 < headings.length ? headings[last + 1].index : body.length;
101
+ return {
102
+ body: `${body.slice(start, end).trim()}\n`,
103
+ versions: headings.slice(first, last + 1).map((match) => match[1]),
104
+ };
106
105
  };
107
106
  /**
108
107
  * Põe os dois históricos na raiz da instalação, SOBREPONDO — pela mesma razão que o `MICROTEIA.md` é
@@ -32,6 +32,7 @@ const EXAMPLES_DIR = 'examples';
32
32
  */
33
33
  export const EXAMPLE_SOURCES = {
34
34
  'automation-example': 'examples/automation-example',
35
+ agent: 'agent.ts',
35
36
  brasil: 'brasil.ts',
36
37
  calendar: 'calendar.ts',
37
38
  dashboard: 'dashboard.ts',
@@ -6,8 +6,8 @@ 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, serializeIndex, withEntry, } from '../kernel/channel.js';
10
- import { CONSOLE_CHANGELOG, KERNEL_CHANGELOG, changelogEntry, readChangelog } from './changelogs.js';
9
+ import { emptyIndex, entryOf, kernelTarballName, parseIndex, publishedBefore, serializeIndex, withEntry, } from '../kernel/channel.js';
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';
13
13
  /**
@@ -170,20 +170,30 @@ const readKernelPackage = async (root, version) => {
170
170
  + 'One of the two was rebuilt without the other. Run "npm run pack:kernel" again.');
171
171
  }
172
172
  const changelog = await readChangelog(root, KERNEL_CHANGELOG, version);
173
- return { manifest, tarball, entry: changelogEntry(changelog, version) };
173
+ return { manifest, tarball, changelog };
174
174
  };
175
175
  const publishKernel = async (root, version, repo, dryRun) => {
176
176
  await assertGitHubCli();
177
- const { manifest, tarball, entry } = await readKernelPackage(root, version);
177
+ const { manifest, tarball, changelog } = await readKernelPackage(root, version);
178
178
  const directory = join(root, 'distributions', 'kernel', version);
179
179
  const manifestPath = join(directory, 'manifest.json');
180
180
  const published = await readPublishedIndex(repo);
181
+ /**
182
+ * O corpo do release cobre o INTERVALO, e não só a versão que está saindo. Um pacote leva tudo o que
183
+ * veio antes dele; um release que só cita a própria entrada esconde as versões que foram empacotadas e
184
+ * não publicadas, e nenhum release posterior volta para contá-las.
185
+ */
186
+ const since = publishedBefore(published.index, version);
187
+ const notes = changelogEntries(changelog, version, since);
181
188
  const next = withEntry(published.index, entryOf(manifest, {
182
189
  tarball: assetUrl(repo, version, kernelTarballName(version)),
183
190
  manifest: assetUrl(repo, version, 'manifest.json'),
184
191
  }, new Date().toISOString()));
185
192
  success(`kernel ${version} verified: ${manifest.tarball?.bytes} bytes, sha256 ${manifest.tarball?.sha256.slice(0, 12)}…`);
186
193
  info(` tag ${tagOf(version)} in ${repo}, and ${INDEX_PATH} promoting it to "latest"`);
194
+ info(notes.versions.length === 1
195
+ ? ` release notes: the ${version} entry of ${KERNEL_CHANGELOG}`
196
+ : ` release notes: ${notes.versions.join(', ')} — every entry since ${since ?? 'the first one'}`);
187
197
  if (dryRun) {
188
198
  if (published.empty)
189
199
  info(` ${repo} has no commits, so it would be seeded with a README first`);
@@ -192,9 +202,9 @@ const publishKernel = async (root, version, repo, dryRun) => {
192
202
  }
193
203
  if (published.empty)
194
204
  await seedRepository(repo);
195
- const notes = await mkdtemp(join(tmpdir(), 'microteia-publish-'));
196
- const notesFile = join(notes, 'notes.md');
197
- await writeFile(notesFile, entry);
205
+ const notesRoot = await mkdtemp(join(tmpdir(), 'microteia-publish-'));
206
+ const notesFile = join(notesRoot, 'notes.md');
207
+ await writeFile(notesFile, notes.body);
198
208
  try {
199
209
  if (await releaseExists(repo, version)) {
200
210
  /**
@@ -224,7 +234,7 @@ const publishKernel = async (root, version, repo, dryRun) => {
224
234
  success(`${INDEX_PATH} updated — "microteia unpack --version latest" now takes ${version}`);
225
235
  }
226
236
  finally {
227
- await rm(notes, { recursive: true, force: true });
237
+ await rm(notesRoot, { recursive: true, force: true });
228
238
  }
229
239
  };
230
240
  const publishedConsoleVersion = async (version) => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@microteia/console",
3
- "version": "2.21.0",
3
+ "version": "2.23.0",
4
4
  "description": "Install and update Microteia in a Firebase project you own.",
5
5
  "license": "UNLICENSED",
6
6
  "type": "module",