@codemation/host 0.0.15 → 0.0.16
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.
- package/dist/{AppConfigFactory-DmHOpg8O.d.ts → AppConfigFactory-D_ReOKfV.d.ts} +5 -3
- package/dist/{AppConfigFactory-DWIz2hy-.js → AppConfigFactory-JjNqYnGg.js} +41 -3
- package/dist/AppConfigFactory-JjNqYnGg.js.map +1 -0
- package/dist/{AppContainerFactory-B5eRpvAa.js → AppContainerFactory-_dnF0VOf.js} +272 -105
- package/dist/AppContainerFactory-_dnF0VOf.js.map +1 -0
- package/dist/{CodemationConfig-D2ULNkec.d.ts → CodemationConfig-u80ZoIrc.d.ts} +18 -10
- package/dist/{CodemationConfigNormalizer-CBLxXaRV.d.ts → CodemationConfigNormalizer-DCt0mPPz.d.ts} +2 -2
- package/dist/{CodemationConsumerConfigLoader-BLvzcfb7.d.ts → CodemationConsumerConfigLoader-C8iscLBJ.d.ts} +4 -2
- package/dist/{CodemationConsumerConfigLoader-BBzAr6L_.js → CodemationConsumerConfigLoader-LGrCspIx.js} +18 -4
- package/dist/CodemationConsumerConfigLoader-LGrCspIx.js.map +1 -0
- package/dist/CodemationPluginListMerger-CGwOTdZ7.js +57 -0
- package/dist/CodemationPluginListMerger-CGwOTdZ7.js.map +1 -0
- package/dist/{CodemationPluginListMerger-B0-e4CJ6.d.ts → CodemationPluginListMerger-ZY3R_kTo.d.ts} +79 -117
- package/dist/{CredentialServices-BeuMtqYA.d.ts → CredentialServices-Uo1jxSYB.d.ts} +2 -2
- package/dist/PublicFrontendBootstrap-p7mS8aWG.d.ts +50 -0
- package/dist/{FrontendAppConfigFactory-Bj-DZNlt.d.ts → PublicFrontendBootstrapFactory-BA65IXL4.d.ts} +19 -4
- package/dist/{FrontendAppConfigJsonCodec-nOCQI0ag.js → PublicFrontendBootstrapJsonCodec-BdiVGG5R.js} +76 -2
- package/dist/PublicFrontendBootstrapJsonCodec-BdiVGG5R.js.map +1 -0
- package/dist/PublicFrontendBootstrapJsonCodec-DjkkKXcq.d.ts +35 -0
- package/dist/client.d.ts +3 -3
- package/dist/client.js +2 -2
- package/dist/consumer.d.ts +3 -3
- package/dist/consumer.js +1 -1
- package/dist/credentials.d.ts +2 -2
- package/dist/index.d.ts +44 -12
- package/dist/index.js +8 -8
- package/dist/nextServer.d.ts +30 -9
- package/dist/nextServer.js +4 -4
- package/dist/{persistenceServer-CaehMh3M.d.ts → persistenceServer-BKbOs-TQ.d.ts} +2 -2
- package/dist/{persistenceServer-DVeWUbc3.js → persistenceServer-J2teHIIg.js} +2 -2
- package/dist/{persistenceServer-DVeWUbc3.js.map → persistenceServer-J2teHIIg.js.map} +1 -1
- package/dist/persistenceServer.d.ts +4 -4
- package/dist/persistenceServer.js +2 -2
- package/dist/{server-C65z_kqm.js → server-CVC7QBwT.js} +42 -25
- package/dist/server-CVC7QBwT.js.map +1 -0
- package/dist/{server-Dyo8qh4k.d.ts → server-CbcnGZHm.d.ts} +14 -19
- package/dist/server.d.ts +10 -10
- package/dist/server.js +7 -7
- package/package.json +5 -5
- package/src/application/dev/BootRuntimeSummary.types.ts +2 -0
- package/src/application/dev/DevBootstrapSummaryAssembler.ts +1 -0
- package/src/application/dev/DevBootstrapSummaryJson.types.ts +6 -0
- package/src/bootstrap/AppContainerFactory.ts +101 -6
- package/src/bootstrap/runtime/AppConfigFactory.ts +22 -2
- package/src/client.ts +4 -0
- package/src/index.ts +13 -5
- package/src/infrastructure/config/CodemationPluginRegistrar.ts +6 -0
- package/src/infrastructure/di/HandlesCommandRegistry.ts +1 -10
- package/src/infrastructure/di/HandlesQueryRegistry.ts +1 -10
- package/src/nextServer.ts +4 -0
- package/src/presentation/config/AppConfig.ts +6 -0
- package/src/presentation/config/CodemationPackageManifest.ts +1 -7
- package/src/presentation/config/CodemationPlugin.ts +45 -9
- package/src/presentation/config/CodemationPluginListMerger.ts +30 -26
- package/src/presentation/frontend/InternalAuthBootstrap.ts +12 -0
- package/src/presentation/frontend/InternalAuthBootstrapFactory.ts +26 -0
- package/src/presentation/frontend/InternalAuthBootstrapJsonCodec.ts +49 -0
- package/src/presentation/frontend/PublicFrontendBootstrap.ts +12 -0
- package/src/presentation/frontend/PublicFrontendBootstrapFactory.ts +23 -0
- package/src/presentation/frontend/PublicFrontendBootstrapJsonCodec.ts +48 -0
- package/src/presentation/http/ApiPaths.ts +10 -0
- package/src/presentation/http/hono/HonoHttpAnonymousRoutePolicyRegistry.ts +3 -0
- package/src/presentation/http/hono/registrars/BootstrapHonoApiRouteRegistrar.ts +32 -0
- package/src/presentation/http/routeHandlers/InternalAuthBootstrapHttpRouteHandler.ts +18 -0
- package/src/presentation/http/routeHandlers/PublicFrontendBootstrapHttpRouteHandler.ts +18 -0
- package/src/presentation/server/CodemationConsumerConfigLoader.ts +16 -3
- package/src/presentation/server/CodemationPluginDiscovery.ts +59 -28
- package/src/server.ts +6 -0
- package/dist/AppConfigFactory-DWIz2hy-.js.map +0 -1
- package/dist/AppContainerFactory-B5eRpvAa.js.map +0 -1
- package/dist/CodemationConsumerConfigLoader-BBzAr6L_.js.map +0 -1
- package/dist/CodemationPluginListMerger-DrVOw9KP.js +0 -57
- package/dist/CodemationPluginListMerger-DrVOw9KP.js.map +0 -1
- package/dist/FrontendAppConfig-D50wjj_n.d.ts +0 -27
- package/dist/FrontendAppConfigJsonCodec-1_L7H_Qo.d.ts +0 -20
- package/dist/FrontendAppConfigJsonCodec-nOCQI0ag.js.map +0 -1
- package/dist/server-C65z_kqm.js.map +0 -1
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
import type { Container } from "@codemation/core";
|
|
1
|
+
import type { AnyCredentialType, Container } from "@codemation/core";
|
|
2
2
|
import type { LoggerFactory } from "../../application/logging/Logger";
|
|
3
3
|
import type { AppConfig } from "./AppConfig";
|
|
4
4
|
import type { CodemationRegistrationContextBase } from "./CodemationAppContext";
|
|
5
|
+
import type { CodemationConfig } from "./CodemationConfig";
|
|
5
6
|
|
|
6
7
|
export interface CodemationPluginContext extends CodemationRegistrationContextBase {
|
|
7
8
|
readonly container: Container;
|
|
@@ -9,12 +10,47 @@ export interface CodemationPluginContext extends CodemationRegistrationContextBa
|
|
|
9
10
|
readonly loggerFactory: LoggerFactory;
|
|
10
11
|
}
|
|
11
12
|
|
|
12
|
-
export interface
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
13
|
+
export interface CodemationPluginConfig {
|
|
14
|
+
readonly credentialTypes?: ReadonlyArray<AnyCredentialType>;
|
|
15
|
+
readonly register?: (context: CodemationPluginContext) => void | Promise<void>;
|
|
16
|
+
readonly sandbox?: CodemationConfig;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export type CodemationPlugin = CodemationPluginConfig;
|
|
20
|
+
|
|
21
|
+
const definePlugin = <TConfig extends CodemationPluginConfig>(config: TConfig): TConfig => config;
|
|
22
|
+
|
|
23
|
+
export { definePlugin };
|
|
24
|
+
|
|
25
|
+
export class CodemationPluginPackageMetadata {
|
|
26
|
+
private static readonly packageNameSymbol = Symbol.for("@codemation/plugin-package-name");
|
|
27
|
+
|
|
28
|
+
attachPackageName(plugin: CodemationPlugin, packageName: string): CodemationPlugin {
|
|
29
|
+
if (packageName.trim().length === 0) {
|
|
30
|
+
return plugin;
|
|
31
|
+
}
|
|
32
|
+
const mutablePlugin = plugin as CodemationPlugin & {
|
|
33
|
+
[CodemationPluginPackageMetadata.packageNameSymbol]?: string;
|
|
34
|
+
};
|
|
35
|
+
const existingPackageName = mutablePlugin[CodemationPluginPackageMetadata.packageNameSymbol];
|
|
36
|
+
if (existingPackageName === packageName) {
|
|
37
|
+
return plugin;
|
|
38
|
+
}
|
|
39
|
+
Object.defineProperty(mutablePlugin, CodemationPluginPackageMetadata.packageNameSymbol, {
|
|
40
|
+
configurable: true,
|
|
41
|
+
enumerable: false,
|
|
42
|
+
value: packageName,
|
|
43
|
+
writable: true,
|
|
44
|
+
});
|
|
45
|
+
return plugin;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
readPackageName(plugin: CodemationPlugin): string | undefined {
|
|
49
|
+
const packageName = (
|
|
50
|
+
plugin as CodemationPlugin & {
|
|
51
|
+
[CodemationPluginPackageMetadata.packageNameSymbol]?: unknown;
|
|
52
|
+
}
|
|
53
|
+
)[CodemationPluginPackageMetadata.packageNameSymbol];
|
|
54
|
+
return typeof packageName === "string" && packageName.trim().length > 0 ? packageName : undefined;
|
|
55
|
+
}
|
|
20
56
|
}
|
|
@@ -1,46 +1,50 @@
|
|
|
1
|
-
import type
|
|
1
|
+
import { CodemationPluginPackageMetadata, type CodemationPlugin } from "./CodemationPlugin";
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* Merges explicitly configured plugins with auto-discovered plugins.
|
|
5
5
|
* Configured plugins are applied first; discovered plugins fill in gaps.
|
|
6
|
-
* Plugins
|
|
7
|
-
* registered twice when the consumer config lists a plugin and discovery also finds it
|
|
8
|
-
* module evaluations produce different `constructor` identities for the same logical plugin.
|
|
6
|
+
* Plugins discovered from package.json manifests are deduped by npm package name so the same package is not
|
|
7
|
+
* registered twice when the consumer config lists a discovered plugin explicitly and auto-discovery also finds it.
|
|
9
8
|
*/
|
|
10
9
|
export class CodemationPluginListMerger {
|
|
10
|
+
constructor(private readonly packageMetadata: CodemationPluginPackageMetadata) {}
|
|
11
|
+
|
|
11
12
|
merge(
|
|
12
13
|
configuredPlugins: ReadonlyArray<CodemationPlugin>,
|
|
13
14
|
discoveredPlugins: ReadonlyArray<CodemationPlugin>,
|
|
14
15
|
): ReadonlyArray<CodemationPlugin> {
|
|
15
16
|
const pluginsByPackageId = new Map<string, CodemationPlugin>();
|
|
16
|
-
const
|
|
17
|
+
const pluginsByReference = new Set<CodemationPlugin>();
|
|
17
18
|
const result: CodemationPlugin[] = [];
|
|
18
19
|
|
|
19
|
-
const tryAdd = (plugin: CodemationPlugin): void => {
|
|
20
|
-
const packageId = plugin.pluginPackageId;
|
|
21
|
-
if (typeof packageId === "string" && packageId.trim().length > 0) {
|
|
22
|
-
const key = packageId.trim();
|
|
23
|
-
if (pluginsByPackageId.has(key)) {
|
|
24
|
-
return;
|
|
25
|
-
}
|
|
26
|
-
pluginsByPackageId.set(key, plugin);
|
|
27
|
-
result.push(plugin);
|
|
28
|
-
return;
|
|
29
|
-
}
|
|
30
|
-
const constructorKey = Object.getPrototypeOf(plugin)?.constructor ?? plugin;
|
|
31
|
-
if (pluginsByConstructor.has(constructorKey)) {
|
|
32
|
-
return;
|
|
33
|
-
}
|
|
34
|
-
pluginsByConstructor.set(constructorKey, plugin);
|
|
35
|
-
result.push(plugin);
|
|
36
|
-
};
|
|
37
|
-
|
|
38
20
|
for (const plugin of configuredPlugins) {
|
|
39
|
-
tryAdd(plugin);
|
|
21
|
+
this.tryAdd(plugin, pluginsByPackageId, pluginsByReference, result);
|
|
40
22
|
}
|
|
41
23
|
for (const plugin of discoveredPlugins) {
|
|
42
|
-
tryAdd(plugin);
|
|
24
|
+
this.tryAdd(plugin, pluginsByPackageId, pluginsByReference, result);
|
|
43
25
|
}
|
|
44
26
|
return result;
|
|
45
27
|
}
|
|
28
|
+
|
|
29
|
+
private tryAdd(
|
|
30
|
+
plugin: CodemationPlugin,
|
|
31
|
+
pluginsByPackageId: Map<string, CodemationPlugin>,
|
|
32
|
+
pluginsByReference: Set<CodemationPlugin>,
|
|
33
|
+
result: CodemationPlugin[],
|
|
34
|
+
): void {
|
|
35
|
+
const packageId = this.packageMetadata.readPackageName(plugin);
|
|
36
|
+
if (packageId) {
|
|
37
|
+
if (pluginsByPackageId.has(packageId)) {
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
pluginsByPackageId.set(packageId, plugin);
|
|
41
|
+
result.push(plugin);
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
if (pluginsByReference.has(plugin)) {
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
pluginsByReference.add(plugin);
|
|
48
|
+
result.push(plugin);
|
|
49
|
+
}
|
|
46
50
|
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { CodemationAuthConfig } from "../config/CodemationAuthConfig";
|
|
2
|
+
import type { CodemationFrontendAuthProviderSnapshot } from "./CodemationFrontendAuthSnapshot";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Node-side runtime bootstrap for NextAuth provider wiring.
|
|
6
|
+
*/
|
|
7
|
+
export type InternalAuthBootstrap = Readonly<{
|
|
8
|
+
authConfig: CodemationAuthConfig | undefined;
|
|
9
|
+
credentialsEnabled: boolean;
|
|
10
|
+
oauthProviders: ReadonlyArray<CodemationFrontendAuthProviderSnapshot>;
|
|
11
|
+
uiAuthEnabled: boolean;
|
|
12
|
+
}>;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { inject, injectable } from "@codemation/core";
|
|
2
|
+
|
|
3
|
+
import { ApplicationTokens } from "../../applicationTokens";
|
|
4
|
+
import type { AppConfig } from "../config/AppConfig";
|
|
5
|
+
import { CodemationFrontendAuthSnapshotFactory } from "./CodemationFrontendAuthSnapshotFactory";
|
|
6
|
+
import type { InternalAuthBootstrap } from "./InternalAuthBootstrap";
|
|
7
|
+
|
|
8
|
+
@injectable()
|
|
9
|
+
export class InternalAuthBootstrapFactory {
|
|
10
|
+
constructor(
|
|
11
|
+
@inject(ApplicationTokens.AppConfig)
|
|
12
|
+
private readonly appConfig: AppConfig,
|
|
13
|
+
@inject(CodemationFrontendAuthSnapshotFactory)
|
|
14
|
+
private readonly authSnapshotFactory: CodemationFrontendAuthSnapshotFactory,
|
|
15
|
+
) {}
|
|
16
|
+
|
|
17
|
+
create(): InternalAuthBootstrap {
|
|
18
|
+
const authSnapshot = this.authSnapshotFactory.createFromAppConfig(this.appConfig);
|
|
19
|
+
return {
|
|
20
|
+
authConfig: this.appConfig.auth,
|
|
21
|
+
credentialsEnabled: authSnapshot.credentialsEnabled,
|
|
22
|
+
oauthProviders: authSnapshot.oauthProviders,
|
|
23
|
+
uiAuthEnabled: authSnapshot.uiAuthEnabled,
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import type { CodemationAuthConfig } from "../config/CodemationAuthConfig";
|
|
2
|
+
import type { CodemationFrontendAuthProviderSnapshot } from "./CodemationFrontendAuthSnapshot";
|
|
3
|
+
import type { InternalAuthBootstrap } from "./InternalAuthBootstrap";
|
|
4
|
+
|
|
5
|
+
export class InternalAuthBootstrapJsonCodec {
|
|
6
|
+
serialize(bootstrap: InternalAuthBootstrap): string {
|
|
7
|
+
return JSON.stringify(bootstrap);
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
deserialize(serialized: string | undefined): InternalAuthBootstrap | null {
|
|
11
|
+
if (!serialized || serialized.trim().length === 0) {
|
|
12
|
+
return null;
|
|
13
|
+
}
|
|
14
|
+
try {
|
|
15
|
+
const parsed = JSON.parse(serialized) as Partial<InternalAuthBootstrap> | null;
|
|
16
|
+
if (!parsed || typeof parsed !== "object") {
|
|
17
|
+
return null;
|
|
18
|
+
}
|
|
19
|
+
return {
|
|
20
|
+
authConfig: this.resolveAuthConfig(parsed.authConfig),
|
|
21
|
+
credentialsEnabled: parsed.credentialsEnabled === true,
|
|
22
|
+
oauthProviders: this.resolveOauthProviders(parsed.oauthProviders),
|
|
23
|
+
uiAuthEnabled: parsed.uiAuthEnabled !== false,
|
|
24
|
+
};
|
|
25
|
+
} catch {
|
|
26
|
+
return null;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
private resolveAuthConfig(value: unknown): CodemationAuthConfig | undefined {
|
|
31
|
+
return value && typeof value === "object" ? (value as CodemationAuthConfig) : undefined;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
private resolveOauthProviders(value: unknown): ReadonlyArray<CodemationFrontendAuthProviderSnapshot> {
|
|
35
|
+
if (!Array.isArray(value)) {
|
|
36
|
+
return [];
|
|
37
|
+
}
|
|
38
|
+
return value.flatMap((entry) => {
|
|
39
|
+
if (!entry || typeof entry !== "object") {
|
|
40
|
+
return [];
|
|
41
|
+
}
|
|
42
|
+
const provider = entry as Partial<CodemationFrontendAuthProviderSnapshot>;
|
|
43
|
+
if (typeof provider.id !== "string" || typeof provider.name !== "string") {
|
|
44
|
+
return [];
|
|
45
|
+
}
|
|
46
|
+
return [{ id: provider.id, name: provider.name }];
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { CodemationFrontendAuthProviderSnapshot } from "./CodemationFrontendAuthSnapshot";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Frontend-safe runtime bootstrap consumed by the Next.js shell.
|
|
5
|
+
*/
|
|
6
|
+
export type PublicFrontendBootstrap = Readonly<{
|
|
7
|
+
credentialsEnabled: boolean;
|
|
8
|
+
logoUrl: string | null;
|
|
9
|
+
oauthProviders: ReadonlyArray<CodemationFrontendAuthProviderSnapshot>;
|
|
10
|
+
productName: string;
|
|
11
|
+
uiAuthEnabled: boolean;
|
|
12
|
+
}>;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { inject, injectable } from "@codemation/core";
|
|
2
|
+
|
|
3
|
+
import { FrontendAppConfigFactory } from "./FrontendAppConfigFactory";
|
|
4
|
+
import type { PublicFrontendBootstrap } from "./PublicFrontendBootstrap";
|
|
5
|
+
|
|
6
|
+
@injectable()
|
|
7
|
+
export class PublicFrontendBootstrapFactory {
|
|
8
|
+
constructor(
|
|
9
|
+
@inject(FrontendAppConfigFactory)
|
|
10
|
+
private readonly frontendAppConfigFactory: FrontendAppConfigFactory,
|
|
11
|
+
) {}
|
|
12
|
+
|
|
13
|
+
create(): PublicFrontendBootstrap {
|
|
14
|
+
const frontendAppConfig = this.frontendAppConfigFactory.create();
|
|
15
|
+
return {
|
|
16
|
+
credentialsEnabled: frontendAppConfig.auth.credentialsEnabled,
|
|
17
|
+
logoUrl: frontendAppConfig.logoUrl,
|
|
18
|
+
oauthProviders: frontendAppConfig.auth.oauthProviders,
|
|
19
|
+
productName: frontendAppConfig.productName,
|
|
20
|
+
uiAuthEnabled: frontendAppConfig.auth.uiAuthEnabled,
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import type { PublicFrontendBootstrap } from "./PublicFrontendBootstrap";
|
|
2
|
+
import type { CodemationFrontendAuthProviderSnapshot } from "./CodemationFrontendAuthSnapshot";
|
|
3
|
+
|
|
4
|
+
export class PublicFrontendBootstrapJsonCodec {
|
|
5
|
+
serialize(bootstrap: PublicFrontendBootstrap): string {
|
|
6
|
+
return JSON.stringify(bootstrap);
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
deserialize(serialized: string | undefined): PublicFrontendBootstrap | null {
|
|
10
|
+
if (!serialized || serialized.trim().length === 0) {
|
|
11
|
+
return null;
|
|
12
|
+
}
|
|
13
|
+
try {
|
|
14
|
+
const parsed = JSON.parse(serialized) as Partial<PublicFrontendBootstrap> | null;
|
|
15
|
+
if (!parsed || typeof parsed !== "object") {
|
|
16
|
+
return null;
|
|
17
|
+
}
|
|
18
|
+
return {
|
|
19
|
+
credentialsEnabled: parsed.credentialsEnabled === true,
|
|
20
|
+
logoUrl: typeof parsed.logoUrl === "string" && parsed.logoUrl.trim().length > 0 ? parsed.logoUrl : null,
|
|
21
|
+
oauthProviders: this.resolveOauthProviders(parsed.oauthProviders),
|
|
22
|
+
productName:
|
|
23
|
+
typeof parsed.productName === "string" && parsed.productName.trim().length > 0
|
|
24
|
+
? parsed.productName
|
|
25
|
+
: "Codemation",
|
|
26
|
+
uiAuthEnabled: parsed.uiAuthEnabled !== false,
|
|
27
|
+
};
|
|
28
|
+
} catch {
|
|
29
|
+
return null;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
private resolveOauthProviders(value: unknown): ReadonlyArray<CodemationFrontendAuthProviderSnapshot> {
|
|
34
|
+
if (!Array.isArray(value)) {
|
|
35
|
+
return [];
|
|
36
|
+
}
|
|
37
|
+
return value.flatMap((entry) => {
|
|
38
|
+
if (!entry || typeof entry !== "object") {
|
|
39
|
+
return [];
|
|
40
|
+
}
|
|
41
|
+
const provider = entry as Partial<CodemationFrontendAuthProviderSnapshot>;
|
|
42
|
+
if (typeof provider.id !== "string" || typeof provider.name !== "string") {
|
|
43
|
+
return [];
|
|
44
|
+
}
|
|
45
|
+
return [{ id: provider.id, name: provider.name }];
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
}
|
|
@@ -15,6 +15,8 @@ export class ApiPaths {
|
|
|
15
15
|
|
|
16
16
|
private static readonly whitelabelBasePath = `${this.apiBasePath}/whitelabel`;
|
|
17
17
|
|
|
18
|
+
private static readonly bootstrapBasePath = `${this.apiBasePath}/bootstrap`;
|
|
19
|
+
|
|
18
20
|
static workflows(): string {
|
|
19
21
|
return this.workflowsBasePath;
|
|
20
22
|
}
|
|
@@ -162,4 +164,12 @@ export class ApiPaths {
|
|
|
162
164
|
static whitelabelLogo(): string {
|
|
163
165
|
return `${this.whitelabelBasePath}/logo`;
|
|
164
166
|
}
|
|
167
|
+
|
|
168
|
+
static frontendBootstrap(): string {
|
|
169
|
+
return `${this.bootstrapBasePath}/frontend`;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
static internalAuthBootstrap(): string {
|
|
173
|
+
return `${this.bootstrapBasePath}/auth/internal`;
|
|
174
|
+
}
|
|
165
175
|
}
|
|
@@ -16,6 +16,9 @@ export class HonoHttpAnonymousRoutePolicy {
|
|
|
16
16
|
if (pathname === "/api/dev/runtime" || pathname === "/api/dev/bootstrap-summary") {
|
|
17
17
|
return true;
|
|
18
18
|
}
|
|
19
|
+
if (pathname === ApiPaths.frontendBootstrap() || pathname === ApiPaths.internalAuthBootstrap()) {
|
|
20
|
+
return true;
|
|
21
|
+
}
|
|
19
22
|
if (pathname === ApiPaths.userInviteVerify() || pathname === ApiPaths.userInviteAccept()) {
|
|
20
23
|
return true;
|
|
21
24
|
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { inject, injectable, registry } from "@codemation/core";
|
|
2
|
+
import { Hono } from "hono";
|
|
3
|
+
|
|
4
|
+
import { ApplicationTokens } from "../../../../applicationTokens";
|
|
5
|
+
import { ApiPaths } from "../../ApiPaths";
|
|
6
|
+
import { InternalAuthBootstrapHttpRouteHandler } from "../../routeHandlers/InternalAuthBootstrapHttpRouteHandler";
|
|
7
|
+
import { PublicFrontendBootstrapHttpRouteHandler } from "../../routeHandlers/PublicFrontendBootstrapHttpRouteHandler";
|
|
8
|
+
import type { HonoApiRouteRegistrar } from "../HonoApiRouteRegistrar";
|
|
9
|
+
|
|
10
|
+
@injectable()
|
|
11
|
+
@registry([{ token: ApplicationTokens.HonoApiRouteRegistrar, useClass: BootstrapHonoApiRouteRegistrar }])
|
|
12
|
+
export class BootstrapHonoApiRouteRegistrar implements HonoApiRouteRegistrar {
|
|
13
|
+
constructor(
|
|
14
|
+
@inject(PublicFrontendBootstrapHttpRouteHandler)
|
|
15
|
+
private readonly publicFrontendBootstrapHttpRouteHandler: PublicFrontendBootstrapHttpRouteHandler,
|
|
16
|
+
@inject(InternalAuthBootstrapHttpRouteHandler)
|
|
17
|
+
private readonly internalAuthBootstrapHttpRouteHandler: InternalAuthBootstrapHttpRouteHandler,
|
|
18
|
+
) {}
|
|
19
|
+
|
|
20
|
+
register(app: Hono): void {
|
|
21
|
+
app.get(this.resolveRelativePath(ApiPaths.frontendBootstrap()), () =>
|
|
22
|
+
this.publicFrontendBootstrapHttpRouteHandler.getBootstrap(),
|
|
23
|
+
);
|
|
24
|
+
app.get(this.resolveRelativePath(ApiPaths.internalAuthBootstrap()), () =>
|
|
25
|
+
this.internalAuthBootstrapHttpRouteHandler.getBootstrap(),
|
|
26
|
+
);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
private resolveRelativePath(pathname: string): string {
|
|
30
|
+
return pathname.replace(/^\/api/, "");
|
|
31
|
+
}
|
|
32
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { inject, injectable } from "@codemation/core";
|
|
2
|
+
|
|
3
|
+
import { InternalAuthBootstrapFactory } from "../../frontend/InternalAuthBootstrapFactory";
|
|
4
|
+
|
|
5
|
+
@injectable()
|
|
6
|
+
export class InternalAuthBootstrapHttpRouteHandler {
|
|
7
|
+
constructor(
|
|
8
|
+
@inject(InternalAuthBootstrapFactory)
|
|
9
|
+
private readonly bootstrapFactory: InternalAuthBootstrapFactory,
|
|
10
|
+
) {}
|
|
11
|
+
|
|
12
|
+
getBootstrap(): Response {
|
|
13
|
+
return new Response(JSON.stringify(this.bootstrapFactory.create()), {
|
|
14
|
+
status: 200,
|
|
15
|
+
headers: { "content-type": "application/json" },
|
|
16
|
+
});
|
|
17
|
+
}
|
|
18
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { inject, injectable } from "@codemation/core";
|
|
2
|
+
|
|
3
|
+
import { PublicFrontendBootstrapFactory } from "../../frontend/PublicFrontendBootstrapFactory";
|
|
4
|
+
|
|
5
|
+
@injectable()
|
|
6
|
+
export class PublicFrontendBootstrapHttpRouteHandler {
|
|
7
|
+
constructor(
|
|
8
|
+
@inject(PublicFrontendBootstrapFactory)
|
|
9
|
+
private readonly bootstrapFactory: PublicFrontendBootstrapFactory,
|
|
10
|
+
) {}
|
|
11
|
+
|
|
12
|
+
getBootstrap(): Response {
|
|
13
|
+
return new Response(JSON.stringify(this.bootstrapFactory.create()), {
|
|
14
|
+
status: 200,
|
|
15
|
+
headers: { "content-type": "application/json" },
|
|
16
|
+
});
|
|
17
|
+
}
|
|
18
|
+
}
|
|
@@ -3,7 +3,6 @@ import { access, stat } from "node:fs/promises";
|
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
import { pathToFileURL } from "node:url";
|
|
5
5
|
import type { NamespacedUnregister } from "tsx/esm/api";
|
|
6
|
-
import { register } from "tsx/esm/api";
|
|
7
6
|
import type { CodemationConfig } from "../config/CodemationConfig";
|
|
8
7
|
import { CodemationConfigNormalizer } from "../config/CodemationConfigNormalizer";
|
|
9
8
|
import type { NormalizedCodemationConfig } from "../config/CodemationConfigNormalizer";
|
|
@@ -173,10 +172,15 @@ export class CodemationConsumerConfigLoader {
|
|
|
173
172
|
return await this.importModuleWithNativeRuntime(modulePath);
|
|
174
173
|
}
|
|
175
174
|
const tsconfigPath = await this.resolveTsconfigPath(modulePath);
|
|
175
|
+
if (this.shouldResetImporterBeforeImport()) {
|
|
176
|
+
await this.resetImporter(tsconfigPath);
|
|
177
|
+
}
|
|
176
178
|
const importSpecifier = await this.createImportSpecifier(modulePath);
|
|
177
179
|
for (let attempt = 0; attempt < 3; attempt += 1) {
|
|
178
180
|
try {
|
|
179
|
-
const importedModule = await
|
|
181
|
+
const importedModule = await (
|
|
182
|
+
await this.getOrCreateImporter(tsconfigPath)
|
|
183
|
+
).import(importSpecifier, import.meta.url);
|
|
180
184
|
return importedModule as Record<string, unknown>;
|
|
181
185
|
} catch (error) {
|
|
182
186
|
if (!this.isStoppedTransformServiceError(error) || attempt === 2) {
|
|
@@ -202,12 +206,13 @@ export class CodemationConsumerConfigLoader {
|
|
|
202
206
|
return discoveredPath ?? false;
|
|
203
207
|
}
|
|
204
208
|
|
|
205
|
-
private getOrCreateImporter(tsconfigPath: string | false): NamespacedUnregister {
|
|
209
|
+
private async getOrCreateImporter(tsconfigPath: string | false): Promise<NamespacedUnregister> {
|
|
206
210
|
const cacheKey = tsconfigPath || "default";
|
|
207
211
|
const existingImporter = CodemationConsumerConfigLoader.importerRegistrationsByTsconfig.get(cacheKey);
|
|
208
212
|
if (existingImporter) {
|
|
209
213
|
return existingImporter;
|
|
210
214
|
}
|
|
215
|
+
const { register } = await import(/* webpackIgnore: true */ this.resolveTsxImporterModuleSpecifier());
|
|
211
216
|
const nextImporter = register({
|
|
212
217
|
namespace: this.toNamespace(cacheKey),
|
|
213
218
|
tsconfig: tsconfigPath,
|
|
@@ -230,6 +235,10 @@ export class CodemationConsumerConfigLoader {
|
|
|
230
235
|
return `codemation_consumer_${cacheKey.replace(/[^a-zA-Z0-9_-]+/g, "_")}`;
|
|
231
236
|
}
|
|
232
237
|
|
|
238
|
+
private resolveTsxImporterModuleSpecifier(): string {
|
|
239
|
+
return ["tsx", "esm", "api"].join("/");
|
|
240
|
+
}
|
|
241
|
+
|
|
233
242
|
private async findNearestTsconfig(modulePath: string): Promise<string | null> {
|
|
234
243
|
let currentDirectory = path.dirname(modulePath);
|
|
235
244
|
while (true) {
|
|
@@ -256,6 +265,10 @@ export class CodemationConsumerConfigLoader {
|
|
|
256
265
|
return process.env.CODEMATION_TS_RUNTIME === "ts-node";
|
|
257
266
|
}
|
|
258
267
|
|
|
268
|
+
private shouldResetImporterBeforeImport(): boolean {
|
|
269
|
+
return (process.env.CODEMATION_DEV_SERVER_TOKEN?.trim().length ?? 0) > 0;
|
|
270
|
+
}
|
|
271
|
+
|
|
259
272
|
private isStoppedTransformServiceError(error: unknown): boolean {
|
|
260
273
|
return error instanceof Error && error.message.includes("The service is no longer running");
|
|
261
274
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { CodemationPackageManifest } from "../config/CodemationPackageManifest";
|
|
2
|
-
import type
|
|
2
|
+
import { CodemationPluginPackageMetadata, type CodemationPlugin } from "../config/CodemationPlugin";
|
|
3
3
|
import { readFile, readdir } from "node:fs/promises";
|
|
4
4
|
import path from "node:path";
|
|
5
5
|
import { pathToFileURL } from "node:url";
|
|
@@ -7,7 +7,7 @@ import { pathToFileURL } from "node:url";
|
|
|
7
7
|
export type CodemationDiscoveredPluginPackage = Readonly<{
|
|
8
8
|
packageName: string;
|
|
9
9
|
packageRoot: string;
|
|
10
|
-
|
|
10
|
+
pluginEntry: string;
|
|
11
11
|
developmentEntry?: string;
|
|
12
12
|
}>;
|
|
13
13
|
|
|
@@ -24,6 +24,8 @@ type PackageJsonShape = Readonly<{
|
|
|
24
24
|
}>;
|
|
25
25
|
|
|
26
26
|
export class CodemationPluginDiscovery {
|
|
27
|
+
private readonly pluginPackageMetadata = new CodemationPluginPackageMetadata();
|
|
28
|
+
|
|
27
29
|
async discover(consumerRoot: string): Promise<ReadonlyArray<CodemationDiscoveredPluginPackage>> {
|
|
28
30
|
const nodeModulesRoot = path.resolve(consumerRoot, "node_modules");
|
|
29
31
|
const packageRoots = await this.collectPackageRoots(nodeModulesRoot);
|
|
@@ -31,14 +33,14 @@ export class CodemationPluginDiscovery {
|
|
|
31
33
|
for (const packageRoot of packageRoots) {
|
|
32
34
|
const packageJson = await this.readPackageJson(path.resolve(packageRoot, "package.json"));
|
|
33
35
|
const pluginManifest = packageJson.codemation?.plugin;
|
|
34
|
-
if (!packageJson.name ||
|
|
36
|
+
if (!packageJson.name || typeof pluginManifest !== "string" || pluginManifest.trim().length === 0) {
|
|
35
37
|
continue;
|
|
36
38
|
}
|
|
37
39
|
discoveredPackages.push({
|
|
38
40
|
packageName: packageJson.name,
|
|
39
41
|
packageRoot,
|
|
40
|
-
|
|
41
|
-
developmentEntry: this.resolveDevelopmentPluginEntry(
|
|
42
|
+
pluginEntry: pluginManifest,
|
|
43
|
+
developmentEntry: await this.resolveDevelopmentPluginEntry(packageRoot),
|
|
42
44
|
});
|
|
43
45
|
}
|
|
44
46
|
return discoveredPackages.sort((left, right) => left.packageName.localeCompare(right.packageName));
|
|
@@ -98,37 +100,47 @@ export class CodemationPluginDiscovery {
|
|
|
98
100
|
|
|
99
101
|
private async loadPlugin(discoveredPackage: CodemationDiscoveredPluginPackage): Promise<CodemationPlugin> {
|
|
100
102
|
const pluginModulePath = path.resolve(discoveredPackage.packageRoot, this.resolvePluginEntry(discoveredPackage));
|
|
101
|
-
const importedModule = (await import(
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
const exportedValue =
|
|
103
|
+
const importedModule = (await import(
|
|
104
|
+
/* webpackIgnore: true */ this.resolvePluginModuleSpecifier(pluginModulePath)
|
|
105
|
+
)) as Record<string, unknown>;
|
|
106
|
+
const exportedValue = importedModule.default;
|
|
105
107
|
const plugin = this.resolvePluginValue(exportedValue);
|
|
106
108
|
if (!plugin) {
|
|
107
|
-
throw new Error(`Plugin package "${discoveredPackage.packageName}" did not export a Codemation plugin
|
|
109
|
+
throw new Error(`Plugin package "${discoveredPackage.packageName}" did not default-export a Codemation plugin.`);
|
|
108
110
|
}
|
|
109
|
-
return plugin;
|
|
111
|
+
return this.pluginPackageMetadata.attachPackageName(plugin, discoveredPackage.packageName);
|
|
110
112
|
}
|
|
111
113
|
|
|
112
114
|
private resolvePluginValue(value: unknown): CodemationPlugin | null {
|
|
113
|
-
if (this.
|
|
115
|
+
if (this.isPluginConfig(value)) {
|
|
114
116
|
return value;
|
|
115
117
|
}
|
|
116
|
-
if (this.isPluginConstructor(value)) {
|
|
117
|
-
return new value();
|
|
118
|
-
}
|
|
119
118
|
return null;
|
|
120
119
|
}
|
|
121
120
|
|
|
122
|
-
private
|
|
121
|
+
private isPluginConfig(value: unknown): value is CodemationPlugin {
|
|
122
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
123
|
+
return false;
|
|
124
|
+
}
|
|
125
|
+
const pluginValue = value as {
|
|
126
|
+
credentialTypes?: unknown;
|
|
127
|
+
register?: unknown;
|
|
128
|
+
sandbox?: unknown;
|
|
129
|
+
};
|
|
130
|
+
if (pluginValue.register !== undefined && typeof pluginValue.register !== "function") {
|
|
131
|
+
return false;
|
|
132
|
+
}
|
|
133
|
+
if (pluginValue.credentialTypes !== undefined && !Array.isArray(pluginValue.credentialTypes)) {
|
|
134
|
+
return false;
|
|
135
|
+
}
|
|
123
136
|
return (
|
|
124
|
-
|
|
137
|
+
pluginValue.register !== undefined ||
|
|
138
|
+
pluginValue.credentialTypes !== undefined ||
|
|
139
|
+
pluginValue.sandbox !== undefined ||
|
|
140
|
+
Object.keys(pluginValue).length === 0
|
|
125
141
|
);
|
|
126
142
|
}
|
|
127
143
|
|
|
128
|
-
private isPluginConstructor(value: unknown): value is new () => CodemationPlugin {
|
|
129
|
-
return typeof value === "function" && this.isPlugin(value.prototype);
|
|
130
|
-
}
|
|
131
|
-
|
|
132
144
|
private resolvePluginEntry(discoveredPackage: CodemationDiscoveredPluginPackage): string {
|
|
133
145
|
if (
|
|
134
146
|
process.env.CODEMATION_PREFER_PLUGIN_SOURCE_ENTRY === "true" &&
|
|
@@ -137,15 +149,34 @@ export class CodemationPluginDiscovery {
|
|
|
137
149
|
) {
|
|
138
150
|
return discoveredPackage.developmentEntry;
|
|
139
151
|
}
|
|
140
|
-
return discoveredPackage.
|
|
152
|
+
return discoveredPackage.pluginEntry;
|
|
141
153
|
}
|
|
142
154
|
|
|
143
|
-
private resolveDevelopmentPluginEntry(
|
|
144
|
-
const
|
|
145
|
-
|
|
146
|
-
|
|
155
|
+
private async resolveDevelopmentPluginEntry(packageRoot: string): Promise<string | undefined> {
|
|
156
|
+
const candidates = [
|
|
157
|
+
path.resolve(packageRoot, "codemation.plugin.ts"),
|
|
158
|
+
path.resolve(packageRoot, "codemation.plugin.js"),
|
|
159
|
+
path.resolve(packageRoot, "src", "codemation.plugin.ts"),
|
|
160
|
+
path.resolve(packageRoot, "src", "codemation.plugin.js"),
|
|
161
|
+
];
|
|
162
|
+
for (const candidate of candidates) {
|
|
163
|
+
if (await this.exists(candidate)) {
|
|
164
|
+
return path.relative(packageRoot, candidate);
|
|
165
|
+
}
|
|
147
166
|
}
|
|
148
|
-
|
|
149
|
-
|
|
167
|
+
return undefined;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
private async exists(filePath: string): Promise<boolean> {
|
|
171
|
+
try {
|
|
172
|
+
await readFile(filePath, "utf8");
|
|
173
|
+
return true;
|
|
174
|
+
} catch {
|
|
175
|
+
return false;
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
private resolvePluginModuleSpecifier(pluginModulePath: string): string {
|
|
180
|
+
return pathToFileURL(pluginModulePath).href;
|
|
150
181
|
}
|
|
151
182
|
}
|
package/src/server.ts
CHANGED
|
@@ -12,7 +12,13 @@ export { CodemationFrontendAuthSnapshotFactory } from "./presentation/frontend/C
|
|
|
12
12
|
export { CodemationFrontendAuthSnapshotJsonCodec } from "./presentation/frontend/CodemationFrontendAuthSnapshotJsonCodec";
|
|
13
13
|
export { FrontendAppConfigFactory } from "./presentation/frontend/FrontendAppConfigFactory";
|
|
14
14
|
export { FrontendAppConfigJsonCodec } from "./presentation/frontend/FrontendAppConfigJsonCodec";
|
|
15
|
+
export { InternalAuthBootstrapFactory } from "./presentation/frontend/InternalAuthBootstrapFactory";
|
|
16
|
+
export { InternalAuthBootstrapJsonCodec } from "./presentation/frontend/InternalAuthBootstrapJsonCodec";
|
|
17
|
+
export { PublicFrontendBootstrapFactory } from "./presentation/frontend/PublicFrontendBootstrapFactory";
|
|
18
|
+
export { PublicFrontendBootstrapJsonCodec } from "./presentation/frontend/PublicFrontendBootstrapJsonCodec";
|
|
15
19
|
export type { FrontendAppConfig } from "./presentation/frontend/FrontendAppConfig";
|
|
20
|
+
export type { InternalAuthBootstrap } from "./presentation/frontend/InternalAuthBootstrap";
|
|
21
|
+
export type { PublicFrontendBootstrap } from "./presentation/frontend/PublicFrontendBootstrap";
|
|
16
22
|
export type {
|
|
17
23
|
CodemationDiscoveredPluginPackage,
|
|
18
24
|
CodemationResolvedPluginPackage,
|