Repository navigation
Commit cd3a167
feat(app-shell): the hosted marketplace install offers and sends the install mode a package permits (#30992)
<!-- claude-projects-attribution: {"github_login":"os-tesla"} -->
_Requested by **Tesla** · [project
thread](https://claude.ai/code/project/chan_01AXsQkLFKRdQCqQ3RTr6Jao?thread=cmsg_01AXsQkLFKRdQCqQ3RTr6JaoVXP4s3MFTrPqUtxatiSwyk)_
Fixes #30963
Clause-②: no
Metadata-protocol: none
## 维护者速读
- 改了什么:托管云环境里的应用市场详情页,现在也会读取包声明的 `installModes`;包允许 unmanaged
时显示与本地安装页相同的「安装模式」选择,并把所选模式作为 `install_mode` 发给
`/api/v1/cloud-connection/install`。只允许 managed 或未声明的包,请求与以前逐字节相同。
- 为什么改:cloud#3624 裁决 A(一个安装入口,`installMode`
作为可选参数由控制面裁决)。此前托管路径无法表达模式,为「被拥有」而做的起步模板在托管云上只能装成 managed。
- 风险与回滚:仅 Console 代码(`@object-ui/app-shell`
patch)。环境侧转发(#30964)落地前,该参数在下游被忽略;此时页面会明确提示「已按 managed 安装」,不会假装成功复制。回滚 =
revert 本 PR。
- 先看:`MarketplacePackagePage.tsx` 的 `hostedInstallMode` 与 `doInstall`
答复分支;`marketplaceApi.ts` 的 `installPackage()`;测试文件新增的两个 describe。
- 两分钟验证:`pnpm --dir ui exec vitest run
packages/app-shell/src/console/marketplace/__tests__/MarketplacePackagePage.installMode-12288.test.tsx`,预期
32/32 通过。
- 需要你做的:无,席位落地。
- **席位意见**:ui 席 2 对照 head `374cdc9c` 的 diff 复核:4 个文件、+278 /
−43,无受治理路径。托管安装 `installPackage()` 新增可选 `installMode`(取自 spec 的
`INSTALL_MODES`),只在设定时发送
`install_mode`;只允许托管或未声明安装方式的包,请求体逐字节不变(有断言)。环境控制台(有云端地址)的包页用与本地安装同一解析读取
`installModes`,画同一选择;两个安装入口共用一个拒绝文案函数。#30964(服务端)落地前,选择「非托管副本」会按托管安装并如实提示;控制面自身控制台不画选择,因副本只能由环境内的
cloud-connection 安装路由生成,理由写在 Acceptance notes。CI 59 项全绿,Clause-②
no,不欠契约复审。同意落地。
## What changes
- **`installPackage()`** (`marketplaceApi.ts`) takes an optional
`installMode`, typed from the spec's `INSTALL_MODES` (`LocalInstallMode`
is now the spec's `InstallMode`, and the console's mode list is
`INSTALL_MODES` itself, so a mode the spec adds reaches the page without
a second list). It is sent as `install_mode` only when set: on the
hosted route `POST /api/v1/cloud-connection/install` as a top-level body
key, and on the control plane's own `install_package` action inside
`params`. Unset, neither body gains a key.
- **Wire key `install_mode`.** The cloud-connection install route reads
its body in snake case (`package_id`, `seed_sample_data`, with
camel-case fallbacks), and it forwards to the control plane's
`install_package` action, whose `params` are snake case too. The route
does not read the key yet: that is #30964 (`pm:blocked`), which forwards
the mode.
- **The package page on the hosted path** (a cloud base, no
install-local door) reads the manifest the marketplace serves (`GET
/api/v1/marketplace/packages/:id/versions/latest/manifest`, proxied on
hosted) and resolves it through `declaredInstallModesOf`, the
install-local page's resolver; no second reading. The same choice
element is drawn: both modes ⇒ the radio choice and Install waits for a
pick; `unmanaged` alone ⇒ the "this install is a copy" note and
`unmanaged` is sent. A package that declares no `installModes`, permits
only `managed`, or whose manifest cannot be read sends no mode. An
update of an installed copy sends no mode and draws no choice.
- **The answer** is read as install-local reads its own: `installMode:
'unmanaged'` ⇒ the success sentence names the base and the CTA does not
flip to Installed (the copy is not a package installation, per the
ruling's acceptance note). A requested copy answered without it ⇒ the
existing "installed managed instead" sentence
(`marketplace.install.unmanagedIgnored`), because that is what an
environment that does not forward the mode does today.
- **Refusals**: the install-local catch block's code-to-text mapping is
now one helper, `installRefusalText`, used by both install doors. The
hosted error now carries the envelope's `details`, so
`DEPENDENCY_MISSING` is drawn from its facts there too; the install-mode
refusals show the server's own message on both.
- No i18n key is added: every sentence used exists already.
## Tests
`MarketplacePackagePage.installMode-12288.test.tsx` is the shared
harness for install modes (`marketplaceApi` not mocked, one stubbed
`fetch` answering by path), so the new pins are two top-level `describe`
blocks there:
- the hosted path: both modes draw the choice and gate Install; each
picked mode is sent; `unmanaged` alone is sent with the copy note; no
`installModes`, `managed` only and an unreadable manifest each send a
body with no `install_mode` key (`not.toHaveProperty`); the unmanaged
and ignored answers; two install-mode refusals; `DEPENDENCY_MISSING`
drawn from its facts.
- `installPackage()` on both branches: absent sends no key, set sends
it.
- The list page's one-click install keeps its pinned body:
`MarketplacePage.disabledInstall-28521.test.tsx` asserts `[{ package_id:
'pkg_1', seed_sample_data: true }]` on `POST
/api/v1/cloud-connection/install`, unchanged and green.
## Acceptance notes
- **End to end needs #30964.** Until the environment's
`CloudConnectionPlugin` forwards `install_mode`, a hosted request for a
copy installs managed, and the page shows
`marketplace.install.unmanagedIgnored` ("installed managed instead").
That is the truthful outcome until then. The proof here is unit and
harness readings (below); no live hosted install was run.
- **Answer shape assumed for the hosted copy.** The page recognises an
unmanaged hosted install by `installMode: 'unmanaged'` on the answer,
the field install-local answers with (with `targetPackageId` and
`version`). #30964 defines the hosted answer; if it answers another
shape, the page reads it as "installed managed instead", loudly, not as
success. The install-local answer's `strippedLocks` list is not drawn on
the hosted path yet.
- **The choice is drawn only where a cloud base is set** (an environment
console on hosted cloud), not on the control plane's own console,
because no copy can happen there: per #30964 the unmanaged copy is run
by the environment's `CloudConnectionPlugin` (`importUnmanagedPackage`
in the environment) on its own install route, and the control-plane
console's install goes straight to the `install_package` action, never
through that environment. Its sentences would also be false there: the
mode hint and success text say "this runtime", while that dialog targets
another environment. `installPackage()` still sends a given mode on that
branch, so the parameter is never silently dropped.
- **Sample data with an unmanaged pick.** The hosted dialog still offers
"Include sample data" when `unmanaged` is picked; per #30964 an
unmanaged grant loads no sample data. Not changed here, since what the
control plane does with the flag on a copy is #30964's and cloud's.
- **Not a bug, per the ruling:** an unmanaged copy does not appear in
the marketplace "Installed" list (it reads `sys_package_installation`);
no code was added for it.
- Lint: `eslint` on the three files reports 0 errors; warnings 52 → 53
on the two source files, the one new `no-explicit-any` being `(err as
any).details`, the same spelling install-local's error already uses.
## Verification
All readings on `objectstack-ai/objectstack`, head `374cdc9c` (base
`1b3e22f0`).
<details><summary>Before / after: hosted harness reading (cloud base
set, install-local off)</summary>
A throwaway probe (not committed) rendered the page with three
manifests, picked `unmanaged` when a choice was drawn, and installed
through the dialog.
Before, page code at `1b3e22f0`:
```
READING both: manifestRead=false radios=0 installDisabledBeforePick=false picked=none body={"package_id":"pkg_crm","seed_sample_data":false}
READING managed-only: manifestRead=false radios=0 installDisabledBeforePick=false picked=none body={"package_id":"pkg_crm","seed_sample_data":false}
READING absent: manifestRead=false radios=0 installDisabledBeforePick=false picked=none body={"package_id":"pkg_crm","seed_sample_data":false}
```
After:
```
READING both: manifestRead=true radios=2 installDisabledBeforePick=true picked=unmanaged body={"package_id":"pkg_crm","seed_sample_data":false,"install_mode":"unmanaged"}
READING managed-only: manifestRead=true radios=0 installDisabledBeforePick=false picked=none body={"package_id":"pkg_crm","seed_sample_data":false}
READING absent: manifestRead=true radios=0 installDisabledBeforePick=false picked=none body={"package_id":"pkg_crm","seed_sample_data":false}
```
The new pins against the base page code: 14 failed, 18 passed (32). With
the fix: 32 passed.
</details>
<details><summary>Ablation (each mutation landed via
scripts/ablation-replace.mjs, restored to the HEAD blob)</summary>
- A: `hostedInstallMode` never takes the resolved mode ⇒ 4 failed (both
picked modes, `unmanaged` alone, the ignored answer).
- B: the `managed`-only guard removed ⇒ 1 failed: "a manifest that
permits `managed` only draws no choice, and the request carries no
`install_mode` key".
- C: the hosted catch shows the bare message instead of
`installRefusalText` ⇒ 1 failed: the `DEPENDENCY_MISSING` pin.
The subject resolves from source (relative imports), so no dist build
was owed.
</details>
<details><summary>Tests and gates</summary>
- `pnpm --dir ui exec vitest run --maxWorkers=2
packages/app-shell/src/console/marketplace/` (under the verify lock):
Test Files 55 passed (55), Tests 485 passed (485).
- `cd ui/packages/app-shell && pnpm run type-check` (both `tsc --noEmit`
and `tsc -p tsconfig.test.json`, after a full dts build of
`@object-ui/app-shell^...`, under the verify lock): VERDICT command-exit
0.
- Derived gates, `node scripts/pm/dispatch-gates.mjs --commands --repo
objectstack-ai/objectstack` on head `374cdc9c`: 36 commands, each run
with stdin closed, every one `:: exit 0`; `--ran` reconciliation: "36
derived famil(ies) accounted for — 36 run, 0 NOT-MEASURED". The list
includes `node scripts/check-sdui-manifest.mjs` and its `--self-test`,
`pnpm check:nul-bytes`, `pnpm --dir ui exec vitest run scripts/`.
- `pnpm --dir ui run check:new-line-citations`: "0 new citation(s)".
- `eslint` on the three changed files: 0 errors.
</details>
<!-- signed: domain:ui#2 · os-tesla · session_01L9qibw7DekEe3wx5f3zphf
-->
---------
Co-authored-by: Claude <noreply@anthropic.com>1 parent d463b99 commit cd3a167
4 files changed
Lines changed: 278 additions & 43 deletions
File tree
- .changeset
- ui/packages/app-shell/src/console/marketplace
- __tests__
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
373 | 373 | | |
374 | 374 | | |
375 | 375 | | |
376 | | - | |
377 | | - | |
| 376 | + | |
| 377 | + | |
| 378 | + | |
378 | 379 | | |
379 | 380 | | |
380 | 381 | | |
381 | 382 | | |
382 | | - | |
| 383 | + | |
383 | 384 | | |
384 | 385 | | |
385 | 386 | | |
| |||
403 | 404 | | |
404 | 405 | | |
405 | 406 | | |
| 407 | + | |
| 408 | + | |
| 409 | + | |
| 410 | + | |
| 411 | + | |
| 412 | + | |
| 413 | + | |
406 | 414 | | |
407 | 415 | | |
408 | 416 | | |
| |||
592 | 600 | | |
593 | 601 | | |
594 | 602 | | |
| 603 | + | |
| 604 | + | |
| 605 | + | |
| 606 | + | |
| 607 | + | |
| 608 | + | |
| 609 | + | |
| 610 | + | |
| 611 | + | |
| 612 | + | |
| 613 | + | |
| 614 | + | |
| 615 | + | |
| 616 | + | |
| 617 | + | |
| 618 | + | |
| 619 | + | |
| 620 | + | |
| 621 | + | |
| 622 | + | |
| 623 | + | |
595 | 624 | | |
596 | 625 | | |
| 626 | + | |
| 627 | + | |
| 628 | + | |
597 | 629 | | |
598 | 630 | | |
599 | 631 | | |
| |||
605 | 637 | | |
606 | 638 | | |
607 | 639 | | |
| 640 | + | |
608 | 641 | | |
| 642 | + | |
| 643 | + | |
| 644 | + | |
| 645 | + | |
| 646 | + | |
| 647 | + | |
| 648 | + | |
| 649 | + | |
| 650 | + | |
| 651 | + | |
| 652 | + | |
| 653 | + | |
| 654 | + | |
| 655 | + | |
| 656 | + | |
| 657 | + | |
| 658 | + | |
609 | 659 | | |
610 | 660 | | |
611 | 661 | | |
| |||
628 | 678 | | |
629 | 679 | | |
630 | 680 | | |
631 | | - | |
| 681 | + | |
| 682 | + | |
| 683 | + | |
| 684 | + | |
| 685 | + | |
| 686 | + | |
632 | 687 | | |
633 | 688 | | |
634 | 689 | | |
| |||
648 | 703 | | |
649 | 704 | | |
650 | 705 | | |
651 | | - | |
| 706 | + | |
652 | 707 | | |
653 | 708 | | |
654 | 709 | | |
| |||
748 | 803 | | |
749 | 804 | | |
750 | 805 | | |
751 | | - | |
752 | | - | |
753 | | - | |
754 | | - | |
755 | | - | |
756 | | - | |
757 | | - | |
758 | | - | |
759 | | - | |
760 | | - | |
761 | | - | |
762 | | - | |
763 | | - | |
764 | | - | |
765 | | - | |
766 | | - | |
767 | | - | |
768 | | - | |
769 | | - | |
770 | | - | |
| 806 | + | |
771 | 807 | | |
772 | 808 | | |
773 | 809 | | |
| |||
1372 | 1408 | | |
1373 | 1409 | | |
1374 | 1410 | | |
1375 | | - | |
| 1411 | + | |
| 1412 | + | |
| 1413 | + | |
| 1414 | + | |
1376 | 1415 | | |
1377 | 1416 | | |
1378 | 1417 | | |
| |||
1431 | 1470 | | |
1432 | 1471 | | |
1433 | 1472 | | |
1434 | | - | |
| 1473 | + | |
1435 | 1474 | | |
1436 | 1475 | | |
1437 | 1476 | | |
| |||
1441 | 1480 | | |
1442 | 1481 | | |
1443 | 1482 | | |
1444 | | - | |
| 1483 | + | |
1445 | 1484 | | |
1446 | 1485 | | |
1447 | 1486 | | |
| |||
1459 | 1498 | | |
1460 | 1499 | | |
1461 | 1500 | | |
1462 | | - | |
| 1501 | + | |
1463 | 1502 | | |
1464 | 1503 | | |
1465 | 1504 | | |
| |||
0 commit comments