Skip to content

Commit 42c6a11

Browse files
hotlongCopilot
andcommitted
fix(marketplace): install_package action returns 'sys_package not registered'
When clicking 'Install into Environment' from the Marketplace package detail page in cloud_control, users saw: {error: "Object 'sys_package' is not registered", code: 'object_not_found'} Root cause: @object-ui app-shell 4.7's RecordDetailView apiHandler ignores action.target for unknown action names and falls back to dataSource.update(objectName, recordId, params) which tries to PATCH sys_package with non-existent fields (environment_id, seed_sample_data). The misleading 'not registered' message comes from the project-scoped data route, where sys_package isn't visible. Same upstream bug we already worked around for sys_environment's install_application: change action type to 'script' so RecordDetailView routes through serverActionHandler → POST /api/v1/actions/{object}/{action}, then register a server-side handler. - sys_package.install_package: type 'api' → 'script', target rewritten to bare 'install_package' (the action name, not a URL). - service-cloud project-lifecycle: register POST /actions/sys_package/:actionName dispatcher that pulls packageId from body.recordId and environment_id/seed_sample_data from body.params, then forwards to the existing installPackageIntoEnvironment helper. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent d602851 commit 42c6a11

2 files changed

Lines changed: 59 additions & 4 deletions

File tree

packages/services/service-cloud/src/routes/project-lifecycle.ts

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -615,6 +615,55 @@ export function registerProjectLifecycleRoutes(server: IHttpServer, deps: Packag
615615
server.post(`${prefix}/actions/sys_environment/:actionName`, actionHandler);
616616
server.post(`${prefix}/actions/sys_project/:actionName`, actionHandler); // legacy alias
617617

618+
// ── sys_package action dispatcher ──────────────────────────────────────
619+
// The Marketplace "Install into Environment" action on sys_package is
620+
// declared as `type: 'script'` (not 'api') because @object-ui's
621+
// RecordDetailView.apiHandler ignores action.target for unknown action
622+
// names and falls back to `dataSource.update(object, id, params)`, which
623+
// tries to PATCH the sys_package row with non-existent fields
624+
// (environment_id, seed_sample_data) and surfaces a misleading
625+
// `Object 'sys_package' is not registered` error. Routing through
626+
// `serverActionHandler` instead gives us this dedicated endpoint.
627+
//
628+
// Body shape from app-shell: `{ recordId, params: { environment_id, seed_sample_data } }`
629+
server.post(`${prefix}/actions/sys_package/:actionName`, async (req: any, res: any) => {
630+
const auth = await checkAuth(req); if (!auth.ok) return res.status(auth.status).json(auth.body);
631+
const actionName = String(req.params?.actionName ?? '').trim();
632+
if (actionName !== 'install_package') {
633+
return res.status(404).json(fail(`Unknown sys_package action '${actionName}'`, 404));
634+
}
635+
const body = (req.body ?? {}) as AnyRow;
636+
const params = (body.params && typeof body.params === 'object') ? (body.params as AnyRow) : {};
637+
const rowRecord = (params._rowRecord && typeof params._rowRecord === 'object')
638+
? (params._rowRecord as AnyRow) : null;
639+
const packageId = String(
640+
body.recordId
641+
?? body.record_id
642+
?? params.recordId
643+
?? params.record_id
644+
?? rowRecord?.id
645+
?? ''
646+
).trim();
647+
if (!packageId) return res.status(400).json(fail('recordId (package_id) is required', 400));
648+
const environmentId = String(params.environment_id ?? params.environmentId ?? '').trim();
649+
if (!environmentId) return res.status(400).json(fail('environment_id is required', 400));
650+
const seedSampleData = params.seed_sample_data === true
651+
|| params.seed_sample_data === 'true'
652+
|| params.seedSampleData === true
653+
|| params.seedSampleData === 'true';
654+
const callerUserId = (await resolveActorId(req)) ?? null;
655+
const callerActiveOrgId = (await resolveActiveOrgId(req)) ?? null;
656+
const result = await installPackageIntoEnvironment({
657+
deps,
658+
packageId,
659+
environmentId,
660+
seedSampleData,
661+
callerUserId,
662+
callerActiveOrgId,
663+
});
664+
return res.status(result.status).json(result.body);
665+
});
666+
618667
// Reference the randomUUID import (silences unused warnings) — used by
619668
// adapter fallbacks elsewhere in the file in the future.
620669
void randomUUID;

packages/services/service-tenant/src/objects/sys-package.object.ts

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -186,11 +186,17 @@ export const SysPackage = ObjectSchema.create({
186186
label: 'Install into Environment',
187187
icon: 'download-cloud',
188188
variant: 'primary',
189-
type: 'api',
189+
// MUST be 'script' (not 'api'): app-shell's RecordDetailView apiHandler
190+
// ignores `action.target` for unknown action names and falls back to
191+
// `dataSource.update(object, id, params)` — which tries to PATCH the
192+
// sys_package row with non-existent fields (environment_id, etc.) and
193+
// surfaces as `Object 'sys_package' is not registered` on the
194+
// project-scoped data route. 'script' routes through serverActionHandler
195+
// → POST /api/v1/actions/sys_package/install_package, which we handle
196+
// server-side in service-cloud's project-lifecycle routes.
197+
type: 'script',
190198
locations: ['list_item', 'record_header'],
191-
target: '/api/v1/cloud/packages/{id}/install',
192-
method: 'POST',
193-
recordIdParam: 'id',
199+
target: 'install_package',
194200
successMessage: 'Package installed. Open your environment to see it.',
195201
refreshAfter: true,
196202
params: [

0 commit comments

Comments
 (0)