@sanity/workbench 0.1.0-alpha.2 → 0.1.0-alpha.21

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.
Files changed (52) hide show
  1. package/README.md +24 -0
  2. package/dist/_chunks-es/index.js +39 -0
  3. package/dist/_chunks-es/index.js.map +1 -0
  4. package/dist/_chunks-es/module-federation.js +59 -0
  5. package/dist/_chunks-es/module-federation.js.map +1 -0
  6. package/dist/_chunks-es/studio.js +892 -0
  7. package/dist/_chunks-es/studio.js.map +1 -0
  8. package/dist/_internal.d.ts +16 -4
  9. package/dist/_internal.js +34 -27
  10. package/dist/_internal.js.map +1 -1
  11. package/dist/core.d.ts +2250 -0
  12. package/dist/core.js +74 -0
  13. package/dist/core.js.map +1 -0
  14. package/dist/system.d.ts +2135 -0
  15. package/dist/system.js +887 -0
  16. package/dist/system.js.map +1 -0
  17. package/package.json +34 -6
  18. package/src/_exports/core.ts +1 -0
  19. package/src/_exports/system.ts +1 -0
  20. package/src/_internal/index.ts +2 -1
  21. package/src/_internal/render.ts +72 -43
  22. package/src/core/applications/application-list.ts +104 -0
  23. package/src/core/applications/application.ts +177 -0
  24. package/src/core/applications/interface.ts +126 -0
  25. package/src/core/canvases.ts +92 -0
  26. package/src/core/config.ts +34 -0
  27. package/src/core/env.ts +43 -0
  28. package/src/core/index.ts +13 -0
  29. package/src/core/log/index.ts +125 -0
  30. package/src/core/media-libraries.ts +93 -0
  31. package/src/core/organizations.ts +115 -0
  32. package/src/core/projects.ts +114 -0
  33. package/src/core/shared/urls.ts +129 -0
  34. package/src/core/user-applications/core-app.ts +148 -0
  35. package/src/core/user-applications/studios/index.ts +3 -0
  36. package/src/core/user-applications/studios/schemas.ts +128 -0
  37. package/src/core/user-applications/studios/studio.ts +533 -0
  38. package/src/core/user-applications/studios/workspace.ts +156 -0
  39. package/src/core/user-applications/user-application.ts +222 -0
  40. package/src/system/auth.machine.ts +223 -0
  41. package/src/system/index.ts +22 -0
  42. package/src/system/inspect.ts +40 -0
  43. package/src/system/load-federated-module.ts +53 -0
  44. package/src/system/module-federation.ts +116 -0
  45. package/src/system/remote.machine.ts +219 -0
  46. package/src/system/remotes.machine.ts +92 -0
  47. package/src/system/root.machine.ts +224 -0
  48. package/src/system/service.machine.ts +207 -0
  49. package/src/system/services.machine.ts +120 -0
  50. package/src/system/system-preferences.machine.ts +215 -0
  51. package/src/system/telemetry.machine.ts +179 -0
  52. package/src/_internal/render.test.ts +0 -18
@@ -0,0 +1,222 @@
1
+ // eslint-disable-next-line no-restricted-imports
2
+ import type { Resource as ProtocolResource } from "@sanity/message-protocol";
3
+ import { z } from "zod";
4
+
5
+ import {
6
+ AbstractApplication,
7
+ type AbstractApplicationType,
8
+ type LocalInterface,
9
+ LocalInterfaceSchema,
10
+ } from "../applications/application";
11
+ import { getSanityDomain, getSanityEnv } from "../env";
12
+ import type { CoreAppUserApplicationManifest } from "./core-app";
13
+ import type { ClientManifest, ServerManifest } from "./studios/schemas";
14
+
15
+ /**
16
+ * User application ID schema, branded for type safety.
17
+ * @public
18
+ */
19
+ export const UserApplicationId = z
20
+ .string()
21
+ .nonempty()
22
+ .brand("UserApplicationId");
23
+
24
+ /**
25
+ * User application ID type, branded for type safety.
26
+ * @public
27
+ */
28
+ export type UserApplicationId = z.output<typeof UserApplicationId>;
29
+
30
+ /**
31
+ * Validates and brands a string as a UserApplicationId.
32
+ * @public
33
+ */
34
+ export function brandUserApplicationId(id: string): UserApplicationId {
35
+ return UserApplicationId.parse(id);
36
+ }
37
+
38
+ const LocalUserApplicationBase = z.object({
39
+ host: z.string(),
40
+ port: z.number(),
41
+ /**
42
+ * Interfaces the app exposes — dock panels (`interface_type: "panel"`) and
43
+ * background workers (`interface_type: "worker"`), forwarded from the dev
44
+ * server in one list.
45
+ */
46
+ interfaces: z.array(LocalInterfaceSchema).optional(),
47
+ /** The `deployment.appId` from the application's `sanity.cli.ts`, when set. */
48
+ id: z.string().optional(),
49
+ /**
50
+ * The `api.projectId` from the application's `sanity.cli.ts`. Available
51
+ * synchronously at dev-server startup (no manifest extraction required), so
52
+ * the studio's primary project is resolvable from the very first local
53
+ * application event.
54
+ */
55
+ projectId: z.string().optional(),
56
+ });
57
+
58
+ /**
59
+ * Raw data for a local application discovered by the CLI dev server.
60
+ *
61
+ * The CLI forwards the full studio or app manifest so the workbench can
62
+ * derive icons, titles, workspaces and schema references the same way it
63
+ * does for deployed applications. The manifest shape is discriminated on
64
+ * `type`: studios receive a `ClientManifest`, core apps a `CoreAppUserApplicationManifest`.
65
+ *
66
+ * When set on a `UserApplication`, `isLocal` is `true` and `url`/`isFederated`
67
+ * honour the local dev-server instead of the deployed application's host.
68
+ *
69
+ * @public
70
+ */
71
+ export const LocalUserApplication = z.discriminatedUnion("type", [
72
+ LocalUserApplicationBase.extend({
73
+ type: z.literal("studio"),
74
+ manifest: z.custom<ClientManifest>().optional(),
75
+ }),
76
+ LocalUserApplicationBase.extend({
77
+ type: z.literal("coreApp"),
78
+ manifest: z.custom<CoreAppUserApplicationManifest>().optional(),
79
+ }),
80
+ ]);
81
+
82
+ /**
83
+ * @public
84
+ */
85
+ export type LocalUserApplication = z.output<typeof LocalUserApplication>;
86
+
87
+ /**
88
+ * @public
89
+ */
90
+ export const UserApplicationBase = z.object({
91
+ id: UserApplicationId,
92
+ appHost: z.string(),
93
+ urlType: z.enum(["internal", "external"]),
94
+ createdAt: z.string(),
95
+ updatedAt: z.string(),
96
+ dashboardStatus: z.enum(["default", "disabled"]),
97
+ /**
98
+ * Interfaces the app exposes (dock panels and background workers). Not part
99
+ * of the manifest.
100
+ */
101
+ interfaces: z.array(LocalInterfaceSchema).optional(),
102
+ });
103
+
104
+ /**
105
+ * @public
106
+ */
107
+ export type UserApplicationBase = z.output<typeof UserApplicationBase>;
108
+
109
+ /**
110
+ * @public
111
+ */
112
+ export const ActiveDeployment = z.object({
113
+ id: z.string(),
114
+ version: z.string(),
115
+ isActiveDeployment: z.boolean(),
116
+ userApplicationId: z.string(),
117
+ isAutoUpdating: z.boolean().nullable(),
118
+ size: z.number().nullable(),
119
+ deployedAt: z.string(),
120
+ deployedBy: z.string(),
121
+ createdAt: z.string(),
122
+ updatedAt: z.string(),
123
+ });
124
+
125
+ /**
126
+ * @public
127
+ */
128
+ export abstract class UserApplication<
129
+ TUserApplication extends UserApplicationBase,
130
+ TType extends Extract<AbstractApplicationType, "coreApp" | "studio">,
131
+ TProtocolResource extends ProtocolResource = Extract<
132
+ ProtocolResource,
133
+ { type: TType }
134
+ >,
135
+ > extends AbstractApplication<TType, TProtocolResource> {
136
+ readonly application: TUserApplication;
137
+
138
+ readonly id: UserApplicationId;
139
+
140
+ /**
141
+ * For local applications (`isLocal === true`), the deployed application
142
+ * that shares the same `id` — if one was passed at construction. `null`
143
+ * for deployed applications or when no remote twin was provided.
144
+ */
145
+ readonly remoteApplication: this | null;
146
+
147
+ readonly #isLocal: boolean;
148
+
149
+ constructor(
150
+ application: TUserApplication,
151
+ type: TType,
152
+ options: {
153
+ isLocal?: boolean;
154
+ remoteApplication?: UserApplication<
155
+ TUserApplication,
156
+ TType,
157
+ TProtocolResource
158
+ > | null;
159
+ } = {},
160
+ ) {
161
+ super(type);
162
+ this.application = application;
163
+ this.id = brandUserApplicationId(application.id);
164
+ this.#isLocal = options.isLocal ?? false;
165
+ this.remoteApplication =
166
+ (options.remoteApplication as this | undefined) ?? null;
167
+ }
168
+
169
+ abstract get subtitle(): string | undefined;
170
+
171
+ /**
172
+ * Local dev servers are rendered as federated remotes; deployed applications
173
+ * are rendered in an iframe.
174
+ */
175
+ get isFederated(): boolean {
176
+ return this.isLocal;
177
+ }
178
+
179
+ override get isLocal(): boolean {
180
+ return this.#isLocal;
181
+ }
182
+
183
+ override get interfaces(): readonly LocalInterface[] {
184
+ return this.application.interfaces ?? [];
185
+ }
186
+
187
+ /**
188
+ * @returns A fully resolved URL instance for the application.
189
+ * For internal applications, constructs a URL using the Sanity studio domain
190
+ * pattern resolved from the environment at the consuming app's build time.
191
+ * For external applications (including local dev servers), returns the
192
+ * provided app host as a URL.
193
+ */
194
+ get url(): URL {
195
+ if (this.application.urlType === "internal") {
196
+ if (getSanityEnv() === "production") {
197
+ return new URL(`https://${this.application.appHost}.sanity.studio`);
198
+ }
199
+ return new URL(
200
+ `https://${this.application.appHost}.studio.${getSanityDomain()}`,
201
+ );
202
+ }
203
+
204
+ return new URL(this.application.appHost);
205
+ }
206
+
207
+ /**
208
+ * Unified manifest accessor for user applications. Resolves the most
209
+ * authoritative manifest available for the application, preferring the
210
+ * deployment manifest (`activeDeployment.manifest`) before falling back to
211
+ * any client-side manifest. The returned shape depends on the application
212
+ * type and source: studios may yield either a `ServerManifest` (deployment)
213
+ * or a `ClientManifest` (fallback); core apps yield a
214
+ * `CoreAppUserApplicationManifest`. Returns `null` when no manifest is
215
+ * available.
216
+ */
217
+ abstract get manifest():
218
+ | ClientManifest
219
+ | ServerManifest
220
+ | CoreAppUserApplicationManifest
221
+ | null;
222
+ }
@@ -0,0 +1,223 @@
1
+ import {
2
+ type AuthState,
3
+ AuthStateType,
4
+ type CurrentUser,
5
+ getAuthState,
6
+ type LoggedInAuthState,
7
+ logout,
8
+ type SanityInstance,
9
+ } from "@sanity/sdk";
10
+ import { assign, fromObservable, fromPromise, setup } from "xstate";
11
+
12
+ import type { OSBaseInput } from "./root.machine";
13
+
14
+ /**
15
+ * @internal
16
+ */
17
+ export interface AuthInput extends OSBaseInput {}
18
+
19
+ const authStateLogic = fromObservable<AuthState, AuthInput>(
20
+ ({ input }) => getAuthState(input.instance).observable,
21
+ );
22
+
23
+ /**
24
+ * @internal
25
+ */
26
+ export interface LogoutInput extends OSBaseInput {}
27
+
28
+ const logoutActorLogic = fromPromise<void, LogoutInput>(async ({ input }) => {
29
+ await logout(input.instance);
30
+ });
31
+
32
+ type AuthContext = {
33
+ instance: SanityInstance;
34
+ token: string | null;
35
+ currentUser: CurrentUser | null;
36
+ error: unknown;
37
+ };
38
+
39
+ type AuthEvent = { type: "auth.logout" };
40
+
41
+ export const authLogic = setup({
42
+ types: {
43
+ input: {} as AuthInput,
44
+ context: {} as AuthContext,
45
+ events: {} as AuthEvent,
46
+ tags: {} as "authenticating" | "authenticated" | "error",
47
+ },
48
+ actors: {
49
+ authState: authStateLogic,
50
+ logoutActor: logoutActorLogic,
51
+ },
52
+ delays: {
53
+ authTimeout: 30_000,
54
+ },
55
+ guards: {
56
+ isLoggedInComplete: (_, params: { state: AuthState | undefined }) =>
57
+ params.state?.type === AuthStateType.LOGGED_IN &&
58
+ Boolean((params.state as LoggedInAuthState).token) &&
59
+ (params.state as LoggedInAuthState).currentUser !== null,
60
+ isAuthState: (
61
+ _,
62
+ params: { state: AuthState | undefined; type: AuthStateType },
63
+ ) => params.state?.type === params.type,
64
+ },
65
+ actions: {
66
+ setLoggedIn: assign({
67
+ token: (_, params: { token: string; currentUser: CurrentUser }) =>
68
+ params.token,
69
+ currentUser: (_, params: { token: string; currentUser: CurrentUser }) =>
70
+ params.currentUser,
71
+ error: () => null,
72
+ }),
73
+ clearAuth: assign({
74
+ token: () => null,
75
+ currentUser: () => null,
76
+ error: () => null,
77
+ }),
78
+ setError: assign({
79
+ token: () => null,
80
+ currentUser: () => null,
81
+ error: (_, params: { error: unknown }) => params.error,
82
+ }),
83
+ },
84
+ }).createMachine({
85
+ id: "auth",
86
+ initial: "init",
87
+ context: ({ input }) => ({
88
+ instance: input.instance,
89
+ token: null,
90
+ currentUser: null,
91
+ error: null,
92
+ }),
93
+ invoke: {
94
+ src: "authState",
95
+ input: ({ context }) => ({ instance: context.instance }),
96
+ onSnapshot: [
97
+ {
98
+ guard: {
99
+ type: "isLoggedInComplete",
100
+ params: ({ event }) => ({
101
+ state: event.snapshot.context,
102
+ }),
103
+ },
104
+ actions: [
105
+ {
106
+ type: "setLoggedIn",
107
+ params: ({ event }) => {
108
+ const state = event.snapshot.context as LoggedInAuthState;
109
+ return {
110
+ token: state.token,
111
+ currentUser: state.currentUser!,
112
+ };
113
+ },
114
+ },
115
+ ],
116
+ target: `.${AuthStateType.LOGGED_IN}`,
117
+ },
118
+ {
119
+ guard: {
120
+ type: "isAuthState",
121
+ params: ({ event }) => ({
122
+ state: event.snapshot.context,
123
+ type: AuthStateType.LOGGING_IN,
124
+ }),
125
+ },
126
+ actions: [{ type: "clearAuth" }],
127
+ target: `.${AuthStateType.LOGGING_IN}`,
128
+ },
129
+ {
130
+ guard: {
131
+ type: "isAuthState",
132
+ params: ({ event }) => ({
133
+ state: event.snapshot.context,
134
+ type: AuthStateType.ERROR,
135
+ }),
136
+ },
137
+ actions: [
138
+ {
139
+ type: "setError",
140
+ params: ({ event }) => ({
141
+ error:
142
+ event.snapshot.context?.type === AuthStateType.ERROR
143
+ ? event.snapshot.context.error
144
+ : null,
145
+ }),
146
+ },
147
+ ],
148
+ target: `.${AuthStateType.ERROR}`,
149
+ },
150
+ {
151
+ guard: {
152
+ type: "isAuthState",
153
+ params: ({ event }) => ({
154
+ state: event.snapshot.context,
155
+ type: AuthStateType.LOGGED_OUT,
156
+ }),
157
+ },
158
+ actions: [{ type: "clearAuth" }],
159
+ target: `.${AuthStateType.LOGGED_OUT}`,
160
+ },
161
+ ],
162
+ },
163
+ states: {
164
+ init: {
165
+ tags: ["authenticating"],
166
+ after: {
167
+ authTimeout: {
168
+ actions: [
169
+ {
170
+ type: "setError",
171
+ params: () => ({
172
+ error: new Error("Authentication timed out"),
173
+ }),
174
+ },
175
+ ],
176
+ target: AuthStateType.ERROR,
177
+ },
178
+ },
179
+ },
180
+ [AuthStateType.LOGGING_IN]: {
181
+ tags: ["authenticating"],
182
+ after: {
183
+ authTimeout: {
184
+ actions: [
185
+ {
186
+ type: "setError",
187
+ params: () => ({
188
+ error: new Error("Authentication timed out"),
189
+ }),
190
+ },
191
+ ],
192
+ target: AuthStateType.ERROR,
193
+ },
194
+ },
195
+ },
196
+ [AuthStateType.LOGGED_IN]: {
197
+ tags: ["authenticated"],
198
+ on: {
199
+ "auth.logout": { target: "logging-out" },
200
+ },
201
+ },
202
+ ["logging-out"]: {
203
+ invoke: {
204
+ src: "logoutActor",
205
+ input: ({ context }) => ({ instance: context.instance }),
206
+ onDone: {
207
+ target: AuthStateType.LOGGED_OUT,
208
+ },
209
+ onError: {
210
+ actions: [
211
+ {
212
+ type: "setError",
213
+ params: ({ event }) => ({ error: event.error }),
214
+ },
215
+ ],
216
+ target: AuthStateType.ERROR,
217
+ },
218
+ },
219
+ },
220
+ [AuthStateType.LOGGED_OUT]: {},
221
+ [AuthStateType.ERROR]: { tags: ["error"] },
222
+ },
223
+ });
@@ -0,0 +1,22 @@
1
+ export type { AuthInput, LogoutInput } from "./auth.machine";
2
+ export { ModuleShapeError } from "./remote.machine";
3
+ export type {
4
+ LoadedRemoteModule,
5
+ RemoteError,
6
+ RemoteInput,
7
+ RemoteModuleByInterfaceType,
8
+ RemoteRenderOptions,
9
+ ServiceLoaderModule,
10
+ } from "./remote.machine";
11
+ export type {
12
+ SystemPreferencesAdapter,
13
+ SystemPreferencesContext,
14
+ SystemPreferencesEvent,
15
+ } from "./system-preferences.machine";
16
+ export { loadFederatedModule } from "./load-federated-module";
17
+ export type { FederatedModuleRef } from "./load-federated-module";
18
+ export { os, createOSOptions } from "./root.machine";
19
+ export type {
20
+ TelemetryInput,
21
+ WorkbenchUserProperties,
22
+ } from "./telemetry.machine";
@@ -0,0 +1,40 @@
1
+ import { type ActorRefLike, type InspectionEvent } from "xstate";
2
+
3
+ import { logger } from "../core";
4
+
5
+ // Mirrors @statelyai/inspect's ActorRefLikeWithData — the inspect
6
+ // callback receives full Actor instances at runtime, but the type is
7
+ // narrowed for @xstate/store compat.
8
+ type ActorRefWithAncestry = ActorRefLike & {
9
+ id?: string;
10
+ _parent?: ActorRefWithAncestry;
11
+ };
12
+
13
+ const actorPath = (ref: ActorRefWithAncestry): string => {
14
+ const segments: string[] = [];
15
+ let current: ActorRefWithAncestry | undefined = ref;
16
+ while (current) {
17
+ segments.unshift(current.id ?? current.sessionId);
18
+ current = current._parent;
19
+ }
20
+ return segments.join(":");
21
+ };
22
+
23
+ /** @internal exported for testing */
24
+ export const inspect = (event: InspectionEvent): void => {
25
+ const ref = event.actorRef as ActorRefWithAncestry;
26
+ const log = logger.child(actorPath(ref));
27
+
28
+ switch (event.type) {
29
+ case "@xstate.snapshot": {
30
+ log.debug("snapshot", event.snapshot);
31
+ break;
32
+ }
33
+ case "@xstate.event":
34
+ log.debug("event", event.event);
35
+ break;
36
+ case "@xstate.action":
37
+ log.debug("action", event.action);
38
+ break;
39
+ }
40
+ };
@@ -0,0 +1,53 @@
1
+ import { logger } from "../core/log";
2
+ import type { FederationInstance } from "./module-federation";
3
+
4
+ /**
5
+ * A module exposed by an application's federation remote.
6
+ *
7
+ * @public
8
+ */
9
+ export interface FederatedModuleRef {
10
+ /**
11
+ * Fully-qualified module id — `${appId}/${expose}`, e.g.
12
+ * `studio-1/views/feed/panel` (a view component) or `studio-1/App` (the
13
+ * full-page view). The remote's name is its first segment.
14
+ */
15
+ moduleId: string;
16
+ /** The remote's `mf-manifest.json` URL (served from the app's origin). */
17
+ entry: string;
18
+ }
19
+
20
+ /**
21
+ * Register an app's federation remote (idempotent) and load one of its exposes
22
+ * by its fully-qualified `moduleId`. Failures propagate, so the caller drives
23
+ * its own error handling (e.g. a machine's error state with the original
24
+ * cause). For the render-empty-on-failure policy, use {@link loadFederatedModule}.
25
+ */
26
+ export async function loadRemoteModule<T>(
27
+ instance: FederationInstance,
28
+ { moduleId, entry }: FederatedModuleRef,
29
+ ): Promise<T | null> {
30
+ // The remote name is the first segment; the expose is the remainder.
31
+ const name = moduleId.slice(0, moduleId.indexOf("/"));
32
+ instance.registerRemotes([{ name, entry }]);
33
+ return (await instance.loadRemote<T | null>(moduleId)) ?? null;
34
+ }
35
+
36
+ /**
37
+ * {@link loadRemoteModule} with the render-empty-on-failure policy: any failure
38
+ * resolves to `null` (and logs), so every caller handles a missing interface
39
+ * the same way — a panel renders empty, a worker doesn't spawn.
40
+ *
41
+ * @public
42
+ */
43
+ export async function loadFederatedModule<T>(
44
+ instance: FederationInstance,
45
+ ref: FederatedModuleRef,
46
+ ): Promise<T | null> {
47
+ try {
48
+ return await loadRemoteModule<T>(instance, ref);
49
+ } catch (error) {
50
+ logger.error(`Failed to load federated module "${ref.moduleId}"`, error);
51
+ return null;
52
+ }
53
+ }
@@ -0,0 +1,116 @@
1
+ import { createInstance as createMFInstance } from "@module-federation/runtime";
2
+ import type {
3
+ ModuleFederationRuntimePlugin,
4
+ UserOptions,
5
+ } from "@module-federation/runtime/types";
6
+
7
+ /** Make some properties of a type optional. */
8
+ type MakeOptional<T, K extends keyof T> = Omit<T, K> & Partial<Pick<T, K>>;
9
+
10
+ /**
11
+ * @public
12
+ */
13
+ export type FederationRemote = {
14
+ name: string;
15
+ entry: string;
16
+ };
17
+
18
+ /**
19
+ * @public
20
+ */
21
+ export type CreateInstanceOptions = MakeOptional<
22
+ Pick<UserOptions, "name" | "remotes" | "shared" | "plugins">,
23
+ "remotes" | "plugins"
24
+ >;
25
+
26
+ /**
27
+ * @public
28
+ */
29
+ export interface FederationInstance {
30
+ registerRemotes(remotes: FederationRemote[]): void;
31
+ loadRemote<T>(id: string): Promise<T | null>;
32
+ }
33
+
34
+ /**
35
+ * @public
36
+ */
37
+ export function createInstance(
38
+ options: CreateInstanceOptions,
39
+ ): FederationInstance {
40
+ const instance = createMFInstance({
41
+ remotes: [],
42
+ ...options,
43
+ });
44
+
45
+ return {
46
+ registerRemotes: (remotes) =>
47
+ instance.registerRemotes(remotes, { force: false }),
48
+ loadRemote: async <T>(id: string) => {
49
+ let remoteModule: T | null;
50
+
51
+ try {
52
+ remoteModule = await instance.loadRemote<T>(id);
53
+ } catch (error) {
54
+ throw new Error(`Failed to load remote module "${id}"`, {
55
+ cause: error,
56
+ });
57
+ }
58
+
59
+ return remoteModule;
60
+ },
61
+ };
62
+ }
63
+
64
+ /**
65
+ * Forward Module Federation lifecycle events to a provided logging function
66
+ * from workbench.
67
+ *
68
+ * @public
69
+ */
70
+ export function log(
71
+ onDebug: (message: string, context?: Record<string, unknown>) => void,
72
+ ): ModuleFederationRuntimePlugin {
73
+ const logEvent = (eventName: string, args: unknown) => {
74
+ // `origin` logs the whole instance, which is noisy
75
+ const { origin: _origin, ...data } =
76
+ typeof args === "object" && args !== null
77
+ ? (args as Record<string, unknown>)
78
+ : { args };
79
+
80
+ onDebug(`[Lifecycle] ${eventName}`, data);
81
+ };
82
+
83
+ return {
84
+ name: "sanity-logger",
85
+ beforeInit(args) {
86
+ logEvent("beforeInit", args);
87
+ return args;
88
+ },
89
+ beforeRegisterRemote(args) {
90
+ logEvent("beforeRegisterRemote", args);
91
+ return args;
92
+ },
93
+ beforePreloadRemote(args) {
94
+ logEvent("beforePreloadRemote", args);
95
+ },
96
+ beforeRequest(args) {
97
+ logEvent("beforeRequest", args);
98
+ return args;
99
+ },
100
+ afterResolve(args) {
101
+ logEvent("afterResolve", args);
102
+ return args;
103
+ },
104
+ onLoad(args) {
105
+ logEvent("onLoad", args);
106
+ return args;
107
+ },
108
+ loadShare(args) {
109
+ logEvent("loadShare", args);
110
+ },
111
+ beforeLoadShare(args) {
112
+ logEvent("beforeLoadShare", args);
113
+ return args;
114
+ },
115
+ };
116
+ }