@tapestry-mud/cli 0.9.0 → 0.10.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/CLAUDE.md +42 -0
- package/bin/tapestry.js +14 -1
- package/package.json +5 -2
- package/specs/README.md +59 -0
- package/specs/engine-management.md +140 -0
- package/specs/harvest.md +159 -0
- package/specs/lint.config.json +1 -0
- package/specs/pack-lifecycle.md +206 -0
- package/specs/registry-auth.md +127 -0
- package/specs/validate.md +70 -0
- package/src/commands/pack.js +3 -0
- package/src/commands/publish.js +2 -0
- package/src/commands/types.js +15 -0
- package/src/lib/ts-build.js +22 -0
- package/types/tapestry-engine.d.ts +630 -0
- package/validation-ledger.md +29 -0
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
---
|
|
2
|
+
capability: pack-lifecycle
|
|
3
|
+
last-updated: 2026-06-13
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# pack-lifecycle
|
|
7
|
+
|
|
8
|
+
Capability spec for project scaffolding, pack installation and removal, boot-order management,
|
|
9
|
+
local development linking, tarball building, and publishing to the registry.
|
|
10
|
+
|
|
11
|
+
## Overview
|
|
12
|
+
|
|
13
|
+
pack-lifecycle covers the full lifecycle a user exercises to create a game project and manage
|
|
14
|
+
its installed packs: scaffolding a new project (`tapestry init`), installing and removing
|
|
15
|
+
packs from the registry, updating to newer compatible versions, toggling packs in or out of
|
|
16
|
+
the engine boot order, attaching a local working copy for development (`link`), building a
|
|
17
|
+
pack tarball for inspection, and publishing or unpublishing a pack on the registry.
|
|
18
|
+
|
|
19
|
+
Supporting commands `create pack`, `list`, and `outdated` are also in this boundary.
|
|
20
|
+
|
|
21
|
+
The project manifest is `tapestry.yaml`; the lock file is `tapestry-lock.yaml`; the boot
|
|
22
|
+
order file is `tapestry-boot.yaml`; links are tracked in `tapestry-links.yaml`.
|
|
23
|
+
|
|
24
|
+
## Behavior
|
|
25
|
+
|
|
26
|
+
### init
|
|
27
|
+
|
|
28
|
+
- Aborts if `tapestry.yaml` already exists in the target directory.
|
|
29
|
+
(src/commands/init.js:127-129)
|
|
30
|
+
- Fetches the preset list from the registry (`/v1/presets`). If the list endpoint returns 404,
|
|
31
|
+
falls back to fetching the `starter` preset directly. If exactly one preset is available it
|
|
32
|
+
is selected automatically; if multiple exist, the user is prompted to choose.
|
|
33
|
+
(src/commands/init.js:133-151)
|
|
34
|
+
- With `--yes`, skips all interactive prompts and uses directory name as game name, handle
|
|
35
|
+
`admin`, email `admin@localhost`, password `changeme`, telemetry off; prints a warning about
|
|
36
|
+
default credentials. (src/commands/init.js:166-175)
|
|
37
|
+
- Writes `tapestry.yaml` with the preset's engine version and channel, and dependency ranges
|
|
38
|
+
pinned with a `^` prefix from the preset's pack versions. (src/commands/init.js:157-161)
|
|
39
|
+
(src/commands/init.js:226-227)
|
|
40
|
+
- Writes `server.yaml` with server name, telnet port 4000, websocket port 4001, admin handle
|
|
41
|
+
and email, and telemetry block (commented out unless telemetry was selected).
|
|
42
|
+
(src/commands/init.js:228-229)
|
|
43
|
+
- Creates a `packs/` directory and a `.gitignore` excluding `packs/`, `.tapestry-engine/`,
|
|
44
|
+
`tapestry-links.yaml`, and `data/`. (src/commands/init.js:238-241)
|
|
45
|
+
- Warns if no `.git` directory is found. (src/commands/init.js:263-266)
|
|
46
|
+
- `buildManifest` and `buildServerYaml` are exported for test injection.
|
|
47
|
+
(src/commands/init.js:269)
|
|
48
|
+
|
|
49
|
+
### install
|
|
50
|
+
|
|
51
|
+
- Requires `tapestry.yaml`; throws if absent. (src/commands/install.js:83-86)
|
|
52
|
+
- With no argument, skips all linked packs, then checks whether the lock file is current
|
|
53
|
+
(deps_hash matches a SHA-256 of the sorted `name@range` dependency entries). If current,
|
|
54
|
+
installs from the lock; otherwise resolves freshly via the registry.
|
|
55
|
+
(src/commands/install.js:107-122) (src/lib/lock-file.js:10-13)
|
|
56
|
+
- With a package argument, adds or updates the entry in `tapestry.yaml` dependencies, then
|
|
57
|
+
resolves and installs. A bare `@scope/name` (no range) is resolved and the entry is pinned
|
|
58
|
+
at `^<resolvedVersion>`; an explicit `@scope/name@range` writes the supplied range as-is and
|
|
59
|
+
is not re-pinned to the resolved version. (src/commands/install.js:92-105)
|
|
60
|
+
(src/commands/install.js:101-103)
|
|
61
|
+
- Skips a package entirely if it is already linked (local working copy takes precedence).
|
|
62
|
+
(src/commands/install.js:37-41)
|
|
63
|
+
- Skips a package if the installed version already matches the resolved version.
|
|
64
|
+
(src/commands/install.js:50-54)
|
|
65
|
+
- Downloads each tarball to a temp file, verifies its `sha256-` integrity hash, extracts into
|
|
66
|
+
`packs/<scope>/<name>/`, then removes the temp file. (src/commands/install.js:64-75)
|
|
67
|
+
- After installing, calls `addPackageToBoot` to register the pack (and any module entry)
|
|
68
|
+
in `tapestry-boot.yaml`. (src/commands/install.js:77-78)
|
|
69
|
+
- Writes the lock file with `lockfile_version: 1`, `deps_hash`, and the full resolved map.
|
|
70
|
+
(src/commands/install.js:128)
|
|
71
|
+
|
|
72
|
+
### uninstall
|
|
73
|
+
|
|
74
|
+
- Requires `tapestry.yaml` and that the package appears in `dependencies`; throws otherwise.
|
|
75
|
+
(src/commands/uninstall.js:14-22)
|
|
76
|
+
- Deletes `packs/<scope>/<name>/` if present. (src/commands/uninstall.js:24-28)
|
|
77
|
+
- Removes the entry from `tapestry.yaml` and from `lock.resolved`, then writes both files.
|
|
78
|
+
(src/commands/uninstall.js:29-39)
|
|
79
|
+
- Removes the pack (and its module entry) from `tapestry-boot.yaml`.
|
|
80
|
+
(src/commands/uninstall.js:40)
|
|
81
|
+
- Transitive dependencies are NOT automatically removed; a note to run `tapestry install` is
|
|
82
|
+
printed. (src/commands/uninstall.js:43)
|
|
83
|
+
|
|
84
|
+
### update
|
|
85
|
+
|
|
86
|
+
- Resolves fresh versions for all dependencies (or one if a package name is given), bypassing
|
|
87
|
+
the lock file. Merges with the existing lock so packages not being updated keep their pinned
|
|
88
|
+
entries. (src/commands/update.js:36-43)
|
|
89
|
+
- Does NOT pass an auth token to the resolver (update.js:39 calls `resolve(deps, url)` with
|
|
90
|
+
no token, unlike install which passes `loadAccess()`). UNVERIFIED: whether this prevents
|
|
91
|
+
updating private packs; the install path always loads the token.
|
|
92
|
+
- Deletes the old install directory before downloading the new version.
|
|
93
|
+
(src/commands/update.js:54-62)
|
|
94
|
+
- Updates `tapestry-boot.yaml` for the new version via `addPackageToBoot`.
|
|
95
|
+
(src/commands/update.js:80-81)
|
|
96
|
+
- Reports "up to date" and skips download when the resolved version matches what the lock
|
|
97
|
+
already has. (src/commands/update.js:46-49)
|
|
98
|
+
|
|
99
|
+
### enable / disable
|
|
100
|
+
|
|
101
|
+
- Both require `tapestry.yaml`. (src/commands/enable.js:4-9) (src/commands/disable.js:1-16)
|
|
102
|
+
- Delegate to `enablePackage` / `disablePackage` in boot.js, which set `enabled: true/false`
|
|
103
|
+
on the pack entry and on every module entry for that package in `tapestry-boot.yaml`.
|
|
104
|
+
(src/lib/boot.js:51-76)
|
|
105
|
+
- Throw if the package is not in `tapestry-boot.yaml`. (src/lib/boot.js:56) (src/lib/boot.js:68)
|
|
106
|
+
|
|
107
|
+
### link / unlink
|
|
108
|
+
|
|
109
|
+
- `link` requires `tapestry.yaml` and an existing path. (src/commands/link.js:33-38)
|
|
110
|
+
- Reads the target's `pack.yaml`, records the name-to-absolute-path mapping in
|
|
111
|
+
`tapestry-links.yaml`, and adds the pack to `tapestry-boot.yaml`. (src/commands/link.js:45-47)
|
|
112
|
+
- Appends `tapestry-links.yaml` to `.gitignore` if not already present.
|
|
113
|
+
(src/commands/link.js:22-30)
|
|
114
|
+
- Warns if `active: false` is set in the linked pack's manifest. (src/commands/link.js:49-52)
|
|
115
|
+
- With `--skip-install`, skips dependency resolution and prints warnings for any missing deps
|
|
116
|
+
instead. (src/commands/link.js:53-58)
|
|
117
|
+
- Without `--skip-install`, resolves and installs only the deps of the linked pack that are not
|
|
118
|
+
already on disk. Rolls back all changes (link record, boot entry, any newly installed deps)
|
|
119
|
+
on resolution failure. (src/commands/link.js:62-109)
|
|
120
|
+
- `unlink` removes the link record from `tapestry-links.yaml`, deletes any materialized copy
|
|
121
|
+
from `packs/`, and removes the pack and its module from `tapestry-boot.yaml`.
|
|
122
|
+
(src/commands/link.js:112-121)
|
|
123
|
+
- `link --list` (or `link` with no path) prints each linked name and absolute path, flagging
|
|
124
|
+
missing paths with `(MISSING)`. (src/commands/link.js:123-133)
|
|
125
|
+
|
|
126
|
+
### Boot-order management
|
|
127
|
+
|
|
128
|
+
- `tapestry-boot.yaml` has two keys: `modules` (list of module class entries) and `packs`
|
|
129
|
+
(map of pack name to `{ enabled }` flag). (src/lib/boot.js:9-14)
|
|
130
|
+
- `addPackageToBoot` writes the pack entry (always `enabled: true`) and, if the manifest has a
|
|
131
|
+
`module.class`, appends a module entry then topologically sorts all modules.
|
|
132
|
+
(src/lib/boot.js:22-41)
|
|
133
|
+
- `removePackageFromBoot` deletes the pack entry and all module entries for that package.
|
|
134
|
+
(src/lib/boot.js:44-49)
|
|
135
|
+
- Topological sort honors `module.after` to order .NET classes that depend on each other.
|
|
136
|
+
Cycles throw. (src/lib/boot.js:79-110)
|
|
137
|
+
|
|
138
|
+
### Dependency resolution
|
|
139
|
+
|
|
140
|
+
- Resolution is a breadth-first queue over `name@range` pairs, fetching package metadata from
|
|
141
|
+
`/v1/packages/<name>`. (src/lib/semver-resolver.js:21-79)
|
|
142
|
+
- Dist-tag names (all lowercase letters) are resolved to a version range via `meta.dist_tags`
|
|
143
|
+
before semver selection. (src/lib/semver-resolver.js:39-44)
|
|
144
|
+
- Picks the maximum semver version satisfying the range. Conflicts (same package, incompatible
|
|
145
|
+
ranges from different requirers) throw with a descriptive message.
|
|
146
|
+
(src/lib/semver-resolver.js:24-32) (src/lib/semver-resolver.js:46-51)
|
|
147
|
+
- Transitive dependencies are queued from the resolved manifest's `dependencies` field; peer
|
|
148
|
+
dependencies emit a warning if not present but do not block resolution.
|
|
149
|
+
(src/lib/semver-resolver.js:69-79)
|
|
150
|
+
- Package names must match `@scope/name` format; path traversal (`..`, `//`) is rejected at
|
|
151
|
+
the HTTP layer. (src/lib/registry-client.js:7-16)
|
|
152
|
+
|
|
153
|
+
### pack (build tarball)
|
|
154
|
+
|
|
155
|
+
- Runs `validate` first; aborts on validation failure. (src/commands/pack.js:7)
|
|
156
|
+
- For ESM packs (`scripts_format: esm`), compiles `scripts/**/*.ts` to `dist/scripts/**/*.js`
|
|
157
|
+
using the bundled `tsc` and the pack's `tsconfig.json`; legacy packs are a no-op.
|
|
158
|
+
(src/commands/pack.js:14) (src/lib/ts-build.js:8-20)
|
|
159
|
+
- Builds a `.tgz` under a `package/` prefix in the current directory named
|
|
160
|
+
`<shortName>-<version>.tgz`. (src/commands/pack.js:16) (src/lib/tarball-builder.js:16-26)
|
|
161
|
+
- Excludes `.git`, `node_modules`, `.DS_Store`, and `.tgz` files from the archive.
|
|
162
|
+
(src/lib/tarball-builder.js:7-12)
|
|
163
|
+
- Prints the `sha256-<base64>` integrity hash of the output file.
|
|
164
|
+
(src/commands/pack.js:22) (src/lib/tarball-builder.js:29-33)
|
|
165
|
+
|
|
166
|
+
### publish
|
|
167
|
+
|
|
168
|
+
- Runs `validate` first; aborts on validation failure. (src/commands/publish.js:18)
|
|
169
|
+
- For ESM packs (`scripts_format: esm`), compiles `scripts/**/*.ts` to `dist/scripts/**/*.js`
|
|
170
|
+
using the bundled `tsc` and the pack's `tsconfig.json` before any network or auth work;
|
|
171
|
+
legacy packs are a no-op. (src/commands/publish.js:21) (src/lib/ts-build.js:8-20)
|
|
172
|
+
- In a GitHub Actions OIDC environment (both `ACTIONS_ID_TOKEN_REQUEST_URL` and
|
|
173
|
+
`ACTIONS_ID_TOKEN_REQUEST_TOKEN` set), fetches a GitHub id-token and exchanges it for a
|
|
174
|
+
scoped access token; also sets a `tag: stable` field on the form.
|
|
175
|
+
(src/commands/publish.js:26-30)
|
|
176
|
+
- Otherwise, requires an existing authenticated session. (src/commands/publish.js:31)
|
|
177
|
+
- Builds the tarball to a temp file, computes integrity, POSTs a multipart form with the
|
|
178
|
+
tarball and JSON metadata (manifest fields plus integrity) to `/v1/publish`.
|
|
179
|
+
(src/commands/publish.js:40-65)
|
|
180
|
+
- Deletes the temp file in a `finally` block. (src/commands/publish.js:71-73)
|
|
181
|
+
|
|
182
|
+
### create pack (scaffold)
|
|
183
|
+
|
|
184
|
+
- Accepts `@scope/name` (used as-is) or a plain name (scope defaults to `@todo`).
|
|
185
|
+
(src/commands/create-pack.js:8-18)
|
|
186
|
+
- Creates a subdirectory named after the short name in the current directory; throws if it
|
|
187
|
+
already exists. (src/commands/create-pack.js:41-46)
|
|
188
|
+
- Writes the scaffold files from `generatePackFiles` and prints each one.
|
|
189
|
+
(src/commands/create-pack.js:48-57) (src/scaffold/templates.js)
|
|
190
|
+
|
|
191
|
+
### list
|
|
192
|
+
|
|
193
|
+
- Reads the lock file and boot file; prints package name, resolved version, type (from the
|
|
194
|
+
installed `pack.yaml`), and enabled/disabled status.
|
|
195
|
+
(src/commands/list.js:17-48)
|
|
196
|
+
- If no packages are installed, prints "No packages installed." (src/commands/list.js:46-48)
|
|
197
|
+
- Also prints linked packs below the installed list, flagging missing paths.
|
|
198
|
+
(src/commands/list.js:50-58)
|
|
199
|
+
|
|
200
|
+
## Rejected and Reverted
|
|
201
|
+
|
|
202
|
+
- None on record.
|
|
203
|
+
|
|
204
|
+
## Change Log
|
|
205
|
+
|
|
206
|
+
- None on record.
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
---
|
|
2
|
+
capability: registry-auth
|
|
3
|
+
last-updated: 2026-06-13
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# registry-auth
|
|
7
|
+
|
|
8
|
+
Capability spec for user identity, session management, and OIDC trusted publishing.
|
|
9
|
+
|
|
10
|
+
## Overview
|
|
11
|
+
|
|
12
|
+
registry-auth covers everything the CLI does on behalf of a user's identity: password-based
|
|
13
|
+
login that exchanges credentials for an access/refresh token pair; silent proactive token
|
|
14
|
+
refresh before expiry; logout with best-effort server-side revocation; account registration
|
|
15
|
+
and password change; and the OIDC trusted-publishing path that lets a GitHub Actions workflow
|
|
16
|
+
publish a pack without stored credentials.
|
|
17
|
+
|
|
18
|
+
Trust binding management (`tapestry trust add/list/rm`) is in this boundary because it
|
|
19
|
+
configures the OIDC flow on the registry side.
|
|
20
|
+
|
|
21
|
+
## Behavior
|
|
22
|
+
|
|
23
|
+
### Session storage
|
|
24
|
+
|
|
25
|
+
- The session file lives at `~/.tapestryrc` (resolved via `os.homedir()`).
|
|
26
|
+
(src/lib/auth.js:9)
|
|
27
|
+
- Written with mode `0o600` (user-read/write only). (src/lib/auth.js:28)
|
|
28
|
+
- Contains four YAML keys: `registry` (the registry URL), `access` (the access JWT),
|
|
29
|
+
`access_exp` (the JWT `exp` claim decoded as a Unix timestamp), and `refresh` (the refresh
|
|
30
|
+
token string). (src/lib/auth.js:24-29)
|
|
31
|
+
- `decodeExp` extracts `exp` from the JWT payload by base64url-decoding the second segment.
|
|
32
|
+
Returns null on any parse error. (src/lib/auth.js:38-45)
|
|
33
|
+
- `readSession` returns null if the file is absent or unparseable.
|
|
34
|
+
(src/lib/auth.js:13-20)
|
|
35
|
+
- `clearSession` deletes the file if it exists; does nothing otherwise.
|
|
36
|
+
(src/lib/auth.js:32-36)
|
|
37
|
+
|
|
38
|
+
### Token refresh
|
|
39
|
+
|
|
40
|
+
- `loadAccess` returns the stored access token if `access_exp` is present and the token will
|
|
41
|
+
not expire within the next 60 seconds (`REFRESH_SKEW_SECONDS`). (src/lib/auth.js:52-54)
|
|
42
|
+
- If the access token is expired or near expiry, `loadAccess` silently POSTs the refresh
|
|
43
|
+
token to `<registry>/v1/auth/refresh` and saves the new access and refresh tokens.
|
|
44
|
+
(src/lib/auth.js:58-68)
|
|
45
|
+
- If the refresh request fails, the session is cleared and `loadAccess` returns null (treated
|
|
46
|
+
as "not logged in" by callers). (src/lib/auth.js:63-65)
|
|
47
|
+
- `requireAccess` wraps `loadAccess` and throws "Not logged in. Run: tapestry login" if the
|
|
48
|
+
result is null. (src/lib/auth.js:72-77)
|
|
49
|
+
- Sessions that have an `access` token but no `refresh` token (legacy format) are treated as
|
|
50
|
+
absent by `loadAccess`. (src/lib/auth.js:48-50)
|
|
51
|
+
|
|
52
|
+
### login
|
|
53
|
+
|
|
54
|
+
- Prompts for email and password interactively unless both are passed programmatically.
|
|
55
|
+
(src/commands/login.js:8-16) (src/commands/login.js:19-21)
|
|
56
|
+
- POSTs `{ email, password }` to `<registry>/v1/auth/login`. (src/commands/login.js:23-28)
|
|
57
|
+
- On success, saves the returned `access_token`, its decoded `exp`, and `refresh_token` to
|
|
58
|
+
`~/.tapestryrc`. Prints "Logged in." (src/commands/login.js:33-34)
|
|
59
|
+
- The registry URL defaults to `TAPESTRY_REGISTRY` env var or
|
|
60
|
+
`https://registry.tapestryengine.com`. (src/lib/registry-client.js:5)
|
|
61
|
+
|
|
62
|
+
### logout
|
|
63
|
+
|
|
64
|
+
- Reads the current session. If a refresh token is present, attempts a server-side revocation
|
|
65
|
+
by POSTing the refresh token to `<registry>/v1/auth/logout`. Server errors are swallowed;
|
|
66
|
+
the local session is always cleared regardless. (src/commands/logout.js:6-17)
|
|
67
|
+
- Deletes `~/.tapestryrc`. Prints "Logged out." (src/commands/logout.js:14-16)
|
|
68
|
+
|
|
69
|
+
### register
|
|
70
|
+
|
|
71
|
+
- Prompts for handle, email, and password unless all three are passed programmatically.
|
|
72
|
+
(src/commands/register.js:8-18)
|
|
73
|
+
- POSTs `{ handle, email, password }` to `<registry>/v1/auth/register`. On success, saves
|
|
74
|
+
the returned token pair and prints "Registered as <handle>. Logged in."
|
|
75
|
+
(src/commands/register.js:24-36)
|
|
76
|
+
|
|
77
|
+
### change-password
|
|
78
|
+
|
|
79
|
+
- Requires an active session (`requireAccess`); throws if not logged in.
|
|
80
|
+
(src/commands/change-password.js:9)
|
|
81
|
+
- Prompts for current password, new password, and confirmation; throws if the two new-password
|
|
82
|
+
entries do not match. (src/commands/change-password.js:10-17)
|
|
83
|
+
- POSTs `{ currentPassword, newPassword }` to `<registry>/v1/auth/change-password` with a
|
|
84
|
+
Bearer token. Prints "Password changed." on success. (src/commands/change-password.js:18-24)
|
|
85
|
+
|
|
86
|
+
### OIDC trusted publishing
|
|
87
|
+
|
|
88
|
+
- CI mode is detected when both `ACTIONS_ID_TOKEN_REQUEST_URL` and
|
|
89
|
+
`ACTIONS_ID_TOKEN_REQUEST_TOKEN` environment variables are set. (src/lib/oidc.js:7-9)
|
|
90
|
+
- `fetchGitHubIdToken` fetches the GitHub OIDC id-token by GETting
|
|
91
|
+
`$ACTIONS_ID_TOKEN_REQUEST_URL&audience=<audience>` with the request token in the
|
|
92
|
+
Authorization header. The audience constant is `https://registry.tapestryengine.com`.
|
|
93
|
+
(src/lib/oidc.js:5) (src/lib/oidc.js:11-24)
|
|
94
|
+
- `exchangeOIDCForAccess` POSTs the id-token (as a Bearer token) and the target scope to
|
|
95
|
+
`<registry>/v1/token`, returning the `access_token` from the response.
|
|
96
|
+
(src/lib/registry-client.js:169-178)
|
|
97
|
+
- `publish` uses OIDC in CI mode and also appends `tag: stable` to the publish form;
|
|
98
|
+
in interactive mode it uses the standard session access token.
|
|
99
|
+
(src/commands/publish.js:22-30)
|
|
100
|
+
|
|
101
|
+
### Trust binding management
|
|
102
|
+
|
|
103
|
+
Trust bindings authorize a GitHub repository to publish packs to a scope via OIDC without
|
|
104
|
+
stored credentials. All three sub-commands require an active session.
|
|
105
|
+
|
|
106
|
+
- `trust add <scope> <repo>` POSTs `{ scope, repo }` (plus optional `ref` and `environment`)
|
|
107
|
+
to `<registry>/v1/trusted-publishers`. Prints the assigned id on success.
|
|
108
|
+
(src/lib/registry-client.js:141-149) (src/commands/trust.js:13-19)
|
|
109
|
+
- `trust list` GETs `/v1/trusted-publishers` (optionally filtered with `?scope=`).
|
|
110
|
+
Prints each binding as `#<id> @<scope> <- <repo> (ref=... env=...)`.
|
|
111
|
+
(src/lib/registry-client.js:151-158) (src/commands/trust.js:22-33)
|
|
112
|
+
- `trust rm <id>` DELETEs `/v1/trusted-publishers/<id>`.
|
|
113
|
+
(src/lib/registry-client.js:160-167) (src/commands/trust.js:36-40)
|
|
114
|
+
|
|
115
|
+
### Rate limiting
|
|
116
|
+
|
|
117
|
+
- HTTP 429 responses are detected before any other error handling. If the response includes a
|
|
118
|
+
`retry-after` header (in seconds), the error message includes a "Try again in N min." hint.
|
|
119
|
+
(src/lib/registry-client.js:20-24)
|
|
120
|
+
|
|
121
|
+
## Rejected and Reverted
|
|
122
|
+
|
|
123
|
+
- None on record.
|
|
124
|
+
|
|
125
|
+
## Change Log
|
|
126
|
+
|
|
127
|
+
- None on record.
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
---
|
|
2
|
+
capability: validate
|
|
3
|
+
last-updated: 2026-06-13
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# validate
|
|
7
|
+
|
|
8
|
+
Capability spec for offline pack manifest validation.
|
|
9
|
+
|
|
10
|
+
## Overview
|
|
11
|
+
|
|
12
|
+
`tapestry validate` reads `pack.yaml` from the current directory and checks it against the
|
|
13
|
+
Zod schema that defines a valid pack manifest. No network calls are made; no registry is
|
|
14
|
+
consulted. The command is also called internally by `pack` and `publish` before they proceed.
|
|
15
|
+
|
|
16
|
+
## Behavior
|
|
17
|
+
|
|
18
|
+
- Looks for `pack.yaml` (the constant `PACK_MANIFEST`) in the current working directory.
|
|
19
|
+
(src/lib/manifest.js:3) (src/commands/validate.js:10-11)
|
|
20
|
+
- If `pack.yaml` is absent but `tapestry.yaml` is present, throws a specific error explaining
|
|
21
|
+
that the file found is a server manifest, not a pack manifest.
|
|
22
|
+
(src/commands/validate.js:13-20)
|
|
23
|
+
- If neither file is present, throws "No pack.yaml found in current directory".
|
|
24
|
+
(src/commands/validate.js:21)
|
|
25
|
+
- Runs `validatePackageManifest` which calls Zod's `safeParse` on the YAML-parsed data.
|
|
26
|
+
(src/commands/validate.js:25-26) (src/schema/manifest.js:58-60)
|
|
27
|
+
- On success, prints "OK <name> v<version>" with two leading spaces. (src/commands/validate.js:38-39)
|
|
28
|
+
- On failure, iterates Zod issues and prints each as " error: <fieldPath> - <message>".
|
|
29
|
+
The field path is the dot-joined `issue.path`, or "root" if the path is empty.
|
|
30
|
+
(src/commands/validate.js:29-36)
|
|
31
|
+
- For an `engine` field that is an object (the server manifest format) in a document being
|
|
32
|
+
validated as a pack manifest, appends a hint that `engine` must be a version constraint
|
|
33
|
+
string in pack manifests. (src/commands/validate.js:31-35)
|
|
34
|
+
- Throws with a count of validation errors so callers (`pack`, `publish`) can abort.
|
|
35
|
+
(src/commands/validate.js:37)
|
|
36
|
+
|
|
37
|
+
### Pack manifest schema
|
|
38
|
+
|
|
39
|
+
Required fields (src/schema/manifest.js:7-39):
|
|
40
|
+
|
|
41
|
+
- `name` -- string matching `@scope/package-name` (lowercase letters, digits, hyphens only).
|
|
42
|
+
- `version` -- non-empty string (no semver enforcement at this layer).
|
|
43
|
+
- `type` -- one of `core`, `module`, `world`.
|
|
44
|
+
- `display_name` -- non-empty string.
|
|
45
|
+
- `description` -- non-empty string.
|
|
46
|
+
- `author` -- non-empty string.
|
|
47
|
+
- `license` -- non-empty string.
|
|
48
|
+
- `engine` -- non-empty string (a version constraint, e.g. `>=0.0.1`).
|
|
49
|
+
- `validation` -- one of `strict`, `lenient`.
|
|
50
|
+
|
|
51
|
+
Optional fields (src/schema/manifest.js:17-38):
|
|
52
|
+
|
|
53
|
+
- `dependencies` -- record of package name to version range string.
|
|
54
|
+
- `peerDependencies` -- record of package name to version range string.
|
|
55
|
+
- `provides` -- array of strings.
|
|
56
|
+
- `tags` -- string.
|
|
57
|
+
- `module` -- object with required `assembly`, `class`, `implements`; optional `after`.
|
|
58
|
+
- `content` -- record of glob key to glob pattern string.
|
|
59
|
+
- `client` -- object with `manifest`, `assets`, `min_client_version`.
|
|
60
|
+
- `meta` -- object with optional `commands` (string array), `properties` (number), `keywords`
|
|
61
|
+
(string array).
|
|
62
|
+
- `private` -- boolean.
|
|
63
|
+
|
|
64
|
+
## Rejected and Reverted
|
|
65
|
+
|
|
66
|
+
- None on record.
|
|
67
|
+
|
|
68
|
+
## Change Log
|
|
69
|
+
|
|
70
|
+
- None on record.
|
package/src/commands/pack.js
CHANGED
|
@@ -5,11 +5,14 @@ const { readYaml } = require('../util/yaml');
|
|
|
5
5
|
const { buildTarball, computeIntegrity } = require('../lib/tarball-builder');
|
|
6
6
|
const { PACK_MANIFEST } = require('../lib/manifest');
|
|
7
7
|
const { validate } = require('./validate');
|
|
8
|
+
const { buildTypeScript } = require('../lib/ts-build');
|
|
8
9
|
|
|
9
10
|
async function pack({ cwd = process.cwd() } = {}) {
|
|
10
11
|
validate({ cwd });
|
|
11
12
|
|
|
12
13
|
const manifest = readYaml(path.join(cwd, PACK_MANIFEST));
|
|
14
|
+
buildTypeScript(cwd, manifest);
|
|
15
|
+
|
|
13
16
|
const shortName = manifest.name.split('/')[1];
|
|
14
17
|
const outputPath = path.join(cwd, `${shortName}-${manifest.version}.tgz`);
|
|
15
18
|
|
package/src/commands/publish.js
CHANGED
|
@@ -12,11 +12,13 @@ const { PACK_MANIFEST } = require('../lib/manifest');
|
|
|
12
12
|
const { requireAccess } = require('../lib/auth');
|
|
13
13
|
const { DEFAULT_REGISTRY, throwIfError, exchangeOIDCForAccess } = require('../lib/registry-client');
|
|
14
14
|
const { detectCI, fetchGitHubIdToken, AUDIENCE } = require('../lib/oidc');
|
|
15
|
+
const { buildTypeScript } = require('../lib/ts-build');
|
|
15
16
|
|
|
16
17
|
async function publish({ cwd = process.cwd(), registryUrl = DEFAULT_REGISTRY } = {}) {
|
|
17
18
|
validate({ cwd });
|
|
18
19
|
|
|
19
20
|
const manifest = readYaml(path.join(cwd, PACK_MANIFEST));
|
|
21
|
+
buildTypeScript(cwd, manifest);
|
|
20
22
|
|
|
21
23
|
const scope = manifest.name.match(/^@([^/]+)\//)[1];
|
|
22
24
|
const ciMode = detectCI();
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
|
|
6
|
+
function types({ cwd = process.cwd() } = {}) {
|
|
7
|
+
const src = path.join(__dirname, '..', '..', 'types', 'tapestry-engine.d.ts');
|
|
8
|
+
const destDir = path.join(cwd, 'types');
|
|
9
|
+
fs.mkdirSync(destDir, { recursive: true });
|
|
10
|
+
const dest = path.join(destDir, 'tapestry-engine.d.ts');
|
|
11
|
+
fs.copyFileSync(src, dest);
|
|
12
|
+
console.log(`Wrote ${path.relative(cwd, dest)}`);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
module.exports = { types };
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const path = require('path');
|
|
4
|
+
const { spawnSync } = require('child_process');
|
|
5
|
+
|
|
6
|
+
// Compiles an ESM pack's TypeScript (scripts/**/*.ts -> dist/scripts/**/*.js) using the
|
|
7
|
+
// bundled tsc and the pack's tsconfig.json. No-op for legacy packs (raw .js, no build).
|
|
8
|
+
function buildTypeScript(cwd, manifest) {
|
|
9
|
+
const format = manifest && manifest.content && manifest.content.scripts_format;
|
|
10
|
+
if (format !== 'esm') {
|
|
11
|
+
return;
|
|
12
|
+
}
|
|
13
|
+
const tsc = require.resolve('typescript/bin/tsc');
|
|
14
|
+
const tsconfig = path.join(cwd, 'tsconfig.json');
|
|
15
|
+
console.log('Compiling TypeScript (tsc)...');
|
|
16
|
+
const res = spawnSync(process.execPath, [tsc, '-p', tsconfig], { cwd, stdio: 'inherit' });
|
|
17
|
+
if (res.status !== 0) {
|
|
18
|
+
throw new Error('TypeScript compile failed');
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
module.exports = { buildTypeScript };
|