@obsidiane/meridiane 2.0.10 → 2.0.12
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 +19 -2
- package/cli.js +21 -37
- package/package.json +13 -6
- package/src/README.md +9 -0
- package/{tools/app → src/application}/bridge-workflow.js +29 -23
- package/src/application/generate-bridge.js +50 -0
- package/{tools/app → src/application}/run-build.js +1 -1
- package/{tools/app → src/application}/run-dev.js +30 -36
- package/{tools/app → src/application}/run-generate.js +3 -3
- package/{tools → src}/domain/bridge-options.js +8 -21
- package/src/domain/list-values.js +6 -0
- package/src/generators/bridge/write-bridge-files.js +100 -0
- package/src/generators/models/interface-schema.js +116 -0
- package/src/generators/models/model-selection.js +148 -0
- package/src/generators/models/oas-contract.js +170 -0
- package/{tools/generator → src/generators}/models/oas-schema-name.js +6 -15
- package/src/generators/models/openapi-to-models.js +136 -0
- package/{tools/generator → src/generators}/models/schema-utils.js +27 -3
- package/src/generators/models/templates/type-alias.hbs +2 -0
- package/src/generators/models/type-expression.js +65 -0
- package/src/generators/models/type-resolver.js +240 -0
- package/src/generators/models/utils.js +89 -0
- package/src/generators/models/write-model-files.js +55 -0
- package/src/infrastructure/dev-project.js +48 -0
- package/{tools/infra → src/infrastructure}/exec.js +3 -2
- package/src/infrastructure/local-package-install.js +104 -0
- package/src/infrastructure/spec-loader.js +65 -0
- package/templates/{_lib_template → angular-bridge}/README.md +9 -0
- package/templates/angular-bridge/src/lib/bridge/rest/api-platform.adapter.ts +82 -0
- package/templates/angular-bridge/src/lib/bridge/rest/http-request.options.ts +30 -0
- package/templates/{_lib_template → angular-bridge}/src/lib/bridge/rest/query-builder.ts +10 -10
- package/templates/angular-bridge/src/lib/bridge/sse/mercure-connections.ts +255 -0
- package/templates/angular-bridge/src/lib/bridge/sse/mercure-payload.ts +82 -0
- package/templates/angular-bridge/src/lib/bridge/sse/mercure-url.builder.ts +39 -0
- package/templates/angular-bridge/src/lib/bridge/sse/mercure.adapter.ts +358 -0
- package/templates/{_lib_template → angular-bridge}/src/lib/bridge/sse/ref-count-topic.registry.ts +3 -12
- package/templates/angular-bridge/src/lib/bridge/sse/subscription-claims.ts +76 -0
- package/templates/angular-bridge/src/lib/bridge-options.ts +59 -0
- package/templates/{_lib_template → angular-bridge}/src/lib/facades/bridge.facade.ts +50 -51
- package/templates/{_lib_template → angular-bridge}/src/lib/facades/facade.factory.ts +0 -1
- package/templates/angular-bridge/src/lib/facades/facade.interface.ts +13 -0
- package/templates/{_lib_template → angular-bridge}/src/lib/facades/resource.facade.ts +24 -31
- package/templates/angular-bridge/src/lib/interceptors/bearer-auth.interceptor.ts +33 -0
- package/templates/angular-bridge/src/lib/interceptors/bridge-defaults.interceptor.ts +65 -0
- package/templates/angular-bridge/src/lib/interceptors/content-type.interceptor.ts +37 -0
- package/templates/angular-bridge/src/lib/interceptors/scoped.interceptor.ts +9 -0
- package/templates/angular-bridge/src/lib/interceptors/singleflight.interceptor.ts +46 -0
- package/templates/{_lib_template → angular-bridge}/src/lib/ports/realtime.port.ts +6 -2
- package/templates/{_lib_template → angular-bridge}/src/lib/ports/resource-repository.port.ts +7 -7
- package/templates/angular-bridge/src/lib/provide-bridge.ts +98 -0
- package/templates/angular-bridge/src/lib/utils/iri.ts +6 -0
- package/templates/angular-bridge/src/lib/utils/request-url.ts +31 -0
- package/templates/{_lib_template → angular-bridge}/src/public-api.ts +2 -0
- package/templates/{_lib_template → angular-bridge}/tsconfig.lib.json +13 -2
- package/templates/_lib_template/src/lib/bridge/rest/api-platform.adapter.ts +0 -84
- package/templates/_lib_template/src/lib/bridge/rest/http-request.options.ts +0 -21
- package/templates/_lib_template/src/lib/bridge/sse/mercure-url.builder.ts +0 -17
- package/templates/_lib_template/src/lib/bridge/sse/mercure.adapter.ts +0 -806
- package/templates/_lib_template/src/lib/facades/facade.interface.ts +0 -31
- package/templates/_lib_template/src/lib/interceptors/bridge-defaults.interceptor.ts +0 -53
- package/templates/_lib_template/src/lib/interceptors/content-type.interceptor.ts +0 -49
- package/templates/_lib_template/src/lib/interceptors/singleflight.interceptor.ts +0 -55
- package/templates/_lib_template/src/lib/provide-bridge.ts +0 -186
- package/tools/build.js +0 -1
- package/tools/dev.js +0 -1
- package/tools/domain/paths.js +0 -10
- package/tools/generate.js +0 -1
- package/tools/generator/models/oas-contract.js +0 -159
- package/tools/generator/models/openapi-to-models.js +0 -240
- package/tools/generator/models/type-resolver.js +0 -107
- package/tools/generator/models/utils.js +0 -62
- package/tools/infra/dev-support.js +0 -189
- package/tools/infra/generate/bridge.js +0 -212
- package/tools/infra/spec-loader.js +0 -59
- package/tools/infra/standalone-workspace.js +0 -162
- /package/{tools → src}/domain/lib-name.js +0 -0
- /package/{tools/generator → src/generators}/models/handlebars.js +0 -0
- /package/{tools/generator → src/generators}/models/templates/index.hbs +0 -0
- /package/{tools/generator → src/generators}/models/templates/model.hbs +0 -0
- /package/{tools/infra → src/infrastructure}/json.js +0 -0
- /package/{tools/infra → src/infrastructure}/logger.js +0 -0
- /package/templates/{_lib_template → angular-bridge}/ng-package.json +0 -0
- /package/templates/{_lib_template → angular-bridge}/package.json +0 -0
- /package/templates/{_lib_template → angular-bridge}/src/lib/bridge/sse/eventsource-wrapper.ts +0 -0
- /package/templates/{_lib_template → angular-bridge}/src/lib/bridge/sse/mercure-topic.mapper.ts +0 -0
- /package/templates/{_lib_template → angular-bridge}/src/lib/bridge.types.ts +0 -0
- /package/templates/{_lib_template → angular-bridge}/src/lib/interceptors/bridge-debug.interceptor.ts +0 -0
- /package/templates/{_lib_template → angular-bridge}/src/lib/tokens.ts +0 -0
- /package/templates/{_lib_template → angular-bridge}/src/lib/utils/url.ts +0 -0
- /package/templates/{_lib_template → angular-bridge}/src/models/index.ts +0 -0
- /package/templates/{_lib_template → angular-bridge}/tsconfig.lib.prod.json +0 -0
package/README.md
CHANGED
|
@@ -59,10 +59,27 @@ Sorties :
|
|
|
59
59
|
## Notes importantes
|
|
60
60
|
|
|
61
61
|
- fallback automatique `.../api/docs.json` -> `.../api/docs.jsonopenapi` si nécessaire
|
|
62
|
-
- mode contract-driven : uniquement les schémas atteignables via `paths` pour les formats sélectionnés
|
|
62
|
+
- mode contract-driven : uniquement les schémas atteignables via `paths` et `webhooks` pour les formats sélectionnés
|
|
63
63
|
- modèles `*jsonMergePatch*` non générés (`PATCH` typé `Partial<T>`)
|
|
64
64
|
- Meridiane ne publie pas : publication via `npm publish` côté CI
|
|
65
65
|
|
|
66
|
+
## Couverture des schémas
|
|
67
|
+
|
|
68
|
+
Le compilateur suit les types d’[OpenAPI 3.0](https://spec.openapis.org/oas/v3.0.4.html) et le dialecte
|
|
69
|
+
[JSON Schema 2020-12 d’OpenAPI 3.1](https://spec.openapis.org/oas/v3.1.0.html#schema-object) :
|
|
70
|
+
|
|
71
|
+
- primitives, unions de `type`, objets, propriétés requises, dictionnaires et schémas booléens ;
|
|
72
|
+
- tableaux homogènes, tuples `prefixItems`, tuples historiques et bornes de longueur des tuples ;
|
|
73
|
+
- `enum` et `const`, y compris les valeurs JSON structurées ;
|
|
74
|
+
- `allOf`, `oneOf`, `anyOf`, `nullable` et références locales vers `components.schemas` ou `$defs` ;
|
|
75
|
+
- schémas racine objets sous forme d’interfaces et autres schémas sous forme d’aliases TypeScript ;
|
|
76
|
+
- contrats atteignables depuis les bodies, paramètres, en-têtes, callbacks et webhooks.
|
|
77
|
+
|
|
78
|
+
Les règles de validation sans équivalent structurel TypeScript (`pattern`, bornes numériques,
|
|
79
|
+
`uniqueItems`, `contains`, `not`, conditions, fermeture exacte des propriétés, etc.) restent à la
|
|
80
|
+
charge de la validation runtime. Les références externes doivent être préalablement regroupées dans
|
|
81
|
+
le document OpenAPI fourni à Meridiane.
|
|
82
|
+
|
|
66
83
|
## Spécifique à ce repo
|
|
67
84
|
|
|
68
|
-
Dans ce monorepo, `meridiane dev` peut être exécuté sans `packageName` et cible alors `@obsidiane/bridge-sandbox` (app `
|
|
85
|
+
Dans ce monorepo, `meridiane dev` peut être exécuté sans `packageName` et cible alors `@obsidiane/bridge-sandbox` (app `playground/angular-app`).
|
package/cli.js
CHANGED
|
@@ -2,10 +2,10 @@
|
|
|
2
2
|
import process from 'node:process';
|
|
3
3
|
import { Command } from 'commander';
|
|
4
4
|
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import { runGenerate } from './
|
|
8
|
-
import { createLogger } from './
|
|
5
|
+
import { runBuild } from './src/application/run-build.js';
|
|
6
|
+
import { runDev } from './src/application/run-dev.js';
|
|
7
|
+
import { runGenerate } from './src/application/run-generate.js';
|
|
8
|
+
import { createLogger } from './src/infrastructure/logger.js';
|
|
9
9
|
|
|
10
10
|
const program = new Command();
|
|
11
11
|
|
|
@@ -30,6 +30,20 @@ function commonOptions(cmd) {
|
|
|
30
30
|
.option('--debug', 'Enable debug logs');
|
|
31
31
|
}
|
|
32
32
|
|
|
33
|
+
function action(run) {
|
|
34
|
+
return async (packageName, opts) => {
|
|
35
|
+
const log = createLogger({debug: !!opts?.debug});
|
|
36
|
+
|
|
37
|
+
try {
|
|
38
|
+
const exitCode = await run(packageName, opts);
|
|
39
|
+
if (typeof exitCode === 'number' && exitCode !== 0) process.exit(exitCode);
|
|
40
|
+
} catch (error) {
|
|
41
|
+
log.error(error);
|
|
42
|
+
process.exit(typeof error?.exitCode === 'number' ? error.exitCode : 1);
|
|
43
|
+
}
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
|
|
33
47
|
commonOptions(
|
|
34
48
|
program
|
|
35
49
|
.command('dev')
|
|
@@ -37,17 +51,7 @@ commonOptions(
|
|
|
37
51
|
'[packageName]',
|
|
38
52
|
'NPM package name of the generated bridge (e.g. @acme/backend-bridge). Optional in this repo (defaults to @obsidiane/bridge-sandbox).'
|
|
39
53
|
)
|
|
40
|
-
.action(
|
|
41
|
-
const log = createLogger({ debug: !!opts?.debug });
|
|
42
|
-
try {
|
|
43
|
-
const exitCode = await runDev(packageName, opts);
|
|
44
|
-
if (typeof exitCode === 'number' && exitCode !== 0) process.exit(exitCode);
|
|
45
|
-
} catch (err) {
|
|
46
|
-
log.error(err);
|
|
47
|
-
const exitCode = typeof err?.exitCode === 'number' ? err.exitCode : 1;
|
|
48
|
-
process.exit(exitCode);
|
|
49
|
-
}
|
|
50
|
-
})
|
|
54
|
+
.action(action(runDev))
|
|
51
55
|
);
|
|
52
56
|
|
|
53
57
|
commonOptions(
|
|
@@ -55,17 +59,7 @@ commonOptions(
|
|
|
55
59
|
.command('build')
|
|
56
60
|
.argument('<packageName>', 'NPM package name of the generated bridge (e.g. @acme/backend-bridge)')
|
|
57
61
|
.option('--version <semver>', 'Version to write in the generated bridge package.json (CI/CD)')
|
|
58
|
-
.action(
|
|
59
|
-
const log = createLogger({ debug: !!opts?.debug });
|
|
60
|
-
try {
|
|
61
|
-
const exitCode = await runBuild(packageName, opts);
|
|
62
|
-
if (typeof exitCode === 'number' && exitCode !== 0) process.exit(exitCode);
|
|
63
|
-
} catch (err) {
|
|
64
|
-
log.error(err);
|
|
65
|
-
const exitCode = typeof err?.exitCode === 'number' ? err.exitCode : 1;
|
|
66
|
-
process.exit(exitCode);
|
|
67
|
-
}
|
|
68
|
-
})
|
|
62
|
+
.action(action(runBuild))
|
|
69
63
|
);
|
|
70
64
|
|
|
71
65
|
commonOptions(
|
|
@@ -74,17 +68,7 @@ commonOptions(
|
|
|
74
68
|
.argument('<packageName>', 'NPM package name of the generated bridge (e.g. @acme/backend-bridge)')
|
|
75
69
|
.option('--version <semver>', 'Version to write in the generated bridge package.json')
|
|
76
70
|
.option('--out <dir>', 'Output directory (default: projects/<libName>)')
|
|
77
|
-
.action(
|
|
78
|
-
const log = createLogger({ debug: !!opts?.debug });
|
|
79
|
-
try {
|
|
80
|
-
const exitCode = await runGenerate(packageName, opts);
|
|
81
|
-
if (typeof exitCode === 'number' && exitCode !== 0) process.exit(exitCode);
|
|
82
|
-
} catch (err) {
|
|
83
|
-
log.error(err);
|
|
84
|
-
const exitCode = typeof err?.exitCode === 'number' ? err.exitCode : 1;
|
|
85
|
-
process.exit(exitCode);
|
|
86
|
-
}
|
|
87
|
-
})
|
|
71
|
+
.action(action(runGenerate))
|
|
88
72
|
);
|
|
89
73
|
|
|
90
74
|
program.parse(process.argv);
|
package/package.json
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@obsidiane/meridiane",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.12",
|
|
4
4
|
"description": "Générateur de librairie Angular et modèles TypeScript (OpenAPI) pour un bridge Symfony/API Platform + Mercure.",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
7
|
-
"url": "https://github.com/obsidiane-lab/meridiane"
|
|
7
|
+
"url": "git+https://github.com/obsidiane-lab/meridiane.git"
|
|
8
8
|
},
|
|
9
9
|
"type": "module",
|
|
10
10
|
"publishConfig": {
|
|
@@ -18,16 +18,23 @@
|
|
|
18
18
|
},
|
|
19
19
|
"files": [
|
|
20
20
|
"cli.js",
|
|
21
|
-
"
|
|
21
|
+
"src/**",
|
|
22
22
|
"templates/**",
|
|
23
23
|
"README.md"
|
|
24
24
|
],
|
|
25
25
|
"engines": {
|
|
26
|
-
"node": ">=
|
|
26
|
+
"node": ">=24.0.0"
|
|
27
27
|
},
|
|
28
28
|
"dependencies": {
|
|
29
|
+
"@angular/common": "20.3.29",
|
|
30
|
+
"@angular/compiler": "20.3.29",
|
|
31
|
+
"@angular/compiler-cli": "20.3.29",
|
|
32
|
+
"@angular/core": "20.3.29",
|
|
29
33
|
"commander": "^14.0.3",
|
|
30
|
-
"handlebars": "^4.7.
|
|
31
|
-
"
|
|
34
|
+
"handlebars": "^4.7.9",
|
|
35
|
+
"ng-packagr": "20.3.2",
|
|
36
|
+
"rxjs": "7.8.2",
|
|
37
|
+
"tslib": "^2.8.1",
|
|
38
|
+
"typescript": "5.9.3"
|
|
32
39
|
}
|
|
33
40
|
}
|
package/src/README.md
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
# Sources internes du générateur
|
|
2
|
+
|
|
3
|
+
- `application/` orchestre les workflows du CLI ;
|
|
4
|
+
- `domain/` contient les règles et la normalisation des options ;
|
|
5
|
+
- `infrastructure/` gère fichiers, processus, specs, workspaces et installation locale ;
|
|
6
|
+
- `generators/bridge/` écrit les fichiers du runtime Angular ;
|
|
7
|
+
- `generators/models/` transforme OpenAPI et écrit les modèles TypeScript.
|
|
8
|
+
|
|
9
|
+
Le runtime Angular généré se trouve dans `../templates/angular-bridge/`.
|
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
import process from 'node:process';
|
|
2
2
|
import path from 'node:path';
|
|
3
3
|
import fs from 'node:fs/promises';
|
|
4
|
+
import { fileURLToPath } from 'node:url';
|
|
4
5
|
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
|
|
6
|
+
import { generateBridgeWorkspace } from './generate-bridge.js';
|
|
7
|
+
import { readOpenApiSpec } from '../infrastructure/spec-loader.js';
|
|
8
|
+
import { ExitCodeError, runCommand } from '../infrastructure/exec.js';
|
|
9
|
+
|
|
10
|
+
const ngPackagrPath = fileURLToPath(import.meta.resolve('ng-packagr/src/cli/main.js'));
|
|
9
11
|
|
|
10
12
|
export async function runBridgeWorkflow({
|
|
11
13
|
repoRoot,
|
|
@@ -25,23 +27,17 @@ export async function runBridgeWorkflow({
|
|
|
25
27
|
packRelativeTo,
|
|
26
28
|
projectReadmePath,
|
|
27
29
|
}) {
|
|
28
|
-
const
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
if (!specSource) throw new Error('Missing --spec (url or JSON file path)');
|
|
35
|
-
log?.step?.('lecture de la spec OpenAPI');
|
|
36
|
-
resolvedSpec = await readOpenApiSpec(specSource);
|
|
37
|
-
log?.success?.('spec chargée');
|
|
38
|
-
}
|
|
39
|
-
}
|
|
30
|
+
const distRoot = path.join(repoRoot, 'dist');
|
|
31
|
+
const buildRoot = path.join(distRoot, '.meridiane-workspace');
|
|
32
|
+
const npmCacheDir = path.join(buildRoot, '.npm-cache');
|
|
33
|
+
await fs.rm(buildRoot, { recursive: true, force: true });
|
|
34
|
+
await fs.mkdir(buildRoot, { recursive: true });
|
|
35
|
+
log?.info?.(`workspace: ${buildRoot}`);
|
|
40
36
|
|
|
41
|
-
await
|
|
37
|
+
const resolvedSpec = await loadSpecIfNeeded({ noModels, spec, specSource, log });
|
|
42
38
|
|
|
43
39
|
await generateBridgeWorkspace({
|
|
44
|
-
cwd:
|
|
40
|
+
cwd: buildRoot,
|
|
45
41
|
libName,
|
|
46
42
|
packageName,
|
|
47
43
|
version,
|
|
@@ -57,13 +53,13 @@ export async function runBridgeWorkflow({
|
|
|
57
53
|
projectReadmePath,
|
|
58
54
|
});
|
|
59
55
|
|
|
60
|
-
const
|
|
61
|
-
const
|
|
56
|
+
const projectDir = path.join(buildRoot, 'projects', libName);
|
|
57
|
+
const ngPackageJsonPath = path.join(projectDir, 'ng-package.json');
|
|
58
|
+
const tsconfigAbsPath = path.join(projectDir, tsconfigPath);
|
|
62
59
|
|
|
63
60
|
log?.step?.('ng-packagr');
|
|
64
|
-
const ngPackagrPath = requireFromWs.resolve('ng-packagr/src/cli/main.js');
|
|
65
61
|
const packagrCode = await runCommand(process.execPath, [ngPackagrPath, '-p', ngPackageJsonPath, '-c', tsconfigAbsPath], {
|
|
66
|
-
cwd:
|
|
62
|
+
cwd: buildRoot,
|
|
67
63
|
});
|
|
68
64
|
if (packagrCode !== 0) throw new ExitCodeError(`ng-packagr failed (exit ${packagrCode})`, packagrCode);
|
|
69
65
|
|
|
@@ -83,7 +79,17 @@ export async function runBridgeWorkflow({
|
|
|
83
79
|
});
|
|
84
80
|
if (packCode !== 0) throw new ExitCodeError(`npm pack failed (exit ${packCode})`, packCode);
|
|
85
81
|
|
|
86
|
-
return {
|
|
82
|
+
return { distDir };
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
async function loadSpecIfNeeded({ noModels, spec, specSource, log }) {
|
|
86
|
+
if (noModels || spec) return spec;
|
|
87
|
+
if (!specSource) throw new Error('Missing --spec (url or JSON file path)');
|
|
88
|
+
|
|
89
|
+
log?.step?.('lecture de la spec OpenAPI');
|
|
90
|
+
const loadedSpec = await readOpenApiSpec(specSource);
|
|
91
|
+
log?.success?.('spec chargée');
|
|
92
|
+
return loadedSpec;
|
|
87
93
|
}
|
|
88
94
|
|
|
89
95
|
async function cleanupPackedTarballs(distDir) {
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
|
|
3
|
+
import { configureBridgeOutput, writeBridgeFiles } from '../generators/bridge/write-bridge-files.js';
|
|
4
|
+
import { writeModelFiles } from '../generators/models/write-model-files.js';
|
|
5
|
+
|
|
6
|
+
/** Generates the Angular bridge sources in a workspace project. */
|
|
7
|
+
export async function generateBridgeWorkspace({
|
|
8
|
+
cwd,
|
|
9
|
+
libName,
|
|
10
|
+
packageName,
|
|
11
|
+
version,
|
|
12
|
+
noModels,
|
|
13
|
+
spec,
|
|
14
|
+
requiredMode,
|
|
15
|
+
formats,
|
|
16
|
+
include,
|
|
17
|
+
exclude,
|
|
18
|
+
debug,
|
|
19
|
+
log,
|
|
20
|
+
distRoot,
|
|
21
|
+
projectDir: configuredProjectDir,
|
|
22
|
+
projectReadmePath,
|
|
23
|
+
}) {
|
|
24
|
+
const projectDir = configuredProjectDir ?? path.resolve(cwd, 'projects', libName);
|
|
25
|
+
|
|
26
|
+
log?.step?.(`génération de la librairie (${path.relative(cwd, projectDir)})`);
|
|
27
|
+
await writeBridgeFiles({
|
|
28
|
+
projectDir,
|
|
29
|
+
libName,
|
|
30
|
+
packageName,
|
|
31
|
+
version,
|
|
32
|
+
projectReadmePath,
|
|
33
|
+
debug,
|
|
34
|
+
log,
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
if (distRoot) await configureBridgeOutput({ projectDir, distRoot, libName, log });
|
|
38
|
+
|
|
39
|
+
if (noModels) {
|
|
40
|
+
log?.info?.('models ignorés (--no-models)');
|
|
41
|
+
if (debug) log?.debug?.('[meridiane] --no-models enabled, skipping models generation');
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
if (!spec) throw new Error('Internal error: spec is required when noModels=false');
|
|
46
|
+
|
|
47
|
+
log?.step?.('génération des models (OpenAPI)');
|
|
48
|
+
await writeModelFiles({ projectDir, spec, requiredMode, formats, include, exclude, debug, log });
|
|
49
|
+
log?.success?.('génération terminée');
|
|
50
|
+
}
|
|
@@ -2,7 +2,7 @@ import process from 'node:process';
|
|
|
2
2
|
import path from 'node:path';
|
|
3
3
|
|
|
4
4
|
import { normalizeBuildOptions } from '../domain/bridge-options.js';
|
|
5
|
-
import { createLogger } from '../
|
|
5
|
+
import { createLogger } from '../infrastructure/logger.js';
|
|
6
6
|
import { runBridgeWorkflow } from './bridge-workflow.js';
|
|
7
7
|
|
|
8
8
|
export async function runBuild(packageName, opts) {
|
|
@@ -2,17 +2,37 @@ import process from 'node:process';
|
|
|
2
2
|
import path from 'node:path';
|
|
3
3
|
|
|
4
4
|
import { normalizeDevOptions } from '../domain/bridge-options.js';
|
|
5
|
-
import { createLogger } from '../
|
|
6
|
-
import {
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
resolveDevAppRoot,
|
|
10
|
-
resolveSandboxAppRoot,
|
|
11
|
-
resolveSpecSource,
|
|
12
|
-
} from '../infra/dev-support.js';
|
|
13
|
-
import { readOpenApiSpec } from '../infra/spec-loader.js';
|
|
5
|
+
import { createLogger } from '../infrastructure/logger.js';
|
|
6
|
+
import {resolveDevAppRoot, resolveSandboxAppRoot, resolveSpecSource} from '../infrastructure/dev-project.js';
|
|
7
|
+
import {installPackageFromDist} from '../infrastructure/local-package-install.js';
|
|
8
|
+
import { readOpenApiSpec } from '../infrastructure/spec-loader.js';
|
|
14
9
|
import { runBridgeWorkflow } from './bridge-workflow.js';
|
|
15
10
|
|
|
11
|
+
async function loadDevSpec({config, opts, appRoot, log}) {
|
|
12
|
+
if (config.noModels) return {noModels: true};
|
|
13
|
+
|
|
14
|
+
const specSource = await resolveSpecSource({
|
|
15
|
+
useSandboxDefaults: config.useSandboxDefaults,
|
|
16
|
+
appRoot,
|
|
17
|
+
specArg: opts.spec,
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
if (!specSource) throw new Error('Missing --spec (url or JSON file path)');
|
|
21
|
+
|
|
22
|
+
try {
|
|
23
|
+
return {noModels: false, specSource, spec: await readOpenApiSpec(specSource)};
|
|
24
|
+
} catch (error) {
|
|
25
|
+
const canSkipModels = config.useSandboxDefaults && opts.spec == null;
|
|
26
|
+
if (!canSkipModels) throw error;
|
|
27
|
+
|
|
28
|
+
log.warn(
|
|
29
|
+
'spec OpenAPI indisponible (backend non démarré ?) → génération des models ignorée. ' +
|
|
30
|
+
'Démarrez le backend ou passez `--spec ./openapi.json` (ou `--no-models`).'
|
|
31
|
+
);
|
|
32
|
+
return {noModels: true, specSource};
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
16
36
|
export async function runDev(packageName, opts) {
|
|
17
37
|
const startCwd = process.cwd();
|
|
18
38
|
const config = normalizeDevOptions(packageName, opts);
|
|
@@ -20,32 +40,7 @@ export async function runDev(packageName, opts) {
|
|
|
20
40
|
|
|
21
41
|
const appRoot = config.useSandboxDefaults ? await resolveSandboxAppRoot(startCwd) : await resolveDevAppRoot(startCwd);
|
|
22
42
|
const projectReadmePath = path.join(appRoot, 'README.md');
|
|
23
|
-
|
|
24
|
-
const specSource = config.noModels
|
|
25
|
-
? undefined
|
|
26
|
-
: await resolveSpecSource({
|
|
27
|
-
useSandboxDefaults: config.useSandboxDefaults,
|
|
28
|
-
appRoot,
|
|
29
|
-
specArg: opts.spec,
|
|
30
|
-
});
|
|
31
|
-
let noModels = config.noModels;
|
|
32
|
-
let spec;
|
|
33
|
-
if (!noModels) {
|
|
34
|
-
if (!specSource) throw new Error('Missing --spec (url or JSON file path)');
|
|
35
|
-
try {
|
|
36
|
-
spec = await readOpenApiSpec(specSource);
|
|
37
|
-
} catch (err) {
|
|
38
|
-
if (config.useSandboxDefaults && opts.spec == null) {
|
|
39
|
-
noModels = true;
|
|
40
|
-
log.warn(
|
|
41
|
-
"spec OpenAPI indisponible (backend non démarré ?) → génération des models ignorée. " +
|
|
42
|
-
'Démarrez le backend ou passez `--spec ./openapi.json` (ou `--no-models`).'
|
|
43
|
-
);
|
|
44
|
-
} else {
|
|
45
|
-
throw err;
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
}
|
|
43
|
+
const {noModels, specSource, spec} = await loadDevSpec({config, opts, appRoot, log});
|
|
49
44
|
|
|
50
45
|
log.title('dev — build standalone + install local');
|
|
51
46
|
log.info(`app: ${appRoot}`);
|
|
@@ -74,7 +69,6 @@ export async function runDev(packageName, opts) {
|
|
|
74
69
|
projectReadmePath,
|
|
75
70
|
});
|
|
76
71
|
|
|
77
|
-
await findLatestTgz(distDir);
|
|
78
72
|
await installPackageFromDist({ packageName: config.packageName, distDir, appRoot, log });
|
|
79
73
|
|
|
80
74
|
log.success('bridge installé (node_modules) ✅');
|
|
@@ -2,9 +2,9 @@ import process from 'node:process';
|
|
|
2
2
|
import path from 'node:path';
|
|
3
3
|
|
|
4
4
|
import { normalizeGenerateOptions } from '../domain/bridge-options.js';
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
5
|
+
import { generateBridgeWorkspace } from './generate-bridge.js';
|
|
6
|
+
import { createLogger } from '../infrastructure/logger.js';
|
|
7
|
+
import { readOpenApiSpec } from '../infrastructure/spec-loader.js';
|
|
8
8
|
|
|
9
9
|
function resolveProjectDir({ cwd, libName, outDir }) {
|
|
10
10
|
if (outDir && outDir.length > 0) return path.resolve(cwd, outDir);
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { deriveLibName } from './lib-name.js';
|
|
2
|
-
import {
|
|
2
|
+
import {splitListArgs} from './list-values.js';
|
|
3
3
|
|
|
4
4
|
const DEFAULT_FORMATS = ['application/ld+json'];
|
|
5
5
|
const SANDBOX_PACKAGE_NAME = '@obsidiane/bridge-sandbox';
|
|
@@ -23,13 +23,12 @@ function normalizeVersion(version) {
|
|
|
23
23
|
return raw || '0.0.0';
|
|
24
24
|
}
|
|
25
25
|
|
|
26
|
-
|
|
26
|
+
function normalizeNamedOptions(packageName, opts) {
|
|
27
27
|
const common = normalizeCommonOptions(opts);
|
|
28
28
|
const normalizedPackageName = String(packageName || '').trim();
|
|
29
29
|
if (!normalizedPackageName) throw new Error('Missing packageName');
|
|
30
30
|
|
|
31
31
|
return {
|
|
32
|
-
mode: 'build',
|
|
33
32
|
packageName: normalizedPackageName,
|
|
34
33
|
libName: deriveLibName(normalizedPackageName),
|
|
35
34
|
version: normalizeVersion(opts?.version),
|
|
@@ -42,25 +41,14 @@ export function normalizeBuildOptions(packageName, opts) {
|
|
|
42
41
|
};
|
|
43
42
|
}
|
|
44
43
|
|
|
45
|
-
export function
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
if (!normalizedPackageName) throw new Error('Missing packageName');
|
|
49
|
-
|
|
50
|
-
const outDir = typeof opts?.out === 'string' ? opts.out.trim() : '';
|
|
44
|
+
export function normalizeBuildOptions(packageName, opts) {
|
|
45
|
+
return normalizeNamedOptions(packageName, opts);
|
|
46
|
+
}
|
|
51
47
|
|
|
48
|
+
export function normalizeGenerateOptions(packageName, opts) {
|
|
52
49
|
return {
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
libName: deriveLibName(normalizedPackageName),
|
|
56
|
-
version: normalizeVersion(opts?.version),
|
|
57
|
-
requiredMode: 'spec',
|
|
58
|
-
formats: normalizeFormats(common.formats),
|
|
59
|
-
include: common.include,
|
|
60
|
-
exclude: common.exclude,
|
|
61
|
-
noModels: common.noModels,
|
|
62
|
-
debug: common.debug,
|
|
63
|
-
outDir,
|
|
50
|
+
...normalizeNamedOptions(packageName, opts),
|
|
51
|
+
outDir: typeof opts?.out === 'string' ? opts.out.trim() : '',
|
|
64
52
|
};
|
|
65
53
|
}
|
|
66
54
|
|
|
@@ -70,7 +58,6 @@ export function normalizeDevOptions(packageName, opts) {
|
|
|
70
58
|
const effectivePackageName = String(packageName ?? SANDBOX_PACKAGE_NAME).trim();
|
|
71
59
|
|
|
72
60
|
return {
|
|
73
|
-
mode: 'dev',
|
|
74
61
|
useSandboxDefaults,
|
|
75
62
|
packageName: effectivePackageName,
|
|
76
63
|
libName: deriveLibName(effectivePackageName),
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import fs from 'node:fs/promises';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { fileURLToPath } from 'node:url';
|
|
4
|
+
|
|
5
|
+
import { readJson, writeJsonIfChanged } from '../../infrastructure/json.js';
|
|
6
|
+
|
|
7
|
+
const currentDir = path.dirname(fileURLToPath(import.meta.url));
|
|
8
|
+
const packageRoot = path.resolve(currentDir, '../../..');
|
|
9
|
+
const bridgeTemplateDir = path.join(packageRoot, 'templates', 'angular-bridge');
|
|
10
|
+
|
|
11
|
+
export async function writeBridgeFiles({
|
|
12
|
+
projectDir,
|
|
13
|
+
libName,
|
|
14
|
+
packageName,
|
|
15
|
+
version,
|
|
16
|
+
projectReadmePath,
|
|
17
|
+
debug,
|
|
18
|
+
log,
|
|
19
|
+
}) {
|
|
20
|
+
if (debug) {
|
|
21
|
+
log?.debug?.('[meridiane] generate lib', {
|
|
22
|
+
libName,
|
|
23
|
+
packageName,
|
|
24
|
+
version,
|
|
25
|
+
targetDir: projectDir,
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
await fs.rm(projectDir, { recursive: true, force: true });
|
|
30
|
+
await fs.mkdir(projectDir, { recursive: true });
|
|
31
|
+
await fs.cp(bridgeTemplateDir, projectDir, { recursive: true });
|
|
32
|
+
|
|
33
|
+
await replacePlaceholders(projectDir, {
|
|
34
|
+
'__LIB_NAME__': libName,
|
|
35
|
+
'__PACKAGE_NAME__': packageName,
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
const packageJsonPath = path.join(projectDir, 'package.json');
|
|
39
|
+
const packageJson = await readJson(packageJsonPath);
|
|
40
|
+
packageJson.name = packageName;
|
|
41
|
+
packageJson.version = version;
|
|
42
|
+
await writeJsonIfChanged(packageJsonPath, packageJson);
|
|
43
|
+
|
|
44
|
+
await appendProjectReadme(path.join(projectDir, 'README.md'), projectReadmePath, debug, log);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export async function configureBridgeOutput({ projectDir, distRoot, libName, log }) {
|
|
48
|
+
const ngPackagePath = path.join(projectDir, 'ng-package.json');
|
|
49
|
+
const ngPackage = await readJson(ngPackagePath);
|
|
50
|
+
const outputDir = path.join(distRoot, libName);
|
|
51
|
+
|
|
52
|
+
ngPackage.dest = path.relative(projectDir, outputDir).split(path.sep).join('/');
|
|
53
|
+
await writeJsonIfChanged(ngPackagePath, ngPackage);
|
|
54
|
+
log?.debug?.('ng-package.json dest overridden', { dest: ngPackage.dest });
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
async function replacePlaceholders(directory, placeholders) {
|
|
58
|
+
const entries = await fs.readdir(directory, { withFileTypes: true });
|
|
59
|
+
|
|
60
|
+
for (const entry of entries) {
|
|
61
|
+
const filePath = path.join(directory, entry.name);
|
|
62
|
+
if (entry.isDirectory()) {
|
|
63
|
+
await replacePlaceholders(filePath, placeholders);
|
|
64
|
+
continue;
|
|
65
|
+
}
|
|
66
|
+
if (!entry.isFile()) continue;
|
|
67
|
+
|
|
68
|
+
const content = await fs.readFile(filePath, 'utf8');
|
|
69
|
+
const rendered = Object.entries(placeholders)
|
|
70
|
+
.reduce((value, [placeholder, replacement]) => value.replaceAll(placeholder, replacement), content);
|
|
71
|
+
if (rendered !== content) await fs.writeFile(filePath, rendered);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
async function appendProjectReadme(targetPath, sourcePath, debug, log) {
|
|
76
|
+
if (!sourcePath) return;
|
|
77
|
+
|
|
78
|
+
const projectReadme = await readText(sourcePath);
|
|
79
|
+
if (!projectReadme?.trim()) return;
|
|
80
|
+
|
|
81
|
+
const bridgeReadme = await readText(targetPath);
|
|
82
|
+
if (!bridgeReadme?.trim()) {
|
|
83
|
+
await fs.writeFile(targetPath, projectReadme);
|
|
84
|
+
if (debug) log?.debug?.('[meridiane] project README copied', { source: sourcePath });
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const combinedReadme =
|
|
89
|
+
`${bridgeReadme.trimEnd()}\n\n---\n\n## Documentation du projet\n\n${projectReadme.trimStart()}`;
|
|
90
|
+
await fs.writeFile(targetPath, combinedReadme);
|
|
91
|
+
if (debug) log?.debug?.('[meridiane] project README appended', { source: sourcePath });
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
async function readText(filePath) {
|
|
95
|
+
try {
|
|
96
|
+
return await fs.readFile(filePath, 'utf8');
|
|
97
|
+
} catch {
|
|
98
|
+
return undefined;
|
|
99
|
+
}
|
|
100
|
+
}
|