Repository navigation
Commit bcd68a2
feat(cli,create-objectstack): os generate picklist, the src/picklists starter barrel, and a Picklists count in the metadata summary (#21167)
Part of #21018
Clause-②: yes (widening)
This PR is the code half of #21018. It covers scope items 1, 2, 3 and 5
of the card body. Item 4, the "seven generator barrels" line in
`skills/objectstack-platform/SKILL.md`, is on a Tier H governed path. It
follows in its own PR under a second claim, so #21018 remains open for
it.
Dispatched by the `domain:cli` seat's PM under claim 5929373213. Dev
session `session_01VvcEokUG1tvVxkceYfR5XB`.
## Premise, checked on `origin/main`
The card waited on the runtime reader. That reader is on `main` as
88b484e (#21047), and these are the parts this PR relies on:
- `picklists` and `picklistExtensions` are in `METADATA_ARRAY_KEYS`
(`packages/objectql/src/engine.ts`).
- The registry fold resolves a field's `picklist` into served `options`
(`picklist-resolution.ts`, `resolvePicklistFieldsOnto`).
- The write door judges a write against the resolved options.
- The boot refuses an unresolved name
(`packages/objectql/src/plugin.ts`).
The repro below measures all of this end to end on a scaffolded project.
## What changed
**Item 1: `os generate picklist NAME`**
(`packages/cli/src/commands/generate.ts`)
- This is the row written and reverted on #20825's branch (`6ef78d3f29`,
reverted in `e16359a9fa`), re-read against the landed reader.
- It writes `src/picklists/NAME.picklist.ts` through `definePicklist`.
The file name comes from `metadataFileName`, with no override.
- The list is collected under `picklists` (`singularToPlural`).
`namesObject: false`, and `requires` is empty.
- The emitted header states the next rule an author meets: a field that
names the list declares no `options` of its own.
- The row's comment now names the reader: the authoring doors, the boot
refusal, the served options and the write judgement.
- Two existing comments counted the roster ("an app or a skill", "14
emission sites across all 7 generators"). Both are reworded so the new
row does not make them false.
**Item 2: scaffold wiring**
- `os init` derives its wiring from the roster
(`SCAFFOLD_WIRED_BARRELS`), so the `app` and `plugin` templates wire
`src/picklists` with no edit to `init.ts`.
- The blank starter in `create-objectstack` gains
`src/picklists/index.ts`, byte-identical to the empty barrel `os init`
writes. Its `objectstack.config.ts` gains `import * as picklists from
'./src/picklists';` and `picklists: exportsOf(picklists),`.
- `create-objectstack-wiring-parity.test.ts` holds the two scaffolders
equal.
**Item 3: docs**
- `content/docs/deployment/cli.mdx` gets the example line, the table row
(`picklist`, `src/picklists/`, `NAME.picklist.ts`, `picklists`) and the
"What it does" clause.
- `packages/cli/README.md` gets the type list and a short paragraph.
**Item 5: the metadata summary** (`packages/cli/src/utils/format.ts`)
- `MetadataStats` gains `picklists`, and `collectMetadataStats` counts
it through the existing `authoringRuleUnionStack` fold, so both ADR-0130
D4 shapes are covered.
- `printMetadataStats` renders it in the `Data:` row, the kind's own
domain (`domain: 'data'` in the registry).
- A stack with no picklists prints the row it printed before. A
`picklistExtensions` entry is not counted as a list.
- The two existing test fixtures that spell out every `MetadataStats`
member gain `picklists: 0`: `format.metadata-stats-package-fold.test.ts`
and `print-metadata-stats-zero-row.test.ts`.
**Changesets**
- `@objectstack/cli`: `minor`. A new generator kind, and
`stats.picklists` is added to the `--json` output of `os validate`, `os
build` and `os info`.
- `create-objectstack`: `minor`. The starter gains a wired barrel.
- Both carry `Clause-②: yes (widening)`. Nothing is narrowed and nothing
is renamed, so there is no ADR-0087 marker.
## Repro, before and after
Before, on `origin/main` 58a77db:
```text
os generate picklist industry exit 1 ✗ Unknown type: picklist (roster lists 7 types)
npm create objectstack (blank) src/ = actions apps dashboards flows objects skills views
hand-wired picklist + select field
os validate exit 0 Data: 1 Objects 3 Fields
```
After, on this branch:
```text
npm create objectstack (blank) src/ = actions apps dashboards flows objects picklists skills views
os generate picklist industry exit 0 ✓ Reaches the stack: objectstack.config.ts carries it in `picklists` as 'industry'
note.object.ts gains industry: Field.select({ picklist: 'industry', label: 'Industry' })
os validate exit 0 Data: 1 Objects 3 Fields 1 Picklists
os build exit 0 Data: 1 Objects 3 Fields 1 Picklists (artifact carries `picklists` and the field's `picklist`)
os info exit 0 Data: 1 Objects 3 Fields 1 Picklists
os info --json stats.picklists = 1
os dev --fresh -p RANDOM
GET /api/v1/meta/object/my_app_note 200 fields.industry = { picklist: 'industry', options: [option_a, option_b], … }
GET /api/v1/meta/picklist/industry 200
POST /api/v1/data/my_app_note { industry: 'option_a' } 201
POST /api/v1/data/my_app_note { industry: 'option_z' } 400 VALIDATION_FAILED · invalid_option · names picklist "industry"
```
On a project scaffolded before this change (its config wires no
`src/picklists`), `os g picklist region` exits 0. It reports `Not wired`
and prints the import line and the `defineStack` key to add.
## Tests
The pins are:
- `packages/cli/src/commands/generate-picklist.pin.test.ts`:
- the roster row (`src/picklists`, stack key `picklists`, `namesObject:
false`, no `requires`) and the `NAME.picklist.ts` file name;
- the scaffold loaded through the loader `os validate` uses
(`bundle-require`, `BUNDLE_REQUIRE_EXTERNALS`), parsed by
`PicklistSchema`, under the item name `os g` reports;
- that scaffold registered through `ObjectQL.registerApp` under
`picklists`, serving its options on a `Field.select({ picklist })`
field, and passing `PicklistServedFieldSchema`;
- a control: the same field with no list serves no options.
It constructs `ObjectQL`, so it lands in the `integration` tier.
- `packages/cli/src/utils/format.metadata-stats-picklists.test.ts`:
- the count, top level and option-B;
- that an extension is not a list;
- the zero case;
- the rendered row `Data: 1 Objects 2 Fields 1 Picklists`;
- a control: a stack with no picklists prints `Data: 1 Objects 2
Fields`.
The runs, all on HEAD 5a53515 except where noted:
| Run | Files | Tests | Result |
|:---|---:|---:|:---|
| `pnpm --filter @objectstack/cli exec vitest run --project unit
--maxWorkers=2` | 241 + 2 | 3427 + 29 | All passed, 29 skipped. The 2
files first failed with `packages/cli is not built` and passed after
`pnpm --filter @objectstack/cli build`. |
| `--project integration`, the 8 integration files among the related
tests | 8 | 66 | Passed: `generate-picklist.pin`,
`create-objectstack-stack-reach`, `generate-stack-reach`,
`info-detail-package-fold` and 4 more |
| `OS_TEST_TIERS=nightly`, the related `*.e2e` files | 6 | 42 | Passed.
Includes `generate-scaffolds-reach-stack.e2e`: "`os g picklist` exits 0
and reports no wiring to add" |
| `pnpm --filter create-objectstack exec vitest run` | 16 | 249 | Passed
|
| `pnpm --filter @objectstack/cli typecheck` (`tsc --noEmit &&
check:test-typecheck`) | | | Exit 0. The test-typecheck ledger is
unchanged (3 files / 28 errors) |
| `pnpm --filter create-objectstack typecheck` | | | Exit 0 |
## Ablation
The ablations ran on the committed fix (e63b1db). Each mutation went
through `scripts/ablation-replace.mjs` with the anchor counted on disk.
Each restore was proven: the blob equals HEAD and `git diff HEAD` is
empty.
| Leg | Mutation | Red | Control (green) |
|:---|:---|:---|:---|
| generator | The `picklist` row deleted from `GENERATORS` (anchor x1 →
x0, 53 lines) | `generate-picklist.pin` 3 of 5. `wiring-parity`: imports
and stack keys (2) | `generate-scaffold-validates` 19/19; the pin's
file-name case and its no-list control |
| template | `picklists: exportsOf(picklists),` deleted from the blank
config | `wiring-parity`: "hands every wired barrel to its stack key" (1
of 22) | the other 21 |
| collect | `picklists: count(stack.picklists),` deleted |
`metadata-stats-picklists` 5 and `package-fold` 4 (its `ZEROES`
comparisons) | the pin's no-picklists row;
`print-metadata-stats-zero-row` |
| print | `['Picklists', stats.picklists],` deleted |
`metadata-stats-picklists` "Data: row", and
`print-metadata-stats-zero-row` "every metric collectMetadataStats
counts is rendered" | the other 27 |
## Gates
- `node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack
--commands`, run with no paths, derived 96 families. All 96 were run,
each with its exit code recorded before any pipe.
- Five of them first refused with `PREREQUISITE NOT MET` (exit 3)
because their build inputs were missing: `check:skill-examples`,
`check:dual-build-cjs-loads`, `check:i18n`, `check:i18n-coverage` and
`check:i18n-walk-parity`. All five passed once those inputs were built.
- `--ran` reconciliation: `✓ dispatch-gates --ran: 96 derived famil(ies)
accounted for — 96 run, 0 NOT-MEASURED`.
**`pnpm lint`, as a proven narrowing** (on HEAD 5a53515):
1. **Population, read from eslint's own config** (`isPathIgnored` /
`calculateConfigForFile`): all 8 touched `.ts` files are linted. The 4
touched `.md` / `.mdx` files are outside its population.
2. **Count, from `--format json`**: 8 files, 0 errors, 0 warnings, with
`--no-inline-config`.
3. **Invariance**: no resolved config for these files is type-aware (no
`parserOptions.project`, no `projectService`, as `eslint.config.mjs`
states). This diff touches no lint config or baseline. So it cannot move
the verdict on any file it does not touch.
## Not in this PR
- **Item 4** (Tier H). On `db48028f1a`, lines 194–195 of
`skills/objectstack-platform/SKILL.md` read:
```text
generic connector executors in `plugins:`; and the seven generator
barrels
(`objects`, `views`, `actions`, `flows`, `dashboards`, `apps`,
`skills`),
```
The proposed text, net zero lines:
```text
generic connector executors in `plugins:`; and the eight generator
barrels
(`objects`, `views`, `actions`, `flows`, `dashboards`, `apps`, `skills`,
`picklists`),
```
- Not carried here, as the card says: the
`picklist-reference-unverified` trigger, and a dangling
`picklistExtensions[].extend` (folded into #20825).
## Acceptance notes
- **The two starter README listings now name `src/picklists`** (commit
`90d55b1e13`, added on contract review `5930680827`):
- the `Layout` bullet in
`packages/create-objectstack/src/templates/blank/README.md`, which ships
into every new project;
- the tree in `packages/create-objectstack/README.md`.
They ride this PR, not the Tier H item-4 PR, so two non-governed lines
do not wait on a human approval.
- **`Scaffold with repo dist` is red on this PR, and the PR is held
until the next release publishes.** This note was added by the seat.
- **Cause:** the job scaffolds with the repo-built `create-objectstack`,
then installs the project's framework from the npm registry. The
template's `^17.0.0` range resolves to the published `@objectstack/spec`
17.5.0, which predates the `picklists` stack key (`addbbf0`,
`.changeset/19518-picklist-kind.md`, still unreleased). So the generated
project's `defineStack` refuses the key as unrecognized at the validate
step.
- **Scope of the measurement:** the repro table's `npm create
objectstack` rows and the end-to-end chain were measured on the repo
build, not against the registry.
- **It is not this PR's code to fix.** Its gate checks the template
against the real registry, and the template now runs one release ahead
of it.
- The check is not required. It runs only on `pull_request` under its
path filter (`packages/create-objectstack/**`, `docker/**` and its own
workflow file), and it is not red on `main`.
- Landing now would leave it red on every later `create-objectstack` PR
until that release.
- **The hold:** the release that carries the key is the open Version
Packages PR #20639; its `@objectstack/spec` changelog already lists
`addbbf0`. Once it merges and publishes, a re-run of this job reads the
new spec and the template is coherent with it.
- Until then, the PR stays draft and its card is `pm:blocked` on #20639.
- On a project scaffolded by an earlier release, the not-wired hint
prints `picklists: Object.values(picklists),`, not the starter's
`exportsOf(...)` spelling. Both type-check once the barrel exports a
list, which is the only state the hint is printed in.
---
_Generated by [Claude
Code](https://claude.ai/code/session_01VvcEokUG1tvVxkceYfR5XB)_
---------
Co-authored-by: Claude <noreply@anthropic.com>1 parent 6091136 commit bcd68a2
14 files changed
Lines changed: 336 additions & 11 deletions
File tree
- .changeset
- content/docs/deployment
- packages
- cli
- src
- commands
- utils
- test
- create-objectstack
- src/templates/blank
- src/picklists
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
1483 | 1483 | | |
1484 | 1484 | | |
1485 | 1485 | | |
| 1486 | + | |
1486 | 1487 | | |
1487 | 1488 | | |
1488 | 1489 | | |
| |||
1499 | 1500 | | |
1500 | 1501 | | |
1501 | 1502 | | |
| 1503 | + | |
1502 | 1504 | | |
1503 | 1505 | | |
1504 | 1506 | | |
| |||
1555 | 1557 | | |
1556 | 1558 | | |
1557 | 1559 | | |
1558 | | - | |
| 1560 | + | |
1559 | 1561 | | |
1560 | 1562 | | |
1561 | 1563 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
58 | 58 | | |
59 | 59 | | |
60 | 60 | | |
61 | | - | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
| 64 | + | |
| 65 | + | |
62 | 66 | | |
63 | 67 | | |
64 | 68 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
| 64 | + | |
| 65 | + | |
| 66 | + | |
| 67 | + | |
| 68 | + | |
| 69 | + | |
| 70 | + | |
| 71 | + | |
| 72 | + | |
| 73 | + | |
| 74 | + | |
| 75 | + | |
| 76 | + | |
| 77 | + | |
| 78 | + | |
| 79 | + | |
| 80 | + | |
| 81 | + | |
| 82 | + | |
| 83 | + | |
| 84 | + | |
| 85 | + | |
| 86 | + | |
| 87 | + | |
| 88 | + | |
| 89 | + | |
| 90 | + | |
| 91 | + | |
| 92 | + | |
| 93 | + | |
| 94 | + | |
| 95 | + | |
| 96 | + | |
| 97 | + | |
| 98 | + | |
| 99 | + | |
| 100 | + | |
| 101 | + | |
| 102 | + | |
| 103 | + | |
| 104 | + | |
| 105 | + | |
| 106 | + | |
| 107 | + | |
| 108 | + | |
| 109 | + | |
| 110 | + | |
| 111 | + | |
| 112 | + | |
| 113 | + | |
| 114 | + | |
| 115 | + | |
| 116 | + | |
| 117 | + | |
| 118 | + | |
| 119 | + | |
| 120 | + | |
| 121 | + | |
| 122 | + | |
| 123 | + | |
| 124 | + | |
| 125 | + | |
| 126 | + | |
| 127 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
105 | 105 | | |
106 | 106 | | |
107 | 107 | | |
108 | | - | |
109 | | - | |
| 108 | + | |
| 109 | + | |
| 110 | + | |
110 | 111 | | |
111 | 112 | | |
112 | 113 | | |
| |||
539 | 540 | | |
540 | 541 | | |
541 | 542 | | |
| 543 | + | |
| 544 | + | |
| 545 | + | |
| 546 | + | |
| 547 | + | |
| 548 | + | |
| 549 | + | |
| 550 | + | |
| 551 | + | |
| 552 | + | |
| 553 | + | |
| 554 | + | |
| 555 | + | |
| 556 | + | |
| 557 | + | |
| 558 | + | |
| 559 | + | |
| 560 | + | |
| 561 | + | |
| 562 | + | |
| 563 | + | |
| 564 | + | |
| 565 | + | |
| 566 | + | |
| 567 | + | |
| 568 | + | |
| 569 | + | |
| 570 | + | |
| 571 | + | |
| 572 | + | |
| 573 | + | |
| 574 | + | |
| 575 | + | |
| 576 | + | |
| 577 | + | |
| 578 | + | |
| 579 | + | |
| 580 | + | |
| 581 | + | |
| 582 | + | |
| 583 | + | |
| 584 | + | |
| 585 | + | |
| 586 | + | |
| 587 | + | |
| 588 | + | |
| 589 | + | |
| 590 | + | |
| 591 | + | |
| 592 | + | |
| 593 | + | |
| 594 | + | |
| 595 | + | |
542 | 596 | | |
543 | 597 | | |
544 | 598 | | |
| |||
1267 | 1321 | | |
1268 | 1322 | | |
1269 | 1323 | | |
1270 | | - | |
| 1324 | + | |
| 1325 | + | |
1271 | 1326 | | |
1272 | 1327 | | |
1273 | 1328 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
76 | 76 | | |
77 | 77 | | |
78 | 78 | | |
| 79 | + | |
79 | 80 | | |
80 | 81 | | |
81 | 82 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
| 64 | + | |
| 65 | + | |
| 66 | + | |
| 67 | + | |
| 68 | + | |
| 69 | + | |
| 70 | + | |
| 71 | + | |
| 72 | + | |
| 73 | + | |
| 74 | + | |
| 75 | + | |
| 76 | + | |
| 77 | + | |
| 78 | + | |
| 79 | + | |
| 80 | + | |
| 81 | + | |
| 82 | + | |
| 83 | + | |
| 84 | + | |
| 85 | + | |
| 86 | + | |
| 87 | + | |
| 88 | + | |
| 89 | + | |
| 90 | + | |
| 91 | + | |
| 92 | + | |
| 93 | + | |
| 94 | + | |
| 95 | + | |
| 96 | + | |
| 97 | + | |
| 98 | + | |
| 99 | + | |
| 100 | + | |
| 101 | + | |
| 102 | + | |
0 commit comments