create-objectstack 17.1.0 → 17.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 +449 -0
- package/README.md +1 -1
- package/dist/chunk-ZIUW7UEA.js +112 -0
- package/dist/created-summary.d.ts +48 -0
- package/dist/created-summary.js +16 -0
- package/dist/index.js +221 -105
- package/dist/templates/AGENTS.md +1 -1
- package/dist/templates/blank/Dockerfile +2 -2
- package/dist/templates/blank/README.md +32 -8
- package/dist/templates/blank/docker-compose.yml +1 -1
- package/dist/templates/blank/objectstack.config.ts +19 -15
- package/dist/templates/blank/package.json +3 -0
- package/dist/templates/blank/pnpm-workspace.yaml +57 -2
- package/dist/templates/blank/src/objects/note.object.ts +6 -2
- package/package.json +8 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,454 @@
|
|
|
1
1
|
# create-objectstack
|
|
2
2
|
|
|
3
|
+
## 17.3.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- 366f895: feat(auth): migrate `@better-auth/scim` from `1.7.0-rc.1` to stable `1.7.1` — the whole-model SCIM migration (#3653, epic #11632)
|
|
8
|
+
|
|
9
|
+
The stable line is the rc.2-lineage rewrite: the rc.1 `scimProvider` model,
|
|
10
|
+
`/scim/generate-token` endpoint and `storeSCIMToken` option no longer exist,
|
|
11
|
+
replaced by seven new models and a three-way connection contract. This lands
|
|
12
|
+
the migration atomically:
|
|
13
|
+
|
|
14
|
+
- **Seven new platform objects** back the stable models —
|
|
15
|
+
`sys_scim_connection_binding`, `sys_scim_group`, `sys_scim_group_member`,
|
|
16
|
+
`sys_scim_identity_tombstone`, `sys_scim_projection_grant`,
|
|
17
|
+
`sys_scim_subject`, `sys_scim_user` — bridged via `AUTH_MODEL_TO_PROTOCOL`,
|
|
18
|
+
registered in the platform-object-names registry, listed in
|
|
19
|
+
`BETTER_AUTH_MANAGED_OBJECTS`, and column-pinned by the parity gate (whose
|
|
20
|
+
`KNOWN_UNMAPPED_MODELS` shrinks to the empty set: the rc.1-era group
|
|
21
|
+
provisioning gap — IdP `/Groups` pushes hitting tables that did not exist —
|
|
22
|
+
is closed).
|
|
23
|
+
- **SCIM connections stay runtime data.** The stable constructor is satisfied
|
|
24
|
+
with an application-owned `authentication.verifyBearerToken` that resolves
|
|
25
|
+
the connection from a row at request time — not static boot config, and not
|
|
26
|
+
the upstream `managedConnections` catalog (deliberately not adopted).
|
|
27
|
+
- **ObjectStack owns SCIM credentials outright** (stable upstream stores no
|
|
28
|
+
credential at all): `sys_scim_connection_credential` plus
|
|
29
|
+
`scim-connection-service.ts` mint/digest/verify. At rest only an
|
|
30
|
+
HMAC-SHA-256 keyed by the deployment auth secret (base64url,
|
|
31
|
+
domain-separated) is stored — at parity or better than the rc.1 unsalted
|
|
32
|
+
SHA-256 — pinned by `credential-at-rest-posture.test.ts` including live
|
|
33
|
+
401 paths for forged, revoked and expired bearers.
|
|
34
|
+
- **The ObjectQL better-auth adapter gains native transactions**
|
|
35
|
+
(`engine.transaction`, fail-closed on drivers without `beginTransaction`),
|
|
36
|
+
which stable scim requires by assertion for atomic provisioning writes.
|
|
37
|
+
- **Scaffold suppression retired**: the `@better-auth/scim>better-call`
|
|
38
|
+
`allowedVersions` entry (CLI renderer + blank template) is gone — stable
|
|
39
|
+
1.7.1 peers `better-call@1.4.0` exactly — and its presence ratchets flipped
|
|
40
|
+
to absence pins. The `better-auth>better-sqlite3` and four
|
|
41
|
+
`@better-auth/utils` entries stay; their retirement conditions are separate
|
|
42
|
+
and unmet.
|
|
43
|
+
- The pin resolves **1.7.1 exactly** (not `^1.7.1`): 1.7.2 peers
|
|
44
|
+
`better-auth`/`@better-auth/core` at `^1.7.2`, which only the workspace
|
|
45
|
+
overrides' silencing would "satisfy" while the family is 1.7.1. Floating is
|
|
46
|
+
its own follow-up.
|
|
47
|
+
|
|
48
|
+
**Semver: minor, argued.** The rc.1 SCIM surface this replaces (generate-token
|
|
49
|
+
endpoint, rc.1 bearer tokens, `sys_scim_provider` rows) changes incompatibly —
|
|
50
|
+
but that surface is default-off (`OS_SCIM_ENABLED`), was shipped with a
|
|
51
|
+
documented "do not let the IdP push groups" boundary, and the maintainer ruled
|
|
52
|
+
(2026-08-25) that SCIM has no real customers and old data need not carry: the
|
|
53
|
+
one binding constraint is that an existing system upgrades smoothly, which it
|
|
54
|
+
does — every table the installed library can write exists at this version, and
|
|
55
|
+
SCIM-disabled deployments see no behavior change. A major would move the whole
|
|
56
|
+
fixed version group for a feature surface with zero consumers. Deployments
|
|
57
|
+
that had SCIM enabled must mint new connection credentials (digests are not
|
|
58
|
+
portable from rc.1 on any path — IdP token reissue is a migration-day
|
|
59
|
+
operator action regardless of semver level). `sys_scim_provider` itself is
|
|
60
|
+
NOT removed here; its retirement is tracked separately (#11757).
|
|
61
|
+
|
|
62
|
+
### Patch Changes
|
|
63
|
+
|
|
64
|
+
- c6c7fec: fix(create-objectstack): stop reporting a failed pnpm probe as a deliberate npm choice (#11616)
|
|
65
|
+
|
|
66
|
+
`detectPackageManager()` was `try { execSync('pnpm --version') } catch { return
|
|
67
|
+
'npm' }`, so every failure mode collapsed into one answer. `npm install` in the
|
|
68
|
+
scaffolder's output meant either *this machine has no pnpm* or *the probe
|
|
69
|
+
threw*, and nothing — no log line, no message — could tell the two apart.
|
|
70
|
+
|
|
71
|
+
That second case is reachable on an ordinary developer machine, not just in
|
|
72
|
+
theory: `pnpm --version` resolves through Corepack and therefore depends on the
|
|
73
|
+
directory it runs in. Measured on one machine, one binary, two directories —
|
|
74
|
+
`10.31.0` inside a repo that pins `packageManager`, `10.33.0` outside it, where
|
|
75
|
+
Corepack has to resolve, and may have to fetch, a version nothing pinned. A
|
|
76
|
+
user who has pnpm installed but is on a slow or offline network was silently
|
|
77
|
+
told to run npm.
|
|
78
|
+
|
|
79
|
+
The probe now reports why as well as what:
|
|
80
|
+
|
|
81
|
+
- `probe: 'ok'` — pnpm answered, so pnpm is used (unchanged, silent).
|
|
82
|
+
- `probe: 'absent'` — no pnpm on PATH at all, so npm is a real choice
|
|
83
|
+
(unchanged, silent).
|
|
84
|
+
- `probe: 'failed'` — pnpm **is** on PATH and the probe still threw. npm is
|
|
85
|
+
used exactly as before, and the run now says so, naming the underlying
|
|
86
|
+
failure: `pnpm is installed but \`pnpm --version\` failed (<reason>); using
|
|
87
|
+
npm as a fallback.`
|
|
88
|
+
|
|
89
|
+
**Which package manager a run uses is unchanged in all three cases** — it is
|
|
90
|
+
still pnpm if and only if the probe succeeded. The PATH lookup that separates
|
|
91
|
+
`absent` from `failed` runs only after the decision is already made and feeds
|
|
92
|
+
the message alone, so a miss there can change a warning's wording and never the
|
|
93
|
+
tool's behaviour. The only output that moves is one warning in a case that was
|
|
94
|
+
previously silent and wrong.
|
|
95
|
+
|
|
96
|
+
## 17.2.0
|
|
97
|
+
|
|
98
|
+
### Minor Changes
|
|
99
|
+
|
|
100
|
+
- 5a616d5: `create-objectstack` now closes with a "Created files" summary derived from a
|
|
101
|
+
walk of the finished project directory, so it names everything the run wrote —
|
|
102
|
+
including the files written after the template copy (#10323).
|
|
103
|
+
|
|
104
|
+
The old summary was the template copy's own list, printed before
|
|
105
|
+
`<pm> install` and before `npx skills add`. Measured against published
|
|
106
|
+
`create-objectstack@17.1.0` (`create-objectstack demo-app`, then a full walk of
|
|
107
|
+
the result): 12 entries printed, 18,045 paths on disk, **18,033 of them
|
|
108
|
+
unreachable from the summary** — `AGENTS.md`, `.github/copilot-instructions.md`,
|
|
109
|
+
`pnpm-lock.yaml`, `skills-lock.json`, `node_modules/`, and two ~968 KB trees of
|
|
110
|
+
agent instructions at `.agents/skills/` and `agent/skills/`.
|
|
111
|
+
|
|
112
|
+
That mattered because the same run ends with the `skills` CLI printing *"Review
|
|
113
|
+
skills before use; they run with full agent permissions."* Advice to review
|
|
114
|
+
files the run never named, at paths it never showed, is advice a newcomer
|
|
115
|
+
cannot act on — the wrong failure direction for a security-flavoured warning.
|
|
116
|
+
|
|
117
|
+
The list could not have been correct where it stood: two of the three write
|
|
118
|
+
phases belong to other processes, and the `skills` installer's destination set
|
|
119
|
+
moves with **its** releases, not ours. Reading the directory afterwards makes
|
|
120
|
+
the summary self-correcting instead. Large directories collapse to one line
|
|
121
|
+
carrying their path, entry count and size, so the bulk stays reviewable without
|
|
122
|
+
18,000 lines of output, and the paths the skills installer created are marked
|
|
123
|
+
`⚠ skills` with the permissions warning tied to them.
|
|
124
|
+
|
|
125
|
+
Same run, after the change: 20 entries printed, **0 written paths unreachable**.
|
|
126
|
+
|
|
127
|
+
### Patch Changes
|
|
128
|
+
|
|
129
|
+
- cec9d23: Fix `create-objectstack`'s startup banner hardcoding `◆ Create ObjectStack v6.x`
|
|
130
|
+
regardless of the package's real, released version — eleven majors stale, on
|
|
131
|
+
the first line of output a newcomer ever sees (#10325). The banner now calls
|
|
132
|
+
`readCliVersion()`, the same reader `.version()` already used, instead of a
|
|
133
|
+
literal string.
|
|
134
|
+
|
|
135
|
+
Dropping the real version in without recomputing the box's padding would have
|
|
136
|
+
reintroduced the same defect one line later — the border is a fixed run of
|
|
137
|
+
`═` computed for the 4-character `v6.x`, and a longer real version (`v17.1.0`
|
|
138
|
+
is 7 characters) would push the right border out of alignment (the sibling
|
|
139
|
+
bug fixed in #10322, one function away in the same file). The box now derives
|
|
140
|
+
its width from the version string's plain length and widens the frame — never
|
|
141
|
+
truncates — for a version long enough to need more room; ordinary versions
|
|
142
|
+
still render at the historical box size.
|
|
143
|
+
|
|
144
|
+
No behaviour change beyond the printed banner.
|
|
145
|
+
- 3a3f209: Tell a newcomer that the `blank` starter ships no app, so an empty Console
|
|
146
|
+
reads as the intended starting point rather than a broken install (#10317).
|
|
147
|
+
|
|
148
|
+
Measured on a real scaffold-and-boot (`create-objectstack my-app -t blank`,
|
|
149
|
+
published 17.1.0 packages, `objectstack dev --ui`): `GET /api/v1/meta/app`
|
|
150
|
+
returns the two platform apps (Setup, Account) and nothing of the project's
|
|
151
|
+
own, while `GET /api/v1/data/my_app_note` serves the scaffolded object the
|
|
152
|
+
whole time. The template ships `src/objects/` only — deliberately, as every
|
|
153
|
+
scaffolder template in this repo does — but nothing the newcomer could reach
|
|
154
|
+
said so, and `pnpm dev` advertises the Console URL on every boot.
|
|
155
|
+
|
|
156
|
+
Documentation only: a new "The Console" section in the generated `README.md`
|
|
157
|
+
naming the Console path, the consequence, and `src/apps/*.app.ts` as the
|
|
158
|
+
remedy. No change to what the scaffolder writes into `src/`.
|
|
159
|
+
- 7bf3fb7: Point every documentation link in these packages' published READMEs — and in
|
|
160
|
+
the project `create-objectstack` scaffolds — at the canonical docs origin
|
|
161
|
+
`https://objectstack.ai`, replacing the `docs.objectstack.ai` spelling.
|
|
162
|
+
|
|
163
|
+
Both spellings reach the same pages (the alias redirects to the apex,
|
|
164
|
+
path-preserving), so no link was broken. The reason it needs a release rather
|
|
165
|
+
than an in-repo fix alone: a README ships inside the npm tarball, so the
|
|
166
|
+
version already on npm keeps showing the old host to every reader of the
|
|
167
|
+
package page until a new one is published.
|
|
168
|
+
- 675ab57: **First-run polish:** a brand-new scaffold's very first `pnpm install` no longer reports two unmet peer dependencies (#10326).
|
|
169
|
+
|
|
170
|
+
Reproduced on a clean scaffold from published `create-objectstack@17.1.0` — no lockfile, `node_modules` removed, nothing configured by the user — and again on the second scaffold path, `objectstack init`. Both printed the same two:
|
|
171
|
+
|
|
172
|
+
```
|
|
173
|
+
✕ unmet peer better-call
|
|
174
|
+
Installed: 1.4.0
|
|
175
|
+
Wanted:
|
|
176
|
+
1.3.7:
|
|
177
|
+
@better-auth/scim@1.7.0-rc.1
|
|
178
|
+
|
|
179
|
+
✕ unmet peer better-sqlite3
|
|
180
|
+
Installed: 13.0.3
|
|
181
|
+
Wanted:
|
|
182
|
+
^12.0.0:
|
|
183
|
+
better-auth@1.7.1
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
Nothing was broken — but it is the first screen a newcomer sees, and there is nothing they did to cause it or can do about it.
|
|
187
|
+
|
|
188
|
+
**`better-sqlite3`: the pin is right and the upstream range is stale — so it is widened, not corrected.** better-auth 1.7.1 declares `better-sqlite3` as an **optional** peer at `^12.0.0`, and it governs exactly one configuration: a raw better-sqlite3 `Database` handed to better-auth's `database` option, which its Kysely dialect then drives. ObjectStack never takes that path — `AuthManager.createDatabaseConfig()` returns `createObjectQLAdapterFactory(dataEngine)`, and every `better-sqlite3` use under `plugin-auth` is knex's `client: 'better-sqlite3'` beneath ObjectQL. Measured anyway on the configuration the range *does* govern: better-auth 1.7.1 with `database: new Database(':memory:')`, running `getMigrations().runMigrations()`, `signUpEmail`, `signInEmail` and adapter `findOne`/`update`/`delete`, is green on **better-sqlite3 13.0.3** and byte-for-byte equivalent on **12.11.1**. The same probe with `Database.prototype.prepare` neutered fails, so that green is the driver's and not an unexercised path. Pinning our own `^13.0.3` declarations back to `^12` would downgrade a native module across the platform to satisfy a range measurement shows is simply behind.
|
|
189
|
+
|
|
190
|
+
**`@better-auth/scim`: the rc pin stays, and one `better-call` copy is the correct tree.** `npm view @better-auth/scim dist-tags` reads `latest: '1.7.1'`, but stable 1.7.x ships the rc.2 whole-model rewrite, so adopting it is a separate migration rather than a version bump; the exact `1.7.0-rc.1` pin is deliberate. The rc peers an exact `better-call@1.3.7` while better-auth 1.7.1 depends on `1.4.0` — and a better-auth plugin has to share the **host's** better-call instance, so the single 1.4.0 copy every install already resolves is right, not a skew to repair. This declaration retires together with the rc pin.
|
|
191
|
+
|
|
192
|
+
**What changed, and what deliberately did not.** Both remedies are pnpm `peerDependencyRules.allowedVersions` entries, scoped `<declaring package>><peer>` so each widens exactly one declaration. They ship *inside* the scaffold — the bundled `pnpm-workspace.yaml` template and the one `objectstack init` renders — because a block in this repo's own workspace file does not travel with published packages. `allowedVersions` changes what pnpm **reports**, never what it resolves: measured on both scaffold paths, the lockfile is byte-identical with and without it (0 lines of diff), and no dependency version, range or resolution moved anywhere. This repo's own resolutions are untouched.
|
|
193
|
+
- e85182d: Converge the blank scaffold template's `README.md` docs links on the ruled
|
|
194
|
+
canonical origin, `https://objectstack.ai` (maintainer ruling, 2026-08-21:
|
|
195
|
+
「这个仓的文档站规范 URL 是 https://objectstack.ai」; enforced by
|
|
196
|
+
`CANONICAL_DOCS_ORIGIN` in `scripts/check-published-readme-links.mjs`). The
|
|
197
|
+
template previously linked the accepted-but-unratified `docs.objectstack.ai`
|
|
198
|
+
alias in three places, which disagreed with the root `README.md`'s already-
|
|
199
|
+
canonical spelling — so a single `npm create objectstack@latest` run handed
|
|
200
|
+
the user two different hostnames for the same docs site.
|
|
201
|
+
- aea1e64: Fix the declared bin (`bin/create-objectstack.js`) being tracked non-executable
|
|
202
|
+
in git. It carries a `#!/usr/bin/env node` shebang and is pnpm's link target
|
|
203
|
+
for the `create-objectstack` command, but was committed `100644` instead of
|
|
204
|
+
`100755` — matching the sibling declared bin `packages/cli/bin/run.js`, which
|
|
205
|
+
was already tracked executable.
|
|
206
|
+
|
|
207
|
+
Patch bump: this is a packaging-mode correction with no content, API or
|
|
208
|
+
behavior change (the blob hash is identical) — it only fixes how the file is
|
|
209
|
+
tracked in git and therefore how it is packed for npm.
|
|
210
|
+
- 818e027: Fix `objectstack init`'s closing "Created files" summary omitting `pnpm-lock.yaml` / `package-lock.json` and `node_modules/` (#10557).
|
|
211
|
+
|
|
212
|
+
The summary used to be printed from a list accumulated while the template
|
|
213
|
+
files were written — before `<pm> install` ran — so it could never name what
|
|
214
|
+
the package manager wrote. `init` now prints it after the install attempt
|
|
215
|
+
(succeeded or failed) from a walk of the finished project directory, reusing
|
|
216
|
+
`create-objectstack`'s `created-summary.ts` (now published as the
|
|
217
|
+
`create-objectstack/created-summary` subpath) instead of a second copy of the
|
|
218
|
+
same renderer.
|
|
219
|
+
- afe1c4e: fix(cli): declare the four `@better-auth/utils` peer skews a freshly scaffolded project reports (#10931)
|
|
220
|
+
|
|
221
|
+
Both scaffold paths emit a `peerDependencyRules.allowedVersions` block whose
|
|
222
|
+
stated purpose is that a brand-new project's first `pnpm install` does not open
|
|
223
|
+
with a peer-skew report. It declared two skews and left four showing:
|
|
224
|
+
|
|
225
|
+
```
|
|
226
|
+
├─┬ @better-auth/core 1.7.1
|
|
227
|
+
│ └── ✕ unmet peer @better-auth/utils@0.4.2: found 0.5.0
|
|
228
|
+
├─┬ @better-auth/scim 1.7.0-rc.1
|
|
229
|
+
│ └── ✕ unmet peer @better-auth/utils@0.4.2: found 0.5.0
|
|
230
|
+
├─┬ @better-auth/oauth-provider 1.7.1
|
|
231
|
+
│ └── ✕ unmet peer @better-auth/utils@0.4.2: found 0.5.0
|
|
232
|
+
└─┬ @better-auth/sso 1.7.1
|
|
233
|
+
└── ✕ unmet peer @better-auth/utils@0.4.2: found 0.5.0
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
`@better-auth/core`, `/oauth-provider`, `/scim` and `/sso` each peer an **exact**
|
|
237
|
+
`@better-auth/utils@0.4.2`. The 0.5.0 they are handed comes from
|
|
238
|
+
`better-call@1.4.0` — better-auth's own HTTP layer — which *depends* on
|
|
239
|
+
`^0.5.0`; `@objectstack/plugin-auth` names the four as direct dependencies
|
|
240
|
+
without naming utils, so pnpm satisfies their peer from better-call's copy
|
|
241
|
+
instead of better-auth's own exact 0.4.2 dependency.
|
|
242
|
+
|
|
243
|
+
**Measured compatible before widening, not assumed.** Those four import three
|
|
244
|
+
symbols in total: `base64`/`base64Url` (`@better-auth/utils/base64`),
|
|
245
|
+
`createHash` (`/hash`) and, in core only, `createRandomStringGenerator`
|
|
246
|
+
(`/random`). 0.5.0 declares all three with identical signatures; `/random` is
|
|
247
|
+
unchanged apart from formatting, `/base64` swaps `new Uint8Array(data)` for a
|
|
248
|
+
helper that *is* `new Uint8Array(data)` on non-strings, and `/hash` only widens
|
|
249
|
+
its input coercion for views not backed by a plain `ArrayBuffer`. Run against
|
|
250
|
+
the input shapes those call sites actually pass, the two versions agree on every
|
|
251
|
+
value; run end to end — better-auth with the `sso`, `oauth-provider` and `scim`
|
|
252
|
+
plugins — a tree where the four resolve 0.5.0 and one where they resolve 0.4.2
|
|
253
|
+
produce the same transcript: sign-up, sign-in, session, both OAuth metadata
|
|
254
|
+
documents, the RFC 7636 PKCE challenge, and the SCIM and SSO endpoint outcomes.
|
|
255
|
+
|
|
256
|
+
A resolution change was measured too, and rejected: pinning utils back to 0.4.2
|
|
257
|
+
clears the four lines only by dragging `better-call@1.4.0` off its own declared
|
|
258
|
+
`^0.5.0` — manufacturing one real range violation to silence four benign ones.
|
|
259
|
+
|
|
260
|
+
Four scoped entries, one per declaring package, matching the block's convention
|
|
261
|
+
that each rule widens exactly one declaration. `allowedVersions` suppresses the
|
|
262
|
+
report only: the lockfile a scaffold resolves is byte-identical with and without
|
|
263
|
+
the block. The version is spelled `0.5.0` exactly rather than `0.5`, so a future
|
|
264
|
+
`0.6.0` reports again instead of inheriting this finding.
|
|
265
|
+
|
|
266
|
+
Both scaffold paths — `objectstack init` (rendered by the CLI) and
|
|
267
|
+
`npx create-objectstack` (a copied template file) — are changed together, and
|
|
268
|
+
`packages/cli/test/scaffold-workspace-consistency.test.ts` gains a limb that
|
|
269
|
+
compares the peer maps the two produce, so they cannot drift apart again.
|
|
270
|
+
- 21756b3: fix(create-objectstack): converge scaffold docs on the canonical host and drop the last two dead monorepo references (#10990, #11022)
|
|
271
|
+
|
|
272
|
+
A freshly scaffolded project shipped a handful of text lines a reader with
|
|
273
|
+
only their own project — no monorepo, no `docs/adr/`, no issue tracker —
|
|
274
|
+
could not follow:
|
|
275
|
+
|
|
276
|
+
- `templates/AGENTS.md` linked `https://objectstack.com/docs`, a domain that
|
|
277
|
+
is not this project's docs site at all (not even a redirecting alias).
|
|
278
|
+
- `templates/blank/Dockerfile` and `templates/blank/docker-compose.yml` both
|
|
279
|
+
linked `https://docs.objectstack.ai/...`, an accepted-but-unratified alias.
|
|
280
|
+
All three now point at the ruled canonical origin, `https://objectstack.ai`
|
|
281
|
+
(maintainer ruling, 2026-08-21).
|
|
282
|
+
- `templates/blank/README.md` cited `ADR-0097` and named "the ObjectStack
|
|
283
|
+
framework repo" as the home of `skills/` — both rewritten self-contained,
|
|
284
|
+
keeping the fact each was carrying: the connector-materialization line now
|
|
285
|
+
links the public [Automation → Connectors](https://objectstack.ai/docs/automation/connectors)
|
|
286
|
+
page, and the skills line now names the followable
|
|
287
|
+
`npx skills add objectstack-ai/objectstack/skills` install the scaffolder's
|
|
288
|
+
own closing output already uses.
|
|
289
|
+
|
|
290
|
+
`packages/create-objectstack/src/starter-comments-self-contained.test.ts`
|
|
291
|
+
(#10324) gains two pin obligations these two fixes call for: a host-convergence
|
|
292
|
+
assertion driven by the same `shippedFiles()` walker that already enumerates
|
|
293
|
+
everything a scaffold ships (no other repo gate's population reaches these
|
|
294
|
+
template files — `check:published-readme-links` reads publishable packages'
|
|
295
|
+
published markdown only), and a fifth `MONOREPO_ONLY` pattern that catches a
|
|
296
|
+
prose-shaped reference to this repo ("the ObjectStack framework repo") the
|
|
297
|
+
first four, syntax-shaped patterns could not. The self-retiring `EXCLUDED`
|
|
298
|
+
entry for `blank/README.md` is removed now that the file cites nothing
|
|
299
|
+
monorepo-only.
|
|
300
|
+
- 568de19: Scaffolded projects declare an explicit empty `packages: []` in their
|
|
301
|
+
`pnpm-workspace.yaml` (#10933). Both scaffold paths render it —
|
|
302
|
+
`renderPnpmWorkspaceYaml` in `objectstack init`, and the bundled `blank`
|
|
303
|
+
template `npx create-objectstack` copies.
|
|
304
|
+
|
|
305
|
+
The file was deliberately keyless so it would act purely as a settings file.
|
|
306
|
+
That intent is now written down rather than inferred from a missing key, and
|
|
307
|
+
writing it down is what fixes a first-command failure: pnpm 9.x and 10.0–10.4
|
|
308
|
+
parse `pnpm-workspace.yaml` **before** they read `engines`, so they refused a
|
|
309
|
+
brand-new project outright with
|
|
310
|
+
|
|
311
|
+
```
|
|
312
|
+
ERROR packages field missing or empty
|
|
313
|
+
```
|
|
314
|
+
|
|
315
|
+
naming a file the user never wrote and giving no hint that the cause is their
|
|
316
|
+
pnpm version — and no `engines.pnpm` floor could reach them, because they never
|
|
317
|
+
got as far as the engines check. Measured, one clean install per pnpm version,
|
|
318
|
+
each with its own store:
|
|
319
|
+
|
|
320
|
+
| pnpm | before | after |
|
|
321
|
+
|---|---|---|
|
|
322
|
+
| 9.15.9, 10.0.0, 10.4.0 | `ERROR packages field missing or empty` | `ERR_PNPM_UNSUPPORTED_ENGINE`, naming `>=10.15` |
|
|
323
|
+
| 10.5.0–10.14.0 | `ERR_PNPM_UNSUPPORTED_ENGINE` | unchanged |
|
|
324
|
+
| 10.15.0, 10.34.5, 11.22.0 | installs | installs, byte-identical `pnpm-lock.yaml` |
|
|
325
|
+
|
|
326
|
+
So every unsupported pnpm now reports the same actionable cause, and supported
|
|
327
|
+
pnpm is unaffected: the empty key was measured equivalent to omission on
|
|
328
|
+
10.15.0, 10.34.5 and 11.22.0 — identical lockfile bytes, identical
|
|
329
|
+
`node_modules/.modules.yaml` once the run-local `prunedAt`/`storeDir` fields are
|
|
330
|
+
dropped, identical `pnpm ls -r --depth -1`, and an identical second-install
|
|
331
|
+
"Already up to date".
|
|
332
|
+
|
|
333
|
+
The declaration is an **empty** list on purpose. `packages: ['.']` satisfies the
|
|
334
|
+
same parsers but declares the project root a workspace *member* — a monorepo
|
|
335
|
+
root — which a single-package scaffold is not, and which reads to the next
|
|
336
|
+
author (human or AI) as an invitation to add member packages to an app.
|
|
337
|
+
|
|
338
|
+
`engines.pnpm` is unchanged at `>=10.15`.
|
|
339
|
+
- 8d21f7a: Fix `create-objectstack`'s closing "Next steps" and install-failure remedy
|
|
340
|
+
hardcoding `npm` regardless of which package manager the run actually used
|
|
341
|
+
(#10322). `detectPackageManager()` already prefers `pnpm` and falls back to
|
|
342
|
+
`npm` only when `pnpm` is unreachable — confirmed still true at HEAD, and
|
|
343
|
+
confirmed empirically: a real run with `pnpm` on `PATH` installs with `pnpm`
|
|
344
|
+
(`pnpm-lock.yaml`, "Done in … using pnpm vX") and then told the newcomer to
|
|
345
|
+
run `npm run dev` / `npm run validate` next, a package manager the run never
|
|
346
|
+
touched. The detected package manager is now read once, up front, and reused
|
|
347
|
+
consistently for the install command, the install-failure remedy, and every
|
|
348
|
+
line of "Next steps" — so the printed guidance always names the tool the run
|
|
349
|
+
actually used, in both the `pnpm` and the `npm`-fallback case.
|
|
350
|
+
|
|
351
|
+
Also names `validate` — the step the generated `AGENTS.md` calls
|
|
352
|
+
unskippable — in the "Getting started" section of the generated `blank`
|
|
353
|
+
template's README, not only in its later "Verify your changes" section, so a
|
|
354
|
+
newcomer reading top-to-bottom sees it at first touch.
|
|
355
|
+
|
|
356
|
+
No install behaviour changes: the scaffolder still installs by default and
|
|
357
|
+
still supports `--skip-install`; this is a messaging-only fix.
|
|
358
|
+
- 9d101d2: Declare a pnpm floor (`engines.pnpm: ">=10.15"`) in the `package.json` both
|
|
359
|
+
scaffolders write, so an unsupported pnpm reports its own version instead of an
|
|
360
|
+
error about a file the user never wrote.
|
|
361
|
+
|
|
362
|
+
Both scaffold paths emit a settings-only `pnpm-workspace.yaml` with no
|
|
363
|
+
`packages:` key. Early pnpm 10 refuses that file outright — `pnpm install` exits
|
|
364
|
+
1 with `ERROR packages field missing or empty` before resolving a single
|
|
365
|
+
dependency, so a brand-new project could not be installed at all. Measured on
|
|
366
|
+
the rendered shape, one clean install per pnpm version, each with its own store:
|
|
367
|
+
|
|
368
|
+
| pnpm | before | after |
|
|
369
|
+
| --- | --- | --- |
|
|
370
|
+
| 10.0.0 – 10.4.0 | `packages field missing or empty` | unchanged — see below |
|
|
371
|
+
| 10.5.0 – 10.14.0 | `packages field missing or empty` | `ERR_PNPM_UNSUPPORTED_ENGINE`, naming the expected range |
|
|
372
|
+
| >= 10.15.0 | installs | installs |
|
|
373
|
+
|
|
374
|
+
The floor is a diagnosis, not a repair: pnpm 10.0.0–10.4.0 parse
|
|
375
|
+
`pnpm-workspace.yaml` *before* they read `engines`, so they still print the raw
|
|
376
|
+
workspace error. Closing that remaining sliver requires deciding what a
|
|
377
|
+
single-package scaffold should declare under `packages:`, which is tracked
|
|
378
|
+
separately and deliberately not decided here.
|
|
379
|
+
|
|
380
|
+
`engines.pnpm` rather than a `packageManager` stamp: npm, yarn and bun ignore
|
|
381
|
+
`engines.pnpm` entirely, so the scaffold keeps working for all four package
|
|
382
|
+
managers `objectstack init` hands off to. A `packageManager: "pnpm@x.y.z"` stamp
|
|
383
|
+
would declare the project pnpm-only (corepack-driven yarn refuses to run in such
|
|
384
|
+
a project) and pin one exact version that goes stale on every pnpm release — and
|
|
385
|
+
it buys nothing on 10.0–10.4, which reach the workspace error before reading
|
|
386
|
+
that field either.
|
|
387
|
+
|
|
388
|
+
No existing project is affected; this only changes what a newly scaffolded
|
|
389
|
+
`package.json` contains.
|
|
390
|
+
- 6d441e4: Correct the pnpm boundary the blank template states for `allowBuilds`, and gate
|
|
391
|
+
the two scaffold paths against each other (#10498, #10499).
|
|
392
|
+
|
|
393
|
+
`packages/create-objectstack/src/templates/blank/pnpm-workspace.yaml` is copied
|
|
394
|
+
verbatim into every scaffolded project, so its header comment is prose that
|
|
395
|
+
ships **inside the user's own repository**. It said `allowBuilds` needs
|
|
396
|
+
pnpm >= 10.31 and that `onlyBuiltDependencies` covers pnpm 10.0–10.30. Measured
|
|
397
|
+
on a probe depending on `esbuild@0.28.2`, with a workspace file carrying only
|
|
398
|
+
`allowBuilds`, one clean install per pnpm version and each with its own
|
|
399
|
+
`--store-dir` (isolation matters — pnpm's side-effects cache will otherwise hand
|
|
400
|
+
a later run a build an earlier run performed, and it reads as "the key worked"):
|
|
401
|
+
|
|
402
|
+
| pnpm | `allowBuilds` alone |
|
|
403
|
+
|:--|:--|
|
|
404
|
+
| 10.15.0 – 10.25.0 | ignored — build not run |
|
|
405
|
+
| **10.26.0** | **honoured — build ran** |
|
|
406
|
+
| 10.28.0 – 10.33.0 | honoured — build ran |
|
|
407
|
+
|
|
408
|
+
So the floor is 10.26.0 and the older-key band is 10.0–10.25. A user on pnpm
|
|
409
|
+
10.28 was being told by the file in front of them that their pnpm cannot read
|
|
410
|
+
the key it is in fact reading. Both load-bearing claims in that comment were
|
|
411
|
+
correct and are unchanged: both keys are needed, and pnpm 11 reads only
|
|
412
|
+
`allowBuilds`. No setting, no assertion and no install behaviour changes — the
|
|
413
|
+
rendered `onlyBuiltDependencies` / `allowBuilds` values are byte-identical.
|
|
414
|
+
|
|
415
|
+
The reason it was wrong for so long is the second half of this change.
|
|
416
|
+
`objectstack init` renders the same file from `renderPnpmWorkspaceYaml()` in
|
|
417
|
+
`packages/cli`, it was corrected to the measured numbers separately, and each
|
|
418
|
+
package's ratchets are package-local — so neither could ever fail for the other
|
|
419
|
+
file's regression, and the two scaffold paths shipped contradictory prose about
|
|
420
|
+
the same rule with every gate green. `packages/cli/test/scaffold-workspace-consistency.test.ts`
|
|
421
|
+
now compares the two **rendered outputs**: the packages each key actually grants
|
|
422
|
+
a build to, and the pnpm versions each file actually names for each key. It was
|
|
423
|
+
confirmed failing against the live divergence before this correction landed.
|
|
424
|
+
|
|
425
|
+
Bumped `patch` rather than left out: the corrected text is user-visible — it is
|
|
426
|
+
delivered into every new project — while nothing executable moves.
|
|
427
|
+
- ecd06f6: Rewrite the scaffolded project's starter comments so a newcomer can actually
|
|
428
|
+
follow them (#10324). `objectstack.config.ts` and `src/objects/note.object.ts`
|
|
429
|
+
are the first two files opened after scaffolding, and between them they cited
|
|
430
|
+
four ADR identifiers, one bare issue number and the path of a release-time
|
|
431
|
+
script in this monorepo — none of which ship in, or are linked from, a
|
|
432
|
+
scaffolded project. `// per ADR-0097` read as a reference the reader was
|
|
433
|
+
failing to follow rather than as the context it was meant to be.
|
|
434
|
+
|
|
435
|
+
The explanations are kept and made self-contained; only the dead ends are
|
|
436
|
+
gone. Each now states the fact the identifier stood for — the protocol range
|
|
437
|
+
is checked before anything loads and was stamped to match the installed
|
|
438
|
+
version rather than hand-tuned; `automation` must stay whenever `plugins:`
|
|
439
|
+
lists a connector or the executors have nowhere to register; a declarative
|
|
440
|
+
`mcp` stdio transport is denied by default; the org-wide default is required
|
|
441
|
+
so the baseline is an authored decision — and points at the public docs page
|
|
442
|
+
that covers it in full. The blank `Dockerfile` likewise stops pointing at a
|
|
443
|
+
file in this repo and points at the self-hosting guide it already links.
|
|
444
|
+
|
|
445
|
+
A pin (`starter-comments-self-contained.test.ts`) keeps it that way from both
|
|
446
|
+
sides: no shipped template file may cite an ADR identifier, a bare issue
|
|
447
|
+
number or a repo script path, and the facts those references carried must
|
|
448
|
+
still be stated — so the comments cannot be "fixed" by deleting them. It also
|
|
449
|
+
resolves every canonical-origin docs URL in the shipped tree against
|
|
450
|
+
`content/docs`, because a link that 404s is the same defect one level out.
|
|
451
|
+
|
|
3
452
|
## 17.1.0
|
|
4
453
|
|
|
5
454
|
### Minor Changes
|
package/README.md
CHANGED
|
@@ -87,7 +87,7 @@ npm run validate # verify metadata: schema + predicates + bindings
|
|
|
87
87
|
```
|
|
88
88
|
|
|
89
89
|
See the docs:
|
|
90
|
-
[Your First Project](https://
|
|
90
|
+
[Your First Project](https://objectstack.ai/docs/getting-started/your-first-project).
|
|
91
91
|
|
|
92
92
|
## License
|
|
93
93
|
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
// src/created-summary.ts
|
|
2
|
+
import fs from "fs";
|
|
3
|
+
import path from "path";
|
|
4
|
+
var COLLAPSE_AT = 10;
|
|
5
|
+
var MEASURE_BUDGET = 2e3;
|
|
6
|
+
function scan(abs, name, budget) {
|
|
7
|
+
let st;
|
|
8
|
+
try {
|
|
9
|
+
st = fs.lstatSync(abs);
|
|
10
|
+
} catch {
|
|
11
|
+
return { name, dir: false, entries: 0, bytes: 0, truncated: false, children: [] };
|
|
12
|
+
}
|
|
13
|
+
if (!st.isDirectory()) {
|
|
14
|
+
budget.left -= 1;
|
|
15
|
+
return { name, dir: false, entries: 1, bytes: st.size, truncated: false, children: [] };
|
|
16
|
+
}
|
|
17
|
+
const node = { name, dir: true, entries: 0, bytes: 0, truncated: false, children: [] };
|
|
18
|
+
let dirents;
|
|
19
|
+
try {
|
|
20
|
+
dirents = fs.readdirSync(abs, { withFileTypes: true });
|
|
21
|
+
} catch {
|
|
22
|
+
return node;
|
|
23
|
+
}
|
|
24
|
+
for (const entry of dirents) {
|
|
25
|
+
if (budget.left <= 0) {
|
|
26
|
+
node.truncated = true;
|
|
27
|
+
break;
|
|
28
|
+
}
|
|
29
|
+
const child = scan(path.join(abs, entry.name), entry.name, budget);
|
|
30
|
+
node.entries += child.entries;
|
|
31
|
+
node.bytes += child.bytes;
|
|
32
|
+
if (child.truncated) node.truncated = true;
|
|
33
|
+
node.children.push(child);
|
|
34
|
+
}
|
|
35
|
+
return node;
|
|
36
|
+
}
|
|
37
|
+
function byName(a, b) {
|
|
38
|
+
return a.name.localeCompare(b.name, "en");
|
|
39
|
+
}
|
|
40
|
+
function flatten(node, prefix, out) {
|
|
41
|
+
for (const child of [...node.children].sort(byName)) {
|
|
42
|
+
const rel = prefix + child.name;
|
|
43
|
+
if (!child.dir) {
|
|
44
|
+
out.push({ path: rel, kind: "file", entries: 1, bytes: child.bytes, truncated: false });
|
|
45
|
+
continue;
|
|
46
|
+
}
|
|
47
|
+
if (!child.truncated && child.entries <= COLLAPSE_AT) {
|
|
48
|
+
flatten(child, `${rel}/`, out);
|
|
49
|
+
continue;
|
|
50
|
+
}
|
|
51
|
+
let deepest = child;
|
|
52
|
+
let shown = rel;
|
|
53
|
+
while (deepest.children.length === 1 && deepest.children[0].dir) {
|
|
54
|
+
deepest = deepest.children[0];
|
|
55
|
+
shown = `${shown}/${deepest.name}`;
|
|
56
|
+
}
|
|
57
|
+
out.push({
|
|
58
|
+
path: `${shown}/`,
|
|
59
|
+
kind: "dir",
|
|
60
|
+
entries: deepest.entries,
|
|
61
|
+
bytes: deepest.bytes,
|
|
62
|
+
truncated: deepest.truncated
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
function summarizeTree(root) {
|
|
67
|
+
let dirents;
|
|
68
|
+
try {
|
|
69
|
+
dirents = fs.readdirSync(root, { withFileTypes: true });
|
|
70
|
+
} catch {
|
|
71
|
+
return [];
|
|
72
|
+
}
|
|
73
|
+
const top = { name: "", dir: true, entries: 0, bytes: 0, truncated: false, children: [] };
|
|
74
|
+
for (const entry of dirents) {
|
|
75
|
+
top.children.push(scan(path.join(root, entry.name), entry.name, { left: MEASURE_BUDGET }));
|
|
76
|
+
}
|
|
77
|
+
const out = [];
|
|
78
|
+
flatten(top, "", out);
|
|
79
|
+
return [
|
|
80
|
+
...out.filter((e) => e.kind === "file"),
|
|
81
|
+
...out.filter((e) => e.kind === "dir")
|
|
82
|
+
];
|
|
83
|
+
}
|
|
84
|
+
function formatBytes(bytes) {
|
|
85
|
+
if (bytes < 1024) return `${bytes} B`;
|
|
86
|
+
const kb = bytes / 1024;
|
|
87
|
+
if (kb < 1024) return `${kb < 10 ? kb.toFixed(1) : Math.round(kb)} KB`;
|
|
88
|
+
const mb = kb / 1024;
|
|
89
|
+
return `${mb < 10 ? mb.toFixed(1) : Math.round(mb)} MB`;
|
|
90
|
+
}
|
|
91
|
+
function describeEntry(entry) {
|
|
92
|
+
if (entry.kind === "file") return "";
|
|
93
|
+
const noun = entry.entries === 1 ? "file" : "files";
|
|
94
|
+
if (entry.truncated) return `over ${entry.entries.toLocaleString("en-US")} ${noun}`;
|
|
95
|
+
return `${entry.entries.toLocaleString("en-US")} ${noun}, ${formatBytes(entry.bytes)}`;
|
|
96
|
+
}
|
|
97
|
+
function unreachablePaths(entries, written) {
|
|
98
|
+
const named = new Set(entries.filter((e) => e.kind === "file").map((e) => e.path));
|
|
99
|
+
const dirs = entries.filter((e) => e.kind === "dir").map((e) => e.path);
|
|
100
|
+
return written.filter(
|
|
101
|
+
(p) => !named.has(p) && !dirs.some((d) => `${p}/`.startsWith(d))
|
|
102
|
+
);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
export {
|
|
106
|
+
COLLAPSE_AT,
|
|
107
|
+
MEASURE_BUDGET,
|
|
108
|
+
summarizeTree,
|
|
109
|
+
formatBytes,
|
|
110
|
+
describeEntry,
|
|
111
|
+
unreachablePaths
|
|
112
|
+
};
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/** A directory holding more than this many entries is collapsed to one line. */
|
|
2
|
+
declare const COLLAPSE_AT = 10;
|
|
3
|
+
/**
|
|
4
|
+
* Entries this module is willing to `lstat` per top-level entry before it
|
|
5
|
+
* stops counting and reports a lower bound.
|
|
6
|
+
*
|
|
7
|
+
* The budget is PER TOP-LEVEL ENTRY, not global, and that is load-bearing:
|
|
8
|
+
* with one shared budget, `node_modules/` (17,920 paths in the measurement
|
|
9
|
+
* above) exhausts it before the walk reaches the project's own files, and the
|
|
10
|
+
* summary silently truncates the very content it exists to disclose. Whether
|
|
11
|
+
* that happened would depend on `readdir` order.
|
|
12
|
+
*/
|
|
13
|
+
declare const MEASURE_BUDGET = 2000;
|
|
14
|
+
interface SummaryEntry {
|
|
15
|
+
/** Project-relative path. Directories carry a trailing `/`. */
|
|
16
|
+
path: string;
|
|
17
|
+
kind: 'file' | 'dir';
|
|
18
|
+
/** Files and symlinks in the subtree (always 1 for a file). */
|
|
19
|
+
entries: number;
|
|
20
|
+
/** Total size in bytes. Meaningless when `truncated`. */
|
|
21
|
+
bytes: number;
|
|
22
|
+
/** Measurement stopped at the budget — `entries` and `bytes` are lower bounds. */
|
|
23
|
+
truncated: boolean;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Summarize everything under `root`, collapsing large directories.
|
|
27
|
+
*
|
|
28
|
+
* Returns files first (alphabetical), then collapsed directories
|
|
29
|
+
* (alphabetical), so the enumerated content reads as a list and the bulk
|
|
30
|
+
* trees read as a block with their sizes.
|
|
31
|
+
*/
|
|
32
|
+
declare function summarizeTree(root: string): SummaryEntry[];
|
|
33
|
+
/** Human-readable byte count. */
|
|
34
|
+
declare function formatBytes(bytes: number): string;
|
|
35
|
+
/** The measurement note that follows a collapsed directory's path. */
|
|
36
|
+
declare function describeEntry(entry: SummaryEntry): string;
|
|
37
|
+
/**
|
|
38
|
+
* The property this module exists to hold: every path in `written` is either
|
|
39
|
+
* named outright by a summary entry, or lies beneath a directory entry that
|
|
40
|
+
* is. Returns the paths that are NOT reachable — empty means the summary is
|
|
41
|
+
* complete.
|
|
42
|
+
*
|
|
43
|
+
* Exported because it is the assertion, and an assertion that lives only in a
|
|
44
|
+
* test file cannot be run against a real scaffold from anywhere else.
|
|
45
|
+
*/
|
|
46
|
+
declare function unreachablePaths(entries: SummaryEntry[], written: string[]): string[];
|
|
47
|
+
|
|
48
|
+
export { COLLAPSE_AT, MEASURE_BUDGET, type SummaryEntry, describeEntry, formatBytes, summarizeTree, unreachablePaths };
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import {
|
|
2
|
+
COLLAPSE_AT,
|
|
3
|
+
MEASURE_BUDGET,
|
|
4
|
+
describeEntry,
|
|
5
|
+
formatBytes,
|
|
6
|
+
summarizeTree,
|
|
7
|
+
unreachablePaths
|
|
8
|
+
} from "./chunk-ZIUW7UEA.js";
|
|
9
|
+
export {
|
|
10
|
+
COLLAPSE_AT,
|
|
11
|
+
MEASURE_BUDGET,
|
|
12
|
+
describeEntry,
|
|
13
|
+
formatBytes,
|
|
14
|
+
summarizeTree,
|
|
15
|
+
unreachablePaths
|
|
16
|
+
};
|