@microteia/console 2.7.0 → 2.7.2

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,33 @@
22
22
  - fix(command): what was broken, and what it did to whoever hit it.
23
23
  -->
24
24
 
25
+ #### [2.7.2] — 2026-08-25
26
+
27
+ - feat(vendor/exampleSources): stages `automation-example` alongside the others, so the workspace ships a complete automation to read. Every example that travelled carried `workflows: []`, and automation was the one mechanism an author had only prose for.
28
+
29
+ #### [2.7.1] — 2026-08-24
30
+
31
+ - fix(unpack): the default catalog URL is `https://microteia.github.io/releases/kernel/index.json`.
32
+ `2.7.0` shipped pointing at a domain that was never registered, so `--version` on it fails with
33
+ "Could not read the Microteia catalog" and only `--kernel <package>.tgz` works. The choice of host is
34
+ not the point; where the INDEX lives is, because that one URL is the only address a Console knows by
35
+ heart — every other URL is data inside the catalog. A `github.io` address is free, exists today, and
36
+ is forward compatible: adding a custom domain to the Pages site later makes GitHub redirect the old
37
+ address to it, and `fetch` follows redirects, so a Console installed now keeps working. Starting at a
38
+ domain would have required owning it first and offered no way back.
39
+ - fix(publish): a repository with no commits at all is the FIRST publish, not a failure. The contents
40
+ API answers `404 Not Found` when the file is missing and `404 This repository is empty` when there is
41
+ no commit — and the second is the state of a releases repository just created, which is exactly when
42
+ the first publish happens. Matching on the message refused it. A 404 now asks one more question, about
43
+ the repository itself, which separates "the catalog is not there yet" (go on) from "the repository is
44
+ not there" (stop) instead of guessing from prose.
45
+ - fix(publish): a releases repository with no commits is seeded before the release is created. GitHub
46
+ refuses to create a release or a tag in an empty repository with `422 Repository is empty`, and it
47
+ refuses it AFTER the package has been verified — the worst moment to find out. The first publish now
48
+ writes the repository's `README.md`, which is a write the command was going to make three steps later
49
+ anyway, and which the repository should carry regardless. It is written once and never overwritten:
50
+ what promises to be current is the catalog.
51
+
25
52
  #### [2.7.0] — 2026-08-24
26
53
 
27
54
  - feat(unpack): `--version latest` and `--version <x.y.z>` take a platform version from the published
package/README.md CHANGED
@@ -1,4 +1,4 @@
1
- # Microteia Console 2.7.0
1
+ # Microteia Console 2.7.2
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.
@@ -16,7 +16,7 @@ import type { ChannelEntry, ChannelIndex } from '../kernel/channel.js';
16
16
  * mudar de hospedagem — ou passar a emitir URL assinada por escritório, que o host de hoje não faz —
17
17
  * reescreve o índice, e um Console já instalado continua achando o catálogo no mesmo lugar.
18
18
  */
19
- export declare const DEFAULT_INDEX_URL = "https://dist.microteia.app/kernel/index.json";
19
+ export declare const DEFAULT_INDEX_URL = "https://microteia.github.io/releases/kernel/index.json";
20
20
  export declare const indexUrl: () => string;
21
21
  /**
22
22
  * O cache fica FORA da instalação, de propósito: `.microteia/` é apagado por inteiro a cada troca de
@@ -22,7 +22,7 @@ import { LATEST, kernelTarballName, parseIndex, resolveVersion } from '../kernel
22
22
  * mudar de hospedagem — ou passar a emitir URL assinada por escritório, que o host de hoje não faz —
23
23
  * reescreve o índice, e um Console já instalado continua achando o catálogo no mesmo lugar.
24
24
  */
25
- export const DEFAULT_INDEX_URL = 'https://dist.microteia.app/kernel/index.json';
25
+ export const DEFAULT_INDEX_URL = 'https://microteia.github.io/releases/kernel/index.json';
26
26
  export const indexUrl = () => {
27
27
  const override = process.env.MICROTEIA_INDEX_URL;
28
28
  return override === undefined || override === '' ? DEFAULT_INDEX_URL : override;
@@ -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.7.0";
90
+ export declare const CLI_VERSION = "2.7.2";
@@ -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.7.0';
97
+ export const CLI_VERSION = '2.7.2';
@@ -16,6 +16,7 @@ import { join } from 'node:path';
16
16
  */
17
17
  /** Arquivo distribuído → módulo de origem. O nome da pasta do exemplo é o do `.json`, sem extensão. */
18
18
  export const EXAMPLE_SOURCES = {
19
+ 'automation-example': 'automation.ts',
19
20
  brasil: 'brasil.ts',
20
21
  calendar: 'calendar.ts',
21
22
  dashboard: 'dashboard.ts',
@@ -42,24 +42,75 @@ const exists = async (path) => {
42
42
  }
43
43
  };
44
44
  const readJson = async (path) => JSON.parse(await readFile(path, 'utf8'));
45
- /**
46
- * Lê o catálogo pela API de conteúdo, e não pela URL pública: o que interessa é o que está no
47
- * repositório AGORA, e o Pages serve de um cache que pode estar minutos atrás. Publicar contra a
48
- * versão em cache perderia a entrada de quem publicou antes.
49
- */
50
45
  const readPublishedIndex = async (repo) => {
51
46
  const result = await capture('gh', ['api', `repos/${repo}/contents/${INDEX_PATH}`]);
52
47
  if (result.code !== 0) {
53
- if (result.stderr.includes('Not Found') || result.stdout.includes('Not Found')) {
48
+ const detail = `${result.stderr}${result.stdout}`;
49
+ /**
50
+ * Um 404 aqui tem DUAS causas, e elas pedem coisas opostas: o catálogo ainda não existe (primeira
51
+ * publicação, segue em frente) ou o repositório não existe (erro de nome, pare). Distinguir por
52
+ * texto não serve — a API responde "Not Found" quando falta o arquivo e "This repository is empty"
53
+ * quando o repositório não tem commit nenhum, que é o caso de um repo recém-criado e é exatamente
54
+ * quando a primeira publicação acontece.
55
+ *
56
+ * Então quem distingue é uma pergunta a mais, sobre o repositório em si. Ela custa uma chamada e
57
+ * remove a ambiguidade inteira.
58
+ */
59
+ if (detail.includes('HTTP 404')) {
60
+ const repository = await capture('gh', ['api', `repos/${repo}`]);
61
+ if (repository.code !== 0) {
62
+ throw new Error(`The repository ${repo} could not be read.\n${repository.stderr.trim() || repository.stdout.trim()}\n`
63
+ + 'Check the name, and that "gh auth status" reports an account with access to it.');
64
+ }
54
65
  info(` ${INDEX_PATH} does not exist in ${repo} yet — this is the first published version`);
55
- return { index: emptyIndex(), sha: null };
66
+ return { index: emptyIndex(), sha: null, empty: detail.includes('repository is empty') };
56
67
  }
57
68
  throw new Error(`Could not read ${INDEX_PATH} from ${repo}.\n${result.stderr.trim() || result.stdout.trim()}\n`
58
69
  + 'Is "gh" authenticated (gh auth status) and does the repository exist?');
59
70
  }
60
71
  const contents = JSON.parse(result.stdout);
61
72
  const text = Buffer.from(contents.content, 'base64').toString('utf8');
62
- return { index: parseIndex(text, `${repo}/${INDEX_PATH}`), sha: contents.sha };
73
+ return { index: parseIndex(text, `${repo}/${INDEX_PATH}`), sha: contents.sha, empty: false };
74
+ };
75
+ /**
76
+ * O que fica na raiz do repositório de releases, para quem chegar nele sem contexto.
77
+ *
78
+ * Ele é escrito UMA vez, ao semear — não é sobreposto depois. O que promete estar atual é o catálogo,
79
+ * e é ele que a cada publicação é reescrito.
80
+ */
81
+ const releasesReadme = () => `# Microteia — releases
82
+
83
+ Distribution artifacts for the Microteia platform. **No source lives here**: what travels is compiled.
84
+
85
+ - \`kernel/index.json\` — the catalog the Microteia Console reads. It lists every published version with
86
+ its size and sha256, and the absolute URLs of the files that carry it.
87
+ - Each version is a release tagged \`kernel-<version>\`, carrying the package and its manifest.
88
+
89
+ Nobody downloads from here by hand:
90
+
91
+ npm i -g @microteia/console
92
+ microteia unpack --version latest
93
+
94
+ The Console reads the catalog, verifies what it downloads against the sha256 above, and only then puts
95
+ it in place.
96
+ `;
97
+ /**
98
+ * Dá ao repositório o primeiro commit, porque sem ele não existe release.
99
+ *
100
+ * Um repositório de releases recém-criado está vazio, e o GitHub recusa criar release ou tag ali com
101
+ * `422 Repository is empty` — depois de o pacote já ter sido conferido, que é o pior momento para
102
+ * descobrir. Semear é uma escrita que este comando ia fazer de qualquer forma três passos adiante; ela
103
+ * só passou a acontecer antes, e traz junto o README que o repositório precisa ter.
104
+ */
105
+ const seedRepository = async (repo) => {
106
+ pending(`${repo} has no commits yet — writing its README first`);
107
+ const content = Buffer.from(releasesReadme(), 'utf8').toString('base64');
108
+ await captureOrThrow('gh', [
109
+ 'api', '--method', 'PUT', `repos/${repo}/contents/README.md`,
110
+ '-f', 'message=the releases repository',
111
+ '-f', `content=${content}`,
112
+ ]);
113
+ success(`seeded ${repo}`);
63
114
  };
64
115
  const writePublishedIndex = async (repo, index, sha, version) => {
65
116
  const content = Buffer.from(serializeIndex(index), 'utf8').toString('base64');
@@ -134,9 +185,13 @@ const publishKernel = async (root, version, repo, dryRun) => {
134
185
  success(`kernel ${version} verified: ${manifest.tarball?.bytes} bytes, sha256 ${manifest.tarball?.sha256.slice(0, 12)}…`);
135
186
  info(` tag ${tagOf(version)} in ${repo}, and ${INDEX_PATH} promoting it to "latest"`);
136
187
  if (dryRun) {
188
+ if (published.empty)
189
+ info(` ${repo} has no commits, so it would be seeded with a README first`);
137
190
  info(` the catalog would go from ${published.index.kernel.length} to ${next.kernel.length} version(s)`);
138
191
  return;
139
192
  }
193
+ if (published.empty)
194
+ await seedRepository(repo);
140
195
  const notes = await mkdtemp(join(tmpdir(), 'microteia-publish-'));
141
196
  const notesFile = join(notes, 'notes.md');
142
197
  await writeFile(notesFile, entry);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@microteia/console",
3
- "version": "2.7.0",
3
+ "version": "2.7.2",
4
4
  "description": "Install and update Microteia in a Firebase project you own.",
5
5
  "license": "UNLICENSED",
6
6
  "type": "module",