@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.
Files changed (37) hide show
  1. package/README.md +60 -0
  2. package/cli.js +50 -0
  3. package/models.config.example.js +24 -0
  4. package/package.json +37 -0
  5. package/templates/_lib_template/ng-package.json +7 -0
  6. package/templates/_lib_template/package.json +12 -0
  7. package/templates/_lib_template/src/lib/bridge/credentials.policy.ts +9 -0
  8. package/templates/_lib_template/src/lib/bridge/rest/api-platform.adapter.ts +101 -0
  9. package/templates/_lib_template/src/lib/bridge/rest/query-builder.ts +43 -0
  10. package/templates/_lib_template/src/lib/bridge/sse/eventsource-wrapper.ts +74 -0
  11. package/templates/_lib_template/src/lib/bridge/sse/mercure-url.builder.ts +17 -0
  12. package/templates/_lib_template/src/lib/bridge/sse/mercure.adapter.ts +206 -0
  13. package/templates/_lib_template/src/lib/bridge/sse/ref-count-topic.registry.ts +43 -0
  14. package/templates/_lib_template/src/lib/facades/facade.factory.ts +36 -0
  15. package/templates/_lib_template/src/lib/facades/facade.interface.ts +35 -0
  16. package/templates/_lib_template/src/lib/facades/resource.facade.ts +115 -0
  17. package/templates/_lib_template/src/lib/interceptors/content-type.interceptor.ts +50 -0
  18. package/templates/_lib_template/src/lib/ports/realtime.port.ts +34 -0
  19. package/templates/_lib_template/src/lib/ports/resource-repository.port.ts +79 -0
  20. package/templates/_lib_template/src/lib/provide-bridge.ts +35 -0
  21. package/templates/_lib_template/src/lib/tokens.ts +7 -0
  22. package/templates/_lib_template/src/lib/utils/utils.ts +21 -0
  23. package/templates/_lib_template/src/models/index.ts +2 -0
  24. package/templates/_lib_template/src/public-api.ts +8 -0
  25. package/templates/_lib_template/tsconfig.lib.json +18 -0
  26. package/templates/_lib_template/tsconfig.lib.prod.json +11 -0
  27. package/templates/_lib_template/tsconfig.spec.json +14 -0
  28. package/tools/generate-lib.js +136 -0
  29. package/tools/generate-models.js +121 -0
  30. package/tools/generator/models/handlebars.js +15 -0
  31. package/tools/generator/models/naming.js +20 -0
  32. package/tools/generator/models/openapi-to-models.js +98 -0
  33. package/tools/generator/models/schema-utils.js +125 -0
  34. package/tools/generator/models/templates/index.hbs +3 -0
  35. package/tools/generator/models/templates/model.hbs +10 -0
  36. package/tools/generator/models/type-resolver.js +96 -0
  37. 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,2 @@
1
+ export {};
2
+
@@ -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
+ });