@obsidiane/meridiane 2.0.13 → 3.0.1
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 +33 -66
- package/cli.js +1 -10
- package/package.json +1 -1
- package/src/application/bridge-workflow.js +23 -66
- package/src/application/generate-bridge.js +32 -36
- package/src/application/run-build.js +8 -30
- package/src/application/run-dev.js +8 -63
- package/src/application/run-generate.js +8 -47
- package/src/domain/bridge-options.js +10 -63
- package/src/generators/bridge/write-bridge-files.js +11 -29
- package/src/generators/models/interface-schema.js +3 -20
- package/src/generators/models/model-selection.js +67 -106
- package/src/generators/models/oas-contract.js +1 -108
- package/src/generators/models/oas-schema-name.js +1 -19
- package/src/generators/models/openapi-to-models.js +51 -42
- package/src/generators/models/schema-utils.js +0 -34
- package/src/generators/models/type-resolver.js +2 -20
- package/src/generators/models/utils.js +0 -11
- package/src/generators/models/write-model-files.js +5 -15
- package/src/infrastructure/dev-project.js +0 -33
- package/src/infrastructure/files.js +15 -0
- package/src/infrastructure/local-package-install.js +2 -2
- package/src/infrastructure/logger.js +0 -2
- package/templates/angular-bridge/README.md +29 -281
- package/templates/angular-bridge/package.json +2 -2
- package/templates/angular-bridge/src/lib/bridge/rest/http-request.options.ts +10 -19
- package/templates/angular-bridge/src/lib/bridge/sse/mercure-hub.ts +449 -0
- package/templates/angular-bridge/src/lib/bridge/sse/mercure-url.ts +28 -0
- package/templates/angular-bridge/src/lib/bridge/sse/resource-topic.ts +83 -0
- package/templates/angular-bridge/src/lib/bridge-options.ts +21 -17
- package/templates/angular-bridge/src/lib/bridge.types.ts +22 -33
- package/templates/angular-bridge/src/lib/facades/bridge.facade.ts +58 -134
- package/templates/angular-bridge/src/lib/facades/facade.factory.ts +25 -25
- package/templates/angular-bridge/src/lib/facades/facade.interface.ts +4 -9
- package/templates/angular-bridge/src/lib/facades/resource.facade.ts +38 -61
- package/templates/angular-bridge/src/lib/interceptors/bearer-auth.interceptor.ts +8 -12
- package/templates/angular-bridge/src/lib/interceptors/bridge-defaults.interceptor.ts +19 -11
- package/templates/angular-bridge/src/lib/interceptors/content-type.interceptor.ts +5 -17
- package/templates/angular-bridge/src/lib/interceptors/singleflight.interceptor.ts +28 -24
- package/templates/angular-bridge/src/lib/ports/realtime.port.ts +34 -53
- package/templates/angular-bridge/src/lib/ports/resource-repository.port.ts +31 -21
- package/templates/angular-bridge/src/lib/provide-bridge.ts +14 -38
- package/templates/angular-bridge/src/lib/tokens.ts +4 -15
- package/templates/angular-bridge/src/lib/utils/request-url.ts +4 -6
- package/templates/angular-bridge/src/lib/utils/url.ts +32 -9
- package/templates/angular-bridge/src/public-api.ts +0 -1
- package/templates/angular-bridge/tsconfig.lib.json +0 -3
- package/src/README.md +0 -9
- package/src/infrastructure/json.js +0 -71
- package/templates/angular-bridge/src/lib/bridge/rest/api-platform.adapter.ts +0 -82
- package/templates/angular-bridge/src/lib/bridge/sse/eventsource-wrapper.ts +0 -70
- package/templates/angular-bridge/src/lib/bridge/sse/mercure-connections.ts +0 -255
- package/templates/angular-bridge/src/lib/bridge/sse/mercure-payload.ts +0 -82
- package/templates/angular-bridge/src/lib/bridge/sse/mercure-topic.mapper.ts +0 -48
- package/templates/angular-bridge/src/lib/bridge/sse/mercure-url.builder.ts +0 -39
- package/templates/angular-bridge/src/lib/bridge/sse/mercure.adapter.ts +0 -358
- package/templates/angular-bridge/src/lib/bridge/sse/ref-count-topic.registry.ts +0 -36
- package/templates/angular-bridge/src/lib/bridge/sse/subscription-claims.ts +0 -76
- package/templates/angular-bridge/src/lib/utils/iri.ts +0 -6
package/README.md
CHANGED
|
@@ -1,85 +1,52 @@
|
|
|
1
1
|
# @obsidiane/meridiane
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Connecte une application Angular à une API API Platform, en HTTP et en temps réel (Mercure 1.0).
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
Le CLI lit le contrat OpenAPI de l'API et génère une librairie Angular, **le bridge** :
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
- les **modèles TypeScript** de vos ressources ;
|
|
8
|
+
- une **façade HTTP** par ressource, aux conventions d'API Platform (JSON-LD, Hydra, merge-patch) ;
|
|
9
|
+
- le **temps réel Mercure 1.0** : abonnement à une ressource ou à un item, connexions partagées, autorisation par cookie.
|
|
8
10
|
|
|
9
|
-
|
|
10
|
-
npm i -D @obsidiane/meridiane
|
|
11
|
-
```
|
|
12
|
-
|
|
13
|
-
## Commandes
|
|
14
|
-
|
|
15
|
-
### `meridiane generate <packageName>`
|
|
11
|
+
Le bridge gère la communication avec l'API ; la gestion des données reste dans votre application. Il est compatible avec Angular 20, 21 et 22, API Platform 5 et un hub Mercure 1.x.
|
|
16
12
|
|
|
17
|
-
|
|
13
|
+
## En bref
|
|
18
14
|
|
|
19
15
|
```bash
|
|
20
|
-
|
|
16
|
+
# Contrat exporté par Symfony, puis génération dans l'application Angular
|
|
17
|
+
docker compose exec php bin/console api:openapi:export --output=openapi.json
|
|
18
|
+
npx @obsidiane/meridiane@3 generate @app/bridge --spec ../api/openapi.json --out bridge
|
|
21
19
|
```
|
|
22
20
|
|
|
23
|
-
|
|
21
|
+
```ts
|
|
22
|
+
// app.config.ts
|
|
23
|
+
provideBridge({
|
|
24
|
+
baseUrl: '/',
|
|
25
|
+
mercure: {hubUrl: '/.well-known/mercure', authorize: '/api/mercure/authorization'},
|
|
26
|
+
});
|
|
24
27
|
|
|
25
|
-
|
|
28
|
+
// project.repository.ts
|
|
29
|
+
private readonly projects = inject(FacadeFactory).create<Project>({url: '/api/projects'});
|
|
26
30
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
```bash
|
|
30
|
-
npx meridiane dev @acme/backend-bridge --spec http://localhost:8000/api/docs.json
|
|
31
|
+
list$ = this.projects.getCollection$({page: 1, itemsPerPage: 20}); // Collection<Project>
|
|
32
|
+
changes$ = this.projects.watch$(); // Project | ResourceDeletion, en direct
|
|
31
33
|
```
|
|
32
34
|
|
|
33
|
-
|
|
34
|
-
- `dist/<libName>/` + `dist/<libName>/*.tgz`
|
|
35
|
-
- `node_modules/<packageName>/`
|
|
36
|
-
|
|
37
|
-
### `meridiane build <packageName>`
|
|
35
|
+
Le backend publie ses entités sur Mercure (`#[ApiResource(mercure: true)]`) et sert le front, l'API et le hub sur la même origine : voir la [configuration de référence](https://github.com/obsidiane-lab/meridiane/blob/master/docs/backend.md).
|
|
38
36
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
```bash
|
|
42
|
-
npx meridiane build @acme/backend-bridge --version 1.2.3 --spec https://staging.example/api/docs.json
|
|
43
|
-
```
|
|
44
|
-
|
|
45
|
-
Sorties :
|
|
46
|
-
- `dist/<libName>/`
|
|
47
|
-
- `dist/<libName>/*.tgz`
|
|
48
|
-
|
|
49
|
-
## Options
|
|
50
|
-
|
|
51
|
-
- `--spec <url|file>` : URL OpenAPI ou fichier JSON local (requis sauf `--no-models`)
|
|
52
|
-
- `--formats <mimeTypes>` : répétable ou liste `,` ; défaut `application/ld+json`
|
|
53
|
-
- `--include <substr>` / `--exclude <substr>` : filtres de noms de schémas (répétables, support `,`)
|
|
54
|
-
- `--no-models` : runtime only
|
|
55
|
-
- `--version <semver>` : `build` et `generate` (défaut `0.0.0`)
|
|
56
|
-
- `--out <dir>` : uniquement `generate`
|
|
57
|
-
- `--debug` : logs détaillés
|
|
58
|
-
|
|
59
|
-
## Notes importantes
|
|
60
|
-
|
|
61
|
-
- fallback automatique `.../api/docs.json` -> `.../api/docs.jsonopenapi` si nécessaire
|
|
62
|
-
- mode contract-driven : uniquement les schémas atteignables via `paths` et `webhooks` pour les formats sélectionnés
|
|
63
|
-
- modèles `*jsonMergePatch*` non générés (`PATCH` typé `Partial<T>`)
|
|
64
|
-
- Meridiane ne publie pas : publication via `npm publish` côté CI
|
|
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) :
|
|
37
|
+
## Commandes
|
|
70
38
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
- contrats atteignables depuis les bodies, paramètres, en-têtes, callbacks et webhooks.
|
|
39
|
+
| Commande | Produit |
|
|
40
|
+
|---|---|
|
|
41
|
+
| `generate <package>` | les sources du bridge dans le projet (`--out`) |
|
|
42
|
+
| `build <package>` | un package compilé et son `.tgz` dans `dist/`, prêt à publier |
|
|
43
|
+
| `dev <package>` | le package compilé, installé dans `node_modules` |
|
|
77
44
|
|
|
78
|
-
|
|
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.
|
|
45
|
+
Options : `--spec`, `--include`, `--exclude`, `--no-models`, `--version`, `--out`, `--debug`. Node.js ≥ 24.
|
|
82
46
|
|
|
83
|
-
##
|
|
47
|
+
## Documentation
|
|
84
48
|
|
|
85
|
-
|
|
49
|
+
- [Backend](https://github.com/obsidiane-lab/meridiane/blob/master/docs/backend.md) : FrankenPHP, Mercure, Symfony et API Platform
|
|
50
|
+
- [Démarrage](https://github.com/obsidiane-lab/meridiane/blob/master/docs/demarrage.md) · [Configuration Angular](https://github.com/obsidiane-lab/meridiane/blob/master/docs/configuration.md)
|
|
51
|
+
- [HTTP](https://github.com/obsidiane-lab/meridiane/blob/master/docs/http.md) · [Temps réel](https://github.com/obsidiane-lab/meridiane/blob/master/docs/temps-reel.md) · [Recettes](https://github.com/obsidiane-lab/meridiane/blob/master/docs/recettes.md)
|
|
52
|
+
- [Référence](https://github.com/obsidiane-lab/meridiane/blob/master/docs/reference.md) · [CLI](https://github.com/obsidiane-lab/meridiane/blob/master/docs/cli.md) · [Dépannage](https://github.com/obsidiane-lab/meridiane/blob/master/docs/depannage.md) · [Migration 2.x → 3.0](https://github.com/obsidiane-lab/meridiane/blob/master/docs/migration-3.0.md)
|
package/cli.js
CHANGED
|
@@ -18,12 +18,6 @@ function collect(value, previous = []) {
|
|
|
18
18
|
function commonOptions(cmd) {
|
|
19
19
|
return cmd
|
|
20
20
|
.option('--spec <urlOrFile>', 'OpenAPI spec source (URL or local JSON file)')
|
|
21
|
-
.option(
|
|
22
|
-
'--formats <mimeTypes>',
|
|
23
|
-
'Generate only selected media types (repeatable, supports commas). Enables contract-driven mode from paths.',
|
|
24
|
-
collect,
|
|
25
|
-
[]
|
|
26
|
-
)
|
|
27
21
|
.option('--include <substr>', 'Include schema names containing substring (repeatable, supports commas)', collect, [])
|
|
28
22
|
.option('--exclude <substr>', 'Exclude schema names containing substring (repeatable, supports commas)', collect, [])
|
|
29
23
|
.option('--no-models', 'Skip models generation (then --spec is not required)')
|
|
@@ -47,10 +41,7 @@ function action(run) {
|
|
|
47
41
|
commonOptions(
|
|
48
42
|
program
|
|
49
43
|
.command('dev')
|
|
50
|
-
.argument(
|
|
51
|
-
'[packageName]',
|
|
52
|
-
'NPM package name of the generated bridge (e.g. @acme/backend-bridge). Optional in this repo (defaults to @obsidiane/bridge-sandbox).'
|
|
53
|
-
)
|
|
44
|
+
.argument('<packageName>', 'NPM package name of the generated bridge (e.g. @acme/backend-bridge)')
|
|
54
45
|
.action(action(runDev))
|
|
55
46
|
);
|
|
56
47
|
|
package/package.json
CHANGED
|
@@ -3,75 +3,42 @@ import path from 'node:path';
|
|
|
3
3
|
import fs from 'node:fs/promises';
|
|
4
4
|
import { fileURLToPath } from 'node:url';
|
|
5
5
|
|
|
6
|
-
import {
|
|
7
|
-
import { readOpenApiSpec } from '../infrastructure/spec-loader.js';
|
|
6
|
+
import { generateBridge } from './generate-bridge.js';
|
|
8
7
|
import { ExitCodeError, runCommand } from '../infrastructure/exec.js';
|
|
8
|
+
import { ensureCleanDir } from '../infrastructure/files.js';
|
|
9
9
|
|
|
10
10
|
const ngPackagrPath = fileURLToPath(import.meta.resolve('ng-packagr/src/cli/main.js'));
|
|
11
|
+
/** `node_modules` holding the CLI's Angular toolchain (wherever npx installed it). */
|
|
12
|
+
const toolchainModules = path.resolve(fileURLToPath(import.meta.resolve('@angular/core/package.json')), '../../..');
|
|
11
13
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
libName,
|
|
15
|
-
packageName,
|
|
16
|
-
version,
|
|
17
|
-
noModels,
|
|
18
|
-
spec,
|
|
19
|
-
specSource,
|
|
20
|
-
requiredMode,
|
|
21
|
-
formats,
|
|
22
|
-
include,
|
|
23
|
-
exclude,
|
|
24
|
-
debug,
|
|
25
|
-
log,
|
|
26
|
-
tsconfigPath,
|
|
27
|
-
packRelativeTo,
|
|
28
|
-
projectReadmePath,
|
|
29
|
-
}) {
|
|
14
|
+
/** Generates the bridge in an isolated workspace under `dist/`, builds it with ng-packagr and packs it. */
|
|
15
|
+
export async function buildBridgePackage({ config, repoRoot, tsconfigPath, log }) {
|
|
30
16
|
const distRoot = path.join(repoRoot, 'dist');
|
|
31
17
|
const buildRoot = path.join(distRoot, '.meridiane-workspace');
|
|
32
|
-
const
|
|
33
|
-
await
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
requiredMode,
|
|
47
|
-
formats,
|
|
48
|
-
include,
|
|
49
|
-
exclude,
|
|
50
|
-
debug,
|
|
51
|
-
log,
|
|
52
|
-
distRoot,
|
|
53
|
-
projectReadmePath,
|
|
54
|
-
});
|
|
55
|
-
|
|
56
|
-
const projectDir = path.join(buildRoot, 'projects', libName);
|
|
57
|
-
const ngPackageJsonPath = path.join(projectDir, 'ng-package.json');
|
|
58
|
-
const tsconfigAbsPath = path.join(projectDir, tsconfigPath);
|
|
59
|
-
|
|
60
|
-
log?.step?.('ng-packagr');
|
|
61
|
-
const packagrCode = await runCommand(process.execPath, [ngPackagrPath, '-p', ngPackageJsonPath, '-c', tsconfigAbsPath], {
|
|
62
|
-
cwd: buildRoot,
|
|
63
|
-
});
|
|
18
|
+
const projectDir = path.join(buildRoot, 'projects', config.libName);
|
|
19
|
+
await ensureCleanDir(buildRoot);
|
|
20
|
+
// The bridge compiles against the CLI's Angular and RxJS, not the dependencies of the current project.
|
|
21
|
+
await fs.symlink(toolchainModules, path.join(buildRoot, 'node_modules'), 'junction');
|
|
22
|
+
log.info(`workspace: ${path.relative(repoRoot, buildRoot)}`);
|
|
23
|
+
|
|
24
|
+
await generateBridge({ config, root: repoRoot, projectDir, distRoot, log });
|
|
25
|
+
|
|
26
|
+
log.step('ng-packagr');
|
|
27
|
+
const packagrCode = await runCommand(
|
|
28
|
+
process.execPath,
|
|
29
|
+
[ngPackagrPath, '-p', path.join(projectDir, 'ng-package.json'), '-c', path.join(projectDir, tsconfigPath)],
|
|
30
|
+
{ cwd: buildRoot }
|
|
31
|
+
);
|
|
64
32
|
if (packagrCode !== 0) throw new ExitCodeError(`ng-packagr failed (exit ${packagrCode})`, packagrCode);
|
|
65
33
|
|
|
66
|
-
const distDir = path.join(distRoot, libName);
|
|
67
|
-
|
|
68
|
-
log?.step?.(`npm pack (${packRel})`);
|
|
34
|
+
const distDir = path.join(distRoot, config.libName);
|
|
35
|
+
log.step(`npm pack (${path.relative(repoRoot, distDir)})`);
|
|
69
36
|
await cleanupPackedTarballs(distDir);
|
|
70
37
|
const packCode = await runCommand('npm', ['pack'], {
|
|
71
38
|
cwd: distDir,
|
|
72
39
|
env: {
|
|
73
40
|
...process.env,
|
|
74
|
-
npm_config_cache:
|
|
41
|
+
npm_config_cache: path.join(buildRoot, '.npm-cache'),
|
|
75
42
|
npm_config_update_notifier: 'false',
|
|
76
43
|
npm_config_fund: 'false',
|
|
77
44
|
npm_config_audit: 'false',
|
|
@@ -82,16 +49,6 @@ export async function runBridgeWorkflow({
|
|
|
82
49
|
return { distDir };
|
|
83
50
|
}
|
|
84
51
|
|
|
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;
|
|
93
|
-
}
|
|
94
|
-
|
|
95
52
|
async function cleanupPackedTarballs(distDir) {
|
|
96
53
|
const entries = await fs.readdir(distDir, {withFileTypes: true});
|
|
97
54
|
const tarballs = entries.filter((entry) => entry.isFile() && entry.name.endsWith('.tgz'));
|
|
@@ -2,49 +2,45 @@ import path from 'node:path';
|
|
|
2
2
|
|
|
3
3
|
import { configureBridgeOutput, writeBridgeFiles } from '../generators/bridge/write-bridge-files.js';
|
|
4
4
|
import { writeModelFiles } from '../generators/models/write-model-files.js';
|
|
5
|
+
import { readOpenApiSpec } from '../infrastructure/spec-loader.js';
|
|
5
6
|
|
|
6
|
-
/**
|
|
7
|
-
export
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
packageName
|
|
11
|
-
|
|
12
|
-
noModels
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
projectDir: configuredProjectDir,
|
|
22
|
-
projectReadmePath,
|
|
23
|
-
}) {
|
|
24
|
-
const projectDir = configuredProjectDir ?? path.resolve(cwd, 'projects', libName);
|
|
7
|
+
/** Logs the command title and its configuration. */
|
|
8
|
+
export function logConfig(log, title, config, details) {
|
|
9
|
+
log.title(title);
|
|
10
|
+
for (const [label, value] of Object.entries(details)) log.info(`${label}: ${value}`);
|
|
11
|
+
log.info(`package: ${config.packageName}@${config.version}`);
|
|
12
|
+
log.info(`lib: ${config.libName}`);
|
|
13
|
+
log.info(config.noModels ? 'models: désactivés (--no-models)' : `spec: ${config.specSource}`);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Generates the Angular bridge sources (library files and models) in `projectDir`.
|
|
18
|
+
* The project README of `root` is appended to the bridge README.
|
|
19
|
+
*/
|
|
20
|
+
export async function generateBridge({ config, root, projectDir, distRoot, log }) {
|
|
21
|
+
const spec = config.noModels ? undefined : await loadSpec(config.specSource, log);
|
|
25
22
|
|
|
26
|
-
log
|
|
23
|
+
log.step(`génération de la librairie (${path.relative(root, projectDir)})`);
|
|
27
24
|
await writeBridgeFiles({
|
|
28
25
|
projectDir,
|
|
29
|
-
libName,
|
|
30
|
-
packageName,
|
|
31
|
-
version,
|
|
32
|
-
projectReadmePath,
|
|
33
|
-
debug,
|
|
26
|
+
libName: config.libName,
|
|
27
|
+
packageName: config.packageName,
|
|
28
|
+
version: config.version,
|
|
29
|
+
projectReadmePath: path.join(root, 'README.md'),
|
|
34
30
|
log,
|
|
35
31
|
});
|
|
32
|
+
if (distRoot) await configureBridgeOutput({ projectDir, distRoot, libName: config.libName, log });
|
|
36
33
|
|
|
37
|
-
if (
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
if (debug) log?.debug?.('[meridiane] --no-models enabled, skipping models generation');
|
|
42
|
-
return;
|
|
43
|
-
}
|
|
34
|
+
if (!spec) return;
|
|
35
|
+
log.step('génération des models (OpenAPI)');
|
|
36
|
+
await writeModelFiles({ projectDir, spec, include: config.include, exclude: config.exclude, log });
|
|
37
|
+
}
|
|
44
38
|
|
|
45
|
-
|
|
39
|
+
async function loadSpec(specSource, log) {
|
|
40
|
+
if (!specSource) throw new Error('Missing --spec (url or JSON file path)');
|
|
46
41
|
|
|
47
|
-
log
|
|
48
|
-
|
|
49
|
-
log
|
|
42
|
+
log.step('lecture de la spec OpenAPI');
|
|
43
|
+
const spec = await readOpenApiSpec(specSource);
|
|
44
|
+
log.success('spec chargée');
|
|
45
|
+
return spec;
|
|
50
46
|
}
|
|
@@ -1,41 +1,19 @@
|
|
|
1
1
|
import process from 'node:process';
|
|
2
2
|
import path from 'node:path';
|
|
3
3
|
|
|
4
|
-
import {
|
|
4
|
+
import { normalizeOptions } from '../domain/bridge-options.js';
|
|
5
5
|
import { createLogger } from '../infrastructure/logger.js';
|
|
6
|
-
import {
|
|
6
|
+
import { buildBridgePackage } from './bridge-workflow.js';
|
|
7
|
+
import { logConfig } from './generate-bridge.js';
|
|
7
8
|
|
|
8
9
|
export async function runBuild(packageName, opts) {
|
|
9
|
-
const
|
|
10
|
-
const config =
|
|
10
|
+
const repoRoot = process.cwd();
|
|
11
|
+
const config = normalizeOptions(packageName, opts);
|
|
11
12
|
const log = createLogger({ debug: config.debug });
|
|
12
|
-
const projectReadmePath = path.join(cwd, 'README.md');
|
|
13
13
|
|
|
14
|
-
log
|
|
15
|
-
|
|
16
|
-
log.info(`package: ${config.packageName}@${config.version}`);
|
|
17
|
-
log.info(`lib: ${config.libName}`);
|
|
18
|
-
if (config.noModels) log.info('models: désactivés (--no-models)');
|
|
19
|
-
else log.info(`spec: ${opts.spec} | formats: ${config.formats.join(', ')} | nullableMode: ${config.requiredMode}`);
|
|
14
|
+
logConfig(log, 'build — standalone', config, { repo: repoRoot });
|
|
15
|
+
const { distDir } = await buildBridgePackage({ config, repoRoot, tsconfigPath: 'tsconfig.lib.prod.json', log });
|
|
20
16
|
|
|
21
|
-
|
|
22
|
-
repoRoot: cwd,
|
|
23
|
-
libName: config.libName,
|
|
24
|
-
packageName: config.packageName,
|
|
25
|
-
version: config.version,
|
|
26
|
-
noModels: config.noModels,
|
|
27
|
-
specSource: opts.spec,
|
|
28
|
-
requiredMode: config.requiredMode,
|
|
29
|
-
formats: config.formats,
|
|
30
|
-
include: config.include,
|
|
31
|
-
exclude: config.exclude,
|
|
32
|
-
debug: config.debug,
|
|
33
|
-
log,
|
|
34
|
-
tsconfigPath: 'tsconfig.lib.prod.json',
|
|
35
|
-
packRelativeTo: cwd,
|
|
36
|
-
projectReadmePath,
|
|
37
|
-
});
|
|
38
|
-
|
|
39
|
-
log.success(`package prêt: ${path.relative(cwd, distDir)}`);
|
|
17
|
+
log.success(`package prêt: ${path.relative(repoRoot, distDir)}`);
|
|
40
18
|
return 0;
|
|
41
19
|
}
|
|
@@ -1,74 +1,19 @@
|
|
|
1
1
|
import process from 'node:process';
|
|
2
|
-
import path from 'node:path';
|
|
3
2
|
|
|
4
|
-
import {
|
|
3
|
+
import { normalizeOptions } from '../domain/bridge-options.js';
|
|
5
4
|
import { createLogger } from '../infrastructure/logger.js';
|
|
6
|
-
import {resolveDevAppRoot
|
|
5
|
+
import {resolveDevAppRoot} from '../infrastructure/dev-project.js';
|
|
7
6
|
import {installPackageFromDist} from '../infrastructure/local-package-install.js';
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
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
|
-
}
|
|
7
|
+
import { buildBridgePackage } from './bridge-workflow.js';
|
|
8
|
+
import { logConfig } from './generate-bridge.js';
|
|
35
9
|
|
|
36
10
|
export async function runDev(packageName, opts) {
|
|
37
|
-
const
|
|
38
|
-
const config = normalizeDevOptions(packageName, opts);
|
|
11
|
+
const config = { ...normalizeOptions(packageName, opts), version: '0.0.0-dev' };
|
|
39
12
|
const log = createLogger({ debug: config.debug });
|
|
13
|
+
const appRoot = await resolveDevAppRoot(process.cwd());
|
|
40
14
|
|
|
41
|
-
|
|
42
|
-
const
|
|
43
|
-
const {noModels, specSource, spec} = await loadDevSpec({config, opts, appRoot, log});
|
|
44
|
-
|
|
45
|
-
log.title('dev — build standalone + install local');
|
|
46
|
-
log.info(`app: ${appRoot}`);
|
|
47
|
-
log.info(`package: ${config.packageName}@${config.version}`);
|
|
48
|
-
log.info(`lib: ${config.libName}`);
|
|
49
|
-
if (config.useSandboxDefaults) log.info('defaults sandbox appliqués');
|
|
50
|
-
if (noModels) log.info('models: désactivés (--no-models)');
|
|
51
|
-
else log.info(`spec: ${specSource} | formats: ${config.formats.join(', ')} | nullableMode: ${config.requiredMode}`);
|
|
52
|
-
|
|
53
|
-
const { distDir } = await runBridgeWorkflow({
|
|
54
|
-
repoRoot: appRoot,
|
|
55
|
-
libName: config.libName,
|
|
56
|
-
packageName: config.packageName,
|
|
57
|
-
version: config.version,
|
|
58
|
-
noModels,
|
|
59
|
-
spec,
|
|
60
|
-
specSource,
|
|
61
|
-
requiredMode: config.requiredMode,
|
|
62
|
-
formats: config.formats,
|
|
63
|
-
include: config.include,
|
|
64
|
-
exclude: config.exclude,
|
|
65
|
-
debug: config.debug,
|
|
66
|
-
log,
|
|
67
|
-
tsconfigPath: 'tsconfig.lib.json',
|
|
68
|
-
packRelativeTo: appRoot,
|
|
69
|
-
projectReadmePath,
|
|
70
|
-
});
|
|
71
|
-
|
|
15
|
+
logConfig(log, 'dev — build standalone + install local', config, { app: appRoot });
|
|
16
|
+
const { distDir } = await buildBridgePackage({ config, repoRoot: appRoot, tsconfigPath: 'tsconfig.lib.json', log });
|
|
72
17
|
await installPackageFromDist({ packageName: config.packageName, distDir, appRoot, log });
|
|
73
18
|
|
|
74
19
|
log.success('bridge installé (node_modules) ✅');
|
|
@@ -1,58 +1,19 @@
|
|
|
1
1
|
import process from 'node:process';
|
|
2
2
|
import path from 'node:path';
|
|
3
3
|
|
|
4
|
-
import {
|
|
5
|
-
import { generateBridgeWorkspace } from './generate-bridge.js';
|
|
4
|
+
import { normalizeOptions } from '../domain/bridge-options.js';
|
|
6
5
|
import { createLogger } from '../infrastructure/logger.js';
|
|
7
|
-
import {
|
|
8
|
-
|
|
9
|
-
function resolveProjectDir({ cwd, libName, outDir }) {
|
|
10
|
-
if (outDir && outDir.length > 0) return path.resolve(cwd, outDir);
|
|
11
|
-
return path.resolve(cwd, 'projects', libName);
|
|
12
|
-
}
|
|
6
|
+
import { generateBridge, logConfig } from './generate-bridge.js';
|
|
13
7
|
|
|
14
8
|
export async function runGenerate(packageName, opts) {
|
|
15
|
-
const
|
|
16
|
-
const config =
|
|
9
|
+
const root = process.cwd();
|
|
10
|
+
const config = normalizeOptions(packageName, opts);
|
|
17
11
|
const log = createLogger({ debug: config.debug });
|
|
18
|
-
const
|
|
19
|
-
|
|
20
|
-
const projectDir = resolveProjectDir({ cwd, libName: config.libName, outDir: config.outDir });
|
|
21
|
-
const specSource = opts?.spec;
|
|
22
|
-
|
|
23
|
-
log.title('generate — workspace files');
|
|
24
|
-
log.info(`repo: ${cwd}`);
|
|
25
|
-
log.info(`package: ${config.packageName}@${config.version}`);
|
|
26
|
-
log.info(`lib: ${config.libName}`);
|
|
27
|
-
log.info(`target: ${path.relative(cwd, projectDir)}`);
|
|
28
|
-
if (config.noModels) log.info('models: désactivés (--no-models)');
|
|
29
|
-
else log.info(`spec: ${specSource} | formats: ${config.formats.join(', ')} | nullableMode: ${config.requiredMode}`);
|
|
30
|
-
|
|
31
|
-
let spec;
|
|
32
|
-
if (!config.noModels) {
|
|
33
|
-
if (!specSource) throw new Error('Missing --spec (url or JSON file path)');
|
|
34
|
-
log.step('lecture de la spec OpenAPI');
|
|
35
|
-
spec = await readOpenApiSpec(specSource);
|
|
36
|
-
log.success('spec chargée');
|
|
37
|
-
}
|
|
12
|
+
const projectDir = path.resolve(root, config.outDir || path.join('projects', config.libName));
|
|
38
13
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
projectDir,
|
|
42
|
-
libName: config.libName,
|
|
43
|
-
packageName: config.packageName,
|
|
44
|
-
version: config.version,
|
|
45
|
-
noModels: config.noModels,
|
|
46
|
-
spec,
|
|
47
|
-
requiredMode: config.requiredMode,
|
|
48
|
-
formats: config.formats,
|
|
49
|
-
include: config.include,
|
|
50
|
-
exclude: config.exclude,
|
|
51
|
-
debug: config.debug,
|
|
52
|
-
log,
|
|
53
|
-
projectReadmePath,
|
|
54
|
-
});
|
|
14
|
+
logConfig(log, 'generate — workspace files', config, { repo: root, target: path.relative(root, projectDir) });
|
|
15
|
+
await generateBridge({ config, root, projectDir, log });
|
|
55
16
|
|
|
56
|
-
log.success(`bridge généré: ${path.relative(
|
|
17
|
+
log.success(`bridge généré: ${path.relative(root, projectDir)}`);
|
|
57
18
|
return 0;
|
|
58
19
|
}
|
|
@@ -1,72 +1,19 @@
|
|
|
1
1
|
import { deriveLibName } from './lib-name.js';
|
|
2
2
|
import {splitListArgs} from './list-values.js';
|
|
3
3
|
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
function normalizeCommonOptions(opts) {
|
|
8
|
-
const noModels = opts?.models === false;
|
|
9
|
-
const formats = splitListArgs(opts?.formats);
|
|
10
|
-
const include = splitListArgs(opts?.include);
|
|
11
|
-
const exclude = splitListArgs(opts?.exclude);
|
|
12
|
-
const debug = !!opts?.debug;
|
|
13
|
-
|
|
14
|
-
return { noModels, formats, include, exclude, debug };
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
function normalizeFormats(formats) {
|
|
18
|
-
return formats.length > 0 ? formats : [...DEFAULT_FORMATS];
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
function normalizeVersion(version) {
|
|
22
|
-
const raw = typeof version === 'string' ? version.trim() : '';
|
|
23
|
-
return raw || '0.0.0';
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
function normalizeNamedOptions(packageName, opts) {
|
|
27
|
-
const common = normalizeCommonOptions(opts);
|
|
28
|
-
const normalizedPackageName = String(packageName || '').trim();
|
|
29
|
-
if (!normalizedPackageName) throw new Error('Missing packageName');
|
|
4
|
+
/** Normalizes the arguments of `generate`, `build` and `dev` into one configuration. */
|
|
5
|
+
export function normalizeOptions(packageName, opts = {}) {
|
|
6
|
+
const normalizedPackageName = String(packageName ?? '').trim();
|
|
30
7
|
|
|
31
8
|
return {
|
|
32
9
|
packageName: normalizedPackageName,
|
|
33
10
|
libName: deriveLibName(normalizedPackageName),
|
|
34
|
-
version:
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
};
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
export function normalizeBuildOptions(packageName, opts) {
|
|
45
|
-
return normalizeNamedOptions(packageName, opts);
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
export function normalizeGenerateOptions(packageName, opts) {
|
|
49
|
-
return {
|
|
50
|
-
...normalizeNamedOptions(packageName, opts),
|
|
51
|
-
outDir: typeof opts?.out === 'string' ? opts.out.trim() : '',
|
|
52
|
-
};
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
export function normalizeDevOptions(packageName, opts) {
|
|
56
|
-
const common = normalizeCommonOptions(opts);
|
|
57
|
-
const useSandboxDefaults = packageName === undefined;
|
|
58
|
-
const effectivePackageName = String(packageName ?? SANDBOX_PACKAGE_NAME).trim();
|
|
59
|
-
|
|
60
|
-
return {
|
|
61
|
-
useSandboxDefaults,
|
|
62
|
-
packageName: effectivePackageName,
|
|
63
|
-
libName: deriveLibName(effectivePackageName),
|
|
64
|
-
version: '0.0.0-dev',
|
|
65
|
-
requiredMode: 'all',
|
|
66
|
-
formats: normalizeFormats(common.formats),
|
|
67
|
-
include: common.include,
|
|
68
|
-
exclude: common.exclude,
|
|
69
|
-
noModels: common.noModels,
|
|
70
|
-
debug: common.debug,
|
|
11
|
+
version: (typeof opts.version === 'string' && opts.version.trim()) || '0.0.0',
|
|
12
|
+
specSource: opts.spec,
|
|
13
|
+
include: splitListArgs(opts.include),
|
|
14
|
+
exclude: splitListArgs(opts.exclude),
|
|
15
|
+
noModels: opts.models === false,
|
|
16
|
+
debug: !!opts.debug,
|
|
17
|
+
outDir: typeof opts.out === 'string' ? opts.out.trim() : '',
|
|
71
18
|
};
|
|
72
19
|
}
|