create-objectstack 17.0.0 → 17.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,521 @@
1
1
  # create-objectstack
2
2
 
3
+ ## 17.2.0
4
+
5
+ ### Minor Changes
6
+
7
+ - 5a616d5: `create-objectstack` now closes with a "Created files" summary derived from a
8
+ walk of the finished project directory, so it names everything the run wrote —
9
+ including the files written after the template copy (#10323).
10
+
11
+ The old summary was the template copy's own list, printed before
12
+ `<pm> install` and before `npx skills add`. Measured against published
13
+ `create-objectstack@17.1.0` (`create-objectstack demo-app`, then a full walk of
14
+ the result): 12 entries printed, 18,045 paths on disk, **18,033 of them
15
+ unreachable from the summary** — `AGENTS.md`, `.github/copilot-instructions.md`,
16
+ `pnpm-lock.yaml`, `skills-lock.json`, `node_modules/`, and two ~968 KB trees of
17
+ agent instructions at `.agents/skills/` and `agent/skills/`.
18
+
19
+ That mattered because the same run ends with the `skills` CLI printing *"Review
20
+ skills before use; they run with full agent permissions."* Advice to review
21
+ files the run never named, at paths it never showed, is advice a newcomer
22
+ cannot act on — the wrong failure direction for a security-flavoured warning.
23
+
24
+ The list could not have been correct where it stood: two of the three write
25
+ phases belong to other processes, and the `skills` installer's destination set
26
+ moves with **its** releases, not ours. Reading the directory afterwards makes
27
+ the summary self-correcting instead. Large directories collapse to one line
28
+ carrying their path, entry count and size, so the bulk stays reviewable without
29
+ 18,000 lines of output, and the paths the skills installer created are marked
30
+ `⚠ skills` with the permissions warning tied to them.
31
+
32
+ Same run, after the change: 20 entries printed, **0 written paths unreachable**.
33
+
34
+ ### Patch Changes
35
+
36
+ - cec9d23: Fix `create-objectstack`'s startup banner hardcoding `◆ Create ObjectStack v6.x`
37
+ regardless of the package's real, released version — eleven majors stale, on
38
+ the first line of output a newcomer ever sees (#10325). The banner now calls
39
+ `readCliVersion()`, the same reader `.version()` already used, instead of a
40
+ literal string.
41
+
42
+ Dropping the real version in without recomputing the box's padding would have
43
+ reintroduced the same defect one line later — the border is a fixed run of
44
+ `═` computed for the 4-character `v6.x`, and a longer real version (`v17.1.0`
45
+ is 7 characters) would push the right border out of alignment (the sibling
46
+ bug fixed in #10322, one function away in the same file). The box now derives
47
+ its width from the version string's plain length and widens the frame — never
48
+ truncates — for a version long enough to need more room; ordinary versions
49
+ still render at the historical box size.
50
+
51
+ No behaviour change beyond the printed banner.
52
+ - 3a3f209: Tell a newcomer that the `blank` starter ships no app, so an empty Console
53
+ reads as the intended starting point rather than a broken install (#10317).
54
+
55
+ Measured on a real scaffold-and-boot (`create-objectstack my-app -t blank`,
56
+ published 17.1.0 packages, `objectstack dev --ui`): `GET /api/v1/meta/app`
57
+ returns the two platform apps (Setup, Account) and nothing of the project's
58
+ own, while `GET /api/v1/data/my_app_note` serves the scaffolded object the
59
+ whole time. The template ships `src/objects/` only — deliberately, as every
60
+ scaffolder template in this repo does — but nothing the newcomer could reach
61
+ said so, and `pnpm dev` advertises the Console URL on every boot.
62
+
63
+ Documentation only: a new "The Console" section in the generated `README.md`
64
+ naming the Console path, the consequence, and `src/apps/*.app.ts` as the
65
+ remedy. No change to what the scaffolder writes into `src/`.
66
+ - 7bf3fb7: Point every documentation link in these packages' published READMEs — and in
67
+ the project `create-objectstack` scaffolds — at the canonical docs origin
68
+ `https://objectstack.ai`, replacing the `docs.objectstack.ai` spelling.
69
+
70
+ Both spellings reach the same pages (the alias redirects to the apex,
71
+ path-preserving), so no link was broken. The reason it needs a release rather
72
+ than an in-repo fix alone: a README ships inside the npm tarball, so the
73
+ version already on npm keeps showing the old host to every reader of the
74
+ package page until a new one is published.
75
+ - 675ab57: **First-run polish:** a brand-new scaffold's very first `pnpm install` no longer reports two unmet peer dependencies (#10326).
76
+
77
+ 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:
78
+
79
+ ```
80
+ ✕ unmet peer better-call
81
+ Installed: 1.4.0
82
+ Wanted:
83
+ 1.3.7:
84
+ @better-auth/scim@1.7.0-rc.1
85
+
86
+ ✕ unmet peer better-sqlite3
87
+ Installed: 13.0.3
88
+ Wanted:
89
+ ^12.0.0:
90
+ better-auth@1.7.1
91
+ ```
92
+
93
+ 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.
94
+
95
+ **`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.
96
+
97
+ **`@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.
98
+
99
+ **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.
100
+ - e85182d: Converge the blank scaffold template's `README.md` docs links on the ruled
101
+ canonical origin, `https://objectstack.ai` (maintainer ruling, 2026-08-21:
102
+ 「这个仓的文档站规范 URL 是 https://objectstack.ai」; enforced by
103
+ `CANONICAL_DOCS_ORIGIN` in `scripts/check-published-readme-links.mjs`). The
104
+ template previously linked the accepted-but-unratified `docs.objectstack.ai`
105
+ alias in three places, which disagreed with the root `README.md`'s already-
106
+ canonical spelling — so a single `npm create objectstack@latest` run handed
107
+ the user two different hostnames for the same docs site.
108
+ - aea1e64: Fix the declared bin (`bin/create-objectstack.js`) being tracked non-executable
109
+ in git. It carries a `#!/usr/bin/env node` shebang and is pnpm's link target
110
+ for the `create-objectstack` command, but was committed `100644` instead of
111
+ `100755` — matching the sibling declared bin `packages/cli/bin/run.js`, which
112
+ was already tracked executable.
113
+
114
+ Patch bump: this is a packaging-mode correction with no content, API or
115
+ behavior change (the blob hash is identical) — it only fixes how the file is
116
+ tracked in git and therefore how it is packed for npm.
117
+ - 818e027: Fix `objectstack init`'s closing "Created files" summary omitting `pnpm-lock.yaml` / `package-lock.json` and `node_modules/` (#10557).
118
+
119
+ The summary used to be printed from a list accumulated while the template
120
+ files were written — before `<pm> install` ran — so it could never name what
121
+ the package manager wrote. `init` now prints it after the install attempt
122
+ (succeeded or failed) from a walk of the finished project directory, reusing
123
+ `create-objectstack`'s `created-summary.ts` (now published as the
124
+ `create-objectstack/created-summary` subpath) instead of a second copy of the
125
+ same renderer.
126
+ - afe1c4e: fix(cli): declare the four `@better-auth/utils` peer skews a freshly scaffolded project reports (#10931)
127
+
128
+ Both scaffold paths emit a `peerDependencyRules.allowedVersions` block whose
129
+ stated purpose is that a brand-new project's first `pnpm install` does not open
130
+ with a peer-skew report. It declared two skews and left four showing:
131
+
132
+ ```
133
+ ├─┬ @better-auth/core 1.7.1
134
+ │ └── ✕ unmet peer @better-auth/utils@0.4.2: found 0.5.0
135
+ ├─┬ @better-auth/scim 1.7.0-rc.1
136
+ │ └── ✕ unmet peer @better-auth/utils@0.4.2: found 0.5.0
137
+ ├─┬ @better-auth/oauth-provider 1.7.1
138
+ │ └── ✕ unmet peer @better-auth/utils@0.4.2: found 0.5.0
139
+ └─┬ @better-auth/sso 1.7.1
140
+ └── ✕ unmet peer @better-auth/utils@0.4.2: found 0.5.0
141
+ ```
142
+
143
+ `@better-auth/core`, `/oauth-provider`, `/scim` and `/sso` each peer an **exact**
144
+ `@better-auth/utils@0.4.2`. The 0.5.0 they are handed comes from
145
+ `better-call@1.4.0` — better-auth's own HTTP layer — which *depends* on
146
+ `^0.5.0`; `@objectstack/plugin-auth` names the four as direct dependencies
147
+ without naming utils, so pnpm satisfies their peer from better-call's copy
148
+ instead of better-auth's own exact 0.4.2 dependency.
149
+
150
+ **Measured compatible before widening, not assumed.** Those four import three
151
+ symbols in total: `base64`/`base64Url` (`@better-auth/utils/base64`),
152
+ `createHash` (`/hash`) and, in core only, `createRandomStringGenerator`
153
+ (`/random`). 0.5.0 declares all three with identical signatures; `/random` is
154
+ unchanged apart from formatting, `/base64` swaps `new Uint8Array(data)` for a
155
+ helper that *is* `new Uint8Array(data)` on non-strings, and `/hash` only widens
156
+ its input coercion for views not backed by a plain `ArrayBuffer`. Run against
157
+ the input shapes those call sites actually pass, the two versions agree on every
158
+ value; run end to end — better-auth with the `sso`, `oauth-provider` and `scim`
159
+ plugins — a tree where the four resolve 0.5.0 and one where they resolve 0.4.2
160
+ produce the same transcript: sign-up, sign-in, session, both OAuth metadata
161
+ documents, the RFC 7636 PKCE challenge, and the SCIM and SSO endpoint outcomes.
162
+
163
+ A resolution change was measured too, and rejected: pinning utils back to 0.4.2
164
+ clears the four lines only by dragging `better-call@1.4.0` off its own declared
165
+ `^0.5.0` — manufacturing one real range violation to silence four benign ones.
166
+
167
+ Four scoped entries, one per declaring package, matching the block's convention
168
+ that each rule widens exactly one declaration. `allowedVersions` suppresses the
169
+ report only: the lockfile a scaffold resolves is byte-identical with and without
170
+ the block. The version is spelled `0.5.0` exactly rather than `0.5`, so a future
171
+ `0.6.0` reports again instead of inheriting this finding.
172
+
173
+ Both scaffold paths — `objectstack init` (rendered by the CLI) and
174
+ `npx create-objectstack` (a copied template file) — are changed together, and
175
+ `packages/cli/test/scaffold-workspace-consistency.test.ts` gains a limb that
176
+ compares the peer maps the two produce, so they cannot drift apart again.
177
+ - 21756b3: fix(create-objectstack): converge scaffold docs on the canonical host and drop the last two dead monorepo references (#10990, #11022)
178
+
179
+ A freshly scaffolded project shipped a handful of text lines a reader with
180
+ only their own project — no monorepo, no `docs/adr/`, no issue tracker —
181
+ could not follow:
182
+
183
+ - `templates/AGENTS.md` linked `https://objectstack.com/docs`, a domain that
184
+ is not this project's docs site at all (not even a redirecting alias).
185
+ - `templates/blank/Dockerfile` and `templates/blank/docker-compose.yml` both
186
+ linked `https://docs.objectstack.ai/...`, an accepted-but-unratified alias.
187
+ All three now point at the ruled canonical origin, `https://objectstack.ai`
188
+ (maintainer ruling, 2026-08-21).
189
+ - `templates/blank/README.md` cited `ADR-0097` and named "the ObjectStack
190
+ framework repo" as the home of `skills/` — both rewritten self-contained,
191
+ keeping the fact each was carrying: the connector-materialization line now
192
+ links the public [Automation → Connectors](https://objectstack.ai/docs/automation/connectors)
193
+ page, and the skills line now names the followable
194
+ `npx skills add objectstack-ai/objectstack/skills` install the scaffolder's
195
+ own closing output already uses.
196
+
197
+ `packages/create-objectstack/src/starter-comments-self-contained.test.ts`
198
+ (#10324) gains two pin obligations these two fixes call for: a host-convergence
199
+ assertion driven by the same `shippedFiles()` walker that already enumerates
200
+ everything a scaffold ships (no other repo gate's population reaches these
201
+ template files — `check:published-readme-links` reads publishable packages'
202
+ published markdown only), and a fifth `MONOREPO_ONLY` pattern that catches a
203
+ prose-shaped reference to this repo ("the ObjectStack framework repo") the
204
+ first four, syntax-shaped patterns could not. The self-retiring `EXCLUDED`
205
+ entry for `blank/README.md` is removed now that the file cites nothing
206
+ monorepo-only.
207
+ - 568de19: Scaffolded projects declare an explicit empty `packages: []` in their
208
+ `pnpm-workspace.yaml` (#10933). Both scaffold paths render it —
209
+ `renderPnpmWorkspaceYaml` in `objectstack init`, and the bundled `blank`
210
+ template `npx create-objectstack` copies.
211
+
212
+ The file was deliberately keyless so it would act purely as a settings file.
213
+ That intent is now written down rather than inferred from a missing key, and
214
+ writing it down is what fixes a first-command failure: pnpm 9.x and 10.0–10.4
215
+ parse `pnpm-workspace.yaml` **before** they read `engines`, so they refused a
216
+ brand-new project outright with
217
+
218
+ ```
219
+ ERROR packages field missing or empty
220
+ ```
221
+
222
+ naming a file the user never wrote and giving no hint that the cause is their
223
+ pnpm version — and no `engines.pnpm` floor could reach them, because they never
224
+ got as far as the engines check. Measured, one clean install per pnpm version,
225
+ each with its own store:
226
+
227
+ | pnpm | before | after |
228
+ |---|---|---|
229
+ | 9.15.9, 10.0.0, 10.4.0 | `ERROR packages field missing or empty` | `ERR_PNPM_UNSUPPORTED_ENGINE`, naming `>=10.15` |
230
+ | 10.5.0–10.14.0 | `ERR_PNPM_UNSUPPORTED_ENGINE` | unchanged |
231
+ | 10.15.0, 10.34.5, 11.22.0 | installs | installs, byte-identical `pnpm-lock.yaml` |
232
+
233
+ So every unsupported pnpm now reports the same actionable cause, and supported
234
+ pnpm is unaffected: the empty key was measured equivalent to omission on
235
+ 10.15.0, 10.34.5 and 11.22.0 — identical lockfile bytes, identical
236
+ `node_modules/.modules.yaml` once the run-local `prunedAt`/`storeDir` fields are
237
+ dropped, identical `pnpm ls -r --depth -1`, and an identical second-install
238
+ "Already up to date".
239
+
240
+ The declaration is an **empty** list on purpose. `packages: ['.']` satisfies the
241
+ same parsers but declares the project root a workspace *member* — a monorepo
242
+ root — which a single-package scaffold is not, and which reads to the next
243
+ author (human or AI) as an invitation to add member packages to an app.
244
+
245
+ `engines.pnpm` is unchanged at `>=10.15`.
246
+ - 8d21f7a: Fix `create-objectstack`'s closing "Next steps" and install-failure remedy
247
+ hardcoding `npm` regardless of which package manager the run actually used
248
+ (#10322). `detectPackageManager()` already prefers `pnpm` and falls back to
249
+ `npm` only when `pnpm` is unreachable — confirmed still true at HEAD, and
250
+ confirmed empirically: a real run with `pnpm` on `PATH` installs with `pnpm`
251
+ (`pnpm-lock.yaml`, "Done in … using pnpm vX") and then told the newcomer to
252
+ run `npm run dev` / `npm run validate` next, a package manager the run never
253
+ touched. The detected package manager is now read once, up front, and reused
254
+ consistently for the install command, the install-failure remedy, and every
255
+ line of "Next steps" — so the printed guidance always names the tool the run
256
+ actually used, in both the `pnpm` and the `npm`-fallback case.
257
+
258
+ Also names `validate` — the step the generated `AGENTS.md` calls
259
+ unskippable — in the "Getting started" section of the generated `blank`
260
+ template's README, not only in its later "Verify your changes" section, so a
261
+ newcomer reading top-to-bottom sees it at first touch.
262
+
263
+ No install behaviour changes: the scaffolder still installs by default and
264
+ still supports `--skip-install`; this is a messaging-only fix.
265
+ - 9d101d2: Declare a pnpm floor (`engines.pnpm: ">=10.15"`) in the `package.json` both
266
+ scaffolders write, so an unsupported pnpm reports its own version instead of an
267
+ error about a file the user never wrote.
268
+
269
+ Both scaffold paths emit a settings-only `pnpm-workspace.yaml` with no
270
+ `packages:` key. Early pnpm 10 refuses that file outright — `pnpm install` exits
271
+ 1 with `ERROR packages field missing or empty` before resolving a single
272
+ dependency, so a brand-new project could not be installed at all. Measured on
273
+ the rendered shape, one clean install per pnpm version, each with its own store:
274
+
275
+ | pnpm | before | after |
276
+ | --- | --- | --- |
277
+ | 10.0.0 – 10.4.0 | `packages field missing or empty` | unchanged — see below |
278
+ | 10.5.0 – 10.14.0 | `packages field missing or empty` | `ERR_PNPM_UNSUPPORTED_ENGINE`, naming the expected range |
279
+ | >= 10.15.0 | installs | installs |
280
+
281
+ The floor is a diagnosis, not a repair: pnpm 10.0.0–10.4.0 parse
282
+ `pnpm-workspace.yaml` *before* they read `engines`, so they still print the raw
283
+ workspace error. Closing that remaining sliver requires deciding what a
284
+ single-package scaffold should declare under `packages:`, which is tracked
285
+ separately and deliberately not decided here.
286
+
287
+ `engines.pnpm` rather than a `packageManager` stamp: npm, yarn and bun ignore
288
+ `engines.pnpm` entirely, so the scaffold keeps working for all four package
289
+ managers `objectstack init` hands off to. A `packageManager: "pnpm@x.y.z"` stamp
290
+ would declare the project pnpm-only (corepack-driven yarn refuses to run in such
291
+ a project) and pin one exact version that goes stale on every pnpm release — and
292
+ it buys nothing on 10.0–10.4, which reach the workspace error before reading
293
+ that field either.
294
+
295
+ No existing project is affected; this only changes what a newly scaffolded
296
+ `package.json` contains.
297
+ - 6d441e4: Correct the pnpm boundary the blank template states for `allowBuilds`, and gate
298
+ the two scaffold paths against each other (#10498, #10499).
299
+
300
+ `packages/create-objectstack/src/templates/blank/pnpm-workspace.yaml` is copied
301
+ verbatim into every scaffolded project, so its header comment is prose that
302
+ ships **inside the user's own repository**. It said `allowBuilds` needs
303
+ pnpm >= 10.31 and that `onlyBuiltDependencies` covers pnpm 10.0–10.30. Measured
304
+ on a probe depending on `esbuild@0.28.2`, with a workspace file carrying only
305
+ `allowBuilds`, one clean install per pnpm version and each with its own
306
+ `--store-dir` (isolation matters — pnpm's side-effects cache will otherwise hand
307
+ a later run a build an earlier run performed, and it reads as "the key worked"):
308
+
309
+ | pnpm | `allowBuilds` alone |
310
+ |:--|:--|
311
+ | 10.15.0 – 10.25.0 | ignored — build not run |
312
+ | **10.26.0** | **honoured — build ran** |
313
+ | 10.28.0 – 10.33.0 | honoured — build ran |
314
+
315
+ So the floor is 10.26.0 and the older-key band is 10.0–10.25. A user on pnpm
316
+ 10.28 was being told by the file in front of them that their pnpm cannot read
317
+ the key it is in fact reading. Both load-bearing claims in that comment were
318
+ correct and are unchanged: both keys are needed, and pnpm 11 reads only
319
+ `allowBuilds`. No setting, no assertion and no install behaviour changes — the
320
+ rendered `onlyBuiltDependencies` / `allowBuilds` values are byte-identical.
321
+
322
+ The reason it was wrong for so long is the second half of this change.
323
+ `objectstack init` renders the same file from `renderPnpmWorkspaceYaml()` in
324
+ `packages/cli`, it was corrected to the measured numbers separately, and each
325
+ package's ratchets are package-local — so neither could ever fail for the other
326
+ file's regression, and the two scaffold paths shipped contradictory prose about
327
+ the same rule with every gate green. `packages/cli/test/scaffold-workspace-consistency.test.ts`
328
+ now compares the two **rendered outputs**: the packages each key actually grants
329
+ a build to, and the pnpm versions each file actually names for each key. It was
330
+ confirmed failing against the live divergence before this correction landed.
331
+
332
+ Bumped `patch` rather than left out: the corrected text is user-visible — it is
333
+ delivered into every new project — while nothing executable moves.
334
+ - ecd06f6: Rewrite the scaffolded project's starter comments so a newcomer can actually
335
+ follow them (#10324). `objectstack.config.ts` and `src/objects/note.object.ts`
336
+ are the first two files opened after scaffolding, and between them they cited
337
+ four ADR identifiers, one bare issue number and the path of a release-time
338
+ script in this monorepo — none of which ship in, or are linked from, a
339
+ scaffolded project. `// per ADR-0097` read as a reference the reader was
340
+ failing to follow rather than as the context it was meant to be.
341
+
342
+ The explanations are kept and made self-contained; only the dead ends are
343
+ gone. Each now states the fact the identifier stood for — the protocol range
344
+ is checked before anything loads and was stamped to match the installed
345
+ version rather than hand-tuned; `automation` must stay whenever `plugins:`
346
+ lists a connector or the executors have nowhere to register; a declarative
347
+ `mcp` stdio transport is denied by default; the org-wide default is required
348
+ so the baseline is an authored decision — and points at the public docs page
349
+ that covers it in full. The blank `Dockerfile` likewise stops pointing at a
350
+ file in this repo and points at the self-hosting guide it already links.
351
+
352
+ A pin (`starter-comments-self-contained.test.ts`) keeps it that way from both
353
+ sides: no shipped template file may cite an ADR identifier, a bare issue
354
+ number or a repo script path, and the facts those references carried must
355
+ still be stated — so the comments cannot be "fixed" by deleting them. It also
356
+ resolves every canonical-origin docs URL in the shipped tree against
357
+ `content/docs`, because a link that 404s is the same defect one level out.
358
+
359
+ ## 17.1.0
360
+
361
+ ### Minor Changes
362
+
363
+ - 1eb28a1: Retire the five remote content templates from the scaffolder's catalog.
364
+
365
+ `todo`, `compliance`, `content`, `contracts` and `procurement` were delisted
366
+ from the official ObjectStack template marketplace and are no longer
367
+ maintained, but the CLI carried its own hardcoded catalog and never learned
368
+ that: `--help` recommended all five by name with marketing descriptions, and
369
+ the `Available:` line on a bad `-t` offered them too.
370
+
371
+ - `blank` (bundled, offline) is now the whole catalog, so the help text
372
+ advertises only what is actually supported.
373
+ - Asking for one of the five by name — `-t todo` in an old script or tutorial —
374
+ is refused with a message that says the template was retired, instead of the
375
+ generic "Unknown template" error that reads as a typo.
376
+ - The GitHub tarball-fetch path that served the remote templates is removed
377
+ along with its `tar` dependency; nothing else reached it.
378
+
379
+ Note this corrects the catalog at HEAD only. Already-published versions keep
380
+ advertising the retired templates until a new version of `create-objectstack`
381
+ is released.
382
+
383
+ ### Patch Changes
384
+
385
+ - 4906c90: Fix scaffolded projects describing themselves as the blank template (#9263)
386
+
387
+ `rewriteProjectIdentity` rewrote `id` / `namespace` / `name` in both
388
+ `objectstack.config.ts` and `objectstack.manifest.json` from the project name,
389
+ but left `description` untouched — every scaffolded project carried the blank
390
+ template's own line verbatim ("Minimal ObjectStack environment — a clean
391
+ slate for building."), confidently wrong rather than empty, and printed by
392
+ the first command the getting-started flow tells people to run (`os
393
+ validate`).
394
+
395
+ The scaffolder now drops `description` from both files instead of rewriting
396
+ it. There is nothing but the project name to derive a replacement from, and a
397
+ name-derived sentence (e.g. "Support Desk — an ObjectStack environment.")
398
+ would be a bare restatement of the `name`/`displayName` row already shown —
399
+ worse than no sentence at all. `os validate` already omits the description
400
+ line entirely when the field is unset, so a freshly scaffolded project now
401
+ prints cleanly:
402
+
403
+ ```
404
+ Support Desk v0.1.0
405
+ ```
406
+
407
+ instead of
408
+
409
+ ```
410
+ Support Desk v0.1.0
411
+ Minimal ObjectStack environment — a clean slate for building.
412
+ ```
413
+ - f2f09e4: fix(create-objectstack): the scaffolded Dockerfile pins the runtime image to the CLI that builds the artifact, instead of `latest` under a comment saying to pin (#9017)
414
+
415
+ `src/templates/blank/Dockerfile` shipped `FROM ghcr.io/objectstack-ai/objectstack:latest`
416
+ directly beneath a comment instructing the reader to "pin the tag to the
417
+ `@objectstack/cli` version in your package.json so the runtime matches the CLI that built
418
+ the artifact" — an instruction the scaffold itself did not follow. Every app made with
419
+ `npx create-objectstack` shipped that contradiction from day one, and `docker/README.md`'s
420
+ tag table already scopes `latest` to quick starts while documenting `X.Y.Z` as the
421
+ production pin.
422
+
423
+ Measured on scaffolded output rather than the template's bytes, before the fix:
424
+
425
+ ```
426
+ emitted package.json cli range : ^17.0.0
427
+ emitted Dockerfile FROM : FROM ghcr.io/objectstack-ai/objectstack:latest
428
+ agreement (tag vs cli range) : DISAGREE
429
+ ```
430
+
431
+ **The tag is resolved after `install`, from the installed CLI — not from the generated
432
+ `package.json`.** That file carries a caret RANGE, and the two are not interchangeable:
433
+ npm resolves `^17.0.0` to the newest 17.x, so pinning the range's floor would ship a
434
+ runtime image *older* than the CLI that built the artifact — breaking the same promise in
435
+ a new way. The rolling `:17` tag does match the range's float window but is exactly what
436
+ the tag table tells production not to use. The resolved version is the only value that
437
+ makes the sentence true, and it is the rule the repo already applies for this purpose in
438
+ `.github/workflows/scaffold-e2e.yml` ("Pin the runtime's CLI to the SAME version the
439
+ generated project actually resolved to — NOT a hardcoded `latest`").
440
+
441
+ **Both halves move together.** Pinning the line while leaving an imperative to pin by hand
442
+ would relocate the contradiction rather than remove it, so the comment above the `FROM`
443
+ line is replaced in the same rewrite. With `--skip-install` there is no resolved version:
444
+ the tag stays `latest` and the comment keeps telling the reader to pin — which is true on
445
+ that path, because there the user really must do it by hand.
446
+
447
+ The regression proof asserts on **scaffolded output**, never on the template: it scaffolds
448
+ with the real copy/sync/pin path, plants an installed CLI whose version is deliberately
449
+ *not* the range's floor (the normal case, and the one that a package.json-derived tag
450
+ would get wrong), and checks the emitted `FROM` tag against the emitted `package.json`
451
+ range with a satisfies-check rather than equality.
452
+
453
+ `.github/workflows/scaffold-e2e.yml` now reads the tag it builds its local runtime image
454
+ under **out of the generated Dockerfile** instead of hardcoding `:latest`. Those were two
455
+ hand-matched literals; had they skewed, Docker would have quietly pulled the last
456
+ published image instead of the one built from this checkout, and the job's own stated
457
+ hermeticity would have been false while it stayed green.
458
+ - 0a5adba: fix(create-objectstack): the blank template's `specVersion` stops shipping eleven majors stale, and the version-time sync covers every declared surface on every template (#9264)
459
+
460
+ The one bundled template declared the platform it targets in **two** places that
461
+ disagreed by eleven majors:
462
+
463
+ | file | key | was |
464
+ |:--|:--|:--|
465
+ | `objectstack.manifest.json` | `specVersion` | `^6.0.0` |
466
+ | `objectstack.config.ts` | `engines.protocol` | `^17` |
467
+
468
+ `scripts/sync-template-versions.mjs` re-stamped the config key and the template's
469
+ `@objectstack/*` dependency ranges, and **never opened the manifest at all**. So
470
+ `engines.protocol` tracked every major bump while `specVersion` sat at the value
471
+ it held when the script was written — and a green `sync-template-versions` run
472
+ was never evidence about it, because the script's failure mode was loud for the
473
+ keys it covered and mute for the key it did not.
474
+
475
+ **This is not confined to the registry contract.** `create-objectstack` copies
476
+ the manifest into every scaffolded project, rewriting `name`, `displayName` and
477
+ `namespace` and dropping `description` — it has never touched `specVersion`. So
478
+ every project scaffolded since v7 was stamped with a `^6.0.0` spec range while
479
+ installing `@objectstack/spec@^17.0.0`.
480
+
481
+ **The two keys are two facts, and the fix keeps them apart.** `engines.protocol`
482
+ is the ADR-0087 D1 runtime handshake range and carries the protocol major
483
+ (`^17`). `specVersion` is documented by `TemplateManifestSchema` as the
484
+ "Compatible `@objectstack/spec` semver range" and carries the package range
485
+ (`^17.0.0`) — the same value the script already writes into the template's own
486
+ `@objectstack/spec` dependency, so the manifest and the `package.json` now state
487
+ one fact once. They agree on the major only because the spec package's major and
488
+ the protocol major are kept in lockstep; they are stamped from two different
489
+ values.
490
+
491
+ Deleting the key was not available: `specVersion` is **required** by
492
+ `TemplateManifestSchema`, and every shipped manifest is parsed against it by
493
+ `check:template-manifests`.
494
+
495
+ **Two structural changes, because one-key-one-file coverage is what let this
496
+ sit:**
497
+
498
+ - the sync script's file list is now **discovered**, not hard-coded — templates
499
+ are found by walking `src/templates/`, the same way `check-template-manifests`
500
+ finds the manifests it parses, so a second template is covered on the day it
501
+ lands;
502
+ - **every stamp is required**. A template whose file is missing, whose stamp is
503
+ absent, or whose `package.json` declares no `@objectstack/*` dependency is a
504
+ hard failure naming the path — never a skip. A skipped stamp is
505
+ indistinguishable from a synced one in the log, which is the invisibility this
506
+ fixes.
507
+
508
+ The manifest is rewritten as **text** rather than parsed and re-serialized:
509
+ `objectstack.manifest.json` keeps `scaffold.variables` compact on one line, and
510
+ `JSON.stringify(…, null, 2)` would reformat unrelated structure on every release.
511
+
512
+ CI coverage lands as four per-template ratchets in `template-consistency.test.ts`,
513
+ generalized off `blank` onto the same directory walk — including the invariant
514
+ that catches this exact class: the manifest's `specVersion` must equal the
515
+ `@objectstack/spec` range the template actually installs. Either file alone can
516
+ be self-consistently stale; only comparing them catches a stamp that covered one
517
+ and not the other.
518
+
3
519
  ## 17.0.0
4
520
 
5
521
  ### Major Changes
package/README.md CHANGED
@@ -27,16 +27,14 @@ npx create-objectstack my-app --skip-install
27
27
  | Template | Source | Description |
28
28
  | --- | --- | --- |
29
29
  | `blank` *(default)* | bundled (offline) | Minimal starter — one object, REST API, ready to extend |
30
- | `todo` | remote | Universal task & project management starter |
31
- | `compliance` | remote | Compliance posture & evidence management (SOC2 / ISO27001) |
32
- | `content` | remote | Content marketing pipeline — editorial calendar & channel ROI |
33
- | `contracts` | remote | Post-signature CLM — approvals, obligations, renewals |
34
- | `procurement` | remote | Source-to-pay — vendors, POs, receipts, invoice matching |
35
30
 
36
- Remote templates are fetched from
37
- [`objectstack-ai/templates`](https://github.com/objectstack-ai/templates) at
38
- scaffold time and require network access; `blank` is bundled and always works
39
- offline.
31
+ `blank` is bundled inside the npm package, so scaffolding never needs network
32
+ access.
33
+
34
+ The remote content templates (`todo`, `compliance`, `content`, `contracts`,
35
+ `procurement`) have been **retired** — they were delisted from the ObjectStack
36
+ template marketplace and are no longer maintained. Asking for one by name tells
37
+ you so rather than failing as an unknown template.
40
38
 
41
39
  ## Options
42
40
 
@@ -89,7 +87,7 @@ npm run validate # verify metadata: schema + predicates + bindings
89
87
  ```
90
88
 
91
89
  See the docs:
92
- [Your First Project](https://docs.objectstack.ai/docs/getting-started/your-first-project).
90
+ [Your First Project](https://objectstack.ai/docs/getting-started/your-first-project).
93
91
 
94
92
  ## License
95
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
+ };