@obsidiane/meridiane 0.0.6 → 0.0.7
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 +42 -2
- package/cli.js +27 -1
- package/models.config.example.js +15 -0
- package/package.json +1 -3
- package/templates/_lib_template/src/lib/bridge/rest/api-platform.adapter.ts +48 -47
- package/templates/_lib_template/src/lib/bridge/rest/query-builder.ts +29 -29
- package/templates/_lib_template/src/lib/bridge/sse/eventsource-wrapper.ts +50 -54
- package/templates/_lib_template/src/lib/bridge/sse/mercure-topic.mapper.ts +48 -0
- package/templates/_lib_template/src/lib/bridge/sse/mercure-url.builder.ts +7 -7
- package/templates/_lib_template/src/lib/bridge/sse/mercure.adapter.ts +104 -49
- package/templates/_lib_template/src/lib/bridge/sse/ref-count-topic.registry.ts +23 -21
- package/templates/_lib_template/src/lib/bridge.types.ts +33 -0
- package/templates/_lib_template/src/lib/facades/bridge.facade.ts +116 -0
- package/templates/_lib_template/src/lib/facades/facade.factory.ts +21 -19
- package/templates/_lib_template/src/lib/facades/facade.interface.ts +13 -18
- package/templates/_lib_template/src/lib/facades/resource.facade.ts +42 -56
- package/templates/_lib_template/src/lib/interceptors/bridge-debug.interceptor.ts +32 -0
- package/templates/_lib_template/src/lib/interceptors/bridge-defaults.interceptor.ts +53 -0
- package/templates/_lib_template/src/lib/interceptors/content-type.interceptor.ts +36 -37
- package/templates/_lib_template/src/lib/ports/realtime.port.ts +5 -3
- package/templates/_lib_template/src/lib/ports/resource-repository.port.ts +14 -15
- package/templates/_lib_template/src/lib/provide-bridge.ts +122 -19
- package/templates/_lib_template/src/lib/tokens.ts +14 -1
- package/templates/_lib_template/src/lib/utils/url.ts +15 -0
- package/templates/_lib_template/src/public-api.ts +3 -2
- package/tools/dev-bridge.js +138 -0
- package/tools/generate-lib.js +60 -45
- package/tools/generate-models.js +73 -19
- package/tools/generator/models/openapi-to-models.js +13 -2
- package/tools/generator/models/schema-utils.js +59 -0
- package/tools/generator/models/utils.js +8 -2
- package/tools/init.js +148 -0
- package/tools/sandbox-bridge.js +77 -0
- package/tools/utils/cli-args.js +41 -0
- package/tools/utils/dotenv.js +55 -0
- package/templates/_lib_template/src/lib/bridge/credentials.policy.ts +0 -9
- package/templates/_lib_template/src/lib/utils/utils.ts +0 -21
package/tools/init.js
ADDED
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import process from 'node:process';
|
|
4
|
+
import {fileURLToPath} from 'node:url';
|
|
5
|
+
import fs from 'node:fs/promises';
|
|
6
|
+
|
|
7
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
8
|
+
|
|
9
|
+
function hasFlag(name) {
|
|
10
|
+
return process.argv.includes(`--${name}`);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
async function pathExists(p) {
|
|
14
|
+
try {
|
|
15
|
+
await fs.stat(p);
|
|
16
|
+
return true;
|
|
17
|
+
} catch {
|
|
18
|
+
return false;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
async function ensureDir(p) {
|
|
23
|
+
await fs.mkdir(p, {recursive: true});
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
async function writeFileIfMissing(filePath, content, {force} = {}) {
|
|
27
|
+
const exists = await pathExists(filePath);
|
|
28
|
+
if (exists && !force) return {written: false, reason: 'exists'};
|
|
29
|
+
await ensureDir(path.dirname(filePath));
|
|
30
|
+
await fs.writeFile(filePath, content, 'utf8');
|
|
31
|
+
return {written: true};
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
async function copyFileIfMissing(src, dest, {force} = {}) {
|
|
35
|
+
const exists = await pathExists(dest);
|
|
36
|
+
if (exists && !force) return {written: false, reason: 'exists'};
|
|
37
|
+
await ensureDir(path.dirname(dest));
|
|
38
|
+
await fs.copyFile(src, dest);
|
|
39
|
+
return {written: true};
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
async function main() {
|
|
43
|
+
const force = hasFlag('force');
|
|
44
|
+
const debug = /^(1|true|yes)$/i.test(process.env.MERIDIANE_DEBUG ?? '') || hasFlag('debug');
|
|
45
|
+
|
|
46
|
+
const cwd = process.cwd();
|
|
47
|
+
const pkgRoot = path.resolve(__dirname, '..');
|
|
48
|
+
|
|
49
|
+
const outDir = path.resolve(cwd, 'meridiane');
|
|
50
|
+
await ensureDir(outDir);
|
|
51
|
+
|
|
52
|
+
const modelsExample = path.join(pkgRoot, 'models.config.example.js');
|
|
53
|
+
const modelsOut = path.resolve(cwd, 'models.config.js');
|
|
54
|
+
const envExampleOut = path.resolve(cwd, '.env.example');
|
|
55
|
+
|
|
56
|
+
const angularExampleOut = path.join(outDir, 'angular.example.ts');
|
|
57
|
+
const readmeOut = path.join(outDir, 'README.md');
|
|
58
|
+
|
|
59
|
+
if (debug) console.log('[meridiane init] cwd=', cwd);
|
|
60
|
+
|
|
61
|
+
const copiedCfg = await copyFileIfMissing(modelsExample, modelsOut, {force});
|
|
62
|
+
const wroteEnv = await writeFileIfMissing(
|
|
63
|
+
envExampleOut,
|
|
64
|
+
[
|
|
65
|
+
'# Meridiane (CLI) - exemple de configuration',
|
|
66
|
+
'# Copiez en `.env` pour activer la config locale.',
|
|
67
|
+
'',
|
|
68
|
+
'# Logs',
|
|
69
|
+
'MERIDIANE_DEBUG=0',
|
|
70
|
+
'',
|
|
71
|
+
'# meridiane lib',
|
|
72
|
+
'# MERIDIANE_LIB_VERSION=0.1.0',
|
|
73
|
+
'# MERIDIANE_NPM_REGISTRY_URL=https://registry.npmjs.org/',
|
|
74
|
+
'',
|
|
75
|
+
'# meridiane models',
|
|
76
|
+
'# MERIDIANE_MODELS_OUT=projects/backend-bridge/src/models',
|
|
77
|
+
'# MERIDIANE_MODELS_REQUIRED_MODE=spec',
|
|
78
|
+
'# MERIDIANE_MODELS_ITEM_IMPORT=../lib/ports/resource-repository.port',
|
|
79
|
+
'# MERIDIANE_MODELS_PRESET=all',
|
|
80
|
+
'# MERIDIANE_MODELS_INDEX=1',
|
|
81
|
+
'',
|
|
82
|
+
'# Runtime (Angular) - à recopier dans provideBridge(...)',
|
|
83
|
+
'# MERIDIANE_API_BASE_URL=http://localhost:8000',
|
|
84
|
+
'# MERIDIANE_MERCURE_HUB_URL=http://localhost:8000/.well-known/mercure',
|
|
85
|
+
'',
|
|
86
|
+
].join('\n'),
|
|
87
|
+
{force}
|
|
88
|
+
);
|
|
89
|
+
|
|
90
|
+
const wroteAngular = await writeFileIfMissing(
|
|
91
|
+
angularExampleOut,
|
|
92
|
+
[
|
|
93
|
+
"import {provideBridge} from '@acme/backend-bridge';",
|
|
94
|
+
'',
|
|
95
|
+
'export const BRIDGE_PROVIDERS = [',
|
|
96
|
+
' provideBridge({',
|
|
97
|
+
" baseUrl: 'http://localhost:8000',",
|
|
98
|
+
' mercure: {',
|
|
99
|
+
" hubUrl: 'http://localhost:8000/.well-known/mercure',",
|
|
100
|
+
" init: {credentials: 'include'},",
|
|
101
|
+
' },',
|
|
102
|
+
' defaults: {',
|
|
103
|
+
' headers: {',
|
|
104
|
+
" // 'X-Correlation-Id': crypto.randomUUID(),",
|
|
105
|
+
' },',
|
|
106
|
+
' timeoutMs: 30_000,',
|
|
107
|
+
' retries: {count: 1, delayMs: 250},',
|
|
108
|
+
' },',
|
|
109
|
+
' // auth: {type: \"bearer\", token: \"<JWT>\"},',
|
|
110
|
+
' debug: false,',
|
|
111
|
+
' }),',
|
|
112
|
+
'];',
|
|
113
|
+
'',
|
|
114
|
+
].join('\n'),
|
|
115
|
+
{force}
|
|
116
|
+
);
|
|
117
|
+
|
|
118
|
+
const wroteReadme = await writeFileIfMissing(
|
|
119
|
+
readmeOut,
|
|
120
|
+
[
|
|
121
|
+
'# Meridiane – fichiers générés',
|
|
122
|
+
'',
|
|
123
|
+
'Ces fichiers sont créés par `meridiane init` pour accélérer l’intégration.',
|
|
124
|
+
'',
|
|
125
|
+
'## CLI',
|
|
126
|
+
'',
|
|
127
|
+
'- `models.config.js` : configuration par défaut de `meridiane models` (chargée depuis le CWD).',
|
|
128
|
+
"- `.env` : optionnel. Le CLI charge automatiquement `.env` (CWD) avant d'exécuter `lib/models`.",
|
|
129
|
+
'',
|
|
130
|
+
'## Angular',
|
|
131
|
+
'',
|
|
132
|
+
'- `angular.example.ts` : exemple `provideBridge({ baseUrl, mercure, defaults, auth, debug })` à adapter.',
|
|
133
|
+
'',
|
|
134
|
+
].join('\n'),
|
|
135
|
+
{force}
|
|
136
|
+
);
|
|
137
|
+
|
|
138
|
+
console.log('✅ meridiane init');
|
|
139
|
+
console.log(`- ${path.relative(cwd, modelsOut)} ${copiedCfg.written ? 'créé' : '(existe déjà)'}`);
|
|
140
|
+
console.log(`- ${path.relative(cwd, envExampleOut)} ${wroteEnv.written ? 'créé' : '(existe déjà)'}`);
|
|
141
|
+
console.log(`- ${path.relative(cwd, angularExampleOut)} ${wroteAngular.written ? 'créé' : '(existe déjà)'}`);
|
|
142
|
+
console.log(`- ${path.relative(cwd, readmeOut)} ${wroteReadme.written ? 'créé' : '(existe déjà)'}`);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
main().catch((err) => {
|
|
146
|
+
console.error(err);
|
|
147
|
+
process.exit(1);
|
|
148
|
+
});
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import process from 'node:process';
|
|
4
|
+
import {fileURLToPath} from 'node:url';
|
|
5
|
+
import {spawn} from 'node:child_process';
|
|
6
|
+
import {loadDotEnv} from './utils/dotenv.js';
|
|
7
|
+
import {getArg, hasFlag} from './utils/cli-args.js';
|
|
8
|
+
|
|
9
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
10
|
+
const repoRoot = path.resolve(__dirname, '../../..');
|
|
11
|
+
|
|
12
|
+
function run(args) {
|
|
13
|
+
return new Promise((resolve) => {
|
|
14
|
+
const child = spawn(process.execPath, args, {stdio: 'inherit', env: process.env});
|
|
15
|
+
child.on('exit', (code) => resolve(code ?? 0));
|
|
16
|
+
});
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function printHelp() {
|
|
20
|
+
console.log(`
|
|
21
|
+
Usage:
|
|
22
|
+
meridiane sandbox-bridge [options]
|
|
23
|
+
|
|
24
|
+
This command regenerates the Angular bridge library used by the sandbox:
|
|
25
|
+
- workspace: apps/sandbox
|
|
26
|
+
- libName: bridge-sandbox
|
|
27
|
+
- package: @obsidiane/bridge-sandbox
|
|
28
|
+
- version: 0.1.0 (override with --version)
|
|
29
|
+
|
|
30
|
+
Options:
|
|
31
|
+
--backend=http://localhost:8000
|
|
32
|
+
--spec=/api/docs.json
|
|
33
|
+
--models-out=projects/bridge-sandbox/src/models
|
|
34
|
+
--required-mode=all-optional|spec
|
|
35
|
+
--preset=all|native
|
|
36
|
+
--include=<substr>[,<substr>…] (repeatable)
|
|
37
|
+
--exclude=<substr>[,<substr>…] (repeatable)
|
|
38
|
+
--index=1|0
|
|
39
|
+
--no-models
|
|
40
|
+
--debug
|
|
41
|
+
|
|
42
|
+
Example:
|
|
43
|
+
npx -y @obsidiane/meridiane sandbox-bridge --backend=http://localhost:8000
|
|
44
|
+
`);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
async function main() {
|
|
48
|
+
await loadDotEnv();
|
|
49
|
+
|
|
50
|
+
if (hasFlag('help') || hasFlag('h')) {
|
|
51
|
+
printHelp();
|
|
52
|
+
process.exit(0);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const version = getArg('version', process.env.MERIDIANE_SANDBOX_BRIDGE_VERSION ?? '0.1.0');
|
|
56
|
+
|
|
57
|
+
const passThrough = process.argv.slice(2).filter((a) => a.startsWith('--') && !a.startsWith('--version='));
|
|
58
|
+
|
|
59
|
+
const devBridgePath = path.join(__dirname, 'dev-bridge.js');
|
|
60
|
+
const workspaceAbs = path.join(repoRoot, 'apps', 'sandbox');
|
|
61
|
+
const args = [
|
|
62
|
+
devBridgePath,
|
|
63
|
+
'bridge-sandbox',
|
|
64
|
+
'@obsidiane/bridge-sandbox',
|
|
65
|
+
version,
|
|
66
|
+
`--workspace=${workspaceAbs}`,
|
|
67
|
+
...passThrough,
|
|
68
|
+
];
|
|
69
|
+
|
|
70
|
+
const code = await run(args);
|
|
71
|
+
process.exit(code);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
main().catch((err) => {
|
|
75
|
+
console.error(err);
|
|
76
|
+
process.exit(1);
|
|
77
|
+
});
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @param {string} name
|
|
3
|
+
* @param {string|undefined} def
|
|
4
|
+
* @param {string[]} [argv]
|
|
5
|
+
* @returns {string|undefined}
|
|
6
|
+
*/
|
|
7
|
+
export function getArg(name, def, argv = process.argv) {
|
|
8
|
+
const a = argv.find((x) => x.startsWith(`--${name}=`));
|
|
9
|
+
return a ? a.split('=')[1] : def;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* @param {string} name
|
|
14
|
+
* @param {string[]} [argv]
|
|
15
|
+
* @returns {string[]}
|
|
16
|
+
*/
|
|
17
|
+
export function getArgs(name, argv = process.argv) {
|
|
18
|
+
return argv
|
|
19
|
+
.filter((x) => x.startsWith(`--${name}=`))
|
|
20
|
+
.map((x) => x.slice(`--${name}=`.length));
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* @param {string} name
|
|
25
|
+
* @param {string[]} [argv]
|
|
26
|
+
*/
|
|
27
|
+
export function hasFlag(name, argv = process.argv) {
|
|
28
|
+
return argv.includes(`--${name}`);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* @param {unknown} v
|
|
33
|
+
* @param {boolean} def
|
|
34
|
+
*/
|
|
35
|
+
export function asBool(v, def) {
|
|
36
|
+
if (typeof v !== 'string') return def;
|
|
37
|
+
if (/^(1|true|yes)$/i.test(v)) return true;
|
|
38
|
+
if (/^(0|false|no)$/i.test(v)) return false;
|
|
39
|
+
return def;
|
|
40
|
+
}
|
|
41
|
+
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import fs from 'node:fs/promises';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Charge un fichier `.env` (format KEY=VALUE) dans process.env, sans écraser les variables déjà définies.
|
|
6
|
+
* @param {{cwd?: string, filename?: string}} [opts]
|
|
7
|
+
* @returns {Promise<Record<string, string>>} variables chargées
|
|
8
|
+
*/
|
|
9
|
+
export async function loadDotEnv(opts = {}) {
|
|
10
|
+
const cwd = opts.cwd ?? process.cwd();
|
|
11
|
+
const filename = opts.filename ?? '.env';
|
|
12
|
+
const envPath = await findUp(cwd, filename);
|
|
13
|
+
if (!envPath) return {};
|
|
14
|
+
|
|
15
|
+
const raw = await fs.readFile(envPath, 'utf8');
|
|
16
|
+
|
|
17
|
+
const loaded = {};
|
|
18
|
+
for (const line of raw.split(/\r?\n/)) {
|
|
19
|
+
const trimmed = line.trim();
|
|
20
|
+
if (!trimmed || trimmed.startsWith('#')) continue;
|
|
21
|
+
const normalized = trimmed.startsWith('export ') ? trimmed.slice('export '.length).trim() : trimmed;
|
|
22
|
+
const idx = normalized.indexOf('=');
|
|
23
|
+
if (idx < 0) continue;
|
|
24
|
+
const key = normalized.slice(0, idx).trim();
|
|
25
|
+
let value = normalized.slice(idx + 1).trim();
|
|
26
|
+
if (!key) continue;
|
|
27
|
+
|
|
28
|
+
if ((value.startsWith('"') && value.endsWith('"')) || (value.startsWith("'") && value.endsWith("'"))) {
|
|
29
|
+
value = value.slice(1, -1);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
if (process.env[key] === undefined) {
|
|
33
|
+
process.env[key] = value;
|
|
34
|
+
loaded[key] = value;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
return loaded;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
async function findUp(startDir, fileName) {
|
|
42
|
+
let dir = startDir;
|
|
43
|
+
while (true) {
|
|
44
|
+
const candidate = path.join(dir, fileName);
|
|
45
|
+
try {
|
|
46
|
+
const st = await fs.stat(candidate);
|
|
47
|
+
if (st.isFile()) return candidate;
|
|
48
|
+
} catch {
|
|
49
|
+
// ignore
|
|
50
|
+
}
|
|
51
|
+
const parent = path.dirname(dir);
|
|
52
|
+
if (parent === dir) return undefined;
|
|
53
|
+
dir = parent;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
import {WritableSignal} from '@angular/core';
|
|
2
|
-
import {Item} from '../ports/resource-repository.port';
|
|
3
|
-
|
|
4
|
-
export function upsertInSignal(
|
|
5
|
-
signal: WritableSignal<readonly Item[]>,
|
|
6
|
-
item: Item,
|
|
7
|
-
): void {
|
|
8
|
-
signal.update(items => {
|
|
9
|
-
|
|
10
|
-
const id = item['@id'];
|
|
11
|
-
const existingIndex = items.findIndex(it => it['@id'] === id);
|
|
12
|
-
|
|
13
|
-
if (existingIndex === -1) {
|
|
14
|
-
return [item, ...items];
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
const next = items.slice() as Item[];
|
|
18
|
-
next[existingIndex] = item;
|
|
19
|
-
return next;
|
|
20
|
-
});
|
|
21
|
-
}
|