@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
|
@@ -1,16 +1,5 @@
|
|
|
1
|
-
/** Hydra base schemas to ignore in
|
|
2
|
-
const
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* Récupère la RegExp Hydra à partir d'une config optionnelle.
|
|
6
|
-
* @param {{ hydraBaseRegex?: RegExp|string }} [cfg]
|
|
7
|
-
*/
|
|
8
|
-
function hydraRe(cfg) {
|
|
9
|
-
const r = cfg?.hydraBaseRegex;
|
|
10
|
-
if (!r) return DEFAULT_HYDRA_BASE_RE;
|
|
11
|
-
if (r instanceof RegExp) return r;
|
|
12
|
-
try { return new RegExp(r); } catch { return DEFAULT_HYDRA_BASE_RE; }
|
|
13
|
-
}
|
|
1
|
+
/** Hydra base schemas to ignore in safe `allOf` merges */
|
|
2
|
+
const HYDRA_BASE_RE = /^Hydra(?:Item|Collection)BaseSchema$/;
|
|
14
3
|
|
|
15
4
|
/**
|
|
16
5
|
* Détermine si une valeur est un objet simple (non tableau)
|
|
@@ -37,12 +26,12 @@ export function schemaNameFromRef($ref) {
|
|
|
37
26
|
* @param {any[]} schemas
|
|
38
27
|
* @returns {any|null}
|
|
39
28
|
*/
|
|
40
|
-
export function mergeAllOf(schemas
|
|
29
|
+
export function mergeAllOf(schemas) {
|
|
41
30
|
const out = {type: 'object', properties: {}, required: []};
|
|
42
31
|
for (const s of schemas) {
|
|
43
32
|
if (s.$ref) {
|
|
44
33
|
const ref = schemaNameFromRef(s.$ref);
|
|
45
|
-
if (
|
|
34
|
+
if (HYDRA_BASE_RE.test(ref)) {
|
|
46
35
|
continue;
|
|
47
36
|
}
|
|
48
37
|
return null;
|
|
@@ -59,69 +48,13 @@ export function mergeAllOf(schemas, cfg) {
|
|
|
59
48
|
}
|
|
60
49
|
|
|
61
50
|
/**
|
|
62
|
-
*
|
|
63
|
-
*
|
|
64
|
-
* - Exclut HydraItem/Collection base schemas
|
|
65
|
-
* - Garde les noms groupés (avec `-`) en choisissant la meilleure variante
|
|
66
|
-
* - Garde les racines sans point et sans suffixe jsonld/jsonapi
|
|
51
|
+
* Liste les noms de schémas dans `components.schemas`.
|
|
52
|
+
* La sélection/filtres est gérée par `applySchemaNameFilters`.
|
|
67
53
|
* @param {Record<string, any>} schemas
|
|
68
54
|
* @returns {string[]}
|
|
69
55
|
*/
|
|
70
56
|
export function filterSchemaNames(schemas, cfg) {
|
|
71
|
-
|
|
72
|
-
: (cfg?.preferFlavor === 'none') ? ['none','jsonld','jsonapi']
|
|
73
|
-
: ['jsonld','none','jsonapi']; // default jsonld
|
|
74
|
-
const flavorOf = (name) => /\.jsonld\b/i.test(name) ? 'jsonld' : /\.jsonapi\b/i.test(name) ? 'jsonapi' : 'none';
|
|
75
|
-
const rank = (name) => 3 - prefer.indexOf(flavorOf(name));
|
|
76
|
-
|
|
77
|
-
const roots = [];
|
|
78
|
-
const grouped = new Map();
|
|
79
|
-
const variantsNoGroup = new Map(); // base -> best variant (no '-')
|
|
80
|
-
|
|
81
|
-
for (const n of Object.keys(schemas)) {
|
|
82
|
-
if (hydraRe(cfg).test(n)) continue;
|
|
83
|
-
|
|
84
|
-
const hasGroup = n.includes('-');
|
|
85
|
-
const hasDot = n.includes('.');
|
|
86
|
-
const isJsonFlavor = /\.jsonld\b|\.jsonapi\b/i.test(n);
|
|
87
|
-
|
|
88
|
-
if (!hasGroup) {
|
|
89
|
-
// Cas 1: racine simple (pas de point, pas de variante jsonld/jsonapi)
|
|
90
|
-
if (!hasDot && !isJsonFlavor) {
|
|
91
|
-
roots.push(n);
|
|
92
|
-
continue;
|
|
93
|
-
}
|
|
94
|
-
// Cas 2: variante jsonld/jsonapi sans groupe (ex: Workflow.WorkflowInput.jsonld ou Foo.jsonld)
|
|
95
|
-
if (isJsonFlavor) {
|
|
96
|
-
const base = n.replace(/\.(jsonld|jsonapi)\b/i, '');
|
|
97
|
-
// Si une base "pure" existe dans la spec, on préfère l’ignorer pour éviter les doublons
|
|
98
|
-
if (Object.prototype.hasOwnProperty.call(schemas, base)) {
|
|
99
|
-
continue;
|
|
100
|
-
}
|
|
101
|
-
const curr = variantsNoGroup.get(base);
|
|
102
|
-
if (!curr || rank(n) > rank(curr)) variantsNoGroup.set(base, n);
|
|
103
|
-
continue;
|
|
104
|
-
}
|
|
105
|
-
// Cas 3: nom avec point mais sans suffixe jsonld/jsonapi et sans group — on le considère tel quel
|
|
106
|
-
variantsNoGroup.set(n, n);
|
|
107
|
-
continue;
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
const m = n.match(/^(.*?)(?:\.(?:jsonld|jsonapi))?-(.+)$/i);
|
|
111
|
-
if (!m) {
|
|
112
|
-
const curr = grouped.get(n);
|
|
113
|
-
if (!curr || rank(n) > rank(curr)) grouped.set(n, n);
|
|
114
|
-
continue;
|
|
115
|
-
}
|
|
116
|
-
const base = m[1];
|
|
117
|
-
const group = m[2];
|
|
118
|
-
const key = `${base}|${group}`;
|
|
119
|
-
const curr = grouped.get(key);
|
|
120
|
-
if (!curr || rank(n) > rank(curr)) grouped.set(key, n);
|
|
121
|
-
}
|
|
122
|
-
// Ajoute les variantes sans groupe sélectionnées
|
|
123
|
-
const noGroupSelected = [...variantsNoGroup.values()];
|
|
124
|
-
return [...roots, ...[...grouped.values()], ...noGroupSelected].sort((a, b) => a.localeCompare(b));
|
|
57
|
+
return Object.keys(schemas).sort((a, b) => a.localeCompare(b));
|
|
125
58
|
}
|
|
126
59
|
|
|
127
60
|
/**
|
|
@@ -167,18 +100,44 @@ export function applySchemaNameFilters(schemaNames, cfg) {
|
|
|
167
100
|
/** @type {Array<RegExp|string|((name: string) => boolean)>} */
|
|
168
101
|
const exclude = Array.isArray(cfg?.excludeSchemaNames) ? [...cfg.excludeSchemaNames] : [];
|
|
169
102
|
|
|
170
|
-
|
|
171
|
-
exclude.push(/^Hydra/i);
|
|
172
|
-
exclude.push(/jsonMergePatch/i);
|
|
173
|
-
exclude.push(/\.jsonld\b/i);
|
|
174
|
-
exclude.push(/\.jsonapi\b/i);
|
|
175
|
-
}
|
|
176
|
-
|
|
103
|
+
// 1) include/exclude user filters (always applied first)
|
|
177
104
|
const hasInclude = include.length > 0;
|
|
178
|
-
|
|
179
|
-
return schemaNames.filter((name) => {
|
|
105
|
+
let out = schemaNames.filter((name) => {
|
|
180
106
|
if (hasInclude && !include.some((r) => matchesRule(name, r))) return false;
|
|
181
107
|
if (exclude.some((r) => matchesRule(name, r))) return false;
|
|
182
108
|
return true;
|
|
183
109
|
});
|
|
110
|
+
|
|
111
|
+
// 2) preset filters
|
|
112
|
+
if (preset === 'native') {
|
|
113
|
+
out = out
|
|
114
|
+
.filter((n) => !/^Hydra/i.test(n))
|
|
115
|
+
.filter((n) => !/jsonMergePatch/i.test(n))
|
|
116
|
+
.filter((n) => !/\.jsonld\b/i.test(n))
|
|
117
|
+
.filter((n) => !/\.jsonapi\b/i.test(n));
|
|
118
|
+
|
|
119
|
+
// 3) collapse group variants: keep one schema per base (before `-`)
|
|
120
|
+
const score = (name) => {
|
|
121
|
+
if (!name.includes('-')) return 100;
|
|
122
|
+
const group = name.split('-').slice(1).join('-');
|
|
123
|
+
if (/(^|[.\-])read($|[.\-])/i.test(group)) return 50;
|
|
124
|
+
return 0;
|
|
125
|
+
};
|
|
126
|
+
const byBase = new Map();
|
|
127
|
+
for (const name of out) {
|
|
128
|
+
const base = name.split('-')[0];
|
|
129
|
+
const prev = byBase.get(base);
|
|
130
|
+
if (!prev) {
|
|
131
|
+
byBase.set(base, name);
|
|
132
|
+
continue;
|
|
133
|
+
}
|
|
134
|
+
const a = prev, b = name;
|
|
135
|
+
const sa = score(a), sb = score(b);
|
|
136
|
+
if (sb > sa) byBase.set(base, b);
|
|
137
|
+
else if (sb === sa && b.localeCompare(a) < 0) byBase.set(base, b);
|
|
138
|
+
}
|
|
139
|
+
out = [...byBase.values()].sort((a, b) => a.localeCompare(b));
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
return out;
|
|
184
143
|
}
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import { Item } from '
|
|
1
|
+
import type { Item } from '../lib/ports/resource-repository.port';
|
|
2
2
|
{{#each imports}}
|
|
3
|
-
import { {{this}} } from './{{this}}';
|
|
3
|
+
import type { {{this}} } from './{{this}}';
|
|
4
4
|
{{/each}}
|
|
5
5
|
|
|
6
|
-
export interface {{name}} extends Item {
|
|
6
|
+
export interface {{name}} extends Item{{#each extendsTypes}}, {{this}}{{/each}} {
|
|
7
7
|
{{#each props}}
|
|
8
8
|
{{{tsKey}}}{{#if optional}}?{{/if}}: {{type}};
|
|
9
9
|
{{/each}}
|
|
@@ -33,14 +33,7 @@ export function pascalCase(tokens) {
|
|
|
33
33
|
* @param {Set<string>} used
|
|
34
34
|
*/
|
|
35
35
|
export function sanitizeTypeName(original, used) {
|
|
36
|
-
const
|
|
37
|
-
const keepDottedName = typeof dotTail === 'string'
|
|
38
|
-
&& ['jsonmergepatch', 'jsonpatch', 'mergepatch', 'jsonld', 'jsonapi'].includes(dotTail.toLowerCase());
|
|
39
|
-
|
|
40
|
-
// "Namespace-like" dotted names (ex: Workflow.WorkflowInput) default to the tail segment,
|
|
41
|
-
// but keep the full name for known format suffixes (ex: Conversation.jsonMergePatch) to avoid collisions.
|
|
42
|
-
const base = (dotTail && !keepDottedName) ? dotTail : original;
|
|
43
|
-
const tokens = String(base).split(/[^A-Za-z0-9]+/);
|
|
36
|
+
const tokens = String(original).split(/[^A-Za-z0-9]+/);
|
|
44
37
|
let name = pascalCase(tokens) || 'Model';
|
|
45
38
|
if (/^[0-9]/.test(name)) name = '_' + name;
|
|
46
39
|
if (RESERVED.has(name)) name = name + 'Model';
|
package/models.config.example.js
DELETED
|
@@ -1,39 +0,0 @@
|
|
|
1
|
-
// Exemple de configuration pour la génération des modèles.
|
|
2
|
-
// Copiez ce fichier en `models.config.js` dans le dossier où vous lancez la commande (`meridiane models`).
|
|
3
|
-
// Format CommonJS pour éviter les warnings Node dans la plupart des workspaces Angular.
|
|
4
|
-
|
|
5
|
-
module.exports = {
|
|
6
|
-
// Dossier de sortie (relatif au CWD)
|
|
7
|
-
outDir: 'projects/backend-bridge/src/models',
|
|
8
|
-
|
|
9
|
-
// Chemin d'import de `Item` dans les templates (relatif au fichier généré)
|
|
10
|
-
itemImportPath: '../lib/ports/resource-repository.port',
|
|
11
|
-
|
|
12
|
-
// Contrôle des propriétés optionnelles
|
|
13
|
-
// - 'all-optional': toutes les props sont optionnelles
|
|
14
|
-
// - 'spec': respecte le tableau `required` de la spec
|
|
15
|
-
requiredMode: 'all-optional',
|
|
16
|
-
|
|
17
|
-
// Préférence entre variantes de schémas (.jsonld, .jsonapi ou aucune)
|
|
18
|
-
// Valeurs possibles: 'jsonld' | 'jsonapi' | 'none'
|
|
19
|
-
preferFlavor: 'jsonld',
|
|
20
|
-
|
|
21
|
-
// Preset de génération:
|
|
22
|
-
// - 'all' (défaut): génère tout ce qui ressemble à un objet
|
|
23
|
-
// - 'native': exclut les schémas "techniques" (Hydra*, jsonMergePatch…)
|
|
24
|
-
preset: 'all',
|
|
25
|
-
|
|
26
|
-
// Générer `index.ts` dans le dossier des models
|
|
27
|
-
writeIndex: true,
|
|
28
|
-
|
|
29
|
-
// Filtres additionnels (sur les noms OpenAPI des schémas, ex: "User-user.read", "Conversation.jsonMergePatch")
|
|
30
|
-
// - includeSchemaNames: si non vide, ne garde que les matchs
|
|
31
|
-
// - excludeSchemaNames: supprime les matchs
|
|
32
|
-
// Règles possibles: RegExp, string (substring), ou (name) => boolean.
|
|
33
|
-
includeSchemaNames: [],
|
|
34
|
-
excludeSchemaNames: [],
|
|
35
|
-
|
|
36
|
-
// Hydra: regex pour les schémas de base à ignorer (merge allOf / filtrage)
|
|
37
|
-
// Peut être une RegExp ou une string passée à `new RegExp(...)`
|
|
38
|
-
hydraBaseRegex: /^Hydra(?:Item|Collection)BaseSchema$/,
|
|
39
|
-
};
|
|
@@ -1,14 +0,0 @@
|
|
|
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
|
-
}
|
package/tools/dev-bridge.js
DELETED
|
@@ -1,138 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
import process from 'node:process';
|
|
3
|
-
import path from 'node:path';
|
|
4
|
-
import {fileURLToPath} from 'node:url';
|
|
5
|
-
import {spawn} from 'node:child_process';
|
|
6
|
-
import {loadDotEnv} from './utils/dotenv.js';
|
|
7
|
-
import {getArg, getArgs, hasFlag} from './utils/cli-args.js';
|
|
8
|
-
|
|
9
|
-
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
10
|
-
|
|
11
|
-
function normalizeBaseUrl(u) {
|
|
12
|
-
if (!u) return u;
|
|
13
|
-
return u.endsWith('/') ? u.slice(0, -1) : u;
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
function joinUrl(base, p) {
|
|
17
|
-
const b = normalizeBaseUrl(base);
|
|
18
|
-
if (!b) return p;
|
|
19
|
-
if (/^https?:\/\//i.test(p)) return p;
|
|
20
|
-
if (!p.startsWith('/')) return `${b}/${p}`;
|
|
21
|
-
return `${b}${p}`;
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
function runNode(scriptPath, args, {cwd} = {}) {
|
|
25
|
-
return new Promise((resolve) => {
|
|
26
|
-
const child = spawn(process.execPath, [scriptPath, ...args], {
|
|
27
|
-
stdio: 'inherit',
|
|
28
|
-
env: process.env,
|
|
29
|
-
cwd: cwd ?? process.cwd(),
|
|
30
|
-
});
|
|
31
|
-
child.on('exit', (code) => resolve(code ?? 0));
|
|
32
|
-
});
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
function printHelp() {
|
|
36
|
-
console.log(`
|
|
37
|
-
Usage:
|
|
38
|
-
meridiane dev-bridge <lib-name> <npm-package-name> [version]
|
|
39
|
-
|
|
40
|
-
Options:
|
|
41
|
-
--workspace=apps/sandbox Working directory (default: current directory)
|
|
42
|
-
--backend=http://localhost:8000 Base URL of the backend (default: http://localhost:8000)
|
|
43
|
-
--spec=/api/docs.json Spec path or full URL (default: /api/docs.json)
|
|
44
|
-
--models-out=projects/<lib>/src/models Output dir for generated models
|
|
45
|
-
--required-mode=all-optional|spec Models required-mode (default: all-optional)
|
|
46
|
-
--preset=all|native Models preset (default: all)
|
|
47
|
-
--include=<substr>[,<substr>…] Include schema names (repeatable)
|
|
48
|
-
--exclude=<substr>[,<substr>…] Exclude schema names (repeatable)
|
|
49
|
-
--index=1|0 Generate index.ts (default: 1)
|
|
50
|
-
--no-models Skip models generation
|
|
51
|
-
--debug Enable debug logs
|
|
52
|
-
|
|
53
|
-
Examples:
|
|
54
|
-
npx -y @obsidiane/meridiane dev-bridge bridge-sandbox @obsidiane/bridge-sandbox 0.1.0
|
|
55
|
-
npx -y @obsidiane/meridiane dev-bridge bridge-sandbox @obsidiane/bridge-sandbox 0.1.0 --workspace=apps/sandbox
|
|
56
|
-
npx -y @obsidiane/meridiane dev-bridge backend-bridge @acme/backend-bridge 0.1.0 --backend=http://localhost:8000 --workspace=apps/sandbox
|
|
57
|
-
`);
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
async function main() {
|
|
61
|
-
await loadDotEnv();
|
|
62
|
-
|
|
63
|
-
const debug = hasFlag('debug') || /^(1|true|yes)$/i.test(process.env.MERIDIANE_DEBUG ?? '');
|
|
64
|
-
if (debug) process.env.MERIDIANE_DEBUG = '1';
|
|
65
|
-
|
|
66
|
-
const positional = process.argv.slice(2).filter((a) => !a.startsWith('--'));
|
|
67
|
-
const [libName, packageName, versionArg] = positional;
|
|
68
|
-
if (!libName || !packageName) {
|
|
69
|
-
printHelp();
|
|
70
|
-
process.exit(1);
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
const workspaceArg = getArg('workspace', process.env.MERIDIANE_DEV_WORKSPACE ?? '.');
|
|
74
|
-
const workspaceDir = path.resolve(process.cwd(), workspaceArg);
|
|
75
|
-
|
|
76
|
-
const backend = normalizeBaseUrl(getArg('backend', process.env.MERIDIANE_DEV_BACKEND ?? 'http://localhost:8000'));
|
|
77
|
-
const specArg = getArg('spec', process.env.MERIDIANE_DEV_SPEC ?? '/api/docs.json');
|
|
78
|
-
const specUrl = /^https?:\/\//i.test(specArg) ? specArg : joinUrl(backend, specArg);
|
|
79
|
-
|
|
80
|
-
const version = versionArg ?? process.env.MERIDIANE_LIB_VERSION ?? '0.0.1';
|
|
81
|
-
const urlRegistry = getArg('registry', process.env.MERIDIANE_NPM_REGISTRY_URL);
|
|
82
|
-
|
|
83
|
-
const toolsDir = __dirname;
|
|
84
|
-
const genLib = path.join(toolsDir, 'generate-lib.js');
|
|
85
|
-
const genModels = path.join(toolsDir, 'generate-models.js');
|
|
86
|
-
|
|
87
|
-
if (debug) {
|
|
88
|
-
console.log('[meridiane dev-bridge] config', {
|
|
89
|
-
workspaceDir,
|
|
90
|
-
backend,
|
|
91
|
-
specUrl,
|
|
92
|
-
libName,
|
|
93
|
-
packageName,
|
|
94
|
-
version,
|
|
95
|
-
urlRegistry: urlRegistry ? '<set>' : '<unset>',
|
|
96
|
-
});
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
// 1) Regenerate the library skeleton.
|
|
100
|
-
const libArgs = [libName, packageName, version, ...(urlRegistry ? [urlRegistry] : [])];
|
|
101
|
-
const libCode = await runNode(genLib, libArgs, {cwd: workspaceDir});
|
|
102
|
-
if (libCode !== 0) process.exit(libCode);
|
|
103
|
-
|
|
104
|
-
// 2) Generate models from the backend spec (dev-only convenience).
|
|
105
|
-
if (hasFlag('no-models')) return;
|
|
106
|
-
|
|
107
|
-
const modelsOut = getArg('models-out', `projects/${libName}/src/models`);
|
|
108
|
-
const requiredMode = getArg('required-mode', process.env.MERIDIANE_MODELS_REQUIRED_MODE ?? 'all-optional');
|
|
109
|
-
const preset = getArg('preset', process.env.MERIDIANE_MODELS_PRESET);
|
|
110
|
-
const include = getArgs('include');
|
|
111
|
-
const exclude = getArgs('exclude');
|
|
112
|
-
const index = getArg('index', process.env.MERIDIANE_MODELS_INDEX);
|
|
113
|
-
|
|
114
|
-
const modelsArgs = [
|
|
115
|
-
specUrl,
|
|
116
|
-
`--out=${modelsOut}`,
|
|
117
|
-
`--required-mode=${requiredMode}`,
|
|
118
|
-
...(preset ? [`--preset=${preset}`] : []),
|
|
119
|
-
...include.map((v) => `--include=${v}`),
|
|
120
|
-
...exclude.map((v) => `--exclude=${v}`),
|
|
121
|
-
...(index ? [`--index=${index}`] : []),
|
|
122
|
-
];
|
|
123
|
-
|
|
124
|
-
// Try the configured URL first, then fallback to API Platform alternative endpoint.
|
|
125
|
-
let modelsCode = await runNode(genModels, modelsArgs, {cwd: workspaceDir});
|
|
126
|
-
if (modelsCode !== 0 && specUrl.endsWith('/api/docs.json')) {
|
|
127
|
-
const fallback = specUrl.replace(/\/api\/docs\.json$/, '/api/docs.jsonopenapi');
|
|
128
|
-
console.warn(`⚠️ Models generation failed for ${specUrl}, retrying with ${fallback}`);
|
|
129
|
-
modelsCode = await runNode(genModels, [fallback, ...modelsArgs.slice(1)], {cwd: workspaceDir});
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
process.exit(modelsCode);
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
main().catch((err) => {
|
|
136
|
-
console.error(err);
|
|
137
|
-
process.exit(1);
|
|
138
|
-
});
|
package/tools/generate-lib.js
DELETED
|
@@ -1,151 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
import fs from 'node:fs/promises';
|
|
3
|
-
import {existsSync} from 'node:fs';
|
|
4
|
-
import path from 'node:path';
|
|
5
|
-
import {fileURLToPath} from 'node:url';
|
|
6
|
-
|
|
7
|
-
import {loadDotEnv} from './utils/dotenv.js';
|
|
8
|
-
|
|
9
|
-
const __filename = fileURLToPath(import.meta.url);
|
|
10
|
-
const __dirname = path.dirname(__filename);
|
|
11
|
-
|
|
12
|
-
async function replacePlaceholdersInDir(srcDir, placeholders) {
|
|
13
|
-
const entries = await fs.readdir(srcDir, {withFileTypes: true});
|
|
14
|
-
for (const entry of entries) {
|
|
15
|
-
const fullPath = path.join(srcDir, entry.name);
|
|
16
|
-
if (entry.isDirectory()) {
|
|
17
|
-
await replacePlaceholdersInDir(fullPath, placeholders);
|
|
18
|
-
} else if (entry.isFile()) {
|
|
19
|
-
let content = await fs.readFile(fullPath, 'utf8');
|
|
20
|
-
for (const [key, value] of Object.entries(placeholders)) {
|
|
21
|
-
const regex = new RegExp(key, 'g');
|
|
22
|
-
content = content.replace(regex, value);
|
|
23
|
-
}
|
|
24
|
-
await fs.writeFile(fullPath, content);
|
|
25
|
-
}
|
|
26
|
-
}
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
async function readJson(filePath) {
|
|
30
|
-
const raw = await fs.readFile(filePath, 'utf8');
|
|
31
|
-
return JSON.parse(raw);
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
async function writeJson(filePath, data) {
|
|
35
|
-
await fs.writeFile(filePath, JSON.stringify(data, null, 2) + '\n', 'utf8');
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
async function generate(libName, packageName, version, urlRegistry) {
|
|
39
|
-
const tplDir = path.resolve(__dirname, '../templates/_lib_template');
|
|
40
|
-
const workspaceRoot = process.cwd();
|
|
41
|
-
const targetDir = path.resolve(workspaceRoot, 'projects', libName);
|
|
42
|
-
|
|
43
|
-
// 1) Copy the template directory
|
|
44
|
-
await fs.rm(targetDir, {recursive: true, force: true});
|
|
45
|
-
await fs.mkdir(targetDir, {recursive: true});
|
|
46
|
-
await fs.cp(tplDir, targetDir, {recursive: true});
|
|
47
|
-
|
|
48
|
-
// 2) Replace placeholders __LIB_NAME__, __PACKAGE_NAME__, __VERSION__
|
|
49
|
-
const placeholders = {
|
|
50
|
-
'__LIB_NAME__': libName,
|
|
51
|
-
'__PACKAGE_NAME__': packageName,
|
|
52
|
-
'__VERSION__': version,
|
|
53
|
-
};
|
|
54
|
-
await replacePlaceholdersInDir(targetDir, placeholders);
|
|
55
|
-
|
|
56
|
-
// 3) Update the library's package.json
|
|
57
|
-
const libPackageJsonPath = path.join(targetDir, 'package.json');
|
|
58
|
-
try {
|
|
59
|
-
const libPkg = await readJson(libPackageJsonPath);
|
|
60
|
-
libPkg.name = packageName;
|
|
61
|
-
libPkg.version = version;
|
|
62
|
-
if (urlRegistry) {
|
|
63
|
-
libPkg.publishConfig = {...(libPkg.publishConfig || {}), registry: urlRegistry};
|
|
64
|
-
} else if (libPkg.publishConfig?.registry) {
|
|
65
|
-
delete libPkg.publishConfig.registry;
|
|
66
|
-
if (Object.keys(libPkg.publishConfig).length === 0) delete libPkg.publishConfig;
|
|
67
|
-
}
|
|
68
|
-
await writeJson(libPackageJsonPath, libPkg);
|
|
69
|
-
} catch {
|
|
70
|
-
// ignore: missing or invalid package.json in template
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
// 4) Update angular.json
|
|
74
|
-
const angularJsonPath = path.resolve(workspaceRoot, 'angular.json');
|
|
75
|
-
const ng = await readJson(angularJsonPath);
|
|
76
|
-
|
|
77
|
-
ng.projects[libName] = {
|
|
78
|
-
root: `projects/${libName}`,
|
|
79
|
-
sourceRoot: `projects/${libName}/src`,
|
|
80
|
-
projectType: 'library',
|
|
81
|
-
architect: {
|
|
82
|
-
build: {
|
|
83
|
-
builder: '@angular/build:ng-packagr',
|
|
84
|
-
configurations: {
|
|
85
|
-
production: {
|
|
86
|
-
tsConfig: `projects/${libName}/tsconfig.lib.prod.json`,
|
|
87
|
-
},
|
|
88
|
-
development: {
|
|
89
|
-
tsConfig: `projects/${libName}/tsconfig.lib.json`,
|
|
90
|
-
},
|
|
91
|
-
},
|
|
92
|
-
defaultConfiguration: 'production',
|
|
93
|
-
},
|
|
94
|
-
test: {
|
|
95
|
-
builder: '@angular/build:karma',
|
|
96
|
-
options: {
|
|
97
|
-
tsConfig: `projects/${libName}/tsconfig.spec.json`,
|
|
98
|
-
},
|
|
99
|
-
},
|
|
100
|
-
},
|
|
101
|
-
};
|
|
102
|
-
await writeJson(angularJsonPath, ng);
|
|
103
|
-
|
|
104
|
-
console.log(`✅ Library '${libName}' generated at projects/${libName}` +
|
|
105
|
-
` with package '${packageName}@${version}'`);
|
|
106
|
-
|
|
107
|
-
const ngPackagrPkg = findUpNodeModulePackageJson(workspaceRoot, 'ng-packagr');
|
|
108
|
-
if (!ngPackagrPkg) {
|
|
109
|
-
console.warn(
|
|
110
|
-
[
|
|
111
|
-
"⚠️ 'ng-packagr' n'est pas installé dans ce workspace.",
|
|
112
|
-
" Pour builder une librairie (ng-packagr), installez-le dans votre projet Angular :",
|
|
113
|
-
" npm i -D ng-packagr",
|
|
114
|
-
].join('\n')
|
|
115
|
-
);
|
|
116
|
-
}
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
function findUpNodeModulePackageJson(startDir, packageName) {
|
|
120
|
-
let dir = startDir;
|
|
121
|
-
while (true) {
|
|
122
|
-
const candidate = path.join(dir, 'node_modules', packageName, 'package.json');
|
|
123
|
-
if (existsSync(candidate)) return candidate;
|
|
124
|
-
const parent = path.dirname(dir);
|
|
125
|
-
if (parent === dir) return undefined;
|
|
126
|
-
dir = parent;
|
|
127
|
-
}
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
await loadDotEnv();
|
|
131
|
-
const debug = process.argv.includes('--debug') || /^(1|true|yes)$/i.test(process.env.MERIDIANE_DEBUG ?? '');
|
|
132
|
-
|
|
133
|
-
// CLI args: node generate-lib.js <lib-name> <package-name> [version] [url-registry]
|
|
134
|
-
// + support .env via MERIDIANE_LIB_VERSION / MERIDIANE_NPM_REGISTRY_URL
|
|
135
|
-
const positional = process.argv.slice(2).filter((a) => !a.startsWith('--'));
|
|
136
|
-
const [libName, packageName, versionArg, urlRegistryArg] = positional;
|
|
137
|
-
const version = versionArg ?? process.env.MERIDIANE_LIB_VERSION ?? '0.0.1';
|
|
138
|
-
const urlRegistry = urlRegistryArg ?? process.env.MERIDIANE_NPM_REGISTRY_URL;
|
|
139
|
-
if (!libName || !packageName) {
|
|
140
|
-
console.error('Usage: generate-lib.js <lib-name> <package-name> [version] [url-registry]');
|
|
141
|
-
process.exit(1);
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
if (debug) {
|
|
145
|
-
console.log('[meridiane lib] debug', {libName, packageName, version, urlRegistry: urlRegistry ? '<set>' : '<unset>'});
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
generate(libName, packageName, version, urlRegistry).catch((err) => {
|
|
149
|
-
console.error(err);
|
|
150
|
-
process.exit(1);
|
|
151
|
-
});
|