@nurix/etna 0.19.82 → 0.19.83
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/bin/etna.js +99 -7
- package/data/catalog/index.json +20 -0
- package/data/catalog/rules/changelog.md +25 -0
- package/data/catalog/rules/cloudflare.md +180 -0
- package/data/catalog/rules/code-eval-batcher.md +67 -0
- package/data/catalog/rules/database.md +34 -0
- package/data/catalog/rules/frontend.md +40 -0
- package/data/catalog/rules/general.md +73 -0
- package/data/catalog/rules/kb.md +6 -0
- package/data/catalog/rules/nomenclature.md +30 -0
- package/data/catalog/rules/pnpm.md +57 -0
- package/data/catalog/rules/react-native.md +120 -0
- package/data/catalog/rules/rust-desktop.md +109 -0
- package/data/catalog/rules/temporal.md +78 -0
- package/data/catalog/rules/testing.md +64 -0
- package/data/catalog/skills/nustack/SKILL.md +15 -0
- package/data/catalog/skills/nustack/references/cli-design.md +86 -0
- package/data/catalog/skills/nustack/references/monorepo-master-list.md +52 -0
- package/data/catalog/skills/nustack/references/monorepo-setup.md +98 -0
- package/data/catalog/skills/nustack/references/publishing.md +132 -0
- package/data/harness-index.json +1 -1
- package/package.json +2 -2
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
# The monorepo master list
|
|
2
|
+
|
|
3
|
+
Load when: auditing a workspace's health, reviewing a monorepo PR, scaffolding review gates, or deciding what to enforce next in an existing workspace.
|
|
4
|
+
|
|
5
|
+
The definitive requirements for a `@nurix/*` monorepo, in three tiers: **required** (every workspace, day one), **trigger-bound** (required the moment its trigger exists), and **deferred** (explicitly not required before scale). Assembly mechanics are [`monorepo-setup.md`](monorepo-setup.md); the publishing pipeline is [`publishing.md`](publishing.md); naming doctrine is [`pnpm.md`](../../../rules/pnpm.md) §1. Every line is auditable — it holds or it doesn't — and names its enforcement mechanism in the closing table.
|
|
6
|
+
|
|
7
|
+
## Required — every workspace, day one
|
|
8
|
+
|
|
9
|
+
1. **Declared membership** — the `pnpm-workspace.yaml` globs cover every `package.json` in the tree: no undeclared package islands, no second lockfile beside `pnpm-lock.yaml`.
|
|
10
|
+
2. **Catalog as law** — every dep shared by ≥2 members lives in `catalog:`, members reference `"catalog:"`, and `catalogMode: strict` is on so a literal pin of a cataloged dep is an install error. A declared-but-unconsumed catalog is drift with extra steps.
|
|
11
|
+
3. **Private pinned root** — root `package.json` carries `private: true`, an exact `packageManager` pin, and `engines.node: ">=22"`; the root never compiles and never publishes.
|
|
12
|
+
4. **Full-tree fan-out** — root `build` / `typecheck` / `test` run `pnpm -r`; a member the root scripts can't reach is unmaintained by definition.
|
|
13
|
+
5. **One TS policy owner** — `tsconfig.base.json` carries the full strict set (`strict`, `noUncheckedIndexedAccess`, `noImplicitOverride`, `noFallthroughCasesInSwitch`, `noUnusedLocals`/`noUnusedParameters`, `declaration`+`declarationMap`); every member extends it thinly (coordinates + environment only; a framework-owned base is the one exception); no root `tsconfig.json`. A base nobody extends governs nothing.
|
|
14
|
+
6. **Doctrine names with dot⇒private parity** — internal members are `@nurix/<repo>.<thing>` **and** `private: true`: the dot signals, the flag blocks (npm allows dots in names — `socket.io` — so only the flag prevents publishing). A clean `@nurix/<thing>` hyphen name exists only as a deliberate publish surface. Bespoke per-repo scopes are drift.
|
|
15
|
+
7. **Cross-repo dependencies ride npm, never disk** — zero `link:`/`file:` to paths outside the repo; consume published restricted versions (`"^0.14.1"`). A fresh clone either installs or fails loudly, and a sibling-repo rename can't strand manifests. A dep that must be co-developed belongs inside this workspace.
|
|
16
|
+
8. **Siblings and singletons wired right** — sibling deps via `workspace:*`; host singletons (react, the framework client) as `peerDependencies` in libs; apps hold them directly.
|
|
17
|
+
9. **Source-first internals, one-way flow** — internal packages export `./src/index.ts` through typed subpaths, no build step; apps depend on packages, never the reverse; anything not exported stays unreachable.
|
|
18
|
+
10. **Strict hoisting** — no `shamefully-hoist`, no `node-linker=hoisted`; an undeclared import must fail immediately.
|
|
19
|
+
11. **CI that gates** — a workflow installs with `--frozen-lockfile` and gates merges on the root fan-out. No CI is a compliance failure; a CI that mutates manifests before building is worse than none.
|
|
20
|
+
12. **A real suite behind `pnpm test`** — the canonical-command rule applies to every workspace; toolchain configs (eslint + prettier, root-only) are wired to `lint`/`format` scripts that actually run. A fan-out script with zero targets is decoration, not compliance.
|
|
21
|
+
|
|
22
|
+
## Required the moment the trigger exists
|
|
23
|
+
|
|
24
|
+
- **Native or postinstall deps in the lockfile** → `onlyBuiltDependencies` in `pnpm-workspace.yaml` (never the root `package.json` `pnpm` field), kept current as native deps come and go — pnpm 10 silently skips unlisted builds and the failure surfaces at runtime.
|
|
25
|
+
- **A vulnerable transitive** → one `overrides` block in the workspace file; never duplicated into the root manifest.
|
|
26
|
+
- **A member crosses the publish boundary** (`private: false`) → the [`publishing.md`](publishing.md) fields; the build hook **inside** `scripts` (a top-level `prepublishOnly` key silently never runs); `sideEffects: false` (or the CSS-glob variant) so consumers tree-shake; and the parity harness — `pack` → tarball-install into fixtures → build → `publint` + `attw` — re-run on every `exports`-surface change. Dev-mode success proves nothing about what consumers get.
|
|
27
|
+
|
|
28
|
+
## Deferred until scale — explicitly not required before
|
|
29
|
+
|
|
30
|
+
- **Affected-filtering and task-graph runners** (`--filter "...[origin/main]"`, turbo, nx) — adopt when full `pnpm -r` runs are slow enough to feel; never day one.
|
|
31
|
+
- **`CODEOWNERS` by directory** — when multiple teams own members; decoration before that.
|
|
32
|
+
|
|
33
|
+
## Enforcement — every rule names its mechanism
|
|
34
|
+
|
|
35
|
+
A rule that depends on discipline drifts; a rule the tooling enforces doesn't. When adding a rule to this list, name the mechanism and what happens on violation — prefer the variant that fails loudly at install/CI over the one a reviewer must notice.
|
|
36
|
+
|
|
37
|
+
| Rule | Mechanism | On violation |
|
|
38
|
+
| --- | --- | --- |
|
|
39
|
+
| Cross-repo deps via npm | registry resolution + ambient restricted auth | fresh clone/CI fails loudly — never a machine-specific path |
|
|
40
|
+
| Dot-name internal members | `private: true` (`npm publish` refuses); the dot alone blocks nothing | publish attempt hard-fails |
|
|
41
|
+
| Sibling deps | `workspace:*` refuses registry resolution | can't build against a stale published sibling |
|
|
42
|
+
| One version per shared dep | `catalog:` + `catalogMode: strict` | literal pin of a cataloged dep = install error |
|
|
43
|
+
| No deep imports into siblings | subpath `exports` encapsulation | non-exported path is unresolvable |
|
|
44
|
+
| No phantom deps | strict pnpm hoisting (symlinked `node_modules`) | undeclared import = module-not-found immediately |
|
|
45
|
+
| Source never ships | `files: ["dist"]` whitelist | unlisted files can't enter the tarball |
|
|
46
|
+
| Mistaken publish stays private | `publishConfig.access: "restricted"` | lands org-private, not world-readable |
|
|
47
|
+
| One host-singleton copy | `peerDependencies` + package-manager dedupe | duplicate react = loud hook-dispatcher crash |
|
|
48
|
+
| One pnpm version | exact `packageManager` pin (corepack) | wrong pnpm refuses to run |
|
|
49
|
+
| No unvetted postinstall | pnpm 10 default-block + `onlyBuiltDependencies` | script silently skipped until allow-listed |
|
|
50
|
+
| Lockfile honesty | `--frozen-lockfile` in CI | drift fails the build |
|
|
51
|
+
| The tarball is what dev promised | parity harness (`pack` → tarball-install → `publint`+`attw`) | catches field bugs no schema validates (e.g. a build hook outside `scripts`) |
|
|
52
|
+
| Rename a dep, not the imports | npm alias (`"old": "npm:@nurix/new@^x"`) | — (escape hatch, not a guard) |
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
# Setting up a `@nurix/*` pnpm monorepo
|
|
2
|
+
|
|
3
|
+
Load when: scaffolding a new pnpm workspace, converting a repo to one, adding a workspace member, or wiring the root toolchain (catalog, scripts, build gating).
|
|
4
|
+
|
|
5
|
+
How a workspace is **assembled** — the root files, layout globs, catalog, and verification. What packages are *named* and how their boundaries work is [`pnpm.md`](../../../rules/pnpm.md) §1–2; how a member *publishes* is [`publishing.md`](publishing.md); the standing invariants to keep true after assembly are [`monorepo-master-list.md`](monorepo-master-list.md). Reference implementations: `~/dev/workflows` (the canonical `packages/*` + catalog shape), `~/dev/starter` (adds `fixtures/*` and the publish-parity harness), `~/dev/robin` and `~/dev/sync-info` (the `packages/*` + `apps/*` split).
|
|
6
|
+
|
|
7
|
+
## 1. Three root files own the workspace
|
|
8
|
+
|
|
9
|
+
Everything workspace-wide lives in exactly three root files; packages stay thin. Nothing else at root compiles, publishes, or carries policy.
|
|
10
|
+
|
|
11
|
+
**`pnpm-workspace.yaml`** — membership + shared versions:
|
|
12
|
+
|
|
13
|
+
```yaml
|
|
14
|
+
packages:
|
|
15
|
+
- "packages/*"
|
|
16
|
+
- "apps/*" # only if the repo has deployables — see §2
|
|
17
|
+
|
|
18
|
+
catalog: # one version per shared dep — see §3
|
|
19
|
+
typescript: ^5.7.3
|
|
20
|
+
react: ^19.2.0
|
|
21
|
+
react-dom: ^19.2.0
|
|
22
|
+
vite: ^8.0.0
|
|
23
|
+
vitest: latest
|
|
24
|
+
zod: ^4.2.0
|
|
25
|
+
|
|
26
|
+
onlyBuiltDependencies: # postinstall allow-list — see §4
|
|
27
|
+
- esbuild
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
**Root `package.json`** — a private orchestrator, never a package:
|
|
31
|
+
|
|
32
|
+
```jsonc
|
|
33
|
+
{
|
|
34
|
+
"name": "<repo-name>", // or "@nurix/<repo>.root" (starter) — never publishable
|
|
35
|
+
"private": true, // MUST — `pnpm -r publish` would otherwise ship the root
|
|
36
|
+
"packageManager": "pnpm@10.33.1", // exact pin, no range — corepack enforces it; without it contributors churn the lockfile across pnpm majors
|
|
37
|
+
"engines": { "node": ">=22" },
|
|
38
|
+
"scripts": {
|
|
39
|
+
"build": "pnpm -r build",
|
|
40
|
+
"typecheck": "pnpm -r typecheck",
|
|
41
|
+
"test": "pnpm -r test",
|
|
42
|
+
"dev": "pnpm -r --parallel --if-present dev",
|
|
43
|
+
"lint": "eslint .",
|
|
44
|
+
"format": "prettier --write ."
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
- **Fan out with `pnpm -r`; alias with `--filter`.** `--if-present` when not every member has the script; per-app conveniences read `"dev:admin": "pnpm --filter @nurix/apollo-app dev"` (apollo) or `"worker:dev": "pnpm --filter @sync/worker dev"` (sync-info).
|
|
50
|
+
- **One toolchain config at root.** `eslint`/`prettier` run from the root over the whole tree — never per-package configs.
|
|
51
|
+
- **No turbo/nx.** Plain `pnpm -r` is the baseline; reach for a task-graph runner only when the build graph is slow enough to feel — the same threshold as TS project references (`pnpm.md` §2).
|
|
52
|
+
|
|
53
|
+
**`tsconfig.base.json`** — the single TS policy owner (`pnpm.md` §2). The workflows repo's base is the model: `strict`, `noUncheckedIndexedAccess`, `noImplicitOverride`, `noFallthroughCasesInSwitch`, `noUnusedLocals` / `noUnusedParameters`, `declaration` + `declarationMap`, `skipLibCheck`. There is **no root `tsconfig.json`** — the base is extended by members, never compiled from.
|
|
54
|
+
|
|
55
|
+
## 2. Layout — pick the globs by what the repo holds
|
|
56
|
+
|
|
57
|
+
| Shape | When | Model |
|
|
58
|
+
| --- | --- | --- |
|
|
59
|
+
| `packages/*` only | libraries + services, no deployable front-ends | workflows, campaign-studio |
|
|
60
|
+
| `packages/*` + `apps/*` | shared libs consumed by deployables (web, worker, extension, desktop) | robin, sync-info |
|
|
61
|
+
| Explicit member list | few members, nothing picked up by accident | ui-grader (`taxonomy`, `db`, `rater-ui`, `services/*`) |
|
|
62
|
+
| + `fixtures/*` | consumer-simulation apps for parity testing (§5) | starter |
|
|
63
|
+
|
|
64
|
+
- **Dependency direction is one-way** — `apps/*` depend on `packages/*`, never the reverse; siblings import each other's typed subpath exports, never a deep path into `src/` (`pnpm.md` §2).
|
|
65
|
+
- Flat globs are the default; a deep path in the list (`sdk/packages/node/iii`) is for polyglot trees only.
|
|
66
|
+
|
|
67
|
+
## 3. `catalog:` — one version per shared dep
|
|
68
|
+
|
|
69
|
+
Anything two or more members depend on goes in the catalog: the toolchain (`typescript`, `vite`, `vitest`, type packages) and the host singletons (`react`, `react-dom`, `zod`). Members reference it as `"react": "catalog:"` — never a literal version. **A member that pins its own version beside the catalog defeats it** — two `react` copies means two hook dispatchers and runtime errors, not a type error.
|
|
70
|
+
|
|
71
|
+
Internal siblings are `"workspace:*"`; both protocols are rewritten to real versions at publish time.
|
|
72
|
+
|
|
73
|
+
## 4. pnpm 10 build gating
|
|
74
|
+
|
|
75
|
+
pnpm 10 refuses to run dependency postinstall scripts unless allow-listed. Keep the allow-list in `pnpm-workspace.yaml` — `onlyBuiltDependencies` (or the map-form `allowBuilds`; `pnpm approve-builds` writes it for you) — for native/codegen deps: `esbuild`, `@swc/core`, `better-sqlite3`. The failure mode is **not** an install error: install succeeds and the missing binary surfaces at runtime.
|
|
76
|
+
|
|
77
|
+
## 5. Adding a member
|
|
78
|
+
|
|
79
|
+
1. `packages/<thing>/` — name per [`pnpm.md`](../../../rules/pnpm.md) §1 (`@nurix/<repo>.<thing>`, `private: true` for internal).
|
|
80
|
+
2. Internal member: source-first `exports` — `"types"`/`"default"` → `./src/index.ts`, one typed subpath per public surface (`@nurix/workflows.agent-core` is the model). No build step, no `dist`.
|
|
81
|
+
3. `tsconfig.json`: `extends` the base + `rootDir`/`outDir`/`include` only — coordinates and environment, never policy.
|
|
82
|
+
4. Dependencies: `workspace:*` for siblings, `catalog:` for shared externals.
|
|
83
|
+
5. Publishable member: dist-based `exports`, `files: ["dist"]`, `publishConfig` — [`publishing.md`](publishing.md) §2 — and cover it with the parity check (§6).
|
|
84
|
+
|
|
85
|
+
> **Agent guard — never commit a `link:` to a sibling repo on disk** (`"@nurix/contracts": "link:../../../NuStack-contracts"`). The path is machine-specific: every fresh clone and CI run breaks. Consume the published restricted package (`pnpm.md` §3); if the dep must be co-developed, that's the signal it belongs in this workspace.
|
|
86
|
+
|
|
87
|
+
## 6. Verify — publish parity
|
|
88
|
+
|
|
89
|
+
`pnpm install && pnpm -r typecheck` is the smoke test after any membership or catalog change. For workspaces with publishable members, the starter's `scripts/publish-parity.mjs` is the model harness: `npm pack` each publishable member, tarball-install into isolated copies of `fixtures/*`, build them, run `publint` + `attw` — proving consumers of the tarball get what workspace-source dev promises (a deep `src/` import or a missing `exports` entry works in dev and only breaks here). Wire it as the root `parity` script; run before first publish and after any change to an `exports` surface.
|
|
90
|
+
|
|
91
|
+
## 7. Quick checklist for a new workspace
|
|
92
|
+
|
|
93
|
+
- [ ] `pnpm-workspace.yaml`: globs (§2) + `catalog:` + build allow-list if needed
|
|
94
|
+
- [ ] Root `package.json`: `private: true`, exact `packageManager` pin, `engines`, `pnpm -r` scripts
|
|
95
|
+
- [ ] `tsconfig.base.json` with the strict policy set; no root `tsconfig.json`
|
|
96
|
+
- [ ] First member added per §5; siblings via `workspace:*`, shared deps via `catalog:`
|
|
97
|
+
- [ ] `pnpm install && pnpm -r typecheck` green
|
|
98
|
+
- [ ] Publishable member → [`publishing.md`](publishing.md) + parity harness (§6)
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
# Publishing `@nurix/*` packages
|
|
2
|
+
|
|
3
|
+
Load when: creating or publishing a `@nurix/*` package, wiring or debugging a publish workflow, or diagnosing a failed `npm publish`.
|
|
4
|
+
|
|
5
|
+
How a `@nurix/*` package **ships**: the npm pipeline from `package.json` fields to a published restricted-access version. Naming a package and structuring the workspace it lives in are [`pnpm.md`](../../../rules/pnpm.md) §1–2; consuming an already-published package is `pnpm.md` §3.
|
|
6
|
+
|
|
7
|
+
`@nurix/*` packages publish to **npmjs.org with `restricted` access** — only npm users with read rights to the `nurix` org can install them. Reference implementations: `~/dev/components` (`@nurix/components`, library) and `~/dev/etna/packages/cli` (`@nurix/etna`, CLI).
|
|
8
|
+
|
|
9
|
+
npmjs is the source of truth for **both publish and install**. A caching proxy may sit in front of it for reads, but it is **never a publish target** — treat a proxy URL in a publish path as a bug. And publish and install must name the **same** registry: a package published to one and installed from another 404s, and that 404 is indistinguishable from "never published", because a restricted read answers 404 rather than 401 so it doesn't leak which packages exist. (The consumer-side pairing check is [`pnpm.md`](../../../rules/pnpm.md) §3.)
|
|
10
|
+
|
|
11
|
+
## 1. Prerequisites (one-time, org-wide)
|
|
12
|
+
|
|
13
|
+
1. **npm org `nurix`** exists and you're a member with publish rights — <https://www.npmjs.com/settings/nurix/packages>.
|
|
14
|
+
2. **An npm automation token** with `Read and write` for the `nurix` scope — npm → Generate New Token → Automation.
|
|
15
|
+
3. **The token added as a GitHub Actions secret** named `NPM_PUBLISH_TOKEN` on every publishing repo. `NPM_PUBLISH_TOKEN` is the convention; pick another name only with a strong reason.
|
|
16
|
+
|
|
17
|
+
## 2. `package.json` — required fields
|
|
18
|
+
|
|
19
|
+
**Private by default.** A package is born `"private": true` and stays there until someone needs it from *another repo*. Publishing is a deliberate act that creates a contract — once published, an export removed or a peer range widened is a breaking change for consumers you cannot see. Internal-to-the-repo packages never publish; they link with `workspace:*` ([`pnpm.md`](../../../rules/pnpm.md) §2). When a package does cross the boundary, the manifest carries:
|
|
20
|
+
|
|
21
|
+
```jsonc
|
|
22
|
+
{
|
|
23
|
+
"name": "@nurix/<package-name>", // The scope makes it discoverable to org members.
|
|
24
|
+
"version": "0.1.0", // Start at 0.1.0; the workflow bumps from here.
|
|
25
|
+
"private": false, // MUST be false (or omitted). `npm publish` refuses if true.
|
|
26
|
+
"publishConfig": { "access": "restricted" }, // Scoped + private: only org members can install.
|
|
27
|
+
"files": ["dist"] // Whitelist of what ships. Anything outside is excluded.
|
|
28
|
+
}
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
- **`publishConfig.access`** — `"restricted"` (recommended): visible only to `nurix`-org readers. `"public"`: world-readable (free, but only if you mean it). Omitting it on a scoped package defaults to `restricted`; be explicit anyway.
|
|
32
|
+
- **`files`** — without it, npm publishes everything not in `.gitignore`, **including source**. Library → `["dist"]`; CLI (etna) → `["bin/", "data/", "README.md"]`. Don't list `package.json`/`README.md` — npm always includes them.
|
|
33
|
+
- **Build to `dist` — never ship raw `src`.** A published library compiles with `tsc` (`declaration: true` + `declarationMap: true` come from `tsconfig.base.json`) and points `exports`/`types` at `./dist/*` — `types` first in each subpath block ([`pnpm.md`](../../../rules/pnpm.md) §2 owns the ordering rule). To keep the dev entry on source, use `publishConfig.exports` to swap source→dist at publish time.
|
|
34
|
+
- **Build hooks** — `prepack` for build steps that must produce files in `files` (also runs on install-as-git-dep); `prepublishOnly` for publish-only steps. Either alone is fine; `@nurix/components` runs both. With the build hooked here, a publish can never ship a stale `dist`.
|
|
35
|
+
- **Host singletons are `peerDependencies`** — `react`, `react-dom`, the framework client — so consumers keep exactly one copy; **never** bundle them. Apps depend on them directly.
|
|
36
|
+
- **Dependencies carry registry specs only** — **no** `file:`/`link:` (the workspace-root boundary, [`pnpm.md`](../../../rules/pnpm.md) §2). `workspace:*` between workspace members is fine: `pnpm publish` rewrites it into a real range.
|
|
37
|
+
- **Other useful fields** — `type: "module"`, `engines.node`, `bin` (CLIs), `main`/`module`/`types`/`exports` (libraries), `sideEffects: ["**/*.css"]`, `repository` (with `directory` for monorepos), `license: "UNLICENSED"`.
|
|
38
|
+
|
|
39
|
+
## 3. The publish workflow (generic template)
|
|
40
|
+
|
|
41
|
+
The dual-trigger pattern: a push to the default branch auto-ships a patch; a manual dispatch picks minor/major (or `none` — §4B).
|
|
42
|
+
|
|
43
|
+
```yaml
|
|
44
|
+
name: Publish to npm
|
|
45
|
+
on:
|
|
46
|
+
push:
|
|
47
|
+
branches: [dev] # your default branch
|
|
48
|
+
paths: # ONLY paths that change what ships — else every README edit publishes
|
|
49
|
+
- 'src/**'
|
|
50
|
+
- 'scripts/**'
|
|
51
|
+
- 'package.json'
|
|
52
|
+
- 'package-lock.json'
|
|
53
|
+
- '.github/workflows/publish.yml'
|
|
54
|
+
workflow_dispatch:
|
|
55
|
+
inputs:
|
|
56
|
+
bump: { description: 'Version bump', type: choice, options: [patch, minor, major, none], default: patch }
|
|
57
|
+
permissions: { contents: write } # push the version-bump commit + tag
|
|
58
|
+
concurrency: { group: publish, cancel-in-progress: false } # monorepo: group: publish-<pkg>
|
|
59
|
+
jobs:
|
|
60
|
+
publish:
|
|
61
|
+
runs-on: ubuntu-latest
|
|
62
|
+
steps:
|
|
63
|
+
- uses: actions/checkout@v5
|
|
64
|
+
with: { fetch-depth: 0, token: '${{ secrets.GITHUB_TOKEN }}' }
|
|
65
|
+
- uses: actions/setup-node@v5
|
|
66
|
+
with: { node-version: 22, registry-url: 'https://registry.npmjs.org' } # writes .npmrc using $NODE_AUTH_TOKEN
|
|
67
|
+
- run: npm ci # skip ONLY if the build is pure-Node with no deps (e.g. @nurix/etna)
|
|
68
|
+
- run: |
|
|
69
|
+
git config user.name "github-actions[bot]"
|
|
70
|
+
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
|
|
71
|
+
- run: npm version ${{ inputs.bump || 'patch' }} -m "chore: release v%s [skip ci]"
|
|
72
|
+
if: ${{ inputs.bump != 'none' }} # `none` skips the bump and ships the version as-is (§4B)
|
|
73
|
+
- run: npm publish # prepublishOnly / prepack runs the build
|
|
74
|
+
env: { NODE_AUTH_TOKEN: '${{ secrets.NPM_PUBLISH_TOKEN }}' }
|
|
75
|
+
- run: git push --follow-tags # pushes the bump commit AND the v<version> tag npm created
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
**Why dual-trigger is safe** — three safeguards prevent an infinite publish loop: (1) `paths:` scopes the push trigger to artifact-affecting files; (2) `[skip ci]` in the bump commit makes GitHub skip the workflow that bump would otherwise re-trigger; (3) `concurrency: cancel-in-progress: false` queues near-simultaneous merges instead of racing for a version number.
|
|
79
|
+
|
|
80
|
+
**`registry-url` is load-bearing** — without `registry-url: https://registry.npmjs.org` on `setup-node`, it never writes the `.npmrc` line that uses `$NODE_AUTH_TOKEN`, and the publish falls back to anonymous auth and fails.
|
|
81
|
+
|
|
82
|
+
**The token never enters the repo checkout** — `setup-node` writes its auth `.npmrc` outside the workspace, so the bump commit the job pushes cannot pick it up; a hand-rolled auth line goes to `$HOME/.npmrc` for the same reason, never a repo-path `.npmrc`. Expand the token in the shell step that writes it (`echo "//registry.npmjs.org/:_authToken=$NPM_PUBLISH_TOKEN" >> "$HOME/.npmrc"`) rather than leaving a literal `${VAR}` for the npm client to expand at read time — read-time expansion silently breaks in any later step that lacks the variable.
|
|
83
|
+
|
|
84
|
+
**A pnpm workspace publishes with `pnpm publish --no-git-checks`** — swap it into the publish step above (and into flow C, §4). Only pnpm rewrites `catalog:`/`workspace:` specifiers into real ranges (§6); `--no-git-checks` is required because pnpm otherwise refuses to publish from a non-release branch or from a tree carrying the bump commit the previous step just made. `npm publish` stays correct only for a single-package repo on an npm lockfile.
|
|
85
|
+
|
|
86
|
+
## 4. The three ways to ship
|
|
87
|
+
|
|
88
|
+
- **A. Auto on merge (common).** Land on the default branch; if a `paths:`-filtered file changed, the workflow fires and ships a patch.
|
|
89
|
+
- **B. Manual dispatch (minor/major/none).** **Actions → Publish to npm → Run workflow**, pick `bump = minor|major` — or `none`, which publishes the current version as-is: the CI-side bootstrap for a first publish when a repo ships exclusively from CI (§7's checklist bootstraps from a local publish instead; both paths are sanctioned).
|
|
90
|
+
- **C. Local (CI down / one-off / pipeline bootstrap):** `npm version <patch|minor|major>` → publish → `git push --follow-tags` — sanctioned, at the cost that it bypasses CI's build, lockfile, and version-bump discipline. In a pnpm workspace the publish command is **`pnpm publish --no-git-checks`** (never `npm publish` — the catalog-protocol pitfall in §6; the flag rationale is §3); in a subdirectory of the repo root, `npm version` needs `--no-git-tag-version` plus a manual commit + tag.
|
|
91
|
+
|
|
92
|
+
Local auth uses two tokens, two homes: a **read** token in `~/.npmrc` (`//registry.npmjs.org/:_authToken=…`) for `npm install`, and `NPM_PUBLISH_TOKEN` as an env var (sourced from `~/.zsh_secrets` or equivalent, **not** in `.npmrc` or the repo) for `npm publish`.
|
|
93
|
+
|
|
94
|
+
> **Agent guard — if `NPM_PUBLISH_TOKEN` isn't in the environment, do not work around it.** No `npm login` to swap the user's auth, no rewriting `~/.npmrc`, no substituting the read token (it lacks publish rights → `403`), no `--registry` overrides, no temp-file token. Instead **stop and report**: "This repo can't publish locally — `NPM_PUBLISH_TOKEN` isn't set. Source `~/.zsh_secrets` (or wherever you keep it) and re-run, or trigger the CI workflow." Some users publish exclusively from CI — that's the default path for a reason.
|
|
95
|
+
|
|
96
|
+
Verify after any flow: `https://www.npmjs.com/package/@nurix/<name>` and a clean-dir `npx @nurix/<name>@latest`.
|
|
97
|
+
|
|
98
|
+
## 5. Monorepo deviations
|
|
99
|
+
|
|
100
|
+
When the package lives in `packages/<pkg>/` of a multi-package repo:
|
|
101
|
+
|
|
102
|
+
- **`working-directory: packages/<pkg>`** on the bump and publish steps so they operate on the right `package.json`.
|
|
103
|
+
- **Scoped concurrency** — `group: publish-<pkg>` lets sibling packages publish in parallel.
|
|
104
|
+
- **Tag prefix — only with multiple tagging packages.** `npm version --tag-version-prefix="<pkg>-v"` (→ `<pkg>-v0.2.0`) is worth it only when more than one package cuts release tags, so they don't collide. A single publishable package (etna's case, even though it's laid out as a monorepo) uses plain `v0.2.0`.
|
|
105
|
+
- **One workflow file per published package** — each `publish-<pkg>.yml` owns its `paths:` filter, `working-directory`, and concurrency group. A single shared workflow couples every package's release cadence and republishes siblings on unrelated changes.
|
|
106
|
+
- **Publish dependencies first.** If package `A` depends on `B` by `workspace:^`, `B` must already exist on the registry — otherwise `A`'s rewritten range resolves to nothing for every consumer.
|
|
107
|
+
|
|
108
|
+
A repo with one publishable package only needs `working-directory`; skip the prefix, and concurrency scoping is optional.
|
|
109
|
+
|
|
110
|
+
## 6. Common pitfalls
|
|
111
|
+
|
|
112
|
+
- **402 Payment Required** — `access: "public"` on an org without a plan for free public scoped packages. Use `"restricted"` (or upgrade the plan).
|
|
113
|
+
- **403 Forbidden** — `NPM_PUBLISH_TOKEN` missing/wrong, or its user lacks `nurix` write access, or it's a read-only (not Automation) token.
|
|
114
|
+
- **"Cannot publish over previously published version x.y.z"** — a bump ran but publish failed and was re-run without rebasing. `git reset` the bump and let CI re-bump, or `npm version patch && git push --follow-tags && npm publish`.
|
|
115
|
+
- **Publish succeeds but `npx @nurix/foo` is "command not found"** — no `bin` field, or `bin` points outside `files`, or the script is missing its `#!/usr/bin/env node` shebang.
|
|
116
|
+
- **"Git working directory not clean" on `npm version`** — the checkout has untracked/modified files; build only into `.gitignore`d paths (the components repo builds into `dist/`).
|
|
117
|
+
- **Published tarball contains source** — missing `files` in `package.json`. Add `["dist"]` and republish.
|
|
118
|
+
- **pnpm consumers fail with "external package declared a dependency using the catalog protocol"** — the package was published with `npm publish` from a pnpm workspace: only **`pnpm publish`** rewrites `catalog:`/`workspace:` specifiers to real versions; npm ships them verbatim. A workspace member must always publish via `pnpm publish` (locally too, not just CI). Deprecate the broken version and republish.
|
|
119
|
+
- **Tarball has no `dist/` even though `prepublishOnly` ran clean + build** — a stale `tsconfig.tsbuildinfo` (which `incremental: true` writes *next to tsconfig.json*, outside `dist/`) survived `rm -rf dist`, so `tsc` trusted it and silently emitted nothing. The `clean` script must purge it: `rm -rf dist *.tsbuildinfo`. The parity harness catches this class before it ships.
|
|
120
|
+
- **Auto-publish fires too often** — no `paths:` filter or one that's too broad. Tighten it to artifact-affecting files, or drop the push trigger for `workflow_dispatch`-only.
|
|
121
|
+
- **Workflow loops forever** — the bump commit lacks `[skip ci]`. Invoke `npm version` with `-m "chore: release v%s [skip ci]"`.
|
|
122
|
+
- **A normal commit silently skips its own push** — the inverse trap. GitHub scans the head commit's **whole message** (subject _and_ body) for skip directives (`[skip ci]`, `[ci skip]`, `[no ci]`, `[skip actions]`, `***NO_CI***`). If any hand-written commit merely _mentions_ the literal token in prose, the entire push is skipped and the publish never fires (symptom: branch moved, paths matched, Actions on — yet zero runs for the SHA). Never write the literal token in a commit message or PR body unless you mean it; to ship a push that carried it by accident, re-trigger via **Run workflow** / `gh workflow run … --ref dev`.
|
|
123
|
+
|
|
124
|
+
## 7. Quick checklist for a new `@nurix/*` package
|
|
125
|
+
|
|
126
|
+
- [ ] `package.json`: `name: "@nurix/X"`, `version: "0.1.0"`, `private: false`, `publishConfig.access: "restricted"`, `files: [...]`
|
|
127
|
+
- [ ] Build step wired into `prepack` (or `prepublishOnly`)
|
|
128
|
+
- [ ] Name follows [`pnpm.md`](../../../rules/pnpm.md) §1; if it's a workspace member, boundaries follow `pnpm.md` §2
|
|
129
|
+
- [ ] `.github/workflows/publish.yml` from §3
|
|
130
|
+
- [ ] `NPM_PUBLISH_TOKEN` secret added to the repo
|
|
131
|
+
- [ ] First publish from local (flow C, §4) to verify the pipeline end-to-end, then a no-op commit to verify CI — a repo that ships exclusively from CI bootstraps with `bump = none` instead (§4B)
|
|
132
|
+
- [ ] README notes the `@nurix` scope so consumers know they need org access
|
package/data/harness-index.json
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nurix/etna",
|
|
3
|
-
"version": "0.19.
|
|
3
|
+
"version": "0.19.83",
|
|
4
4
|
"description": "Bootstrap a project with a Nurix harness — copies the harness's CLAUDE.md, skills, agents, rules, and hooks directly into the project from a bundled payload (no marketplace fetch, no network).",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -42,5 +42,5 @@
|
|
|
42
42
|
"agent",
|
|
43
43
|
"plugin"
|
|
44
44
|
],
|
|
45
|
-
"etnaFingerprint": "
|
|
45
|
+
"etnaFingerprint": "574f23e45199"
|
|
46
46
|
}
|