beez-rp 0.2.0 → 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.
package/CHANGELOG.md CHANGED
@@ -6,6 +6,28 @@ 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
+
9
31
  ## [0.2.0] - 2026-09-26
10
32
 
11
33
  ### Added
package/README.md CHANGED
@@ -16,6 +16,7 @@ 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. |
20
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`. |
21
22
  | `beez-rp/testing` | Fixtures de versiones permitidas y rechazadas para los tests de cada proyecto. |
@@ -72,14 +73,14 @@ pnpm cv --dry-run # diagnóstico y plan, sin cambiar nada
72
73
 
73
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`).
74
75
 
75
- 1. Actualiza `main` desde origin si está atrás.
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.
76
77
  2. Aplica migraciones pendientes, si el proyecto tiene adaptador, después de pedir confirmación.
77
78
  3. Si `[Unreleased]` está vacío, lo completa Codex a partir de los commits sin publicar.
78
79
  4. Corre los `checks`.
79
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`.
80
81
  6. Corre `prepare`, sube `main` y el tag con `git push --atomic` y corre `publish`.
81
82
 
82
- 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.
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`.
83
84
 
84
85
  ### beez-rp.config.js
85
86
 
@@ -95,17 +96,42 @@ export default {
95
96
  migrations: { check, apply, targetHint }, // adaptador de base de datos
96
97
  prepare: ["pnpm release:prepare"], // comandos o función, sobre el commit de versión
97
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
98
100
  summary: ["Vercel buildea {version}."], // líneas extra del resumen final
99
101
  };
100
102
  ```
101
103
 
102
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`.
103
105
 
104
- `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, referenciado por el `.npmrc` del repo.
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.
119
+
120
+ ## Bloquear publicaciones con pnpm
121
+
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`.
105
131
 
106
132
  ## Publicar beez-rp
107
133
 
108
- beez-rp se publica con su propio comando (`beez-rp.config.js`: `checks: ["pnpm check"]`, `publish: "npm"`):
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:
109
135
 
110
136
  ```bash
111
137
  pnpm cv
package/bin/beez-rp.js CHANGED
@@ -10,6 +10,8 @@
10
10
  * - `beez-rp create-version [--bump patch|minor|major | --set-version X.Y.Z] [--dry-run]`
11
11
  * diagnoses the repository in the current directory and ships its release
12
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.
13
15
  *
14
16
  * @module beez-rp-cli
15
17
  */
@@ -18,14 +20,21 @@ import { decideBuildForCheckout } from "../src/build-gate.js";
18
20
  import { BUILD_DECISION, DECISION_EXIT_CODE, GATE_FAILURE_EXIT_CODE } from "../src/constants/build-gate.js";
19
21
  import { CLI_COMMAND } from "../src/constants/cli.js";
20
22
  import { runCreateVersion } from "../src/create-version/run.js";
23
+ import { decidePublishGuardForEnvironment } from "../src/guard-publish.js";
21
24
 
22
25
  /** Usage printed for unknown commands. */
23
- const USAGE = `Usage: beez-rp ${CLI_COMMAND.ignoreBuild} | beez-rp ${CLI_COMMAND.createVersion} [options]`;
26
+ const USAGE = `Usage: beez-rp ${CLI_COMMAND.ignoreBuild} | beez-rp ${CLI_COMMAND.createVersion} [options] | beez-rp ${CLI_COMMAND.guardPublish}`;
24
27
 
25
28
  const [command, ...commandArguments] = process.argv.slice(2);
26
29
 
27
30
  if (command === CLI_COMMAND.createVersion) {
28
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;
29
38
  } else if (command === CLI_COMMAND.ignoreBuild) {
30
39
  try {
31
40
  const decision = decideBuildForCheckout(process.cwd());
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "beez-rp",
3
- "version": "0.2.0",
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,10 @@
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
+ },
49
53
  "./create-version": {
50
54
  "types": "./types/create-version/index.d.ts",
51
55
  "default": "./src/create-version/index.js"
@@ -71,7 +75,8 @@
71
75
  "check": "pnpm typecheck && pnpm test",
72
76
  "create-version": "node bin/beez-rp.js create-version",
73
77
  "cv": "node bin/beez-rp.js create-version",
74
- "prepack": "pnpm build:types"
78
+ "prepack": "pnpm build:types",
79
+ "prepublishOnly": "node bin/beez-rp.js guard-publish"
75
80
  },
76
81
  "devDependencies": {
77
82
  "@types/node": "^24.13.4",
@@ -8,4 +8,5 @@
8
8
  export const CLI_COMMAND = Object.freeze({
9
9
  ignoreBuild: "ignore-build",
10
10
  createVersion: "create-version",
11
+ guardPublish: "guard-publish",
11
12
  });
@@ -143,8 +143,177 @@ export const MAX_LISTED_ITEMS = 5;
143
143
  /** Length of the abbreviated commit ids shown to the user. */
144
144
  export const SHORT_SHA_LENGTH = 7;
145
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
+
146
309
  /** Placeholder replaced by the released version in `summary` lines. */
147
310
  export const SUMMARY_VERSION_PLACEHOLDER = "{version}";
148
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
+
149
318
  /** Exit code of a release stopped by a failed step, invalid arguments or configuration. */
150
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,6 +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";
13
14
  export * from "./create-version.js";