@obsidiane/meridiane 0.0.7 → 0.0.10
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 +14 -66
- package/cli.js +59 -65
- package/package.json +3 -5
- package/templates/_lib_template/package.json +2 -1
- package/templates/_lib_template/src/models/index.ts +0 -1
- package/tools/build.js +104 -0
- package/tools/core/generate.js +152 -0
- package/tools/core/json.js +71 -0
- package/tools/core/lib-name.js +15 -0
- package/tools/core/logger.js +51 -0
- package/tools/core/paths.js +17 -0
- package/tools/core/spec.js +59 -0
- package/tools/core/standalone.js +117 -0
- package/tools/dev.js +218 -0
- package/tools/generator/models/handlebars.js +8 -2
- package/tools/generator/models/naming.js +21 -15
- package/tools/generator/models/openapi-to-models.js +65 -39
- package/tools/generator/models/schema-utils.js +42 -83
- package/tools/generator/models/templates/model.hbs +3 -3
- package/tools/generator/models/utils.js +1 -8
- package/models.config.example.js +0 -39
- package/templates/_lib_template/tsconfig.spec.json +0 -14
- package/tools/dev-bridge.js +0 -138
- package/tools/generate-lib.js +0 -151
- package/tools/generate-models.js +0 -175
- package/tools/init.js +0 -148
- package/tools/sandbox-bridge.js +0 -77
- package/tools/utils/cli-args.js +0 -41
- package/tools/utils/dotenv.js +0 -55
package/README.md
CHANGED
|
@@ -19,82 +19,30 @@ npm i -g @obsidiane/meridiane
|
|
|
19
19
|
## Usage
|
|
20
20
|
|
|
21
21
|
```bash
|
|
22
|
-
#
|
|
23
|
-
|
|
22
|
+
# Dev: génère le bridge (+ models par défaut)
|
|
23
|
+
# (build standalone + install local dans node_modules)
|
|
24
|
+
npx meridiane dev <packageName> --spec <url|file> [--preset[=native|all]] [--include <substr>]... [--exclude <substr>]... [--no-models] [--debug]
|
|
24
25
|
|
|
25
|
-
#
|
|
26
|
-
npx meridiane
|
|
27
|
-
|
|
28
|
-
# Générer des modèles depuis OpenAPI
|
|
29
|
-
npx meridiane models <SPEC_OPENAPI_URL_OU_FICHIER_JSON> [--out=<dir>] [--item-import=../lib/ports/resource-repository.port] [--required-mode=all-optional|spec] [--preset=all|native] [--include=<substr>] [--exclude=<substr>] [--index=1|0]
|
|
30
|
-
|
|
31
|
-
# Dev: regen lib + models depuis un backend (localhost par défaut)
|
|
32
|
-
npx meridiane dev-bridge <lib-name> <npm-package-name> [version]
|
|
33
|
-
|
|
34
|
-
# Dev (repo meridiane): met à jour le bridge du sandbox
|
|
35
|
-
npx meridiane sandbox-bridge
|
|
26
|
+
# CI/CD: génère + build Angular + npm pack (artefact prêt à publier)
|
|
27
|
+
npx meridiane build <packageName> --version <semver> --spec <url|file> [--preset[=native|all]] [--include <substr>]... [--exclude <substr>]... [--no-models] [--debug]
|
|
36
28
|
```
|
|
37
29
|
|
|
38
30
|
Options :
|
|
39
31
|
- `--debug` : active des logs supplémentaires (CLI).
|
|
40
32
|
|
|
41
|
-
`url-registry` est optionnel. Recommandé : gérer le registry via `.npmrc` / variables CI plutôt que de le passer au générateur.
|
|
42
|
-
|
|
43
33
|
Bonnes pratiques :
|
|
44
|
-
-
|
|
45
|
-
-
|
|
46
|
-
-
|
|
47
|
-
|
|
48
|
-
## Exemple CI (générer + build + publish)
|
|
34
|
+
- utiliser `--preset=native` si vous voulez des modèles “entity-like” (sans variantes `.jsonld/.jsonMergePatch`)
|
|
35
|
+
- utiliser `--no-models` si vous voulez uniquement le runtime (pas besoin de `--spec`)
|
|
36
|
+
- laisser le registry à la CI (`.npmrc`, variables d’environnement, `npm publish --registry …`)
|
|
49
37
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
npx -y @obsidiane/meridiane@0.1.0 lib backend-bridge @acme/backend-bridge 0.1.0
|
|
53
|
-
npx -y @obsidiane/meridiane@0.1.0 models http://localhost:8000/api/docs.json --out=projects/backend-bridge/src/models
|
|
54
|
-
npx ng build backend-bridge
|
|
55
|
-
cd dist/backend-bridge
|
|
56
|
-
npm publish
|
|
57
|
-
```
|
|
58
|
-
|
|
59
|
-
Si votre workspace ne contient pas `ng-packagr` (souvent le cas dans une app Angular “pure”), installez-le :
|
|
60
|
-
|
|
61
|
-
```bash
|
|
62
|
-
npm i -D ng-packagr
|
|
63
|
-
```
|
|
38
|
+
Note (repo Meridiane) :
|
|
39
|
+
- dans le repo Meridiane, `meridiane dev` peut être exécutée sans `packageName` et se préconfigure pour `apps/sandbox` (`@obsidiane/bridge-sandbox`).
|
|
64
40
|
|
|
65
|
-
##
|
|
66
|
-
|
|
67
|
-
Le générateur de modèles charge automatiquement un fichier `models.config.js` s’il existe dans le répertoire courant (CWD) ou dans un dossier parent.
|
|
68
|
-
|
|
69
|
-
Copie rapide (après installation) :
|
|
41
|
+
## Exemple CI (générer + build + publish)
|
|
70
42
|
|
|
71
43
|
```bash
|
|
72
|
-
|
|
44
|
+
npx -y @obsidiane/meridiane@0.1.0 build @acme/backend-bridge --version 0.1.0 --spec https://staging.example/api/docs.json --preset=native
|
|
45
|
+
npm publish dist/backend-bridge
|
|
73
46
|
```
|
|
74
47
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
## Configuration via `.env`
|
|
78
|
-
|
|
79
|
-
Le CLI charge automatiquement un fichier `.env` (CWD ou dossier parent) avant d’exécuter `lib/models`.
|
|
80
|
-
|
|
81
|
-
Variables utiles :
|
|
82
|
-
- `MERIDIANE_DEBUG=1`
|
|
83
|
-
- `MERIDIANE_LIB_VERSION=0.1.0`
|
|
84
|
-
- `MERIDIANE_NPM_REGISTRY_URL=https://…`
|
|
85
|
-
- `MERIDIANE_MODELS_OUT=projects/<lib>/src/models`
|
|
86
|
-
- `MERIDIANE_MODELS_REQUIRED_MODE=spec`
|
|
87
|
-
- `MERIDIANE_MODELS_ITEM_IMPORT=../lib/ports/resource-repository.port`
|
|
88
|
-
- `MERIDIANE_MODELS_INDEX=1`
|
|
89
|
-
|
|
90
|
-
Ordre de priorité :
|
|
91
|
-
1) flags CLI
|
|
92
|
-
2) `models.config.js`
|
|
93
|
-
3) `.env`
|
|
94
|
-
4) valeurs par défaut
|
|
95
|
-
|
|
96
|
-
Sans installation préalable :
|
|
97
|
-
|
|
98
|
-
```bash
|
|
99
|
-
npx -y @obsidiane/meridiane lib <lib-name> <npm-package-name> [version] [url-registry]
|
|
100
|
-
```
|
|
48
|
+
Note : Meridiane installe le toolchain (`ng-packagr`, `@angular/*`, …) dans `dist/.meridiane-workspace` si nécessaire.
|
package/cli.js
CHANGED
|
@@ -1,76 +1,70 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import
|
|
3
|
-
import
|
|
4
|
-
import {spawn} from 'node:child_process';
|
|
2
|
+
import process from 'node:process';
|
|
3
|
+
import { Command } from 'commander';
|
|
5
4
|
|
|
6
|
-
|
|
7
|
-
|
|
5
|
+
import { runDev } from './tools/dev.js';
|
|
6
|
+
import { runBuild } from './tools/build.js';
|
|
7
|
+
import { createLogger } from './tools/core/logger.js';
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
const scriptPath = path.join(__dirname, 'tools', scriptName);
|
|
11
|
-
const child = spawn(process.execPath, [scriptPath, ...args], {
|
|
12
|
-
stdio: 'inherit',
|
|
13
|
-
env: process.env,
|
|
14
|
-
});
|
|
9
|
+
const program = new Command();
|
|
15
10
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
function printHelp() {
|
|
22
|
-
console.log(`
|
|
23
|
-
Usage:
|
|
24
|
-
meridiane init [--force]
|
|
25
|
-
meridiane lib <lib-name> <npm-package-name> [version] [url-registry]
|
|
26
|
-
meridiane models <SPEC_OPENAPI_URL_OU_FICHIER_JSON> [options]
|
|
27
|
-
meridiane dev-bridge <lib-name> <npm-package-name> [version] [options]
|
|
28
|
-
meridiane sandbox-bridge [options]
|
|
11
|
+
program
|
|
12
|
+
.name('meridiane')
|
|
13
|
+
.description('Generate an Angular bridge + models from an API Platform OpenAPI spec.')
|
|
14
|
+
.showHelpAfterError()
|
|
15
|
+
.showSuggestionAfterError();
|
|
29
16
|
|
|
30
|
-
|
|
31
|
-
|
|
17
|
+
function presetParser(value, previous) {
|
|
18
|
+
if (value === undefined && previous === undefined) return true; // `--preset` (no value) => native
|
|
19
|
+
return value ?? previous;
|
|
20
|
+
}
|
|
32
21
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
npx -y @obsidiane/meridiane lib backend-bridge @acme/backend-bridge 0.1.0
|
|
36
|
-
npx -y @obsidiane/meridiane models http://localhost:8000/api/docs.json --out=projects/backend-bridge/src/models
|
|
37
|
-
npx -y @obsidiane/meridiane dev-bridge bridge-sandbox @obsidiane/bridge-sandbox 0.1.0
|
|
38
|
-
npx -y @obsidiane/meridiane sandbox-bridge
|
|
39
|
-
`);
|
|
22
|
+
function collect(value, previous = []) {
|
|
23
|
+
return [...previous, value];
|
|
40
24
|
}
|
|
41
25
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
26
|
+
function commonOptions(cmd) {
|
|
27
|
+
return cmd
|
|
28
|
+
.option('--spec <urlOrFile>', 'OpenAPI spec source (URL or local JSON file)')
|
|
29
|
+
.option('--preset [mode]', 'Preset: native|all (default: all; `--preset` alone => native)', presetParser)
|
|
30
|
+
.option('--include <substr>', 'Include schema names containing substring (repeatable, supports commas)', collect, [])
|
|
31
|
+
.option('--exclude <substr>', 'Exclude schema names containing substring (repeatable, supports commas)', collect, [])
|
|
32
|
+
.option('--no-models', 'Skip models generation (then --spec is not required)')
|
|
33
|
+
.option('--debug', 'Enable debug logs');
|
|
47
34
|
}
|
|
48
35
|
|
|
49
|
-
|
|
36
|
+
commonOptions(
|
|
37
|
+
program
|
|
38
|
+
.command('dev')
|
|
39
|
+
.argument(
|
|
40
|
+
'[packageName]',
|
|
41
|
+
'NPM package name of the generated bridge (e.g. @acme/backend-bridge). Optional in this repo (defaults to @obsidiane/bridge-sandbox).'
|
|
42
|
+
)
|
|
43
|
+
.action(async (packageName, opts) => {
|
|
44
|
+
const log = createLogger({ debug: !!opts?.debug });
|
|
45
|
+
try {
|
|
46
|
+
await runDev(packageName, opts);
|
|
47
|
+
} catch (err) {
|
|
48
|
+
log.error(err);
|
|
49
|
+
process.exit(1);
|
|
50
|
+
}
|
|
51
|
+
})
|
|
52
|
+
);
|
|
50
53
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
case '--help':
|
|
69
|
-
case undefined:
|
|
70
|
-
printHelp();
|
|
71
|
-
break;
|
|
72
|
-
default:
|
|
73
|
-
console.error(`Unknown command: ${cmd}`);
|
|
74
|
-
printHelp();
|
|
75
|
-
process.exit(1);
|
|
76
|
-
}
|
|
54
|
+
commonOptions(
|
|
55
|
+
program
|
|
56
|
+
.command('build')
|
|
57
|
+
.argument('<packageName>', 'NPM package name of the generated bridge (e.g. @acme/backend-bridge)')
|
|
58
|
+
.requiredOption('--version <semver>', 'Version to write in the generated bridge package.json (CI/CD)')
|
|
59
|
+
.action(async (packageName, opts) => {
|
|
60
|
+
const log = createLogger({ debug: !!opts?.debug });
|
|
61
|
+
try {
|
|
62
|
+
await runBuild(packageName, opts);
|
|
63
|
+
} catch (err) {
|
|
64
|
+
log.error(err);
|
|
65
|
+
process.exit(1);
|
|
66
|
+
}
|
|
67
|
+
})
|
|
68
|
+
);
|
|
69
|
+
|
|
70
|
+
program.parse(process.argv);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@obsidiane/meridiane",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.10",
|
|
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",
|
|
@@ -14,21 +14,19 @@
|
|
|
14
14
|
"pack:check": "npm_config_cache=./.npm-cache npm pack --dry-run"
|
|
15
15
|
},
|
|
16
16
|
"bin": {
|
|
17
|
-
"meridiane": "cli.js"
|
|
18
|
-
"meridiane-generate-lib": "tools/generate-lib.js",
|
|
19
|
-
"meridiane-generate-models": "tools/generate-models.js"
|
|
17
|
+
"meridiane": "cli.js"
|
|
20
18
|
},
|
|
21
19
|
"files": [
|
|
22
20
|
"cli.js",
|
|
23
21
|
"tools/**",
|
|
24
22
|
"templates/**",
|
|
25
|
-
"models.config.example.js",
|
|
26
23
|
"README.md"
|
|
27
24
|
],
|
|
28
25
|
"engines": {
|
|
29
26
|
"node": ">=18"
|
|
30
27
|
},
|
|
31
28
|
"dependencies": {
|
|
29
|
+
"commander": "^14.0.2",
|
|
32
30
|
"handlebars": "^4.7.8",
|
|
33
31
|
"tslib": "^2.3.0"
|
|
34
32
|
}
|
package/tools/build.js
ADDED
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import process from 'node:process';
|
|
3
|
+
import { spawn } from 'node:child_process';
|
|
4
|
+
import path from 'node:path';
|
|
5
|
+
|
|
6
|
+
import { deriveLibName } from './core/lib-name.js';
|
|
7
|
+
import { normalizePreset, splitListArgs } from './core/paths.js';
|
|
8
|
+
import { readOpenApiSpec } from './core/spec.js';
|
|
9
|
+
import { createLogger } from './core/logger.js';
|
|
10
|
+
import { generateBridgeWorkspace } from './core/generate.js';
|
|
11
|
+
import { ensureStandaloneWorkspace, ensureToolchainInstalled } from './core/standalone.js';
|
|
12
|
+
|
|
13
|
+
function run(cmd, args, { cwd, env } = {}) {
|
|
14
|
+
return new Promise((resolve) => {
|
|
15
|
+
const child = spawn(cmd, args, {
|
|
16
|
+
stdio: 'inherit',
|
|
17
|
+
cwd: cwd ?? process.cwd(),
|
|
18
|
+
env: env ?? process.env,
|
|
19
|
+
});
|
|
20
|
+
child.on('exit', (code) => resolve(code ?? 0));
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export async function runBuild(packageName, opts) {
|
|
25
|
+
const cwd = process.cwd();
|
|
26
|
+
const debug = !!opts.debug;
|
|
27
|
+
const log = createLogger({ debug });
|
|
28
|
+
|
|
29
|
+
const version = opts.version;
|
|
30
|
+
if (!version) throw new Error('Missing --version');
|
|
31
|
+
|
|
32
|
+
const noModels = opts.models === false;
|
|
33
|
+
const preset = normalizePreset(opts.preset);
|
|
34
|
+
const include = splitListArgs(opts.include);
|
|
35
|
+
const exclude = splitListArgs(opts.exclude);
|
|
36
|
+
|
|
37
|
+
const libName = deriveLibName(packageName);
|
|
38
|
+
const requiredMode = 'spec';
|
|
39
|
+
|
|
40
|
+
// Standalone mode (native): works from any repo (backend pipeline or otherwise).
|
|
41
|
+
log.title('build — standalone');
|
|
42
|
+
log.info(`repo: ${cwd}`);
|
|
43
|
+
log.info(`package: ${packageName}@${version}`);
|
|
44
|
+
log.info(`lib: ${libName}`);
|
|
45
|
+
if (noModels) log.info('models: désactivés (--no-models)');
|
|
46
|
+
else log.info(`spec: ${opts.spec} | preset: ${preset}`);
|
|
47
|
+
|
|
48
|
+
const { workspaceRoot, distRoot, npmCacheDir, requireFromWs } = await ensureStandaloneWorkspace({ repoRoot: cwd, log });
|
|
49
|
+
log.info(`workspace: ${workspaceRoot}`);
|
|
50
|
+
|
|
51
|
+
let spec;
|
|
52
|
+
if (!noModels) {
|
|
53
|
+
log.step('lecture de la spec OpenAPI');
|
|
54
|
+
spec = await readOpenApiSpec(opts.spec);
|
|
55
|
+
log.success('spec chargée');
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
await ensureToolchainInstalled({ workspaceRoot, requireFromWs, npmCacheDir, log });
|
|
59
|
+
|
|
60
|
+
await generateBridgeWorkspace({
|
|
61
|
+
cwd: workspaceRoot,
|
|
62
|
+
libName,
|
|
63
|
+
packageName,
|
|
64
|
+
version,
|
|
65
|
+
noModels,
|
|
66
|
+
spec,
|
|
67
|
+
requiredMode,
|
|
68
|
+
preset,
|
|
69
|
+
include,
|
|
70
|
+
exclude,
|
|
71
|
+
debug,
|
|
72
|
+
log,
|
|
73
|
+
distRoot,
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
const ngPackageJsonPath = path.join(workspaceRoot, 'projects', libName, 'ng-package.json');
|
|
77
|
+
const tsconfigProdPath = path.join(workspaceRoot, 'projects', libName, 'tsconfig.lib.prod.json');
|
|
78
|
+
|
|
79
|
+
log.step('ng-packagr');
|
|
80
|
+
const ngPackagrPath = requireFromWs.resolve('ng-packagr/src/cli/main.js');
|
|
81
|
+
const packagrCode = await run(process.execPath, [ngPackagrPath, '-p', ngPackageJsonPath, '-c', tsconfigProdPath], {
|
|
82
|
+
cwd: workspaceRoot,
|
|
83
|
+
});
|
|
84
|
+
if (packagrCode !== 0) process.exit(packagrCode);
|
|
85
|
+
|
|
86
|
+
const distDir = path.join(distRoot, libName);
|
|
87
|
+
log.step(`npm pack (${path.relative(cwd, distDir)})`);
|
|
88
|
+
const packCode = await run('npm', ['pack'], {
|
|
89
|
+
cwd: distDir,
|
|
90
|
+
env: {
|
|
91
|
+
...process.env,
|
|
92
|
+
npm_config_cache: npmCacheDir,
|
|
93
|
+
npm_config_update_notifier: 'false',
|
|
94
|
+
npm_config_fund: 'false',
|
|
95
|
+
npm_config_audit: 'false',
|
|
96
|
+
},
|
|
97
|
+
});
|
|
98
|
+
if (packCode === 0) log.success(`package prêt: ${path.relative(cwd, distDir)}`);
|
|
99
|
+
process.exit(packCode);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
if (import.meta.url === `file://${process.argv[1]}`) {
|
|
103
|
+
throw new Error('This module is not meant to be executed directly.');
|
|
104
|
+
}
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
import fs from 'node:fs/promises';
|
|
2
|
+
import { existsSync } from 'node:fs';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import { fileURLToPath } from 'node:url';
|
|
5
|
+
|
|
6
|
+
import { readJson, writeJsonIfChanged } from './json.js';
|
|
7
|
+
import { renderTemplateToFile } from '../generator/models/handlebars.js';
|
|
8
|
+
import { ensureCleanDir } from '../generator/models/utils.js';
|
|
9
|
+
import { buildModelsFromOpenAPI } from '../generator/models/openapi-to-models.js';
|
|
10
|
+
|
|
11
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
12
|
+
const __dirname = path.dirname(__filename);
|
|
13
|
+
|
|
14
|
+
const pkgRoot = path.resolve(__dirname, '../..'); // packages/meridiane
|
|
15
|
+
|
|
16
|
+
async function replacePlaceholdersInDir(dir, placeholders) {
|
|
17
|
+
const entries = await fs.readdir(dir, { withFileTypes: true });
|
|
18
|
+
for (const entry of entries) {
|
|
19
|
+
const fullPath = path.join(dir, entry.name);
|
|
20
|
+
if (entry.isDirectory()) {
|
|
21
|
+
await replacePlaceholdersInDir(fullPath, placeholders);
|
|
22
|
+
} else if (entry.isFile()) {
|
|
23
|
+
let content = await fs.readFile(fullPath, 'utf8');
|
|
24
|
+
for (const [key, value] of Object.entries(placeholders)) {
|
|
25
|
+
content = content.replaceAll(key, value);
|
|
26
|
+
}
|
|
27
|
+
await fs.writeFile(fullPath, content);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
async function generateLibrary({ cwd, libName, packageName, version, debug }) {
|
|
33
|
+
const tplDir = path.resolve(pkgRoot, 'templates/_lib_template');
|
|
34
|
+
const targetDir = path.resolve(cwd, 'projects', libName);
|
|
35
|
+
|
|
36
|
+
if (debug) console.log('[meridiane] generate lib', { libName, packageName, version, targetDir: path.relative(cwd, targetDir) });
|
|
37
|
+
|
|
38
|
+
await fs.rm(targetDir, { recursive: true, force: true });
|
|
39
|
+
await fs.mkdir(targetDir, { recursive: true });
|
|
40
|
+
await fs.cp(tplDir, targetDir, { recursive: true });
|
|
41
|
+
|
|
42
|
+
await replacePlaceholdersInDir(targetDir, { '__LIB_NAME__': libName });
|
|
43
|
+
|
|
44
|
+
const libPackageJsonPath = path.join(targetDir, 'package.json');
|
|
45
|
+
if (existsSync(libPackageJsonPath)) {
|
|
46
|
+
const libPkg = await readJson(libPackageJsonPath);
|
|
47
|
+
libPkg.name = packageName;
|
|
48
|
+
libPkg.version = version;
|
|
49
|
+
await writeJsonIfChanged(libPackageJsonPath, libPkg);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// angular.json / workspace patching is intentionally not done here (Meridiane is standalone).
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
async function generateModels({ cwd, libName, spec, requiredMode, preset, include, exclude, debug }) {
|
|
56
|
+
const outDir = path.resolve(cwd, 'projects', libName, 'src', 'models');
|
|
57
|
+
const templatesDir = path.resolve(pkgRoot, 'tools', 'generator', 'models', 'templates');
|
|
58
|
+
|
|
59
|
+
if (debug) console.log('[meridiane] generate models', { outDir: path.relative(cwd, outDir), requiredMode, preset, includeCount: include.length, excludeCount: exclude.length });
|
|
60
|
+
|
|
61
|
+
await ensureCleanDir(outDir);
|
|
62
|
+
|
|
63
|
+
// Write models + index.ts.
|
|
64
|
+
const { models } = buildModelsFromOpenAPI(spec, {
|
|
65
|
+
requiredMode,
|
|
66
|
+
preset,
|
|
67
|
+
includeSchemaNames: include,
|
|
68
|
+
excludeSchemaNames: exclude,
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
for (const m of models) {
|
|
72
|
+
await renderTemplateToFile({
|
|
73
|
+
templatePath: path.join(templatesDir, 'model.hbs'),
|
|
74
|
+
outPath: path.join(outDir, `${m.name}.ts`),
|
|
75
|
+
ctx: {
|
|
76
|
+
name: m.name,
|
|
77
|
+
props: m.props,
|
|
78
|
+
imports: m.imports,
|
|
79
|
+
extendsTypes: m.extendsTypes || [],
|
|
80
|
+
},
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
await renderTemplateToFile({
|
|
85
|
+
templatePath: path.join(templatesDir, 'index.hbs'),
|
|
86
|
+
outPath: path.join(outDir, 'index.ts'),
|
|
87
|
+
ctx: { models: models.map((m) => ({ name: m.name })) },
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
if (debug) console.log(`[meridiane] ${models.length} model(s) generated`);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* @param {{
|
|
95
|
+
* cwd: string,
|
|
96
|
+
* libName: string,
|
|
97
|
+
* packageName: string,
|
|
98
|
+
* version: string,
|
|
99
|
+
* noModels: boolean,
|
|
100
|
+
* spec?: any,
|
|
101
|
+
* requiredMode: 'all-optional'|'spec',
|
|
102
|
+
* preset: 'all'|'native',
|
|
103
|
+
* include: string[],
|
|
104
|
+
* exclude: string[],
|
|
105
|
+
* debug: boolean,
|
|
106
|
+
* log?: { step?: (msg: string) => void, info?: (msg: string) => void, success?: (msg: string) => void, debug?: (msg: string, data?: any) => void },
|
|
107
|
+
* workspaceMode?: 'angular'|'standalone',
|
|
108
|
+
* distRoot?: string,
|
|
109
|
+
* }} params
|
|
110
|
+
*/
|
|
111
|
+
export async function generateBridgeWorkspace(params) {
|
|
112
|
+
const {
|
|
113
|
+
cwd,
|
|
114
|
+
libName,
|
|
115
|
+
packageName,
|
|
116
|
+
version,
|
|
117
|
+
noModels,
|
|
118
|
+
spec,
|
|
119
|
+
requiredMode,
|
|
120
|
+
preset,
|
|
121
|
+
include,
|
|
122
|
+
exclude,
|
|
123
|
+
debug,
|
|
124
|
+
log,
|
|
125
|
+
distRoot,
|
|
126
|
+
} = params;
|
|
127
|
+
|
|
128
|
+
log?.step?.(`génération de la librairie (projects/${libName})`);
|
|
129
|
+
await generateLibrary({ cwd, libName, packageName, version, debug });
|
|
130
|
+
|
|
131
|
+
// If a dist root is provided, force ng-packagr output to that directory.
|
|
132
|
+
if (distRoot) {
|
|
133
|
+
const ngPackagePath = path.resolve(cwd, 'projects', libName, 'ng-package.json');
|
|
134
|
+
const ngPkg = await readJson(ngPackagePath);
|
|
135
|
+
const desiredDestAbs = path.resolve(distRoot, libName);
|
|
136
|
+
const projectDir = path.dirname(ngPackagePath);
|
|
137
|
+
ngPkg.dest = path.relative(projectDir, desiredDestAbs).split(path.sep).join('/');
|
|
138
|
+
await writeJsonIfChanged(ngPackagePath, ngPkg);
|
|
139
|
+
log?.debug?.('ng-package.json dest overridden', { dest: ngPkg.dest });
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
if (noModels) {
|
|
143
|
+
log?.info?.('models ignorés (--no-models)');
|
|
144
|
+
if (debug) console.log('[meridiane] --no-models enabled, skipping models generation');
|
|
145
|
+
return;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
if (!spec) throw new Error('Internal error: spec is required when noModels=false');
|
|
149
|
+
log?.step?.('génération des models (OpenAPI)');
|
|
150
|
+
await generateModels({ cwd, libName, spec, requiredMode, preset, include, exclude, debug });
|
|
151
|
+
log?.success?.('génération terminée');
|
|
152
|
+
}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import fs from 'node:fs/promises';
|
|
2
|
+
|
|
3
|
+
function stripJsonComments(input) {
|
|
4
|
+
let out = '';
|
|
5
|
+
let i = 0;
|
|
6
|
+
let inString = false;
|
|
7
|
+
let stringQuote = '"';
|
|
8
|
+
while (i < input.length) {
|
|
9
|
+
const ch = input[i];
|
|
10
|
+
|
|
11
|
+
if (inString) {
|
|
12
|
+
out += ch;
|
|
13
|
+
if (ch === '\\') {
|
|
14
|
+
// skip escaped char
|
|
15
|
+
i += 2;
|
|
16
|
+
continue;
|
|
17
|
+
}
|
|
18
|
+
if (ch === stringQuote) inString = false;
|
|
19
|
+
i += 1;
|
|
20
|
+
continue;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
if (ch === '"' || ch === "'") {
|
|
24
|
+
inString = true;
|
|
25
|
+
stringQuote = ch;
|
|
26
|
+
out += ch;
|
|
27
|
+
i += 1;
|
|
28
|
+
continue;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// line comment
|
|
32
|
+
if (ch === '/' && input[i + 1] === '/') {
|
|
33
|
+
i += 2;
|
|
34
|
+
while (i < input.length && input[i] !== '\n') i += 1;
|
|
35
|
+
continue;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// block comment
|
|
39
|
+
if (ch === '/' && input[i + 1] === '*') {
|
|
40
|
+
i += 2;
|
|
41
|
+
while (i < input.length && !(input[i] === '*' && input[i + 1] === '/')) i += 1;
|
|
42
|
+
i += 2;
|
|
43
|
+
continue;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
out += ch;
|
|
47
|
+
i += 1;
|
|
48
|
+
}
|
|
49
|
+
return out;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export async function readJson(filePath) {
|
|
53
|
+
const raw = await fs.readFile(filePath, 'utf8');
|
|
54
|
+
try {
|
|
55
|
+
return JSON.parse(raw);
|
|
56
|
+
} catch {
|
|
57
|
+
return JSON.parse(stripJsonComments(raw));
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export async function writeJsonIfChanged(filePath, data) {
|
|
62
|
+
const next = JSON.stringify(data, null, 2) + '\n';
|
|
63
|
+
try {
|
|
64
|
+
const prev = await fs.readFile(filePath, 'utf8');
|
|
65
|
+
if (prev === next) return false;
|
|
66
|
+
} catch {
|
|
67
|
+
// ignore (file missing / unreadable)
|
|
68
|
+
}
|
|
69
|
+
await fs.writeFile(filePath, next, 'utf8');
|
|
70
|
+
return true;
|
|
71
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export function deriveLibName(packageName) {
|
|
2
|
+
const raw = String(packageName || '').trim();
|
|
3
|
+
if (!raw) throw new Error('Missing packageName');
|
|
4
|
+
|
|
5
|
+
const base = raw.startsWith('@') ? (raw.split('/')[1] || '') : raw;
|
|
6
|
+
const normalized = base
|
|
7
|
+
.toLowerCase()
|
|
8
|
+
.replace(/[^a-z0-9-]+/g, '-')
|
|
9
|
+
.replace(/-+/g, '-')
|
|
10
|
+
.replace(/^-|-$/g, '');
|
|
11
|
+
|
|
12
|
+
if (!normalized) throw new Error(`Invalid packageName: ${packageName}`);
|
|
13
|
+
return normalized;
|
|
14
|
+
}
|
|
15
|
+
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import process from 'node:process';
|
|
2
|
+
|
|
3
|
+
function color(code, text) {
|
|
4
|
+
if (!process.stdout.isTTY) return text;
|
|
5
|
+
return `\u001b[${code}m${text}\u001b[0m`;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
function line(prefix, text) {
|
|
9
|
+
return `${prefix} ${text}`;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export function createLogger({ debug = false } = {}) {
|
|
13
|
+
const prefix = color('35', 'meridiane');
|
|
14
|
+
|
|
15
|
+
return {
|
|
16
|
+
debugEnabled: debug,
|
|
17
|
+
|
|
18
|
+
title(text) {
|
|
19
|
+
console.log(line('🌙', `${prefix} ${text}`));
|
|
20
|
+
},
|
|
21
|
+
|
|
22
|
+
step(text) {
|
|
23
|
+
console.log(line('🔧', `${prefix} ${text}`));
|
|
24
|
+
},
|
|
25
|
+
|
|
26
|
+
info(text) {
|
|
27
|
+
console.log(line('ℹ️', `${prefix} ${text}`));
|
|
28
|
+
},
|
|
29
|
+
|
|
30
|
+
success(text) {
|
|
31
|
+
console.log(line('✅', `${prefix} ${text}`));
|
|
32
|
+
},
|
|
33
|
+
|
|
34
|
+
warn(text) {
|
|
35
|
+
console.warn(line('⚠️', `${prefix} ${text}`));
|
|
36
|
+
},
|
|
37
|
+
|
|
38
|
+
debug(text, data) {
|
|
39
|
+
if (!debug) return;
|
|
40
|
+
if (data === undefined) console.log(line('🐛', `${prefix} ${text}`));
|
|
41
|
+
else console.log(line('🐛', `${prefix} ${text} ${JSON.stringify(data, null, 2)}`));
|
|
42
|
+
},
|
|
43
|
+
|
|
44
|
+
error(err) {
|
|
45
|
+
const msg = err?.message ?? String(err);
|
|
46
|
+
console.error(line('❌', `${prefix} ${msg}`));
|
|
47
|
+
if (debug && err?.stack) console.error(err.stack);
|
|
48
|
+
},
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
|