beez-rp 0.1.1 → 0.3.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.
Files changed (36) hide show
  1. package/CHANGELOG.md +35 -0
  2. package/README.md +72 -8
  3. package/bin/beez-rp.js +26 -11
  4. package/package.json +13 -4
  5. package/src/changelog-ai.js +19 -2
  6. package/src/constants/cli.js +2 -0
  7. package/src/constants/create-version.js +319 -0
  8. package/src/constants/guard-publish.js +29 -0
  9. package/src/constants/index.js +2 -0
  10. package/src/create-version/artifact.js +324 -0
  11. package/src/create-version/config.js +241 -0
  12. package/src/create-version/errors.js +19 -0
  13. package/src/create-version/index.js +39 -0
  14. package/src/create-version/npm.js +422 -0
  15. package/src/create-version/plan.js +367 -0
  16. package/src/create-version/process.js +161 -0
  17. package/src/create-version/run.js +969 -0
  18. package/src/create-version/state.js +239 -0
  19. package/src/guard-publish.js +74 -0
  20. package/src/index.js +2 -0
  21. package/types/changelog-ai.d.ts +3 -2
  22. package/types/constants/cli.d.ts +2 -0
  23. package/types/constants/create-version.d.ts +233 -0
  24. package/types/constants/guard-publish.d.ts +25 -0
  25. package/types/constants/index.d.ts +2 -0
  26. package/types/create-version/artifact.d.ts +113 -0
  27. package/types/create-version/config.d.ts +90 -0
  28. package/types/create-version/errors.d.ts +17 -0
  29. package/types/create-version/index.d.ts +18 -0
  30. package/types/create-version/npm.d.ts +158 -0
  31. package/types/create-version/plan.d.ts +148 -0
  32. package/types/create-version/process.d.ts +99 -0
  33. package/types/create-version/run.d.ts +53 -0
  34. package/types/create-version/state.d.ts +88 -0
  35. package/types/guard-publish.d.ts +48 -0
  36. package/types/index.d.ts +2 -0
package/CHANGELOG.md CHANGED
@@ -6,6 +6,41 @@ El formato sigue [Keep a Changelog](https://keepachangelog.com/es-ES/1.1.0/) y e
6
6
 
7
7
  ## [Unreleased]
8
8
 
9
+ ## [0.3.0] - 2026-09-27
10
+
11
+ ### Added
12
+
13
+ - `artifact` en `beez-rp.config.js`: con `publish: "npm"`, publica exactamente el tarball que dejó el paso `prepare` (por ejemplo `releases/{version}-{sha256}/{name}-{version}.tgz`) en vez de reempaquetar el working tree. Los proyectos lo empaquetan con `npm pack --ignore-scripts`.
14
+ - Antes de publicar ese tarball exige que `prepare` no haya modificado archivos versionados, rechaza paquetes que dependen de reescrituras de pnpm al empaquetar (`workspace:`/`catalog:`/`jsr:` o campos del manifest en `publishConfig` que solo pnpm sube a la raíz, como `exports`, `main`, `bin` o `types`; cualquier otra clave de `publishConfig` se acepta como configuración de npm), verifica el SHA-256 de su ruta cuando el patrón usa `{sha256}` y compara su SHA-512 con el `integrity` de `npm pack --dry-run --json --ignore-scripts` del commit de release. Durante esa comparación el tarball queda fuera de la raíz del paquete, así que se verifica aunque el paquete no tenga `files` y su carpeta no esté ignorada.
15
+ - `beez-rp guard-publish` (y `beez-rp/guard-publish`): guard de `prepublishOnly` que bloquea `pnpm publish`, yarn y bun con un mensaje que indica publicar con `pnpm create-version`; `create-version` quita `npm_config_user_agent` del entorno de `npm publish` para no bloquearse.
16
+
17
+ ### Changed
18
+
19
+ - `publish: "npm"` usa una config de npm temporal que solo referencia `${NPM_TOKEN}`, asociada al registry donde se publica (`publishConfig["@scope:registry"]` de un paquete con scope, `publishConfig.registry` o, si no hay, el registry que resuelve la config de npm en el repo: `.npmrc` del proyecto, variables de entorno o config global): los proyectos ya no necesitan `.npmrc` y pnpm deja de advertir por credenciales en el repositorio. La confirmación 2FA de npm sigue siendo interactiva.
20
+ - El diagnóstico y la comprobación posterior a publicar consultan las versiones con `npm view --registry` en ese mismo registry, así que un paquete publicado en un registry propio ya no se vuelve a preparar y publicar. Con `NPM_TOKEN` (entorno o `.env`) esa consulta se autentica con la misma config temporal, así que también funciona con paquetes privados.
21
+ - El resumen final enlaza a npmjs.com solo cuando se publicó en `https://registry.npmjs.org/`; en otro registry muestra `Registro: <url>` con el paquete y la versión.
22
+
23
+ ### Removed
24
+
25
+ - `.npmrc` del repositorio de beez-rp.
26
+
27
+ ### Fixed
28
+
29
+ - Si `main` estaba atrás de `origin/main`, `create-version` lo actualiza y termina sin tocar la versión ni los tags (código de salida 0), pidiendo volver a correr `pnpm create-version`: así el diagnóstico, el plan, las migraciones y `beez-rp.config.(m)js` con sus módulos importados salen del código actualizado y no del `main` anterior.
30
+
31
+ ## [0.2.0] - 2026-09-26
32
+
33
+ ### Added
34
+
35
+ - `beez-rp create-version` (y `beez-rp/create-version`): el comando de release compartido por todos los proyectos, configurable con `beez-rp.config.js` (checks, migraciones, preparación, publicación en npm o propia, textos y resumen).
36
+ - Retoma en un mismo flujo el push de un commit de versión local y la publicación de una versión que npm todavía no tiene.
37
+ - `buildChangelogPrompt` acepta el idioma de las entradas: español por defecto o inglés limitado a ASCII.
38
+
39
+ ### Changed
40
+
41
+ - El último release es el último commit de `origin/main` que cambió la versión de `package.json`, así que también reconoce releases con otros asuntos o versiones subidas a mano.
42
+ - beez-rp se publica con su propio `beez-rp create-version` en lugar de un script aparte.
43
+
9
44
  ## [0.1.1] - 2026-09-26
10
45
 
11
46
  ### Added
package/README.md CHANGED
@@ -16,7 +16,9 @@ Proceso de release compartido por los proyectos Beez (beez-ui, TuTribu, Control
16
16
  | `beez-rp/build-gate` | `decideBuild(previousVersion, currentVersion)` y `decideBuildForCheckout(repositoryRoot)` para el `ignoreCommand` de Vercel. |
17
17
  | `beez-rp/changelog` | Lectura y release del bloque `## [Unreleased]` de `CHANGELOG.md` (Keep a Changelog). |
18
18
  | `beez-rp/changelog-ai` | Prompt e invocación de Codex para completar `[Unreleased]` vacío. |
19
+ | `beez-rp/guard-publish` | `decidePublishGuard(userAgent)` del `prepublishOnly` que bloquea publicaciones con pnpm, yarn o bun. |
19
20
  | `beez-rp/terminal-ui` | Cajas, filas, banner, spinner y selector interactivo sin dependencias. |
21
+ | `beez-rp/create-version` | Comando compartido de release: `runCreateVersion`, el planificador puro `buildReleasePlan`, el lector de estado y los tipos de `beez-rp.config.js`. |
20
22
  | `beez-rp/testing` | Fixtures de versiones permitidas y rechazadas para los tests de cada proyecto. |
21
23
  | `beez-rp/constants` | Todas las constantes, agrupadas por dominio. |
22
24
 
@@ -54,7 +56,13 @@ it.each(REJECTED_VERSION_BUMP_CASES)("rechaza %s (%j)", (_reason, version) => {
54
56
  });
55
57
  ```
56
58
 
57
- ## Publicar beez-rp
59
+ ## create-version
60
+
61
+ Cada proyecto versiona con el mismo comando y describe sus diferencias en `beez-rp.config.js` (o `beez-rp.config.mjs`, que tiene prioridad y conviene en proyectos sin `"type": "module"`):
62
+
63
+ ```json
64
+ { "scripts": { "create-version": "beez-rp create-version", "cv": "beez-rp create-version" } }
65
+ ```
58
66
 
59
67
  ```bash
60
68
  pnpm create-version # o pnpm cv
@@ -63,15 +71,71 @@ pnpm cv --set-version X.Y.Z # solo la siguiente patch, minor o major
63
71
  pnpm cv --dry-run # diagnóstico y plan, sin cambiar nada
64
72
  ```
65
73
 
66
- El comando sale solo desde `main`, limpio y al día con origin (solo `CHANGELOG.md` puede quedar sin commitear).
74
+ El comando sale solo desde `main`, limpio y al día con origin (solo `CHANGELOG.md` puede quedar sin commitear). En una rama feature explica qué falta: pushear, abrir o mergear el PR (con `gh`).
75
+
76
+ 1. Si `main` está atrás de origin, lo actualiza en fast-forward y termina (código de salida 0) sin tocar la versión ni los tags: hay que volver a correr `pnpm create-version`, que en un proceso nuevo carga `beez-rp.config.(m)js`, sus módulos y el diagnóstico desde el código actualizado.
77
+ 2. Aplica migraciones pendientes, si el proyecto tiene adaptador, después de pedir confirmación.
78
+ 3. Si `[Unreleased]` está vacío, lo completa Codex a partir de los commits sin publicar.
79
+ 4. Corre los `checks`.
80
+ 5. Pide la versión, pasa `[Unreleased]` a `## [X.Y.Z] - AAAA-MM-DD` y crea el commit `X.Y.Z` con el tag anotado `vX.Y.Z`.
81
+ 6. Corre `prepare`, sube `main` y el tag con `git push --atomic` y corre `publish`.
82
+
83
+ El último release es el último commit de `origin/main` que cambió el `version` de `package.json`, así que sirve con commits `X.Y.Z`, con otros asuntos de release y con versiones subidas a mano. Si algo falla después del commit, volver a correr el comando retoma solo lo que falta: el push de un commit de versión local o, con `registry: "npm"`, la preparación y publicación de una versión que npm todavía no tiene. Las versiones publicadas se consultan con `npm view --registry` en el mismo registry donde se publica (ver abajo), porque `npm view` no aplica el `publishConfig` del `package.json`.
84
+
85
+ ### beez-rp.config.js
86
+
87
+ ```js
88
+ /** @type {import("beez-rp/create-version").CreateVersionConfig} */
89
+ export default {
90
+ projectName: "TuTribu", // banner; por defecto el name de package.json
91
+ changelog: { audience: "quien usa TuTribu", language: "es" }, // "en": entradas en inglés ASCII
92
+ releaseTypeDescriptions: { patch: "…", minor: "…", major: "…" },
93
+ publishedLabel: "en producción", // banner: vX.Y.Z en producción
94
+ registry: "npm", // retoma y banner según las versiones en npm
95
+ checks: ["pnpm check"], // antes de tocar la versión
96
+ migrations: { check, apply, targetHint }, // adaptador de base de datos
97
+ prepare: ["pnpm release:prepare"], // comandos o función, sobre el commit de versión
98
+ publish: "npm", // npm publish con NPM_TOKEN, o una función
99
+ artifact: "releases/{version}-{sha256}/{name}-{version}.tgz", // con "npm": publica ese tarball verificado
100
+ summary: ["Vercel buildea {version}."], // líneas extra del resumen final
101
+ };
102
+ ```
103
+
104
+ Solo `changelog.audience` es obligatorio. Los hooks (`migrations.check`, `migrations.apply`, `prepare`, `publish`) reciben `{ repositoryRoot, version, git, run, print, fail }`: `git` lee Git, `run("pnpm x")` corre un comando visible y devuelve su exit code, y `fail(mensaje, qué hacer)` corta el paso con una explicación. El config no necesita importar `beez-rp`.
105
+
106
+ `migrations.check` devuelve `{ status: "up-to-date" | "pending" | "unknown", pending, target, reason }`; después de `apply`, el comando vuelve a llamar a `check` y falla si siguen pendientes. `publish: "npm"` toma `NPM_TOKEN` del entorno o de un `.env` ignorado por Git. No hace falta `.npmrc`: el comando escribe una config de npm temporal fuera del repo que asocia `${NPM_TOKEN}` al registry donde realmente se publica, resuelto como npm: `publishConfig["@scope:registry"]` si el paquete tiene ese scope, si no `publishConfig.registry` y, si el `package.json` no declara ninguno, lo que devuelve `npm config get @scope:registry` (paquete con scope) o `npm config get registry` en la raíz del repo, que incluye el `.npmrc` del proyecto, las variables `npm_config_*` y la config global (como la publicación, no lee `~/.npmrc`, que se reemplaza por la config temporal); tiene que ser una URL http(s) válida o no se publica. Con `NPM_TOKEN` disponible, el diagnóstico también consulta `npm view` con esa config temporal, así que funciona con paquetes privados; sin token consulta sin autenticar. El resumen final enlaza a npmjs.com solo si el registry es `https://registry.npmjs.org/`; si no, muestra `Registro: <url>` con el paquete y la versión. Solo guarda esa referencia: npm la expande, el token nunca queda en disco ni en la línea de comandos, y la config se borra al terminar. npm hereda la terminal, así que la confirmación 2FA (navegador o código) funciona igual.
107
+
108
+ Sin `artifact`, `publish: "npm"` publica el working tree. Con `artifact` publica exactamente el tarball que dejó `prepare`. Los proyectos empaquetan siempre con npm: `prepare` construye (con pnpm o lo que use el proyecto) y después corre `npm pack --ignore-scripts`. `npm pack` es reproducible (el mismo commit da siempre los mismos bytes), así que la verificación es una comparación de hash. Después de `prepare` y antes de `npm publish`:
109
+
110
+ - Working tree: `git status --porcelain --untracked-files=no` tiene que estar vacío. `prepare` puede generar archivos ignorados o no versionados (`dist/`, `releases/`), pero no modificar archivos versionados como `package.json`; así lo que lee npm es exactamente el commit de release.
111
+ - Publicable con npm: si el `package.json` depende de algo que solo pnpm reescribe al empaquetar (especificadores `workspace:`, `catalog:` o `jsr:` en `dependencies`, `peerDependencies` u `optionalDependencies`, o campos del manifest dentro de `publishConfig` que pnpm sube a la raíz del `package.json` empaquetado), no se publica. Para npm, `publishConfig` es solo [configuración de npm](https://docs.npmjs.com/cli/v11/configuring-npm/package-json#publishconfig), así que se acepta cualquier clave (`registry`, `access`, `tag`, `provenance`, `@scope:registry`, `otp`...) salvo las que pnpm aplica al manifest: `name`, `bin`, `engines`, `type`, `imports`, `main`, `module`, `typings`, `types`, `exports`, `browser`, `esnext`, `es2015`, `unpkg`, `umd:main`, `os`, `cpu`, `libc` y `typesVersions` (lista de `PUBLISH_CONFIG_WHITELIST` en pnpm). Declaralos en la raíz del `package.json`.
112
+ - Ubicación: el patrón es relativo a la raíz, reemplaza `{version}` (obligatorio) y `{name}` (el nombre de archivo que usa `npm pack`: `@scope/pkg` pasa a `scope-pkg`), y dentro de un segmento acepta `*` y `{sha256}`. Si hay varios, toma el más reciente. La ruta solo puede tener letras, números, `.`, `-`, `_`, `@`, `+`, `~` y `/`.
113
+ - `{sha256}`: el SHA-256 del archivo tiene que coincidir con el de su ruta. Puede repetirse en un segmento o en varios; todas las apariciones tienen que declarar el mismo digest.
114
+ - Hash: beez-rp corre `npm pack --dry-run --json --ignore-scripts` en la raíz y compara su `integrity` (`sha512-<base64>`) con el SHA-512 del tarball. Durante ese dry run el tarball se mueve a un directorio temporal fuera del paquete y siempre vuelve a su ruta, aunque npm falle: sin una lista `files` ni una regla de `.npmignore`/`.gitignore` que excluya su carpeta, npm lo contaría como parte del paquete y la integrity nunca coincidiría. Después se publica desde su ruta original. Si difieren, el tarball no es lo que npm empaquetaría de este commit (por ejemplo, se armó con otra herramienta o antes de construir) y no se publica. Si npm falla o su salida no se puede leer, tampoco.
115
+
116
+ Ejemplo de `prepare` que deja `releases/{version}-{sha256}/{name}-{version}.tgz`: `pnpm build`, `npm pack --ignore-scripts --pack-destination releases/<version>-tmp/` y renombrar la carpeta con el SHA-256 del tarball.
117
+
118
+ Si no hay tarball o la verificación falla, no se publica nada y volver a correr el comando retoma preparación y publicación.
67
119
 
68
- 1. Si `[Unreleased]` está vacío, lo completa Codex a partir de los commits sin publicar.
69
- 2. Corre `pnpm check`.
70
- 3. Pide la versión, pasa `[Unreleased]` a `## [X.Y.Z] - AAAA-MM-DD` y crea el commit `X.Y.Z` con el tag `vX.Y.Z`.
71
- 4. Sube `main` y el tag con `git push --atomic`.
72
- 5. Publica en npm. El `.npmrc` del repo referencia `${NPM_TOKEN}`, que se toma del entorno o de un `.env` ignorado por Git.
120
+ ## Bloquear publicaciones con pnpm
73
121
 
74
- Si algo falla después del commit, volver a correr el comando retoma solo el push o la publicación, sin generar otra versión.
122
+ `create-version` publica siempre con npm y verifica el tarball contra `npm pack --dry-run`, así que un `pnpm publish` manual saltearía esas garantías. Cada proyecto lo bloquea con:
123
+
124
+ ```json
125
+ { "scripts": { "prepublishOnly": "beez-rp guard-publish" } }
126
+ ```
127
+
128
+ `beez-rp guard-publish` lee `npm_config_user_agent`: si empieza con `pnpm/`, `yarn/` o `bun/`, explica por stderr que se publica con `pnpm create-version` y sale con `1`; con `npm/`, sin la variable o con un valor desconocido sale con `0` sin imprimir nada. npm toma un `npm_config_user_agent` heredado como su config `user-agent`, por eso `create-version` lo quita del entorno de `npm publish`: así `pnpm create-version` no se bloquea a sí mismo.
129
+
130
+ Es una protección contra errores, no un candado: `--ignore-scripts` la esquiva a propósito. Aun así, un tarball empaquetado con pnpm no pasa la verificación de hash de `create-version`.
131
+
132
+ ## Publicar beez-rp
133
+
134
+ beez-rp se publica con su propio comando (`beez-rp.config.js`: `checks: ["pnpm check"]`, `publish: "npm"`). Su `prepublishOnly` es `node bin/beez-rp.js guard-publish`, porque no se instala a sí mismo:
135
+
136
+ ```bash
137
+ pnpm cv
138
+ ```
75
139
 
76
140
  ## Desarrollo
77
141
 
package/bin/beez-rp.js CHANGED
@@ -2,11 +2,16 @@
2
2
  /**
3
3
  * `beez-rp` command line.
4
4
  *
5
- * `beez-rp ignore-build` prints why the current checkout is built or skipped
6
- * and, as its last line, the decision (`BUILD` or `SKIP`), exiting with `0`.
7
- * It exits with `2` when it cannot decide. Vercel wrappers must build only
8
- * when the last line is `BUILD`, so any failure (including `npx` itself)
9
- * skips the build.
5
+ * - `beez-rp ignore-build` prints why the current checkout is built or skipped
6
+ * and, as its last line, the decision (`BUILD` or `SKIP`), exiting with `0`.
7
+ * It exits with `2` when it cannot decide. Vercel wrappers must build only
8
+ * when the last line is `BUILD`, so any failure (including `npx` itself)
9
+ * skips the build.
10
+ * - `beez-rp create-version [--bump patch|minor|major | --set-version X.Y.Z] [--dry-run]`
11
+ * diagnoses the repository in the current directory and ships its release
12
+ * from `main`, following its `beez-rp.config.js`.
13
+ * - `beez-rp guard-publish` is the `prepublishOnly` guard: it exits with `1`
14
+ * and explains why when pnpm, yarn or bun publishes, and with `0` otherwise.
10
15
  *
11
16
  * @module beez-rp-cli
12
17
  */
@@ -14,16 +19,23 @@
14
19
  import { decideBuildForCheckout } from "../src/build-gate.js";
15
20
  import { BUILD_DECISION, DECISION_EXIT_CODE, GATE_FAILURE_EXIT_CODE } from "../src/constants/build-gate.js";
16
21
  import { CLI_COMMAND } from "../src/constants/cli.js";
22
+ import { runCreateVersion } from "../src/create-version/run.js";
23
+ import { decidePublishGuardForEnvironment } from "../src/guard-publish.js";
17
24
 
18
25
  /** Usage printed for unknown commands. */
19
- const USAGE = `Usage: beez-rp ${CLI_COMMAND.ignoreBuild}`;
26
+ const USAGE = `Usage: beez-rp ${CLI_COMMAND.ignoreBuild} | beez-rp ${CLI_COMMAND.createVersion} [options] | beez-rp ${CLI_COMMAND.guardPublish}`;
20
27
 
21
- const [command] = process.argv.slice(2);
28
+ const [command, ...commandArguments] = process.argv.slice(2);
22
29
 
23
- if (command !== CLI_COMMAND.ignoreBuild) {
24
- console.error(`beez-rp: unknown command "${command ?? ""}". ${USAGE}`);
25
- process.exitCode = GATE_FAILURE_EXIT_CODE;
26
- } else {
30
+ if (command === CLI_COMMAND.createVersion) {
31
+ process.exitCode = await runCreateVersion({ repositoryRoot: process.cwd(), argv: commandArguments });
32
+ } else if (command === CLI_COMMAND.guardPublish) {
33
+ const decision = decidePublishGuardForEnvironment();
34
+ if (decision.message) {
35
+ console.error(decision.message);
36
+ }
37
+ process.exitCode = decision.exitCode;
38
+ } else if (command === CLI_COMMAND.ignoreBuild) {
27
39
  try {
28
40
  const decision = decideBuildForCheckout(process.cwd());
29
41
  console.log(decision.reason);
@@ -33,4 +45,7 @@ if (command !== CLI_COMMAND.ignoreBuild) {
33
45
  console.error("beez-rp ignore-build: could not decide; the build must be skipped.", error);
34
46
  process.exitCode = GATE_FAILURE_EXIT_CODE;
35
47
  }
48
+ } else {
49
+ console.error(`beez-rp: unknown command "${command ?? ""}". ${USAGE}`);
50
+ process.exitCode = GATE_FAILURE_EXIT_CODE;
36
51
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "beez-rp",
3
- "version": "0.1.1",
3
+ "version": "0.3.0",
4
4
  "description": "Dependency-free release process shared by the Beez projects: stable version rules, Vercel build gate, Keep a Changelog and terminal UI.",
5
5
  "license": "MIT",
6
6
  "author": "Guido Modarelli",
@@ -46,6 +46,14 @@
46
46
  "types": "./types/testing.d.ts",
47
47
  "default": "./src/testing.js"
48
48
  },
49
+ "./guard-publish": {
50
+ "types": "./types/guard-publish.d.ts",
51
+ "default": "./src/guard-publish.js"
52
+ },
53
+ "./create-version": {
54
+ "types": "./types/create-version/index.d.ts",
55
+ "default": "./src/create-version/index.js"
56
+ },
49
57
  "./constants": {
50
58
  "types": "./types/constants/index.d.ts",
51
59
  "default": "./src/constants/index.js"
@@ -65,9 +73,10 @@
65
73
  "typecheck": "tsc -p tsconfig.json",
66
74
  "test": "vitest run",
67
75
  "check": "pnpm typecheck && pnpm test",
68
- "create-version": "node scripts/release.js",
69
- "cv": "node scripts/release.js",
70
- "prepack": "pnpm build:types"
76
+ "create-version": "node bin/beez-rp.js create-version",
77
+ "cv": "node bin/beez-rp.js create-version",
78
+ "prepack": "pnpm build:types",
79
+ "prepublishOnly": "node bin/beez-rp.js guard-publish"
71
80
  },
72
81
  "devDependencies": {
73
82
  "@types/node": "^24.13.4",
@@ -11,16 +11,33 @@ import { spawn } from "node:child_process";
11
11
 
12
12
  import { CHANGE_TYPES, UNRELEASED_HEADING } from "./constants/changelog.js";
13
13
  import { CODEX_COMMAND, CODEX_NOT_FOUND_EXIT_CODE, PROMPT_SHORT_SHA_LENGTH } from "./constants/changelog-ai.js";
14
+ import { CHANGELOG_LANGUAGE } from "./constants/create-version.js";
14
15
 
15
16
  /**
16
17
  * Builds the instructions for Codex from the commits that will ship.
17
18
  *
18
19
  * @param {{ sha: string, subject: string }[]} commits - Unreleased commits, newest first.
19
20
  * @param {string} audience - Who reads the changelog, e.g. "quien consume el paquete".
20
- * @returns {string} Prompt in Spanish.
21
+ * @param {"es" | "en"} [language] - Language of the entries: Spanish by default, or English limited to ASCII.
22
+ * @returns {string} Prompt written in the requested language.
21
23
  */
22
- export function buildChangelogPrompt(commits, audience) {
24
+ export function buildChangelogPrompt(commits, audience, language = CHANGELOG_LANGUAGE.spanish) {
23
25
  const commitList = commits.map((commit) => `- ${commit.sha.slice(0, PROMPT_SHORT_SHA_LENGTH)} ${commit.subject}`).join("\n");
26
+
27
+ if (language === CHANGELOG_LANGUAGE.english) {
28
+ return [
29
+ `Fill the \`${UNRELEASED_HEADING}\` block of CHANGELOG.md following Keep a Changelog.`,
30
+ `- Group entries under \`### ${CHANGE_TYPES.join("`, `### ")}\`, in that order and only the sections that apply.`,
31
+ `- One \`- \` line per change, in English and ASCII only, clear for ${audience}; no internal implementation details.`,
32
+ `- If \`${UNRELEASED_HEADING}\` does not exist, create it right below the document title.`,
33
+ "- Modify only CHANGELOG.md: do not touch released versions or other files, and do not commit.",
34
+ "- Use `git show <sha>` when you need the details of a commit.",
35
+ "",
36
+ "Unreleased commits (newest first):",
37
+ commitList,
38
+ ].join("\n");
39
+ }
40
+
24
41
  return [
25
42
  `Completá el bloque \`${UNRELEASED_HEADING}\` de CHANGELOG.md siguiendo Keep a Changelog.`,
26
43
  `- Agrupá las entradas bajo \`### ${CHANGE_TYPES.join("`, `### ")}\`, en ese orden y solo las secciones que apliquen.`,
@@ -7,4 +7,6 @@
7
7
  /** Command names accepted as the first `beez-rp` argument. */
8
8
  export const CLI_COMMAND = Object.freeze({
9
9
  ignoreBuild: "ignore-build",
10
+ createVersion: "create-version",
11
+ guardPublish: "guard-publish",
10
12
  });
@@ -0,0 +1,319 @@
1
+ /**
2
+ * Git, npm, configuration and display values of the shared `beez-rp create-version` command.
3
+ *
4
+ * @module constants/create-version
5
+ */
6
+
7
+ /** Branch that receives releases. */
8
+ export const MAIN_BRANCH = "main";
9
+
10
+ /** Remote that receives releases. */
11
+ export const RELEASE_REMOTE = "origin";
12
+
13
+ /** Remote-tracking ref of the release branch. */
14
+ export const REMOTE_MAIN_REF = `${RELEASE_REMOTE}/${MAIN_BRANCH}`;
15
+
16
+ /** Configuration module read from the repository root. */
17
+ export const CREATE_VERSION_CONFIG_FILE = "beez-rp.config.js";
18
+
19
+ /**
20
+ * Configuration candidates in lookup order: `.mjs` is always ESM, so projects
21
+ * without `"type": "module"` load it without Node warnings.
22
+ */
23
+ export const CREATE_VERSION_CONFIG_FILES = Object.freeze(["beez-rp.config.mjs", CREATE_VERSION_CONFIG_FILE]);
24
+
25
+ /** Manifest whose `version` is released; shared with the build gate. */
26
+ export { PACKAGE_MANIFEST_FILE } from "./build-gate.js";
27
+
28
+ /** Pinned Node.js version compared with the running one when present; a full version or only its major. */
29
+ export const PINNED_NODE_VERSION_FILE = ".nvmrc";
30
+
31
+ /** Environment file that may hold `NPM_TOKEN` locally; ignored by Git. */
32
+ export const LOCAL_ENVIRONMENT_FILE = ".env";
33
+
34
+ /** Stable identifiers of every step the command knows how to run, in execution order. */
35
+ export const RELEASE_STEP = Object.freeze({
36
+ syncMain: "sync-main",
37
+ applyMigrations: "apply-migrations",
38
+ generateChangelog: "generate-changelog",
39
+ runChecks: "run-checks",
40
+ bumpVersion: "bump-version",
41
+ prepareRelease: "prepare-release",
42
+ pushRelease: "push-release",
43
+ publishRelease: "publish-release",
44
+ });
45
+
46
+ /** What the command does with the current state. */
47
+ export const RELEASE_MODE = Object.freeze({
48
+ newRelease: "new-release",
49
+ resume: "resume",
50
+ upToDate: "up-to-date",
51
+ blocked: "blocked",
52
+ });
53
+
54
+ /** Result of a project migrations adapter. */
55
+ export const MIGRATION_STATUS = Object.freeze({
56
+ upToDate: "up-to-date",
57
+ pending: "pending",
58
+ unknown: "unknown",
59
+ });
60
+
61
+ /** Pull request states reported by `gh pr view --json state`. */
62
+ export const PULL_REQUEST_STATE = Object.freeze({
63
+ open: "OPEN",
64
+ merged: "MERGED",
65
+ closed: "CLOSED",
66
+ });
67
+
68
+ /** Fields requested from `gh pr view`. */
69
+ export const PULL_REQUEST_JSON_FIELDS = "number,url,title,state,isDraft,headRefOid";
70
+
71
+ /** Message `gh pr view` prints when the branch has no pull request. */
72
+ export const NO_PULL_REQUEST_MESSAGE_PATTERN = /no pull requests found/iu;
73
+
74
+ /** Registries whose published versions drive resumes and the banner. */
75
+ export const RELEASE_REGISTRY = Object.freeze({
76
+ npm: "npm",
77
+ });
78
+
79
+ /** Built-in publisher selected with `publish: "npm"`. */
80
+ export const NPM_PUBLISHER = "npm";
81
+
82
+ /** Result of asking npm which versions of the package exist. */
83
+ export const NPM_LOOKUP_STATUS = Object.freeze({
84
+ ok: "ok",
85
+ failed: "failed",
86
+ });
87
+
88
+ /** Valid npm package name (optionally scoped), checked before it reaches a shell command line. */
89
+ export const NPM_PACKAGE_NAME_PATTERN = /^(?:@[a-z0-9-~][a-z0-9-._~]*\/)?[a-z0-9-~][a-z0-9-._~]*$/u;
90
+
91
+ /** npm error code of a package that was never published. */
92
+ export const NPM_NOT_FOUND_CODE = "E404";
93
+
94
+ /** Environment variable that holds the npm token; the repository `.npmrc` references it as `${NPM_TOKEN}`. */
95
+ export const NPM_TOKEN_VARIABLE = "NPM_TOKEN";
96
+
97
+ /** npm dist-tag every stable release is published under. */
98
+ export const NPM_DIST_TAG = "latest";
99
+
100
+ /** Languages the Codex changelog prompt can request. */
101
+ export const CHANGELOG_LANGUAGE = Object.freeze({
102
+ spanish: "es",
103
+ english: "en",
104
+ });
105
+
106
+ /** Command-line flags of `beez-rp create-version`. */
107
+ export const CREATE_VERSION_FLAG = Object.freeze({
108
+ bump: "bump",
109
+ setVersion: "set-version",
110
+ dryRun: "dry-run",
111
+ help: "help",
112
+ helpShort: "h",
113
+ endOfOptions: "--",
114
+ });
115
+
116
+ /** Optional `v` prefix accepted in hand-typed versions. */
117
+ export const VERSION_PREFIX_PATTERN = /^v/u;
118
+
119
+ /** `version` field of `package.json`, replaced in place to keep formatting. */
120
+ export const PACKAGE_VERSION_FIELD_PATTERN = /("version"\s*:\s*")[^"]+(")/u;
121
+
122
+ /** `git log -G` pattern of a changed top-level `version` field; the last such commit is the last release. */
123
+ export const VERSION_FIELD_CHANGE_PATTERN = `^[[:space:]]*"version"[[:space:]]*:`;
124
+
125
+ /** GitHub `owner/repo` inside an SSH or HTTPS remote URL. */
126
+ export const GITHUB_REPOSITORY_PATTERN = /github\.com[:/]([^/]+\/[^/]+?)(?:\.git)?$/u;
127
+
128
+ /** Separates fields inside one `git log --format` record. */
129
+ export const FIELD_SEPARATOR = "\x1f";
130
+
131
+ /** Separates records in `git log --format` output. */
132
+ export const RECORD_SEPARATOR = "\x1e";
133
+
134
+ /** Width of the `git status --porcelain` state columns before each path. */
135
+ export const PORCELAIN_STATUS_WIDTH = 3;
136
+
137
+ /** Maximum commits listed in a box. */
138
+ export const MAX_LISTED_COMMITS = 12;
139
+
140
+ /** Maximum uncommitted files, foreign commits or pending migrations listed before summarizing. */
141
+ export const MAX_LISTED_ITEMS = 5;
142
+
143
+ /** Length of the abbreviated commit ids shown to the user. */
144
+ export const SHORT_SHA_LENGTH = 7;
145
+
146
+ /** Placeholders of the `artifact` pattern: the released version and the npm package name. */
147
+ export const ARTIFACT_VERSION_PLACEHOLDER = "{version}";
148
+ export const ARTIFACT_NAME_PLACEHOLDER = "{name}";
149
+
150
+ /**
151
+ * Scope of a package name as `npm pack` and `pnpm pack` name the tarball: the
152
+ * leading `@` is dropped and the `/` becomes `-` (`@scope/pkg` → `scope-pkg`).
153
+ * The `scope` group keeps the scope without `@`.
154
+ */
155
+ export const PACKAGE_SCOPE_PATTERN = /^@(?<scope>[^/]+)\//u;
156
+
157
+ /** Replacement of {@link PACKAGE_SCOPE_PATTERN} that yields the tarball base name prefix. */
158
+ export const PACKED_SCOPE_REPLACEMENT = "$<scope>-";
159
+
160
+ /** Wildcard of an `artifact` pattern segment, such as the checksum in `releases/{version}-*`. */
161
+ export const ARTIFACT_SEGMENT_WILDCARD = "*";
162
+
163
+ /**
164
+ * Checksum placeholder: the SHA-256 of the archive, verified before publishing. It may repeat,
165
+ * in one segment or several, and every occurrence must declare the same digest.
166
+ */
167
+ export const ARTIFACT_SHA256_PLACEHOLDER = "{sha256}";
168
+
169
+ /** Lowercase hexadecimal SHA-256 digest matched by {@link ARTIFACT_SHA256_PLACEHOLDER}. */
170
+ export const SHA256_HEX_PATTERN_SOURCE = "[0-9a-f]{64}";
171
+
172
+ /**
173
+ * Characters allowed in an artifact path passed to `npm publish` through the Windows shell;
174
+ * `~` is allowed because npm package names (and so tarball names) may contain it.
175
+ */
176
+ export const SAFE_ARTIFACT_PATH_PATTERN = /^[\w.@+~/-]+$/u;
177
+
178
+ /**
179
+ * Prefix that makes npm read an artifact path as a local file: a bare
180
+ * `releases/x/pkg.tgz` operand is parsed as a package spec (a GitHub shorthand).
181
+ */
182
+ export const LOCAL_PATH_PREFIX = "./";
183
+
184
+ /**
185
+ * Arguments of the `npm pack` run that reports the integrity npm would pack from the release
186
+ * checkout: nothing is written and no lifecycle script runs, so it only reads what `prepare` left.
187
+ */
188
+ export const NPM_PACK_DRY_RUN_ARGUMENTS = Object.freeze(["pack", "--dry-run", "--json", "--ignore-scripts"]);
189
+
190
+ /**
191
+ * Prefix of the temporary directory, outside the package root, that holds the prepared archive
192
+ * while `npm pack --dry-run` runs, so npm never packs the archive into the package it describes.
193
+ */
194
+ export const ARTIFACT_HOLDING_DIRECTORY_PREFIX = "beez-rp-artifact-";
195
+
196
+ /** Node.js error code of a `rename` across file systems, retried as copy and delete. */
197
+ export const CROSS_DEVICE_RENAME_ERROR_CODE = "EXDEV";
198
+
199
+ /** Hash algorithm of the npm `integrity` string (`sha512-<base64>`). */
200
+ export const NPM_INTEGRITY_ALGORITHM = "sha512";
201
+
202
+ /** npm `integrity` string as `npm pack --json` reports it. */
203
+ export const NPM_INTEGRITY_PATTERN = /^sha512-[A-Za-z0-9+/]+={0,2}$/u;
204
+
205
+ /** Manifest field whose keys npm applies as configuration when it publishes. */
206
+ export const PUBLISH_CONFIG_FIELD = "publishConfig";
207
+
208
+ /**
209
+ * `publishConfig` keys pnpm hoists onto the packed manifest root. npm treats every
210
+ * `publishConfig` key as npm configuration and never rewrites manifest fields with it, so a
211
+ * package that declares any of these would be published differently by npm than by `pnpm pack`.
212
+ * Any other key (`registry`, `access`, `tag`, `provenance`, `@scope:registry`, `otp`...) is npm
213
+ * configuration and is accepted.
214
+ *
215
+ * Source: `PUBLISH_CONFIG_WHITELIST` in pnpm v12.6.0
216
+ * (`pnpm/crates/exportable-manifest/src/create.rs`; same list as
217
+ * `releasing/exportable-manifest/src/overridePublishConfig.ts` in pnpm 11).
218
+ *
219
+ * @see https://github.com/pnpm/pnpm/blob/v12.6.0/pnpm/crates/exportable-manifest/src/create.rs
220
+ * @see https://docs.npmjs.com/cli/v11/configuring-npm/package-json#publishconfig
221
+ */
222
+ export const PNPM_HOISTED_PUBLISH_CONFIG_KEYS = Object.freeze([
223
+ "name",
224
+ "bin",
225
+ "engines",
226
+ "type",
227
+ "imports",
228
+ "main",
229
+ "module",
230
+ "typings",
231
+ "types",
232
+ "exports",
233
+ "browser",
234
+ "esnext",
235
+ "es2015",
236
+ "unpkg",
237
+ "umd:main",
238
+ "os",
239
+ "cpu",
240
+ "libc",
241
+ "typesVersions",
242
+ ]);
243
+
244
+ /** Dependency maps npm publishes as they are written in `package.json`. */
245
+ export const PUBLISHED_DEPENDENCY_FIELDS = Object.freeze(["dependencies", "peerDependencies", "optionalDependencies"]);
246
+
247
+ /**
248
+ * Dependency specifiers only `pnpm pack` rewrites and npm publishes verbatim, which npm then
249
+ * cannot install (`EUNSUPPORTEDPROTOCOL`):
250
+ * - `catalog:` (dereferenced to the catalog entry);
251
+ * - `workspace:` (resolved to the linked package version), as the whole specifier or, in
252
+ * `peerDependencies`, as a segment of a compound range (`^1.0.0 || workspace:>=1.0.0`);
253
+ * - `jsr:` (turned into an `npm:@jsr/<scope>__<name>` alias).
254
+ *
255
+ * Source: `convert_dependency_for_publish` (catalog → workspace → jsr replacers) in pnpm v12.6.0
256
+ * `pnpm/crates/exportable-manifest/src/create.rs`, and `replace_workspace_protocol_peer_dependency`
257
+ * in `pnpm/crates/exportable-manifest/src/replace.rs`. No other protocol is rewritten there.
258
+ *
259
+ * @see https://github.com/pnpm/pnpm/blob/v12.6.0/pnpm/crates/exportable-manifest/src/create.rs
260
+ * @see https://github.com/pnpm/pnpm/blob/v12.6.0/pnpm/crates/exportable-manifest/src/replace.rs
261
+ */
262
+ export const PNPM_PACK_REWRITTEN_SPECIFIER_PATTERN = /^(?:workspace|catalog|jsr):|\|\|\s*workspace:/u;
263
+
264
+ /** Public npm registry: npm's default, used when no config sets `@scope:registry` nor `registry`. */
265
+ export const DEFAULT_NPM_REGISTRY_URL = "https://registry.npmjs.org/";
266
+
267
+ /** Prefix of a package page on npmjs.com, linked in the summary of a release published to {@link DEFAULT_NPM_REGISTRY_URL}. */
268
+ export const NPMJS_PACKAGE_PAGE_URL = "https://www.npmjs.com/package/";
269
+
270
+ /** `publishConfig` key of the registry for every package, used when no scope-specific registry applies. */
271
+ export const PUBLISH_CONFIG_REGISTRY_KEY = "registry";
272
+
273
+ /** Suffix of the scope-specific `publishConfig` registry key, as in `@scope:registry`. */
274
+ export const SCOPED_REGISTRY_KEY_SUFFIX = ":registry";
275
+
276
+ /**
277
+ * Registry URL allowed on the `npm view` command line built for the Windows shell: only characters
278
+ * without shell meaning (no `%`, `&`, `|`, `^`, quotes or spaces), after URL normalization.
279
+ */
280
+ export const SHELL_SAFE_REGISTRY_URL_PATTERN = /^https?:\/\/[\w.~:/@+-]+$/u;
281
+
282
+ /** npm option that selects the registry `npm view` queries. */
283
+ export const NPM_REGISTRY_OPTION = "--registry";
284
+
285
+ /** npm option that replaces the user config (`~/.npmrc`) with the temporary one. */
286
+ export const NPM_USER_CONFIG_OPTION = "--userconfig";
287
+
288
+ /** npm subcommand that prints the effective value of a config key. */
289
+ export const NPM_CONFIG_GET_ARGUMENTS = Object.freeze(["config", "get"]);
290
+
291
+ /** What `npm config get` prints for a key no config source sets, such as an unused `@scope:registry`. */
292
+ export const NPM_UNSET_CONFIG_VALUE = "undefined";
293
+
294
+ /** Protocols a publish registry URL may use. */
295
+ export const NPM_REGISTRY_PROTOCOLS = Object.freeze(["http:", "https:"]);
296
+
297
+ /**
298
+ * Value of the registry credential in the temporary npm config: npm expands `${NPM_TOKEN}` from
299
+ * the environment, so the token never reaches the disk or a command line.
300
+ */
301
+ export const NPM_AUTH_TOKEN_REFERENCE = `\${${NPM_TOKEN_VARIABLE}}`;
302
+
303
+ /** Prefix of the temporary directory holding the publish-only npm user config. */
304
+ export const NPM_AUTH_DIRECTORY_PREFIX = "beez-rp-npm-auth-";
305
+
306
+ /** Characters that could break out of a quoted path on the Windows shell. */
307
+ export const UNSAFE_QUOTED_PATH_PATTERN = /["%]/u;
308
+
309
+ /** Placeholder replaced by the released version in `summary` lines. */
310
+ export const SUMMARY_VERSION_PLACEHOLDER = "{version}";
311
+
312
+ /**
313
+ * Shown when syncing `main` brought new commits: the run ends (exit code 0, it is not a failure)
314
+ * so the next one loads the updated configuration and diagnoses again.
315
+ */
316
+ export const MAIN_SYNCED_RESTART_MESSAGE = `${MAIN_BRANCH} se actualizó desde origin: volvé a correr pnpm create-version para diagnosticar con el código y la configuración nuevos.`;
317
+
318
+ /** Exit code of a release stopped by a failed step, invalid arguments or configuration. */
319
+ export const FAILURE_EXIT_CODE = 1;
@@ -0,0 +1,29 @@
1
+ /**
2
+ * Contract of `beez-rp guard-publish`, the `prepublishOnly` guard that keeps
3
+ * Beez packages from being published by a package manager other than npm.
4
+ *
5
+ * @module constants/guard-publish
6
+ */
7
+
8
+ /**
9
+ * Environment variable package managers set for lifecycle scripts, for
10
+ * example `pnpm/12.6.0 npm/? node/v24.21.0 win32 x64`. npm also reads it as its
11
+ * `user-agent` config, so an inherited value survives `npm publish`.
12
+ */
13
+ export const PACKAGE_MANAGER_USER_AGENT_VARIABLE = "npm_config_user_agent";
14
+
15
+ /** User agent prefix of npm, the only package manager allowed to publish. */
16
+ export const ALLOWED_PUBLISH_USER_AGENT_PREFIX = "npm/";
17
+
18
+ /** Package managers whose `publish` is blocked, keyed by their user agent prefix. */
19
+ export const BLOCKED_PUBLISH_PACKAGE_MANAGERS = Object.freeze({
20
+ "pnpm/": "pnpm",
21
+ "yarn/": "yarn",
22
+ "bun/": "bun",
23
+ });
24
+
25
+ /** Exit codes of `beez-rp guard-publish`. */
26
+ export const GUARD_PUBLISH_EXIT_CODE = Object.freeze({
27
+ allowed: 0,
28
+ blocked: 1,
29
+ });
@@ -8,5 +8,7 @@ export * from "./build-gate.js";
8
8
  export * from "./changelog.js";
9
9
  export * from "./changelog-ai.js";
10
10
  export * from "./cli.js";
11
+ export * from "./guard-publish.js";
11
12
  export * from "./terminal-ui.js";
12
13
  export * from "./versions.js";
14
+ export * from "./create-version.js";