@microteia/console 2.7.0 → 2.7.1
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 +23 -0
- package/README.md +1 -1
- package/lib/console/channel.d.ts +1 -1
- package/lib/console/channel.js +1 -1
- package/lib/shared/paths.d.ts +1 -1
- package/lib/shared/paths.js +1 -1
- package/lib/vendor/publish.js +63 -8
- package/package.json +1 -1
package/CONSOLE-VERSIONS.md
CHANGED
|
@@ -22,6 +22,29 @@
|
|
|
22
22
|
- fix(command): what was broken, and what it did to whoever hit it.
|
|
23
23
|
-->
|
|
24
24
|
|
|
25
|
+
#### [2.7.1] — 2026-08-24
|
|
26
|
+
|
|
27
|
+
- fix(unpack): the default catalog URL is `https://microteia.github.io/releases/kernel/index.json`.
|
|
28
|
+
`2.7.0` shipped pointing at a domain that was never registered, so `--version` on it fails with
|
|
29
|
+
"Could not read the Microteia catalog" and only `--kernel <package>.tgz` works. The choice of host is
|
|
30
|
+
not the point; where the INDEX lives is, because that one URL is the only address a Console knows by
|
|
31
|
+
heart — every other URL is data inside the catalog. A `github.io` address is free, exists today, and
|
|
32
|
+
is forward compatible: adding a custom domain to the Pages site later makes GitHub redirect the old
|
|
33
|
+
address to it, and `fetch` follows redirects, so a Console installed now keeps working. Starting at a
|
|
34
|
+
domain would have required owning it first and offered no way back.
|
|
35
|
+
- fix(publish): a repository with no commits at all is the FIRST publish, not a failure. The contents
|
|
36
|
+
API answers `404 Not Found` when the file is missing and `404 This repository is empty` when there is
|
|
37
|
+
no commit — and the second is the state of a releases repository just created, which is exactly when
|
|
38
|
+
the first publish happens. Matching on the message refused it. A 404 now asks one more question, about
|
|
39
|
+
the repository itself, which separates "the catalog is not there yet" (go on) from "the repository is
|
|
40
|
+
not there" (stop) instead of guessing from prose.
|
|
41
|
+
- fix(publish): a releases repository with no commits is seeded before the release is created. GitHub
|
|
42
|
+
refuses to create a release or a tag in an empty repository with `422 Repository is empty`, and it
|
|
43
|
+
refuses it AFTER the package has been verified — the worst moment to find out. The first publish now
|
|
44
|
+
writes the repository's `README.md`, which is a write the command was going to make three steps later
|
|
45
|
+
anyway, and which the repository should carry regardless. It is written once and never overwritten:
|
|
46
|
+
what promises to be current is the catalog.
|
|
47
|
+
|
|
25
48
|
#### [2.7.0] — 2026-08-24
|
|
26
49
|
|
|
27
50
|
- feat(unpack): `--version latest` and `--version <x.y.z>` take a platform version from the published
|
package/README.md
CHANGED
package/lib/console/channel.d.ts
CHANGED
|
@@ -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://
|
|
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
|
package/lib/console/channel.js
CHANGED
|
@@ -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://
|
|
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;
|
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.7.
|
|
90
|
+
export declare const CLI_VERSION = "2.7.1";
|
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.7.
|
|
97
|
+
export const CLI_VERSION = '2.7.1';
|
package/lib/vendor/publish.js
CHANGED
|
@@ -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
|
-
|
|
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);
|