Skip to content

Commit 15fc453

Browse files
os-zhuangclaude
andcommitted
fix(apps): app plugins depend on AuthPlugin by plugin name, not package id (ADR-0048)
Live `os dev` boot surfaced: `Dependency 'com.objectstack.plugin-auth' not found for plugin 'com.objectstack.setup'`. The kernel matches plugin `dependencies` by plugin NAME — AuthPlugin's name is `com.objectstack.auth`, while its package id is `com.objectstack.plugin-auth`. Point the class-level dependency at the plugin name (`com.objectstack.auth`); the manifest-level dependency stays the package id. Adds packages/cli/src/adr-0048-app-split.test.ts: boots a real ObjectQL engine, runs each app plugin's start(), asserts setup/studio/account register under their own package ids and coexist. VERIFIED end-to-end: `os dev` (app-showcase) boots clean and /api/v1/meta/app reports setup→com.objectstack.setup, studio→com.objectstack.studio, account→com.objectstack.account (previously all com.objectstack.plugin-auth). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent 756189a commit 15fc453

4 files changed

Lines changed: 87 additions & 3 deletions

File tree

packages/apps/account/src/index.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,9 @@ export class AccountAppPlugin {
4545
readonly name = ACCOUNT_APP_PACKAGE_ID;
4646
readonly type = 'standard';
4747
readonly version = ACCOUNT_APP_VERSION;
48-
readonly dependencies: string[] = ['com.objectstack.plugin-auth'];
48+
// Kernel plugin dependency is matched by plugin NAME (AuthPlugin.name =
49+
// 'com.objectstack.auth'), not by package id.
50+
readonly dependencies: string[] = ['com.objectstack.auth'];
4951

5052
async init(_ctx: any): Promise<void> {
5153
// No-op: registration happens in start() once the manifest service exists.

packages/apps/setup/src/index.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,9 @@ export class SetupAppPlugin {
4141
readonly name = SETUP_APP_PACKAGE_ID;
4242
readonly type = 'standard';
4343
readonly version = SETUP_APP_VERSION;
44-
readonly dependencies: string[] = ['com.objectstack.plugin-auth'];
44+
// Kernel plugin dependency is matched by plugin NAME (AuthPlugin.name =
45+
// 'com.objectstack.auth'), not by package id.
46+
readonly dependencies: string[] = ['com.objectstack.auth'];
4547

4648
async init(_ctx: any): Promise<void> {
4749
// No-op: registration happens in start() once the manifest service exists.

packages/apps/studio/src/index.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,9 @@ export class StudioAppPlugin {
4848
readonly name = STUDIO_APP_PACKAGE_ID;
4949
readonly type = 'standard';
5050
readonly version = STUDIO_APP_VERSION;
51-
readonly dependencies: string[] = ['com.objectstack.plugin-auth'];
51+
// Kernel plugin dependency is matched by plugin NAME (AuthPlugin.name =
52+
// 'com.objectstack.auth'), not by package id.
53+
readonly dependencies: string[] = ['com.objectstack.auth'];
5254

5355
async init(_ctx: any): Promise<void> {
5456
// No-op: registration happens in start() once the manifest service exists.
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.
2+
3+
/**
4+
* ADR-0048 — end-to-end verification that the platform apps now live in their
5+
* own one-app packages and register under DISTINCT package ids.
6+
*
7+
* Boots a real ObjectQL engine, runs each app package's plugin `start()` against
8+
* a manifest service backed by `engine.registerApp` (exactly what the kernel
9+
* wires — see objectql plugin.ts: `register(m) => ql.registerApp(m)`), and
10+
* asserts each app resolves under `com.objectstack.{studio,setup,account}` and
11+
* that all three coexist (the multi-app-package ambiguity is gone).
12+
*/
13+
14+
import { describe, it, expect, beforeEach } from 'vitest';
15+
import { ObjectQL } from '@objectstack/objectql';
16+
import { createStudioAppPlugin } from '@objectstack/studio';
17+
import { createSetupAppPlugin } from '@objectstack/setup';
18+
import { createAccountAppPlugin } from '@objectstack/account';
19+
20+
function makeCtx(engine: ObjectQL) {
21+
return {
22+
getService: (name: string) =>
23+
name === 'manifest' ? { register: (m: any) => engine.registerApp(m) } : undefined,
24+
registerService: () => {},
25+
getServices: () => new Map(),
26+
logger: { info: () => {}, warn: () => {}, error: () => {}, debug: () => {} },
27+
hook: () => {},
28+
} as any;
29+
}
30+
31+
describe('ADR-0048 — platform apps as one-app packages', () => {
32+
let engine: ObjectQL;
33+
34+
beforeEach(async () => {
35+
engine = new ObjectQL();
36+
engine.registry.logLevel = 'silent';
37+
const ctx = makeCtx(engine);
38+
for (const plugin of [createSetupAppPlugin(), createStudioAppPlugin(), createAccountAppPlugin()]) {
39+
await plugin.init?.(ctx);
40+
await plugin.start(ctx);
41+
}
42+
});
43+
44+
it('registers each app under its OWN package id', () => {
45+
expect(engine.registry.getItem<any>('app', 'studio', 'com.objectstack.studio')?._packageId).toBe(
46+
'com.objectstack.studio',
47+
);
48+
expect(engine.registry.getItem<any>('app', 'setup', 'com.objectstack.setup')?._packageId).toBe(
49+
'com.objectstack.setup',
50+
);
51+
expect(engine.registry.getItem<any>('app', 'account', 'com.objectstack.account')?._packageId).toBe(
52+
'com.objectstack.account',
53+
);
54+
});
55+
56+
it('all three apps coexist and resolve by name (getApp)', () => {
57+
expect(engine.registry.getApp('studio')?.name).toBe('studio');
58+
expect(engine.registry.getApp('setup')?.name).toBe('setup');
59+
expect(engine.registry.getApp('account')?.name).toBe('account');
60+
});
61+
62+
it('each app package owns a distinct namespace (no install-gate conflict)', () => {
63+
expect(engine.registry.getNamespaceOwners('studio')).toEqual(['com.objectstack.studio']);
64+
expect(engine.registry.getNamespaceOwners('setup')).toEqual(['com.objectstack.setup']);
65+
expect(engine.registry.getNamespaceOwners('account')).toEqual(['com.objectstack.account']);
66+
});
67+
68+
it('records all three as installed packages', () => {
69+
const ids = engine.registry.getAllPackages().map((p: any) => p.manifest.id);
70+
expect(ids).toEqual(
71+
expect.arrayContaining([
72+
'com.objectstack.studio',
73+
'com.objectstack.setup',
74+
'com.objectstack.account',
75+
]),
76+
);
77+
});
78+
});

0 commit comments

Comments
 (0)