@obsidiane/meridiane 0.0.6
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/README.md +60 -0
- package/cli.js +50 -0
- package/models.config.example.js +24 -0
- package/package.json +37 -0
- package/templates/_lib_template/ng-package.json +7 -0
- package/templates/_lib_template/package.json +12 -0
- package/templates/_lib_template/src/lib/bridge/credentials.policy.ts +9 -0
- package/templates/_lib_template/src/lib/bridge/rest/api-platform.adapter.ts +101 -0
- package/templates/_lib_template/src/lib/bridge/rest/query-builder.ts +43 -0
- package/templates/_lib_template/src/lib/bridge/sse/eventsource-wrapper.ts +74 -0
- package/templates/_lib_template/src/lib/bridge/sse/mercure-url.builder.ts +17 -0
- package/templates/_lib_template/src/lib/bridge/sse/mercure.adapter.ts +206 -0
- package/templates/_lib_template/src/lib/bridge/sse/ref-count-topic.registry.ts +43 -0
- package/templates/_lib_template/src/lib/facades/facade.factory.ts +36 -0
- package/templates/_lib_template/src/lib/facades/facade.interface.ts +35 -0
- package/templates/_lib_template/src/lib/facades/resource.facade.ts +115 -0
- package/templates/_lib_template/src/lib/interceptors/content-type.interceptor.ts +50 -0
- package/templates/_lib_template/src/lib/ports/realtime.port.ts +34 -0
- package/templates/_lib_template/src/lib/ports/resource-repository.port.ts +79 -0
- package/templates/_lib_template/src/lib/provide-bridge.ts +35 -0
- package/templates/_lib_template/src/lib/tokens.ts +7 -0
- package/templates/_lib_template/src/lib/utils/utils.ts +21 -0
- package/templates/_lib_template/src/models/index.ts +2 -0
- package/templates/_lib_template/src/public-api.ts +8 -0
- package/templates/_lib_template/tsconfig.lib.json +18 -0
- package/templates/_lib_template/tsconfig.lib.prod.json +11 -0
- package/templates/_lib_template/tsconfig.spec.json +14 -0
- package/tools/generate-lib.js +136 -0
- package/tools/generate-models.js +121 -0
- package/tools/generator/models/handlebars.js +15 -0
- package/tools/generator/models/naming.js +20 -0
- package/tools/generator/models/openapi-to-models.js +98 -0
- package/tools/generator/models/schema-utils.js +125 -0
- package/tools/generator/models/templates/index.hbs +3 -0
- package/tools/generator/models/templates/model.hbs +10 -0
- package/tools/generator/models/type-resolver.js +96 -0
- package/tools/generator/models/utils.js +63 -0
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import {Injectable, Inject, runInInjectionContext, inject, EnvironmentInjector} from '@angular/core';
|
|
2
|
+
import {HttpClient} from '@angular/common/http';
|
|
3
|
+
import {RealtimePort} from '../ports/realtime.port';
|
|
4
|
+
import {Item, ResourceRepository} from '../ports/resource-repository.port';
|
|
5
|
+
import {API_BASE_URL, MERCURE_CONFIG} from "../tokens";
|
|
6
|
+
import {MercureRealtimeAdapter} from "../bridge/sse/mercure.adapter";
|
|
7
|
+
import {ApiPlatformRestRepository} from "../bridge/rest/api-platform.adapter";
|
|
8
|
+
import {ResourceFacade} from "./resource.facade";
|
|
9
|
+
|
|
10
|
+
export type FacadeConfig<T> = {
|
|
11
|
+
url: string;
|
|
12
|
+
repo?: ResourceRepository<T>;
|
|
13
|
+
realtime?: RealtimePort;
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
@Injectable({providedIn: 'root'})
|
|
18
|
+
export class FacadeFactory {
|
|
19
|
+
|
|
20
|
+
private env = inject(EnvironmentInjector);
|
|
21
|
+
|
|
22
|
+
constructor(
|
|
23
|
+
private readonly http: HttpClient,
|
|
24
|
+
@Inject(API_BASE_URL) private readonly baseUrl: string,
|
|
25
|
+
@Inject(MERCURE_CONFIG) private readonly init: string,
|
|
26
|
+
private readonly mercureAny: MercureRealtimeAdapter
|
|
27
|
+
) {
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
create<T extends Item>(config: FacadeConfig<T>): ResourceFacade<T> {
|
|
31
|
+
const path = config.url;
|
|
32
|
+
const repo = new ApiPlatformRestRepository<T>(this.http, this.baseUrl, path , this.init);
|
|
33
|
+
const realtime = this.mercureAny as MercureRealtimeAdapter;
|
|
34
|
+
return runInInjectionContext(this.env, () => new ResourceFacade<T>(repo, realtime, path));
|
|
35
|
+
}
|
|
36
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import {
|
|
2
|
+
Collection,
|
|
3
|
+
CreateCommand,
|
|
4
|
+
HttpRequestConfig,
|
|
5
|
+
Iri,
|
|
6
|
+
Item,
|
|
7
|
+
Query,
|
|
8
|
+
UpdateCommand
|
|
9
|
+
} from "../ports/resource-repository.port";
|
|
10
|
+
import {Observable} from "rxjs";
|
|
11
|
+
|
|
12
|
+
export interface Facade<T extends Item> {
|
|
13
|
+
|
|
14
|
+
list$(query?: Query): Observable<Collection<T>>;
|
|
15
|
+
|
|
16
|
+
get$(iri: Iri): Observable<T>;
|
|
17
|
+
|
|
18
|
+
create$(cmd: CreateCommand<T>): Observable<T>;
|
|
19
|
+
|
|
20
|
+
update$(cmd: UpdateCommand<T>): Observable<T>;
|
|
21
|
+
|
|
22
|
+
delete$(iri: Iri): Observable<void>;
|
|
23
|
+
|
|
24
|
+
post$<R = unknown, B = unknown>(req: RestRequest<B>): Observable<R>;
|
|
25
|
+
put$<R = unknown, B = unknown>(req: RestRequest<B>): Observable<R>;
|
|
26
|
+
patch$<R = unknown, B = unknown>(req: RestRequest<B>): Observable<R>;
|
|
27
|
+
|
|
28
|
+
request$<R = unknown, B = unknown>(req: HttpRequestConfig<B>): Observable<R>;
|
|
29
|
+
|
|
30
|
+
watch$(iri: Iri|Iri[]): Observable<T>;
|
|
31
|
+
|
|
32
|
+
unwatch(iri: Iri|Iri[]): void;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export type RestRequest<B> = Omit<HttpRequestConfig<B>, 'method'>;
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import {Signal} from '@angular/core';
|
|
2
|
+
import {
|
|
3
|
+
ResourceRepository,
|
|
4
|
+
Query,
|
|
5
|
+
CreateCommand,
|
|
6
|
+
UpdateCommand,
|
|
7
|
+
Collection, Iri, Item, HttpRequestConfig
|
|
8
|
+
} from '../ports/resource-repository.port';
|
|
9
|
+
import {RealtimePort, RealtimeStatus} from '../ports/realtime.port';
|
|
10
|
+
import {toSignal} from '@angular/core/rxjs-interop';
|
|
11
|
+
import {map, shareReplay, filter, Observable} from 'rxjs';
|
|
12
|
+
import {Facade, RestRequest} from './facade.interface';
|
|
13
|
+
|
|
14
|
+
export class ResourceFacade<T extends Item> implements Facade<T> {
|
|
15
|
+
|
|
16
|
+
readonly connectionStatus: Signal<RealtimeStatus>;
|
|
17
|
+
|
|
18
|
+
constructor(
|
|
19
|
+
protected readonly repo: ResourceRepository<T>,
|
|
20
|
+
protected readonly realtime: RealtimePort,
|
|
21
|
+
protected readonly resourcePath: string
|
|
22
|
+
) {
|
|
23
|
+
this.resourcePath = resourcePath;
|
|
24
|
+
this.connectionStatus = toSignal(
|
|
25
|
+
this.realtime.status$(),
|
|
26
|
+
{initialValue: 'offline' as RealtimeStatus}
|
|
27
|
+
);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
list$(query?: Query): Observable<Collection<T>> {
|
|
31
|
+
return this.repo.list$(query)
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
get$(iri: Iri): Observable<T> {
|
|
35
|
+
return this.repo.get$(iri).pipe(
|
|
36
|
+
shareReplay({bufferSize: 1, refCount: true})
|
|
37
|
+
);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
create$(cmd: CreateCommand<T>) {
|
|
41
|
+
return this.repo.create$(cmd).pipe(
|
|
42
|
+
shareReplay({bufferSize: 1, refCount: true})
|
|
43
|
+
);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
update$(cmd: UpdateCommand<T>) {
|
|
47
|
+
return this.repo.update$(cmd).pipe(
|
|
48
|
+
shareReplay({bufferSize: 1, refCount: true})
|
|
49
|
+
);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
delete$(iri: Iri): Observable<void> {
|
|
53
|
+
return this.repo.delete$(iri).pipe(
|
|
54
|
+
shareReplay({bufferSize: 1, refCount: true})
|
|
55
|
+
);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
request$<R = unknown, B = unknown>(req: HttpRequestConfig<B>): Observable<R> {
|
|
59
|
+
return this.repo.request$<R, B>(req).pipe(
|
|
60
|
+
shareReplay({bufferSize: 1, refCount: true})
|
|
61
|
+
);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
post$<R = unknown, B = unknown>(req: RestRequest<B> = {}): Observable<R> {
|
|
65
|
+
return this.request$<R, B>({...req, method: 'POST'});
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
put$<R = unknown, B = unknown>(req: RestRequest<B> = {}): Observable<R> {
|
|
69
|
+
return this.request$<R, B>({...req, method: 'PUT'});
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
patch$<R = unknown, B = unknown>(req: RestRequest<B> = {}): Observable<R> {
|
|
73
|
+
return this.request$<R, B>({...req, method: 'PATCH'});
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
watch$(iri: Iri | Iri[]): Observable<T> {
|
|
77
|
+
if (Array.isArray(iri)) {
|
|
78
|
+
return this.subscribeAndSync(iri);
|
|
79
|
+
}
|
|
80
|
+
return this.subscribeAndSync([iri]);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
unwatch(iri: Iri | Iri[]): void {
|
|
85
|
+
if (Array.isArray(iri)) {
|
|
86
|
+
this.realtime.unsubscribe(iri);
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
this.realtime.unsubscribe([iri]);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
watchSubResource$<R>(
|
|
93
|
+
iri: Iri | Iri[],
|
|
94
|
+
field: string
|
|
95
|
+
): Observable<R> {
|
|
96
|
+
const iris = Array.isArray(iri) ? iri : [iri];
|
|
97
|
+
return this.realtime
|
|
98
|
+
.subscribe$<R>(iris, {field: field})
|
|
99
|
+
.pipe(
|
|
100
|
+
map(e => e.data),
|
|
101
|
+
filter((d): d is R => d !== undefined),
|
|
102
|
+
shareReplay({bufferSize: 1, refCount: true})
|
|
103
|
+
);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
protected subscribeAndSync(iris: Iri[]): Observable<T> {
|
|
107
|
+
return this.realtime
|
|
108
|
+
.subscribe$<T>(iris)
|
|
109
|
+
.pipe(
|
|
110
|
+
map(event => event.data),
|
|
111
|
+
filter((data): data is T => data !== undefined),
|
|
112
|
+
shareReplay({bufferSize: 1, refCount: true})
|
|
113
|
+
)
|
|
114
|
+
}
|
|
115
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import {HttpInterceptorFn} from '@angular/common/http';
|
|
2
|
+
|
|
3
|
+
export interface ApiMediaTypes {
|
|
4
|
+
accept: string;
|
|
5
|
+
post: string;
|
|
6
|
+
put: string;
|
|
7
|
+
patch: string;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export const DEFAULT_MEDIA_TYPES: ApiMediaTypes = {
|
|
11
|
+
accept: 'application/ld+json',
|
|
12
|
+
post: 'application/ld+json',
|
|
13
|
+
put: 'application/ld+json',
|
|
14
|
+
patch: 'application/merge-patch+json'
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
export const contentTypeInterceptor: HttpInterceptorFn = (req, next) => {
|
|
18
|
+
const mediaTypes = DEFAULT_MEDIA_TYPES;
|
|
19
|
+
let headers = req.headers;
|
|
20
|
+
|
|
21
|
+
// Toujours définir Accept si absent
|
|
22
|
+
if (!headers.has('Accept')) {
|
|
23
|
+
headers = headers.set('Accept', mediaTypes.accept);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// Vérifie si la méthode peut avoir un body
|
|
27
|
+
const isBodyMethod = ['POST', 'PUT', 'PATCH'].includes(req.method);
|
|
28
|
+
// Ignore FormData (Content-Type sera défini par le navigateur)
|
|
29
|
+
const isFormData = typeof FormData !== 'undefined' && req.body instanceof FormData;
|
|
30
|
+
|
|
31
|
+
if (isBodyMethod && !isFormData && !headers.has('Content-Type')) {
|
|
32
|
+
let contentType: string;
|
|
33
|
+
|
|
34
|
+
switch (req.method) {
|
|
35
|
+
case 'PATCH':
|
|
36
|
+
contentType = mediaTypes.patch;
|
|
37
|
+
break;
|
|
38
|
+
case 'PUT':
|
|
39
|
+
contentType = mediaTypes.put;
|
|
40
|
+
break;
|
|
41
|
+
default:
|
|
42
|
+
contentType = mediaTypes.post;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
headers = headers.set('Content-Type', contentType);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// Cloner la requête avec les nouveaux headers
|
|
49
|
+
return next(req.clone({headers}));
|
|
50
|
+
};
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import {Observable} from 'rxjs';
|
|
2
|
+
import {Iri} from './resource-repository.port';
|
|
3
|
+
|
|
4
|
+
export type RealtimeStatus = 'connecting' | 'connected' | 'closed';
|
|
5
|
+
|
|
6
|
+
export interface SseEvent {
|
|
7
|
+
type: string;
|
|
8
|
+
data: string;
|
|
9
|
+
lastEventId?: string;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export interface SseOptions {
|
|
13
|
+
withCredentials?: boolean;
|
|
14
|
+
listen?: string[];
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
export interface RealtimeEvent<T> {
|
|
19
|
+
iri: string;
|
|
20
|
+
data?: T;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export type SubscribeFilter = {
|
|
24
|
+
field: string;
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
export interface RealtimePort {
|
|
29
|
+
subscribe$<T>(iris: Iri[], filter?: SubscribeFilter): Observable<RealtimeEvent<T>>;
|
|
30
|
+
|
|
31
|
+
unsubscribe(iris: Iri[]): void;
|
|
32
|
+
|
|
33
|
+
status$(): Observable<RealtimeStatus>;
|
|
34
|
+
}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import {HttpHeaders, HttpParams} from '@angular/common/http';
|
|
2
|
+
import {Observable} from 'rxjs';
|
|
3
|
+
|
|
4
|
+
export type Iri = string | undefined;
|
|
5
|
+
|
|
6
|
+
type BaseHttpMethod = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE' | 'OPTIONS' | 'HEAD';
|
|
7
|
+
export type HttpMethod = BaseHttpMethod | Lowercase<BaseHttpMethod>;
|
|
8
|
+
|
|
9
|
+
export type QueryParamValue = string | number | boolean | Array<string | number | boolean>;
|
|
10
|
+
export type AnyQuery = Query | Record<string, QueryParamValue> | HttpParams;
|
|
11
|
+
|
|
12
|
+
export interface Item {
|
|
13
|
+
'@id'?: Iri;
|
|
14
|
+
'@context'?: string,
|
|
15
|
+
'@type'?: string
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export interface View extends Item{
|
|
19
|
+
first?: Iri;
|
|
20
|
+
last?: Iri;
|
|
21
|
+
next?: Iri;
|
|
22
|
+
previous?: Iri;
|
|
23
|
+
}
|
|
24
|
+
export interface IriTemplateMapping extends Item {
|
|
25
|
+
variable: string;
|
|
26
|
+
property?: string;
|
|
27
|
+
required?: boolean;
|
|
28
|
+
}
|
|
29
|
+
export interface IriTemplate extends Item {
|
|
30
|
+
template: string;
|
|
31
|
+
variableRepresentation?: string;
|
|
32
|
+
mapping: IriTemplateMapping[];
|
|
33
|
+
}
|
|
34
|
+
export interface Collection<T> extends Item {
|
|
35
|
+
member: T[];
|
|
36
|
+
totalItems?: number;
|
|
37
|
+
search?: IriTemplate;
|
|
38
|
+
view?: View;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export interface Query {
|
|
42
|
+
itemsPerPage?: number;
|
|
43
|
+
page?: number;
|
|
44
|
+
filters?: Record<string, QueryParamValue>;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export interface CreateCommand<T> {
|
|
48
|
+
payload: T;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export interface UpdateCommand<T> {
|
|
52
|
+
iri?: Iri;
|
|
53
|
+
changes: Partial<T>;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export interface ResourceRepository<T> {
|
|
57
|
+
list$(query?: Query): Observable<Collection<T>>;
|
|
58
|
+
|
|
59
|
+
get$(iri: Iri): Observable<T>;
|
|
60
|
+
|
|
61
|
+
create$(cmd: CreateCommand<T>): Observable<T>;
|
|
62
|
+
|
|
63
|
+
update$(cmd: UpdateCommand<T>): Observable<T>;
|
|
64
|
+
|
|
65
|
+
delete$(iri: Iri): Observable<void>;
|
|
66
|
+
|
|
67
|
+
request$<R = unknown, B = unknown>(req: HttpRequestConfig<B>): Observable<R>;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export interface HttpRequestConfig<TBody = unknown> {
|
|
71
|
+
method: HttpMethod;
|
|
72
|
+
url?: Iri;
|
|
73
|
+
query?: AnyQuery;
|
|
74
|
+
body?: TBody;
|
|
75
|
+
headers?: HttpHeaders | Record<string, string>;
|
|
76
|
+
responseType?: 'json' | 'text' | 'blob';
|
|
77
|
+
withCredentials?: boolean;
|
|
78
|
+
options?: Record<string, unknown>;
|
|
79
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import {EnvironmentProviders, Provider, makeEnvironmentProviders} from '@angular/core';
|
|
2
|
+
import {provideHttpClient, withFetch, withInterceptors} from '@angular/common/http';
|
|
3
|
+
import {API_BASE_URL, MERCURE_CONFIG, MERCURE_HUB_URL} from './tokens';
|
|
4
|
+
import {contentTypeInterceptor} from './interceptors/content-type.interceptor';
|
|
5
|
+
|
|
6
|
+
export interface BridgeOptions {
|
|
7
|
+
apiBaseUrl: string;
|
|
8
|
+
mercure?: RequestInit;
|
|
9
|
+
mercureHubUrl?: string;
|
|
10
|
+
extraInterceptors?: any[];
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function provideBridge(opts: BridgeOptions): EnvironmentProviders {
|
|
14
|
+
const {
|
|
15
|
+
apiBaseUrl,
|
|
16
|
+
mercure = {credentials: 'include' as RequestCredentials},
|
|
17
|
+
mercureHubUrl,
|
|
18
|
+
extraInterceptors = [],
|
|
19
|
+
|
|
20
|
+
} = opts;
|
|
21
|
+
|
|
22
|
+
return makeEnvironmentProviders([
|
|
23
|
+
provideHttpClient(
|
|
24
|
+
withFetch(),
|
|
25
|
+
withInterceptors([
|
|
26
|
+
contentTypeInterceptor,
|
|
27
|
+
...extraInterceptors
|
|
28
|
+
|
|
29
|
+
])
|
|
30
|
+
),
|
|
31
|
+
{provide: API_BASE_URL, useValue: apiBaseUrl},
|
|
32
|
+
{provide: MERCURE_CONFIG, useValue: mercure},
|
|
33
|
+
...(mercureHubUrl ? [{provide: MERCURE_HUB_URL, useValue: mercureHubUrl}] : []),
|
|
34
|
+
]);
|
|
35
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import {InjectionToken} from '@angular/core';
|
|
2
|
+
|
|
3
|
+
export const API_BASE_URL = new InjectionToken<string>('API_BASE_URL');
|
|
4
|
+
|
|
5
|
+
export const MERCURE_HUB_URL = new InjectionToken<string>('MERCURE_HUB_URL');
|
|
6
|
+
|
|
7
|
+
export const MERCURE_CONFIG = new InjectionToken<RequestInit>('MERCURE_CONFIG');
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import {WritableSignal} from '@angular/core';
|
|
2
|
+
import {Item} from '../ports/resource-repository.port';
|
|
3
|
+
|
|
4
|
+
export function upsertInSignal(
|
|
5
|
+
signal: WritableSignal<readonly Item[]>,
|
|
6
|
+
item: Item,
|
|
7
|
+
): void {
|
|
8
|
+
signal.update(items => {
|
|
9
|
+
|
|
10
|
+
const id = item['@id'];
|
|
11
|
+
const existingIndex = items.findIndex(it => it['@id'] === id);
|
|
12
|
+
|
|
13
|
+
if (existingIndex === -1) {
|
|
14
|
+
return [item, ...items];
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const next = items.slice() as Item[];
|
|
18
|
+
next[existingIndex] = item;
|
|
19
|
+
return next;
|
|
20
|
+
});
|
|
21
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export * from './lib/provide-bridge';
|
|
2
|
+
export * from './lib/tokens';
|
|
3
|
+
export * from './lib/facades/facade.factory';
|
|
4
|
+
export * from './lib/facades/resource.facade';
|
|
5
|
+
export * from './lib/ports/resource-repository.port';
|
|
6
|
+
export * from './lib/utils/utils'
|
|
7
|
+
|
|
8
|
+
export * from './models';
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/* To learn more about Typescript configuration file: https://www.typescriptlang.org/docs/handbook/tsconfig-json.html. */
|
|
2
|
+
/* To learn more about Angular compiler options: https://angular.dev/reference/configs/angular-compiler-options. */
|
|
3
|
+
{
|
|
4
|
+
"extends": "../../tsconfig.json",
|
|
5
|
+
"compilerOptions": {
|
|
6
|
+
"outDir": "../../out-tsc/lib",
|
|
7
|
+
"declaration": true,
|
|
8
|
+
"declarationMap": true,
|
|
9
|
+
"inlineSources": true,
|
|
10
|
+
"types": []
|
|
11
|
+
},
|
|
12
|
+
"include": [
|
|
13
|
+
"src/**/*.ts"
|
|
14
|
+
],
|
|
15
|
+
"exclude": [
|
|
16
|
+
"**/*.spec.ts"
|
|
17
|
+
]
|
|
18
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/* To learn more about Typescript configuration file: https://www.typescriptlang.org/docs/handbook/tsconfig-json.html. */
|
|
2
|
+
/* To learn more about Angular compiler options: https://angular.dev/reference/configs/angular-compiler-options. */
|
|
3
|
+
{
|
|
4
|
+
"extends": "./tsconfig.lib.json",
|
|
5
|
+
"compilerOptions": {
|
|
6
|
+
"declarationMap": false
|
|
7
|
+
},
|
|
8
|
+
"angularCompilerOptions": {
|
|
9
|
+
"compilationMode": "partial"
|
|
10
|
+
}
|
|
11
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/* To learn more about Typescript configuration file: https://www.typescriptlang.org/docs/handbook/tsconfig-json.html. */
|
|
2
|
+
/* To learn more about Angular compiler options: https://angular.dev/reference/configs/angular-compiler-options. */
|
|
3
|
+
{
|
|
4
|
+
"extends": "../../tsconfig.json",
|
|
5
|
+
"compilerOptions": {
|
|
6
|
+
"outDir": "../../out-tsc/spec",
|
|
7
|
+
"types": [
|
|
8
|
+
"jasmine"
|
|
9
|
+
]
|
|
10
|
+
},
|
|
11
|
+
"include": [
|
|
12
|
+
"src/**/*.ts"
|
|
13
|
+
]
|
|
14
|
+
}
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import fs from "fs-extra"
|
|
3
|
+
import path from "path"
|
|
4
|
+
import { fileURLToPath } from 'url';
|
|
5
|
+
|
|
6
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
7
|
+
const __dirname = path.dirname(__filename);
|
|
8
|
+
/**
|
|
9
|
+
* Recursively replace all placeholders in files under a directory
|
|
10
|
+
* @param {string} srcDir - source directory path
|
|
11
|
+
* @param {Object.<string,string>} placeholders - map of placeholder -> value
|
|
12
|
+
*/
|
|
13
|
+
async function replacePlaceholdersInDir(srcDir, placeholders) {
|
|
14
|
+
const entries = await fs.readdir(srcDir, { withFileTypes: true });
|
|
15
|
+
for (const entry of entries) {
|
|
16
|
+
const fullPath = path.join(srcDir, entry.name);
|
|
17
|
+
if (entry.isDirectory()) {
|
|
18
|
+
await replacePlaceholdersInDir(fullPath, placeholders);
|
|
19
|
+
} else if (entry.isFile()) {
|
|
20
|
+
let content = await fs.readFile(fullPath, 'utf8');
|
|
21
|
+
for (const [key, value] of Object.entries(placeholders)) {
|
|
22
|
+
const regex = new RegExp(key, 'g');
|
|
23
|
+
content = content.replace(regex, value);
|
|
24
|
+
}
|
|
25
|
+
await fs.writeFile(fullPath, content);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Generate a new Angular library from template
|
|
32
|
+
* @param {string} libName - folder and project name
|
|
33
|
+
* @param {string} packageName - NPM package name (e.g. @org/lib)
|
|
34
|
+
* @param {string} version - initial version for the package
|
|
35
|
+
* @param {string} urlRegistry - NPM registry url
|
|
36
|
+
*/
|
|
37
|
+
async function generate(libName, packageName, version, urlRegistry) {
|
|
38
|
+
const tplDir = path.resolve(__dirname, '../templates/_lib_template');
|
|
39
|
+
const workspaceRoot = process.cwd();
|
|
40
|
+
const targetDir = path.resolve(workspaceRoot, 'projects', libName);
|
|
41
|
+
|
|
42
|
+
// 1) Copy the template directory
|
|
43
|
+
await fs.copy(tplDir, targetDir);
|
|
44
|
+
|
|
45
|
+
// 2) Replace placeholders __LIB_NAME__, __PACKAGE_NAME__, __VERSION__
|
|
46
|
+
const placeholders = {
|
|
47
|
+
'__LIB_NAME__': libName,
|
|
48
|
+
'__PACKAGE_NAME__': packageName,
|
|
49
|
+
'__VERSION__': version
|
|
50
|
+
};
|
|
51
|
+
await replacePlaceholdersInDir(targetDir, placeholders);
|
|
52
|
+
|
|
53
|
+
// 3) Update the library's package.json
|
|
54
|
+
const libPackageJsonPath = path.join(targetDir, 'package.json');
|
|
55
|
+
if (await fs.pathExists(libPackageJsonPath)) {
|
|
56
|
+
const libPkg = await fs.readJson(libPackageJsonPath);
|
|
57
|
+
libPkg.name = packageName;
|
|
58
|
+
libPkg.version = version;
|
|
59
|
+
if (urlRegistry) {
|
|
60
|
+
libPkg.publishConfig = {...(libPkg.publishConfig || {}), registry: urlRegistry};
|
|
61
|
+
} else if (libPkg.publishConfig?.registry) {
|
|
62
|
+
delete libPkg.publishConfig.registry;
|
|
63
|
+
if (Object.keys(libPkg.publishConfig).length === 0) delete libPkg.publishConfig;
|
|
64
|
+
}
|
|
65
|
+
await fs.writeJson(libPackageJsonPath, libPkg, { spaces: 2 });
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// 4) Update angular.json
|
|
69
|
+
const angularJsonPath = path.resolve(workspaceRoot, 'angular.json');
|
|
70
|
+
const ng = await fs.readJson(angularJsonPath);
|
|
71
|
+
|
|
72
|
+
ng.projects[libName] = {
|
|
73
|
+
root: `projects/${libName}`,
|
|
74
|
+
sourceRoot: `projects/${libName}/src`,
|
|
75
|
+
projectType: 'library',
|
|
76
|
+
"architect": {
|
|
77
|
+
"build": {
|
|
78
|
+
"builder": "@angular/build:ng-packagr",
|
|
79
|
+
"configurations": {
|
|
80
|
+
"production": {
|
|
81
|
+
"tsConfig": `projects/${libName}/tsconfig.lib.prod.json`
|
|
82
|
+
},
|
|
83
|
+
"development": {
|
|
84
|
+
"tsConfig": `projects/${libName}/tsconfig.lib.json`
|
|
85
|
+
}
|
|
86
|
+
},
|
|
87
|
+
"defaultConfiguration": "production"
|
|
88
|
+
},
|
|
89
|
+
"test": {
|
|
90
|
+
"builder": "@angular/build:karma",
|
|
91
|
+
"options": {
|
|
92
|
+
"tsConfig": `projects/${libName}/tsconfig.spec.json`
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
};
|
|
97
|
+
await fs.writeJson(angularJsonPath, ng, { spaces: 2 });
|
|
98
|
+
|
|
99
|
+
console.log(`✅ Library '${libName}' generated at projects/${libName}` +
|
|
100
|
+
` with package '${packageName}@${version}'`);
|
|
101
|
+
|
|
102
|
+
const ngPackagrPkg = findUpNodeModulePackageJson(workspaceRoot, 'ng-packagr');
|
|
103
|
+
if (!ngPackagrPkg) {
|
|
104
|
+
console.warn(
|
|
105
|
+
[
|
|
106
|
+
"⚠️ 'ng-packagr' n'est pas installé dans ce workspace.",
|
|
107
|
+
" Pour builder une librairie (ng-packagr), installez-le dans votre projet Angular :",
|
|
108
|
+
" npm i -D ng-packagr",
|
|
109
|
+
].join('\n')
|
|
110
|
+
);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function findUpNodeModulePackageJson(startDir, packageName) {
|
|
115
|
+
let dir = startDir;
|
|
116
|
+
while (true) {
|
|
117
|
+
const candidate = path.join(dir, 'node_modules', packageName, 'package.json');
|
|
118
|
+
if (fs.existsSync(candidate)) return candidate;
|
|
119
|
+
const parent = path.dirname(dir);
|
|
120
|
+
if (parent === dir) return undefined;
|
|
121
|
+
dir = parent;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// CLI args: node generate-lib.js <lib-name> <package-name> [version] <url-registry>
|
|
126
|
+
// CLI args: node generate-lib.js <lib-name> <package-name> [version] [url-registry]
|
|
127
|
+
const [,, libName, packageName, version = '0.0.1', urlRegistry] = process.argv;
|
|
128
|
+
if (!libName || !packageName) {
|
|
129
|
+
console.error('Usage: generate-lib.js <lib-name> <package-name> [version] [url-registry]');
|
|
130
|
+
process.exit(1);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
generate(libName, packageName, version, urlRegistry).catch(err => {
|
|
134
|
+
console.error(err);
|
|
135
|
+
process.exit(1);
|
|
136
|
+
});
|