proteum 2.2.6 → 2.2.8
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/AGENTS.md +1 -1
- package/README.md +4 -4
- package/agents/project/AGENTS.md +2 -1
- package/agents/project/app-root/AGENTS.md +1 -1
- package/agents/project/diagnostics.md +1 -1
- package/agents/project/root/AGENTS.md +2 -1
- package/cli/commands/configure.ts +14 -35
- package/cli/commands/dev.ts +105 -52
- package/cli/compiler/artifacts/controllers.ts +30 -4
- package/cli/compiler/artifacts/manifest.ts +1 -5
- package/cli/compiler/artifacts/services.ts +67 -29
- package/cli/presentation/commands.ts +9 -9
- package/cli/presentation/help.ts +1 -1
- package/cli/scaffold/index.ts +2 -5
- package/cli/scaffold/templates.ts +3 -10
- package/cli/utils/agents.ts +281 -199
- package/client/dev/profiler/ApexChart.tsx +4 -3
- package/common/dev/serverHotReload.ts +26 -25
- package/package.json +1 -1
- package/server/app/commands.ts +11 -16
- package/server/app/commandsManager.ts +5 -1
- package/server/app/controller/index.ts +68 -16
- package/server/app/devCommands.ts +3 -3
- package/server/app/devDiagnostics.ts +2 -2
- package/server/app/index.ts +19 -8
- package/server/app/service/container.ts +22 -19
- package/server/app/service/index.ts +33 -13
- package/server/app.tsconfig.json +0 -1
- package/server/services/auth/index.ts +12 -6
- package/server/services/auth/router/index.ts +12 -14
- package/server/services/auth/router/request.ts +34 -13
- package/server/services/disks/driver.ts +1 -1
- package/server/services/disks/index.ts +11 -8
- package/server/services/email/index.ts +1 -1
- package/server/services/prisma/Facet.ts +6 -5
- package/server/services/router/index.ts +8 -7
- package/server/services/router/request/validation/zod.ts +2 -0
- package/server/services/router/response/index.ts +9 -9
- package/server/services/router/service.ts +12 -8
- package/tests/agents-utils.test.cjs +207 -0
- package/tests/dev-transpile-watch.test.cjs +513 -0
- package/types/global/vendors.d.ts +70 -0
|
@@ -11,32 +11,30 @@ import {
|
|
|
11
11
|
TAnyRoute,
|
|
12
12
|
RouterService,
|
|
13
13
|
TAnyRouter,
|
|
14
|
+
TServerRouter,
|
|
14
15
|
} from '@server/services/router';
|
|
15
16
|
|
|
16
17
|
import type { Application } from '@server/app/index';
|
|
18
|
+
import AppContainer from '@server/app/container';
|
|
17
19
|
|
|
18
20
|
import type { TRouterServiceArgs } from '@server/services/router/service';
|
|
19
21
|
|
|
20
22
|
// Specific
|
|
21
23
|
import type { default as UsersService, TAuthCheckConditions, TBasicUser } from '..';
|
|
22
|
-
import
|
|
23
|
-
|
|
24
|
-
/*----------------------------------
|
|
25
|
-
- TYPES
|
|
26
|
-
----------------------------------*/
|
|
24
|
+
import { createUsersRequestService, type TUsersRequestContext } from './request';
|
|
27
25
|
|
|
28
26
|
/*----------------------------------
|
|
29
27
|
- SERVICE
|
|
30
28
|
----------------------------------*/
|
|
31
29
|
export default class AuthenticationRouterService<
|
|
32
|
-
TApplication extends Application
|
|
33
|
-
TUser extends TBasicUser
|
|
34
|
-
TRouter extends TAnyRouter =
|
|
30
|
+
TApplication extends Application,
|
|
31
|
+
TUser extends TBasicUser,
|
|
32
|
+
TRouter extends TAnyRouter = TServerRouter,
|
|
35
33
|
TRequest extends ServerRequest<TRouter> = ServerRequest<TRouter>,
|
|
36
34
|
> extends RouterService<
|
|
37
35
|
{ users: UsersService<TUser, TApplication> },
|
|
38
36
|
TRouter,
|
|
39
|
-
|
|
37
|
+
TUsersRequestContext<TUser>
|
|
40
38
|
> {
|
|
41
39
|
/*----------------------------------
|
|
42
40
|
- LIFECYCLE
|
|
@@ -45,7 +43,7 @@ export default class AuthenticationRouterService<
|
|
|
45
43
|
public users: UsersService<TUser, TApplication>;
|
|
46
44
|
|
|
47
45
|
public constructor(
|
|
48
|
-
getConfig: TRouterServiceArgs<{ users: UsersService<TUser, TApplication> }
|
|
46
|
+
getConfig: TRouterServiceArgs<{ users: UsersService<TUser, TApplication> }>[0],
|
|
49
47
|
app: TApplication,
|
|
50
48
|
) {
|
|
51
49
|
super(getConfig, app);
|
|
@@ -59,11 +57,11 @@ export default class AuthenticationRouterService<
|
|
|
59
57
|
details: Record<string, any>,
|
|
60
58
|
minimumCapture: 'summary' | 'resolve' | 'deep' = 'resolve',
|
|
61
59
|
) {
|
|
62
|
-
|
|
60
|
+
AppContainer.Trace.record(
|
|
63
61
|
request.id,
|
|
64
62
|
'auth.route',
|
|
65
63
|
{
|
|
66
|
-
routePath: route.path || '',
|
|
64
|
+
routePath: 'path' in route ? route.path || '' : '',
|
|
67
65
|
routeId: route.options.id || '',
|
|
68
66
|
authInput: route.options.auth ?? null,
|
|
69
67
|
tracking: route.options.authTracking ?? null,
|
|
@@ -219,7 +217,7 @@ export default class AuthenticationRouterService<
|
|
|
219
217
|
- ROUTER SERVICE LIFECYCLE
|
|
220
218
|
----------------------------------*/
|
|
221
219
|
|
|
222
|
-
public requestService(request: TRequest):
|
|
223
|
-
return
|
|
220
|
+
public requestService(request: TRequest): TUsersRequestContext<TUser> {
|
|
221
|
+
return createUsersRequestService(request, this.users);
|
|
224
222
|
}
|
|
225
223
|
}
|
|
@@ -2,13 +2,11 @@
|
|
|
2
2
|
- DEPENDANCES
|
|
3
3
|
----------------------------------*/
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
import type { Request as ServerRequest, TAnyRouter } from '@server/services/router';
|
|
7
|
-
import RequestService from '@server/services/router/request/service';
|
|
5
|
+
import type { Application } from '@server/app/index';
|
|
8
6
|
|
|
9
7
|
// Specific
|
|
10
|
-
import type
|
|
11
|
-
import type { TAuthCheckConditions, TAuthTrackingContext, TUserRole } from '..';
|
|
8
|
+
import type UsersService from '..';
|
|
9
|
+
import type { TAuthCheckConditions, TAuthRequest, TAuthTrackingContext, TUserRole } from '..';
|
|
12
10
|
|
|
13
11
|
// Types
|
|
14
12
|
import type { TBasicUser } from '@server/services/auth';
|
|
@@ -17,21 +15,36 @@ import type { TBasicUser } from '@server/services/auth';
|
|
|
17
15
|
- TYPES
|
|
18
16
|
----------------------------------*/
|
|
19
17
|
|
|
18
|
+
type TUsersRouterService<TUser extends TBasicUser> = {
|
|
19
|
+
users: UsersService<TUser, Application>;
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
export interface TUsersRequestContext<TUser extends TBasicUser> {
|
|
23
|
+
login(email: string): Promise<unknown>;
|
|
24
|
+
logout(): void;
|
|
25
|
+
|
|
26
|
+
check(): TUser;
|
|
27
|
+
check(conditions: null, tracking?: TAuthTrackingContext): TUser;
|
|
28
|
+
check(conditions: TAuthCheckConditions, tracking?: TAuthTrackingContext): TUser;
|
|
29
|
+
check(conditions: false, tracking?: TAuthTrackingContext): null;
|
|
30
|
+
check(role: TUserRole, feature: null): TUser;
|
|
31
|
+
check(role: false): null;
|
|
32
|
+
check(role: TUserRole | true, feature: FeatureKeys, action?: string): TUser;
|
|
33
|
+
check(role: false, feature: FeatureKeys, action?: string): null;
|
|
34
|
+
}
|
|
35
|
+
|
|
20
36
|
/*----------------------------------
|
|
21
37
|
- MODULE
|
|
22
38
|
----------------------------------*/
|
|
23
39
|
export default class UsersRequestService<
|
|
24
|
-
TRouter extends TAnyRouter,
|
|
25
40
|
TUser extends TBasicUser,
|
|
26
|
-
TRequest extends
|
|
27
|
-
>
|
|
41
|
+
TRequest extends TAuthRequest,
|
|
42
|
+
> implements TUsersRequestContext<TUser> {
|
|
28
43
|
public constructor(
|
|
29
|
-
request: TRequest,
|
|
30
|
-
public auth:
|
|
44
|
+
public request: TRequest,
|
|
45
|
+
public auth: TUsersRouterService<TUser>,
|
|
31
46
|
public users = auth.users,
|
|
32
|
-
) {
|
|
33
|
-
super(request);
|
|
34
|
-
}
|
|
47
|
+
) {}
|
|
35
48
|
|
|
36
49
|
public login(email: string) {
|
|
37
50
|
if (!this.users.login) throw new Error('The current auth service does not implement login().');
|
|
@@ -93,3 +106,11 @@ export default class UsersRequestService<
|
|
|
93
106
|
return this.users.check(this.request, roleOrConditions, featureOrTracking, action);
|
|
94
107
|
}
|
|
95
108
|
}
|
|
109
|
+
|
|
110
|
+
export const createUsersRequestService = <
|
|
111
|
+
TUser extends TBasicUser,
|
|
112
|
+
TRequest extends TAuthRequest,
|
|
113
|
+
>(
|
|
114
|
+
request: TRequest,
|
|
115
|
+
users: UsersService<TUser, Application>,
|
|
116
|
+
): TUsersRequestContext<TUser> => new UsersRequestService<TUser, TRequest>(request, { users });
|
|
@@ -27,7 +27,7 @@ export type TDrivercnfig = {
|
|
|
27
27
|
|
|
28
28
|
export type SourceFile = { name: string; path: string; modified: number; parentFolder: string; source: string };
|
|
29
29
|
|
|
30
|
-
export type TOutputFileOptions = { encoding:
|
|
30
|
+
export type TOutputFileOptions = { encoding: BufferEncoding };
|
|
31
31
|
|
|
32
32
|
export type TReadFileOptions = { encoding?: 'string' | 'buffer'; withMetas?: boolean };
|
|
33
33
|
|
|
@@ -32,8 +32,6 @@ export default class DisksManager<
|
|
|
32
32
|
TConfig extends Config & { default: keyof MountpointList & string; drivers: MountpointList },
|
|
33
33
|
TApplication extends Application,
|
|
34
34
|
> extends Service<TConfig, Hooks, TApplication, TApplication> {
|
|
35
|
-
public default!: MountpointList[keyof MountpointList & string];
|
|
36
|
-
|
|
37
35
|
/*----------------------------------
|
|
38
36
|
- LIFECYCLE
|
|
39
37
|
----------------------------------*/
|
|
@@ -50,10 +48,14 @@ export default class DisksManager<
|
|
|
50
48
|
drivers[driverId].parent = this;
|
|
51
49
|
}*/
|
|
52
50
|
|
|
53
|
-
|
|
54
|
-
|
|
51
|
+
this.default;
|
|
52
|
+
}
|
|
55
53
|
|
|
56
|
-
|
|
54
|
+
public get default(): Driver {
|
|
55
|
+
const drivers: Services = this.config.drivers;
|
|
56
|
+
const defaultDisk = drivers[this.config.default];
|
|
57
|
+
if (defaultDisk === undefined) throw new Error(`Default disk "${String(this.config.default)}" not mounted.`);
|
|
58
|
+
return defaultDisk;
|
|
57
59
|
}
|
|
58
60
|
|
|
59
61
|
public async shutdown() {}
|
|
@@ -62,13 +64,14 @@ export default class DisksManager<
|
|
|
62
64
|
- LIFECYCLE
|
|
63
65
|
----------------------------------*/
|
|
64
66
|
|
|
65
|
-
public get(diskName?: 'default' | keyof MountpointList) {
|
|
67
|
+
public get(diskName?: 'default' | keyof MountpointList): Driver {
|
|
68
|
+
const drivers: Services = this.config.drivers;
|
|
66
69
|
const disk =
|
|
67
70
|
diskName === 'default' || diskName === undefined
|
|
68
71
|
? this.default
|
|
69
|
-
:
|
|
72
|
+
: drivers[String(diskName)];
|
|
70
73
|
|
|
71
|
-
if (disk === undefined) throw new Error(`Disk "${diskName
|
|
74
|
+
if (disk === undefined) throw new Error(`Disk "${String(diskName)}" not found.`);
|
|
72
75
|
|
|
73
76
|
return disk;
|
|
74
77
|
}
|
|
@@ -57,7 +57,7 @@ type TOptions = { transporter?: string };
|
|
|
57
57
|
/*----------------------------------
|
|
58
58
|
- FONCTIONS
|
|
59
59
|
----------------------------------*/
|
|
60
|
-
export default abstract class Email<TConfig extends Config, TApplication extends Application
|
|
60
|
+
export default abstract class Email<TConfig extends Config, TApplication extends Application> extends Service<
|
|
61
61
|
TConfig,
|
|
62
62
|
Hooks,
|
|
63
63
|
TApplication,
|
|
@@ -1,9 +1,10 @@
|
|
|
1
|
-
import type { PrismaClient } from '@models/types';
|
|
2
|
-
|
|
3
1
|
export type TDelegate<R = unknown> = {
|
|
4
2
|
findMany(args?: Record<string, unknown>): Promise<R[]>;
|
|
5
3
|
findFirst(args?: Record<string, unknown>): Promise<R | null>;
|
|
6
4
|
};
|
|
5
|
+
type TPrismaRawClient = {
|
|
6
|
+
$queryRawUnsafe(query: string): Promise<Record<string, unknown>[]>;
|
|
7
|
+
};
|
|
7
8
|
|
|
8
9
|
export type TWithStats = { $table: string; $key: string } & Record<string, string>;
|
|
9
10
|
|
|
@@ -18,7 +19,7 @@ export default class Facet<
|
|
|
18
19
|
RT = R,
|
|
19
20
|
> {
|
|
20
21
|
constructor(
|
|
21
|
-
private readonly prisma:
|
|
22
|
+
private readonly prisma: TPrismaRawClient,
|
|
22
23
|
private readonly delegate: D,
|
|
23
24
|
private readonly subset: S,
|
|
24
25
|
private readonly transform?: Transform<R, RT>,
|
|
@@ -57,13 +58,13 @@ export default class Facet<
|
|
|
57
58
|
), 0)) as ${key}`,
|
|
58
59
|
);
|
|
59
60
|
|
|
60
|
-
const statRows =
|
|
61
|
+
const statRows = await this.prisma.$queryRawUnsafe(`
|
|
61
62
|
SELECT ${$key}, ${select.join(', ')}
|
|
62
63
|
FROM ${$table}
|
|
63
64
|
WHERE ${$key} IN (
|
|
64
65
|
${(results as Array<Record<string, unknown>>).map((row) => "'" + row[$key] + "'").join(',')}
|
|
65
66
|
)
|
|
66
|
-
`)
|
|
67
|
+
`);
|
|
67
68
|
|
|
68
69
|
for (const stat of statRows) {
|
|
69
70
|
for (const key in stat) {
|
|
@@ -19,7 +19,8 @@ import zod, { ZodError } from 'zod';
|
|
|
19
19
|
export { default as schema } from 'zod';
|
|
20
20
|
|
|
21
21
|
// Core
|
|
22
|
-
import
|
|
22
|
+
import type { Application } from '@server/app/index';
|
|
23
|
+
import Service, { TServiceArgs } from '@server/app/service';
|
|
23
24
|
import context from '@server/context';
|
|
24
25
|
import type DisksManager from '@server/services/disks';
|
|
25
26
|
import { CoreError, InputError, NotFound, toJson as errorToJson } from '@common/errors';
|
|
@@ -104,16 +105,16 @@ const staticHtmlCacheControl = 'public, max-age=0, must-revalidate';
|
|
|
104
105
|
----------------------------------*/
|
|
105
106
|
|
|
106
107
|
export type TAnyRouter = ServerRouter<
|
|
107
|
-
|
|
108
|
+
Application,
|
|
108
109
|
TRouterServicesList,
|
|
109
|
-
Config<TRouterServicesList,
|
|
110
|
+
Config<TRouterServicesList, Application>
|
|
110
111
|
>;
|
|
111
112
|
|
|
112
113
|
const LogPrefix = '[router]';
|
|
113
114
|
|
|
114
115
|
export type Config<
|
|
115
116
|
TServices extends TRouterServicesList,
|
|
116
|
-
TApplication extends
|
|
117
|
+
TApplication extends Application = Application,
|
|
117
118
|
> = {
|
|
118
119
|
debug: boolean;
|
|
119
120
|
|
|
@@ -147,16 +148,16 @@ export type TControllerDefinition = {
|
|
|
147
148
|
};
|
|
148
149
|
|
|
149
150
|
export type TServerRouter = ServerRouter<
|
|
150
|
-
|
|
151
|
+
Application,
|
|
151
152
|
TRouterServicesList,
|
|
152
|
-
Config<TRouterServicesList,
|
|
153
|
+
Config<TRouterServicesList, Application>
|
|
153
154
|
>;
|
|
154
155
|
|
|
155
156
|
/*----------------------------------
|
|
156
157
|
- CLASSE
|
|
157
158
|
----------------------------------*/
|
|
158
159
|
export default class ServerRouter<
|
|
159
|
-
TApplication extends
|
|
160
|
+
TApplication extends Application = Application,
|
|
160
161
|
TServices extends TRouterServicesList = TRouterServicesList,
|
|
161
162
|
TConfig extends Config<TServices, TApplication> = Config<TServices, TApplication>,
|
|
162
163
|
>
|
|
@@ -4,6 +4,8 @@ import zod from 'zod';
|
|
|
4
4
|
export type TRichTextValidatorOptions = { attachements?: boolean };
|
|
5
5
|
export type TValidationSchema = zod.ZodTypeAny;
|
|
6
6
|
export type TValidationShape = zod.ZodRawShape;
|
|
7
|
+
export type TInferValidationSchema<TSchema extends TValidationSchema> = zod.infer<TSchema>;
|
|
8
|
+
export type TTypedValidationSchema<TOutput> = zod.ZodType<TOutput>;
|
|
7
9
|
|
|
8
10
|
type TChoiceOption = { value: PrimitiveValue; label: string };
|
|
9
11
|
|
|
@@ -12,7 +12,7 @@ import express from 'express';
|
|
|
12
12
|
|
|
13
13
|
// Core
|
|
14
14
|
import context from '@server/context';
|
|
15
|
-
import type {
|
|
15
|
+
import type { default as ServerRouter, TServerRouter, TAnyRouter } from '@server/services/router';
|
|
16
16
|
import ServerRequest from '@server/services/router/request';
|
|
17
17
|
import { TMatchedRoute, TRoute, TAnyRoute } from '@common/router';
|
|
18
18
|
import { NotFound, Forbidden, Anomaly } from '@common/errors';
|
|
@@ -40,8 +40,7 @@ export type TBasicSSrData = {
|
|
|
40
40
|
currentDomain: string;
|
|
41
41
|
};
|
|
42
42
|
|
|
43
|
-
type TServerRouterApplication<TRouter extends TServerRouter> =
|
|
44
|
-
TRouter extends ServerRouter<infer TApplication, any, any> ? TApplication : never;
|
|
43
|
+
type TServerRouterApplication<TRouter extends TServerRouter> = TRouter['app'];
|
|
45
44
|
|
|
46
45
|
type TServerRouterPlugins<TRouter extends TServerRouter> =
|
|
47
46
|
TRouter extends ServerRouter<any, any, infer TConfig>
|
|
@@ -80,8 +79,10 @@ export type TRouterContextServices<
|
|
|
80
79
|
// Custom context via servuces
|
|
81
80
|
// For each roiuter service, return the request service (returned by roiuterService.requestService() )
|
|
82
81
|
{
|
|
83
|
-
[serviceName in keyof TPlugins]: TPlugins[serviceName] extends
|
|
84
|
-
?
|
|
82
|
+
[serviceName in keyof TPlugins]: TPlugins[serviceName] extends { requestService: infer TRequestServiceMethod }
|
|
83
|
+
? TRequestServiceMethod extends (...args: infer TRequestServiceArgs) => infer TRequestService
|
|
84
|
+
? Exclude<TRequestService, null | undefined>
|
|
85
|
+
: TPlugins[serviceName]
|
|
85
86
|
: TPlugins[serviceName];
|
|
86
87
|
};
|
|
87
88
|
|
|
@@ -97,7 +98,6 @@ const getRouteTraceTarget = (route: TAnyRoute<TRouterContext<TServerRouter>>) =>
|
|
|
97
98
|
----------------------------------*/
|
|
98
99
|
export default class ServerResponse<
|
|
99
100
|
TRouter extends TAnyRouter,
|
|
100
|
-
TRequestContext extends TRouterContext<TRouter> = TRouterContext<TRouter>,
|
|
101
101
|
TData extends TResponseData = TResponseData,
|
|
102
102
|
> extends BaseResponse<TData, ServerRequest<TRouter>> {
|
|
103
103
|
// Services
|
|
@@ -220,13 +220,13 @@ export default class ServerResponse<
|
|
|
220
220
|
----------------------------------*/
|
|
221
221
|
|
|
222
222
|
// Start controller services
|
|
223
|
-
private async createContext(route: TAnyRoute<TRouterContext<TRouter>>): Promise<
|
|
223
|
+
private async createContext(route: TAnyRoute<TRouterContext<TRouter>>): Promise<TRouterContext<TRouter>> {
|
|
224
224
|
const contextServices = this.router.createContextServices(this.request);
|
|
225
225
|
const controllers = createControllers(this.request.api);
|
|
226
226
|
const customSsrData = this.router.config.context(this.request, this.app) as TRouterRequestContext<TRouter>;
|
|
227
227
|
|
|
228
228
|
// TODO: transmiss safe data (especially for Router), as Router info could be printed on client side
|
|
229
|
-
const requestContext = {
|
|
229
|
+
const requestContext: TRouterContext<TRouter> = {
|
|
230
230
|
// Router context
|
|
231
231
|
app: this.app,
|
|
232
232
|
context: undefined!,
|
|
@@ -242,7 +242,7 @@ export default class ServerResponse<
|
|
|
242
242
|
// Router services
|
|
243
243
|
...(contextServices as TRouterContextServices<TRouter>),
|
|
244
244
|
...customSsrData,
|
|
245
|
-
}
|
|
245
|
+
};
|
|
246
246
|
|
|
247
247
|
requestContext.context = requestContext;
|
|
248
248
|
|
|
@@ -7,15 +7,16 @@ import type { Application } from '@server/app/index';
|
|
|
7
7
|
import Service, { TSetupConfig } from '@server/app/service';
|
|
8
8
|
|
|
9
9
|
// Specific
|
|
10
|
-
import type { default as Router } from '.';
|
|
11
10
|
import type ServerRequest from './request';
|
|
12
11
|
import type { TAnyRouter } from '.';
|
|
13
12
|
|
|
14
|
-
export type AnyRouterService =
|
|
13
|
+
export type AnyRouterService = Service<{}, {}, Application, object> & {
|
|
14
|
+
requestService(request: object): object | null;
|
|
15
|
+
};
|
|
15
16
|
|
|
16
|
-
export type TRouterServiceArgs<TConfig extends {} = {}
|
|
17
|
+
export type TRouterServiceArgs<TConfig extends {} = {}> = [
|
|
17
18
|
getConfig: TSetupConfig<TConfig> | null | undefined,
|
|
18
|
-
app:
|
|
19
|
+
app: Application,
|
|
19
20
|
];
|
|
20
21
|
|
|
21
22
|
/*----------------------------------
|
|
@@ -23,11 +24,14 @@ export type TRouterServiceArgs<TConfig extends {} = {}, TRouter extends TAnyRout
|
|
|
23
24
|
----------------------------------*/
|
|
24
25
|
export default abstract class RouterService<
|
|
25
26
|
TConfig extends {},
|
|
26
|
-
TRouter extends TAnyRouter
|
|
27
|
+
TRouter extends TAnyRouter,
|
|
27
28
|
TRequestService extends object | null = object | null,
|
|
28
|
-
> extends Service<TConfig, {},
|
|
29
|
-
public
|
|
30
|
-
|
|
29
|
+
> extends Service<TConfig, {}, Application, object> {
|
|
30
|
+
public declare parent: TRouter;
|
|
31
|
+
public declare app: TRouter extends { app: infer TApplication extends Application } ? TApplication : Application;
|
|
32
|
+
|
|
33
|
+
public constructor(...[config, app]: TRouterServiceArgs<TConfig>) {
|
|
34
|
+
super(app, config, app);
|
|
31
35
|
}
|
|
32
36
|
|
|
33
37
|
public abstract requestService(request: ServerRequest<TRouter>): TRequestService;
|
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
const assert = require('node:assert/strict');
|
|
2
|
+
const fs = require('node:fs');
|
|
3
|
+
const os = require('node:os');
|
|
4
|
+
const path = require('node:path');
|
|
5
|
+
const test = require('node:test');
|
|
6
|
+
|
|
7
|
+
const coreRoot = path.resolve(__dirname, '..');
|
|
8
|
+
process.env.TS_NODE_PROJECT = path.join(coreRoot, 'cli', 'tsconfig.json');
|
|
9
|
+
process.env.TS_NODE_TRANSPILE_ONLY = '1';
|
|
10
|
+
require('ts-node/register/transpile-only');
|
|
11
|
+
|
|
12
|
+
const { configureProjectAgentInstructions, resolveProjectAgentMonorepoRoot } = require('../cli/utils/agents.ts');
|
|
13
|
+
|
|
14
|
+
const writeFile = (filepath, content) => {
|
|
15
|
+
fs.mkdirSync(path.dirname(filepath), { recursive: true });
|
|
16
|
+
fs.writeFileSync(filepath, content);
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
const makeTempRoot = () => fs.mkdtempSync(path.join(os.tmpdir(), 'proteum-agents-'));
|
|
20
|
+
|
|
21
|
+
const createCoreFixture = () => {
|
|
22
|
+
const root = makeTempRoot();
|
|
23
|
+
const agentsRoot = path.join(root, 'agents', 'project');
|
|
24
|
+
|
|
25
|
+
writeFile(path.join(agentsRoot, 'AGENTS.md'), '# Root Contract\n\n- Root rule\n');
|
|
26
|
+
writeFile(path.join(agentsRoot, 'CODING_STYLE.md'), '# Coding Style\n\n- Style rule\n');
|
|
27
|
+
writeFile(path.join(agentsRoot, 'client', 'AGENTS.md'), '# Client Rules\n\n- Client rule\n');
|
|
28
|
+
|
|
29
|
+
return root;
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
const createAppFixture = () => {
|
|
33
|
+
const appRoot = makeTempRoot();
|
|
34
|
+
|
|
35
|
+
for (const dir of ['client/pages', 'server/routes', 'server/services', 'tests/e2e']) {
|
|
36
|
+
fs.mkdirSync(path.join(appRoot, dir), { recursive: true });
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
writeFile(
|
|
40
|
+
path.join(appRoot, '.gitignore'),
|
|
41
|
+
[
|
|
42
|
+
'node_modules',
|
|
43
|
+
'# Proteum-managed instruction files',
|
|
44
|
+
'/AGENTS.md',
|
|
45
|
+
'/CODING_STYLE.md',
|
|
46
|
+
'# End Proteum-managed instruction files',
|
|
47
|
+
'/.proteum',
|
|
48
|
+
'',
|
|
49
|
+
].join('\n'),
|
|
50
|
+
);
|
|
51
|
+
|
|
52
|
+
return appRoot;
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
test('standalone configure creates tracked instruction files with embedded corpus', () => {
|
|
56
|
+
const coreRoot = createCoreFixture();
|
|
57
|
+
const appRoot = createAppFixture();
|
|
58
|
+
const result = configureProjectAgentInstructions({ appRoot, coreRoot });
|
|
59
|
+
const agentsContent = fs.readFileSync(path.join(appRoot, 'AGENTS.md'), 'utf8');
|
|
60
|
+
const codingStyleContent = fs.readFileSync(path.join(appRoot, 'CODING_STYLE.md'), 'utf8');
|
|
61
|
+
const gitignoreContent = fs.readFileSync(path.join(appRoot, '.gitignore'), 'utf8');
|
|
62
|
+
|
|
63
|
+
assert.equal(result.blocked.length, 0);
|
|
64
|
+
assert.match(agentsContent, /^# Proteum Instructions/m);
|
|
65
|
+
assert.match(agentsContent, /<!-- proteum-instructions:start -->/);
|
|
66
|
+
assert.match(agentsContent, /## Source: AGENTS\.md/);
|
|
67
|
+
assert.match(agentsContent, /## Root Contract/);
|
|
68
|
+
assert.match(agentsContent, /## Source: CODING_STYLE\.md/);
|
|
69
|
+
assert.match(codingStyleContent, /## Source: client\/AGENTS\.md/);
|
|
70
|
+
assert.doesNotMatch(agentsContent, /Before reading or applying instructions from this file/);
|
|
71
|
+
assert.doesNotMatch(gitignoreContent, /Proteum-managed instruction files/);
|
|
72
|
+
assert.doesNotMatch(gitignoreContent, /^\/AGENTS\.md$/m);
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
test('configure preserves project content outside the managed section', () => {
|
|
76
|
+
const coreRoot = createCoreFixture();
|
|
77
|
+
const appRoot = createAppFixture();
|
|
78
|
+
|
|
79
|
+
writeFile(
|
|
80
|
+
path.join(appRoot, 'AGENTS.md'),
|
|
81
|
+
[
|
|
82
|
+
'# Product Notes',
|
|
83
|
+
'',
|
|
84
|
+
'Keep this product note.',
|
|
85
|
+
'',
|
|
86
|
+
'# Proteum Instructions',
|
|
87
|
+
'<!-- proteum-instructions:start -->',
|
|
88
|
+
'',
|
|
89
|
+
'Old managed content.',
|
|
90
|
+
'',
|
|
91
|
+
'<!-- proteum-instructions:end -->',
|
|
92
|
+
'',
|
|
93
|
+
'# Local Footer',
|
|
94
|
+
'',
|
|
95
|
+
'Keep this footer.',
|
|
96
|
+
'',
|
|
97
|
+
].join('\n'),
|
|
98
|
+
);
|
|
99
|
+
|
|
100
|
+
configureProjectAgentInstructions({ appRoot, coreRoot });
|
|
101
|
+
|
|
102
|
+
const content = fs.readFileSync(path.join(appRoot, 'AGENTS.md'), 'utf8');
|
|
103
|
+
assert.match(content, /# Product Notes/);
|
|
104
|
+
assert.match(content, /Keep this product note\./);
|
|
105
|
+
assert.match(content, /## Source: CODING_STYLE\.md/);
|
|
106
|
+
assert.doesNotMatch(content, /Old managed content/);
|
|
107
|
+
assert.match(content, /# Local Footer/);
|
|
108
|
+
assert.match(content, /Keep this footer\./);
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
test('configure preserves project content around legacy managed stubs', () => {
|
|
112
|
+
const coreRoot = createCoreFixture();
|
|
113
|
+
const appRoot = createAppFixture();
|
|
114
|
+
|
|
115
|
+
writeFile(
|
|
116
|
+
path.join(appRoot, 'AGENTS.md'),
|
|
117
|
+
[
|
|
118
|
+
'## Product Bootstrap',
|
|
119
|
+
'',
|
|
120
|
+
'Keep these local bootstrap notes.',
|
|
121
|
+
'',
|
|
122
|
+
'# Proteum Managed Instructions',
|
|
123
|
+
'',
|
|
124
|
+
'This file is managed by `proteum configure agents`.',
|
|
125
|
+
'',
|
|
126
|
+
'Before reading or applying instructions from this file, read and follow the canonical Proteum instruction file at:',
|
|
127
|
+
'',
|
|
128
|
+
'`node_modules/proteum/agents/project/AGENTS.md`',
|
|
129
|
+
'',
|
|
130
|
+
'Resolve that path relative to this file. Treat the canonical file as if its full contents were written here.',
|
|
131
|
+
'',
|
|
132
|
+
'If the canonical file cannot be read, stop and run `npx proteum configure agents` before continuing.',
|
|
133
|
+
'',
|
|
134
|
+
'## Local Footer',
|
|
135
|
+
'',
|
|
136
|
+
'Keep this footer too.',
|
|
137
|
+
'',
|
|
138
|
+
].join('\n'),
|
|
139
|
+
);
|
|
140
|
+
|
|
141
|
+
configureProjectAgentInstructions({ appRoot, coreRoot });
|
|
142
|
+
|
|
143
|
+
const content = fs.readFileSync(path.join(appRoot, 'AGENTS.md'), 'utf8');
|
|
144
|
+
assert.match(content, /## Product Bootstrap/);
|
|
145
|
+
assert.match(content, /Keep these local bootstrap notes\./);
|
|
146
|
+
assert.match(content, /# Proteum Instructions/);
|
|
147
|
+
assert.match(content, /## Source: CODING_STYLE\.md/);
|
|
148
|
+
assert.doesNotMatch(content, /# Proteum Managed Instructions/);
|
|
149
|
+
assert.doesNotMatch(content, /Before reading or applying instructions from this file/);
|
|
150
|
+
assert.match(content, /## Local Footer/);
|
|
151
|
+
assert.match(content, /Keep this footer too\./);
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
test('monorepo configure writes root and app instruction files', () => {
|
|
155
|
+
const coreRoot = createCoreFixture();
|
|
156
|
+
const monorepoRoot = makeTempRoot();
|
|
157
|
+
const appRoot = path.join(monorepoRoot, 'apps', 'product');
|
|
158
|
+
|
|
159
|
+
fs.mkdirSync(path.join(monorepoRoot, '.git'));
|
|
160
|
+
fs.mkdirSync(path.join(appRoot, 'client'), { recursive: true });
|
|
161
|
+
|
|
162
|
+
const result = configureProjectAgentInstructions({ appRoot, coreRoot, monorepoRoot });
|
|
163
|
+
|
|
164
|
+
assert.equal(result.mode, 'monorepo');
|
|
165
|
+
assert.equal(resolveProjectAgentMonorepoRoot(appRoot), fs.realpathSync(monorepoRoot));
|
|
166
|
+
assert.match(fs.readFileSync(path.join(monorepoRoot, 'AGENTS.md'), 'utf8'), /## Source: AGENTS\.md/);
|
|
167
|
+
assert.match(fs.readFileSync(path.join(appRoot, 'AGENTS.md'), 'utf8'), /## Source: client\/AGENTS\.md/);
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
test('configure migrates legacy managed symlinks to embedded files', () => {
|
|
171
|
+
const coreRoot = createCoreFixture();
|
|
172
|
+
const appRoot = createAppFixture();
|
|
173
|
+
const installedCoreRoot = createCoreFixture();
|
|
174
|
+
const target = path.join(installedCoreRoot, 'agents', 'project', 'AGENTS.md');
|
|
175
|
+
const linkPath = path.join(appRoot, 'AGENTS.md');
|
|
176
|
+
|
|
177
|
+
fs.symlinkSync(target, linkPath);
|
|
178
|
+
|
|
179
|
+
const result = configureProjectAgentInstructions({ appRoot, coreRoot });
|
|
180
|
+
const stats = fs.lstatSync(linkPath);
|
|
181
|
+
const content = fs.readFileSync(linkPath, 'utf8');
|
|
182
|
+
|
|
183
|
+
assert.equal(result.updated.some((entry) => entry.endsWith('/AGENTS.md')), true);
|
|
184
|
+
assert.equal(stats.isSymbolicLink(), false);
|
|
185
|
+
assert.match(content, /# Proteum Instructions/);
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
test('configure reports blocked paths unless overwrite is allowed', () => {
|
|
189
|
+
const coreRoot = createCoreFixture();
|
|
190
|
+
const appRoot = createAppFixture();
|
|
191
|
+
const blockedPath = path.join(appRoot, 'CODING_STYLE.md');
|
|
192
|
+
|
|
193
|
+
fs.mkdirSync(blockedPath);
|
|
194
|
+
|
|
195
|
+
const preview = configureProjectAgentInstructions({ appRoot, coreRoot, dryRun: true });
|
|
196
|
+
assert.equal(preview.blocked.some((entry) => entry.endsWith('/CODING_STYLE.md')), true);
|
|
197
|
+
|
|
198
|
+
const result = configureProjectAgentInstructions({
|
|
199
|
+
appRoot,
|
|
200
|
+
coreRoot,
|
|
201
|
+
overwriteBlockedPaths: [blockedPath],
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
assert.equal(result.overwritten.some((entry) => entry.endsWith('/CODING_STYLE.md')), true);
|
|
205
|
+
assert.equal(fs.lstatSync(blockedPath).isFile(), true);
|
|
206
|
+
assert.match(fs.readFileSync(blockedPath, 'utf8'), /## Source: AGENTS\.md/);
|
|
207
|
+
});
|