@inwink-mcp/backoffice-extension-helpers 1.0.11 → 1.0.13
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 +15 -1
- package/dist/cli.js +133 -34
- package/package.json +1 -1
- package/templates/vite-extension/AGENTS.md.tpl +13 -5
- package/templates/vite-extension/docs/inwink-api/README.md +65 -24
- package/templates/vite-extension/docs/inwink-api/aggregate.md +238 -0
- package/templates/vite-extension/docs/inwink-api/assets.md +107 -107
- package/templates/vite-extension/docs/inwink-api/create.md +3 -3
- package/templates/vite-extension/docs/inwink-api/delete.md +4 -2
- package/templates/vite-extension/docs/inwink-api/entities.md +24 -60
- package/templates/vite-extension/docs/inwink-api/entity-template.md +201 -0
- package/templates/vite-extension/docs/inwink-api/files.md +136 -135
- package/templates/vite-extension/docs/inwink-api/history.md +68 -0
- package/templates/vite-extension/docs/inwink-api/import.md +4 -4
- package/templates/vite-extension/docs/inwink-api/query-language.md +127 -38
- package/templates/vite-extension/docs/inwink-api/query.md +12 -10
- package/templates/vite-extension/docs/inwink-api/update.md +7 -5
package/README.md
CHANGED
|
@@ -75,7 +75,21 @@ Contrôles effectués :
|
|
|
75
75
|
- `extension.manifest.json` présent, `name` (slug) et `version` (semver) valides ;
|
|
76
76
|
- `dist/` contient `index.html` + `extension.manifest.json` à la racine + `assets/` ;
|
|
77
77
|
- le zip contient `index.html` et `extension.manifest.json` à la racine, sans `node_modules/`, `src/` ni `dist/` ;
|
|
78
|
-
- avertissements : `base: './'` absent de `vite.config.ts`, usage de `BrowserRouter
|
|
78
|
+
- avertissements : `base: './'` absent de `vite.config.ts`, usage de `BrowserRouter`, `docs/inwink-api/` différent de la doc de cette version de la CLI.
|
|
79
|
+
|
|
80
|
+
### `update-docs`
|
|
81
|
+
|
|
82
|
+
Met à jour la doc API d'une extension **déjà générée** : `docs/inwink-api/` est remplacé en miroir par celui de cette version (fichiers ajoutés, modifiés, et supprimés s'ils ont disparu du template). Rien d'autre n'est touché, la commande peut être relancée sans risque.
|
|
83
|
+
|
|
84
|
+
```bash
|
|
85
|
+
npx @inwink-mcp/backoffice-extension-helpers@latest update-docs
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
| Option | Description |
|
|
89
|
+
|--------|-------------|
|
|
90
|
+
| `--dir <path>` | Dossier du projet (défaut : dossier courant) |
|
|
91
|
+
|
|
92
|
+
Ce dossier appartient à la CLI : une modification locale est écrasée au prochain `update-docs`. `AGENTS.md` et le code du squelette ne sont pas mis à jour.
|
|
79
93
|
|
|
80
94
|
## Templates
|
|
81
95
|
|
package/dist/cli.js
CHANGED
|
@@ -340,12 +340,70 @@ function bootstrap(opts) {
|
|
|
340
340
|
}
|
|
341
341
|
|
|
342
342
|
// src/commands/pack.ts
|
|
343
|
-
import
|
|
343
|
+
import path8 from "path";
|
|
344
344
|
|
|
345
345
|
// src/lib/verify.ts
|
|
346
|
+
import fs5 from "fs";
|
|
347
|
+
import path6 from "path";
|
|
348
|
+
import AdmZip from "adm-zip";
|
|
349
|
+
|
|
350
|
+
// src/lib/docs.ts
|
|
346
351
|
import fs4 from "fs";
|
|
347
352
|
import path5 from "path";
|
|
348
|
-
|
|
353
|
+
var DOCS_REL_DIR = "docs/inwink-api";
|
|
354
|
+
var DOCS_TEMPLATE = "vite-extension";
|
|
355
|
+
var UPDATE_DOCS_COMMAND = "npx @inwink-mcp/backoffice-extension-helpers@latest update-docs";
|
|
356
|
+
function templateDocsDir() {
|
|
357
|
+
return path5.join(templatesRoot, DOCS_TEMPLATE, DOCS_REL_DIR);
|
|
358
|
+
}
|
|
359
|
+
function listFiles(root, current = root) {
|
|
360
|
+
if (!fs4.existsSync(current)) return [];
|
|
361
|
+
const files = [];
|
|
362
|
+
for (const entry of fs4.readdirSync(current, { withFileTypes: true })) {
|
|
363
|
+
const full = path5.join(current, entry.name);
|
|
364
|
+
if (entry.isDirectory()) files.push(...listFiles(root, full));
|
|
365
|
+
else files.push(path5.relative(root, full).split(path5.sep).join("/"));
|
|
366
|
+
}
|
|
367
|
+
return files.sort();
|
|
368
|
+
}
|
|
369
|
+
function readNormalized(file) {
|
|
370
|
+
return fs4.readFileSync(file, "utf8").replaceAll("\r\n", "\n");
|
|
371
|
+
}
|
|
372
|
+
function diffDocs(projectDir) {
|
|
373
|
+
const src = templateDocsDir();
|
|
374
|
+
const dest = path5.join(projectDir, DOCS_REL_DIR);
|
|
375
|
+
const srcFiles = listFiles(src);
|
|
376
|
+
const destFiles = new Set(listFiles(dest));
|
|
377
|
+
const diff = { added: [], changed: [], removed: [] };
|
|
378
|
+
for (const rel of srcFiles) {
|
|
379
|
+
if (!destFiles.has(rel)) diff.added.push(rel);
|
|
380
|
+
else if (readNormalized(path5.join(src, rel)) !== readNormalized(path5.join(dest, rel))) {
|
|
381
|
+
diff.changed.push(rel);
|
|
382
|
+
}
|
|
383
|
+
destFiles.delete(rel);
|
|
384
|
+
}
|
|
385
|
+
diff.removed = [...destFiles];
|
|
386
|
+
return diff;
|
|
387
|
+
}
|
|
388
|
+
function isDocsUpToDate(diff) {
|
|
389
|
+
return diff.added.length + diff.changed.length + diff.removed.length === 0;
|
|
390
|
+
}
|
|
391
|
+
function syncDocs(projectDir) {
|
|
392
|
+
const diff = diffDocs(projectDir);
|
|
393
|
+
const src = templateDocsDir();
|
|
394
|
+
const dest = path5.join(projectDir, DOCS_REL_DIR);
|
|
395
|
+
for (const rel of [...diff.added, ...diff.changed]) {
|
|
396
|
+
const target = path5.join(dest, rel);
|
|
397
|
+
fs4.mkdirSync(path5.dirname(target), { recursive: true });
|
|
398
|
+
fs4.copyFileSync(path5.join(src, rel), target);
|
|
399
|
+
}
|
|
400
|
+
for (const rel of diff.removed) {
|
|
401
|
+
fs4.rmSync(path5.join(dest, rel));
|
|
402
|
+
}
|
|
403
|
+
return diff;
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
// src/lib/verify.ts
|
|
349
407
|
function verifyExtension(options) {
|
|
350
408
|
const { projectDir, requireDist = false } = options;
|
|
351
409
|
const errors = [];
|
|
@@ -366,40 +424,49 @@ function verifyExtension(options) {
|
|
|
366
424
|
} catch (err) {
|
|
367
425
|
errors.push(err.message);
|
|
368
426
|
}
|
|
369
|
-
const distDir =
|
|
370
|
-
if (!
|
|
427
|
+
const distDir = path6.join(projectDir, "dist");
|
|
428
|
+
if (!fs5.existsSync(distDir)) {
|
|
371
429
|
if (requireDist) {
|
|
372
430
|
errors.push("Dossier dist/ introuvable apr\xE8s le build.");
|
|
373
431
|
}
|
|
374
432
|
} else {
|
|
375
|
-
if (!
|
|
433
|
+
if (!fs5.existsSync(path6.join(distDir, "index.html"))) {
|
|
376
434
|
errors.push("dist/index.html manquant.");
|
|
377
435
|
}
|
|
378
|
-
if (!
|
|
436
|
+
if (!fs5.existsSync(path6.join(distDir, "extension.manifest.json"))) {
|
|
379
437
|
errors.push("dist/extension.manifest.json manquant (le plugin Vite doit le copier).");
|
|
380
438
|
}
|
|
381
|
-
const assetsDir =
|
|
382
|
-
if (!
|
|
439
|
+
const assetsDir = path6.join(distDir, "assets");
|
|
440
|
+
if (!fs5.existsSync(assetsDir) || !fs5.statSync(assetsDir).isDirectory()) {
|
|
383
441
|
warnings.push("dist/assets/ introuvable \u2014 build Vite incomplet ?");
|
|
384
442
|
}
|
|
385
443
|
}
|
|
386
444
|
const zipPath = resolveZipPath(options, projectDir, name, version);
|
|
387
|
-
if (zipPath &&
|
|
445
|
+
if (zipPath && fs5.existsSync(zipPath)) {
|
|
388
446
|
verifyZip(zipPath, errors);
|
|
389
447
|
} else if (options.zipPath) {
|
|
390
448
|
errors.push(`Zip introuvable : ${options.zipPath}`);
|
|
391
449
|
}
|
|
392
450
|
checkViteBase(projectDir, warnings);
|
|
393
451
|
checkRouter(projectDir, warnings);
|
|
452
|
+
checkDocs(projectDir, warnings);
|
|
394
453
|
return { errors, warnings };
|
|
395
454
|
}
|
|
455
|
+
function checkDocs(projectDir, warnings) {
|
|
456
|
+
const diff = diffDocs(projectDir);
|
|
457
|
+
if (isDocsUpToDate(diff)) return;
|
|
458
|
+
const count = diff.added.length + diff.changed.length + diff.removed.length;
|
|
459
|
+
warnings.push(
|
|
460
|
+
`${DOCS_REL_DIR}/ diff\xE8re de la doc API actuelle (${count} fichier(s)). Mettez-la \xE0 jour avec : ${UPDATE_DOCS_COMMAND}`
|
|
461
|
+
);
|
|
462
|
+
}
|
|
396
463
|
function resolveZipPath(options, projectDir, name, version) {
|
|
397
464
|
if (options.zipPath) {
|
|
398
|
-
return
|
|
465
|
+
return path6.isAbsolute(options.zipPath) ? options.zipPath : path6.join(projectDir, options.zipPath);
|
|
399
466
|
}
|
|
400
467
|
if (name && version) {
|
|
401
|
-
const candidate =
|
|
402
|
-
return
|
|
468
|
+
const candidate = path6.join(projectDir, `${name}-${version}.zip`);
|
|
469
|
+
return fs5.existsSync(candidate) ? candidate : void 0;
|
|
403
470
|
}
|
|
404
471
|
return void 0;
|
|
405
472
|
}
|
|
@@ -408,7 +475,7 @@ function verifyZip(zipPath, errors) {
|
|
|
408
475
|
try {
|
|
409
476
|
entries = new AdmZip(zipPath).getEntries();
|
|
410
477
|
} catch (err) {
|
|
411
|
-
errors.push(`Impossible de lire le zip ${
|
|
478
|
+
errors.push(`Impossible de lire le zip ${path6.basename(zipPath)} : ${err.message}`);
|
|
412
479
|
return;
|
|
413
480
|
}
|
|
414
481
|
const names = entries.map((e) => e.entryName.split("\\").join("/"));
|
|
@@ -429,60 +496,60 @@ function verifyZip(zipPath, errors) {
|
|
|
429
496
|
}
|
|
430
497
|
}
|
|
431
498
|
function checkViteBase(projectDir, warnings) {
|
|
432
|
-
const file =
|
|
433
|
-
if (!
|
|
434
|
-
const content =
|
|
499
|
+
const file = path6.join(projectDir, "vite.config.ts");
|
|
500
|
+
if (!fs5.existsSync(file)) return;
|
|
501
|
+
const content = fs5.readFileSync(file, "utf8");
|
|
435
502
|
if (!/base:\s*['"]\.\/['"]/.test(content)) {
|
|
436
503
|
warnings.push("vite.config.ts : `base: './'` introuvable \u2014 les assets risquent de casser apr\xE8s d\xE9ploiement.");
|
|
437
504
|
}
|
|
438
505
|
}
|
|
439
506
|
function checkRouter(projectDir, warnings) {
|
|
440
|
-
const srcDir =
|
|
441
|
-
if (!
|
|
507
|
+
const srcDir = path6.join(projectDir, "src");
|
|
508
|
+
if (!fs5.existsSync(srcDir)) return;
|
|
442
509
|
if (sourceUsesBrowserRouter(srcDir)) {
|
|
443
510
|
warnings.push("Usage de BrowserRouter d\xE9tect\xE9 \u2014 utilisez HashRouter pour l'h\xE9bergement versionn\xE9.");
|
|
444
511
|
}
|
|
445
512
|
}
|
|
446
513
|
function sourceUsesBrowserRouter(dir) {
|
|
447
|
-
for (const entry of
|
|
448
|
-
const full =
|
|
514
|
+
for (const entry of fs5.readdirSync(dir, { withFileTypes: true })) {
|
|
515
|
+
const full = path6.join(dir, entry.name);
|
|
449
516
|
if (entry.isDirectory()) {
|
|
450
517
|
if (sourceUsesBrowserRouter(full)) return true;
|
|
451
518
|
} else if (/\.(tsx?|jsx?)$/.test(entry.name)) {
|
|
452
|
-
if (/\bBrowserRouter\b/.test(
|
|
519
|
+
if (/\bBrowserRouter\b/.test(fs5.readFileSync(full, "utf8"))) return true;
|
|
453
520
|
}
|
|
454
521
|
}
|
|
455
522
|
return false;
|
|
456
523
|
}
|
|
457
524
|
|
|
458
525
|
// src/lib/zip.ts
|
|
459
|
-
import
|
|
460
|
-
import
|
|
526
|
+
import fs6 from "fs";
|
|
527
|
+
import path7 from "path";
|
|
461
528
|
import AdmZip2 from "adm-zip";
|
|
462
529
|
function createSourceZip(projectDir) {
|
|
463
530
|
const sourceZip = new AdmZip2();
|
|
464
|
-
const rootFiles =
|
|
531
|
+
const rootFiles = fs6.readdirSync(projectDir);
|
|
465
532
|
for (const file of rootFiles) {
|
|
466
533
|
if (file === "node_modules" || file === "dist" || file.endsWith(".zip")) {
|
|
467
534
|
continue;
|
|
468
535
|
}
|
|
469
|
-
const filePath =
|
|
470
|
-
const stat =
|
|
536
|
+
const filePath = path7.join(projectDir, file);
|
|
537
|
+
const stat = fs6.statSync(filePath);
|
|
471
538
|
if (stat.isDirectory()) {
|
|
472
539
|
sourceZip.addLocalFolder(filePath, file);
|
|
473
540
|
} else {
|
|
474
|
-
sourceZip.addFile(file,
|
|
541
|
+
sourceZip.addFile(file, fs6.readFileSync(filePath));
|
|
475
542
|
}
|
|
476
543
|
}
|
|
477
544
|
return sourceZip.toBuffer();
|
|
478
545
|
}
|
|
479
546
|
function zipDist(projectDir, name, version) {
|
|
480
|
-
const distDir =
|
|
481
|
-
if (!
|
|
547
|
+
const distDir = path7.join(projectDir, "dist");
|
|
548
|
+
if (!fs6.existsSync(distDir)) {
|
|
482
549
|
throw new Error("Dossier dist/ introuvable. Lancez le build avant de zipper.");
|
|
483
550
|
}
|
|
484
551
|
const zipFileName = `${name}-${version}.zip`;
|
|
485
|
-
const zipFilePath =
|
|
552
|
+
const zipFilePath = path7.join(projectDir, zipFileName);
|
|
486
553
|
const zip = new AdmZip2();
|
|
487
554
|
zip.addLocalFolder(distDir);
|
|
488
555
|
const sourceZipBuffer = createSourceZip(projectDir);
|
|
@@ -493,7 +560,7 @@ function zipDist(projectDir, name, version) {
|
|
|
493
560
|
|
|
494
561
|
// src/commands/pack.ts
|
|
495
562
|
function pack(opts) {
|
|
496
|
-
const projectDir =
|
|
563
|
+
const projectDir = path8.resolve(opts.dir ?? process.cwd());
|
|
497
564
|
let name;
|
|
498
565
|
let version;
|
|
499
566
|
try {
|
|
@@ -515,7 +582,7 @@ function pack(opts) {
|
|
|
515
582
|
error(err.message);
|
|
516
583
|
return 1;
|
|
517
584
|
}
|
|
518
|
-
success(`Zip cr\xE9\xE9 : ${
|
|
585
|
+
success(`Zip cr\xE9\xE9 : ${path8.basename(zipPath)}`);
|
|
519
586
|
info(`Version : ${version}`);
|
|
520
587
|
info("Mise \xE0 jour de la version avec la date du jour\u2026");
|
|
521
588
|
try {
|
|
@@ -531,10 +598,39 @@ function pack(opts) {
|
|
|
531
598
|
return ok ? 0 : 1;
|
|
532
599
|
}
|
|
533
600
|
|
|
601
|
+
// src/commands/update-docs.ts
|
|
602
|
+
import path9 from "path";
|
|
603
|
+
import pc4 from "picocolors";
|
|
604
|
+
function updateDocs(opts) {
|
|
605
|
+
const projectDir = path9.resolve(opts.dir ?? process.cwd());
|
|
606
|
+
try {
|
|
607
|
+
readManifest(projectDir);
|
|
608
|
+
} catch (err) {
|
|
609
|
+
error(`Projet cible invalide : ${err.message}`);
|
|
610
|
+
return 1;
|
|
611
|
+
}
|
|
612
|
+
let diff;
|
|
613
|
+
try {
|
|
614
|
+
diff = syncDocs(projectDir);
|
|
615
|
+
} catch (err) {
|
|
616
|
+
error(`\xC9chec de la mise \xE0 jour : ${err.message}`);
|
|
617
|
+
return 1;
|
|
618
|
+
}
|
|
619
|
+
if (isDocsUpToDate(diff)) {
|
|
620
|
+
success(`${DOCS_REL_DIR}/ est d\xE9j\xE0 \xE0 jour.`);
|
|
621
|
+
return 0;
|
|
622
|
+
}
|
|
623
|
+
success(`${DOCS_REL_DIR}/ mis \xE0 jour :`);
|
|
624
|
+
for (const file of diff.added) info(` ${pc4.green("+")} ${file}`);
|
|
625
|
+
for (const file of diff.changed) info(` ${pc4.dim("~")} ${file}`);
|
|
626
|
+
for (const file of diff.removed) info(` ${pc4.red("-")} ${file}`);
|
|
627
|
+
return 0;
|
|
628
|
+
}
|
|
629
|
+
|
|
534
630
|
// src/commands/verify.ts
|
|
535
|
-
import
|
|
631
|
+
import path10 from "path";
|
|
536
632
|
function verify(opts) {
|
|
537
|
-
const projectDir =
|
|
633
|
+
const projectDir = path10.resolve(opts.dir ?? process.cwd());
|
|
538
634
|
info("V\xE9rification de conformit\xE9\u2026");
|
|
539
635
|
const result = verifyExtension({ projectDir, zipPath: opts.zip });
|
|
540
636
|
const ok = printVerifyResult(result);
|
|
@@ -550,6 +646,9 @@ program.command("bootstrap").description("G\xE9n\xE8re le squelette d'une extens
|
|
|
550
646
|
program.command("apply").description("Applique un template par-dessus un projet existant (overlay).").argument("<template>", "template \xE0 appliquer (sous-dossier de templates/)").option("--dir <path>", "dossier du projet (d\xE9faut : dossier courant)").action((template, options) => {
|
|
551
647
|
process.exitCode = apply(template, options);
|
|
552
648
|
});
|
|
649
|
+
program.command("update-docs").description("Remplace la doc API (docs/inwink-api/) d'une extension existante par celle de cette version.").option("--dir <path>", "dossier du projet (d\xE9faut : dossier courant)").action((options) => {
|
|
650
|
+
process.exitCode = updateDocs(options);
|
|
651
|
+
});
|
|
553
652
|
program.command("package").description("Build + zip + v\xE9rification de conformit\xE9 de l'extension.").option("--dir <path>", "dossier du projet (d\xE9faut : dossier courant)").action((options) => {
|
|
554
653
|
process.exitCode = pack(options);
|
|
555
654
|
});
|
package/package.json
CHANGED
|
@@ -56,19 +56,23 @@ Avant d'écrire **tout** appel API, utilise les outils MCP inwink pour découvri
|
|
|
56
56
|
|
|
57
57
|
### Référence des patterns d'API (`docs/inwink-api/`)
|
|
58
58
|
|
|
59
|
-
Les outils MCP donnent les endpoints/champs **exacts** ; la doc statique dans `docs/inwink-api/` décrit les **patterns d'appel** (verbes, forme des payloads, langage de requête). Lis uniquement le fichier correspondant à ta tâche :
|
|
59
|
+
Les outils MCP donnent les endpoints/champs **exacts** ; la doc statique dans `docs/inwink-api/` décrit les **patterns d'appel** (verbes, forme des payloads, langage de requête). Ce dossier est une copie de la CLI helper, figée à la génération : mets-le à jour une fois par session de travail (pas avant chaque lecture) avec `npx @inwink-mcp/backoffice-extension-helpers@latest update-docs` (ne réécrit que `docs/inwink-api/`), et ne le modifie pas à la main. Lis uniquement le fichier correspondant à ta tâche :
|
|
60
60
|
|
|
61
61
|
| Tâche | Fichier |
|
|
62
62
|
|-------|---------|
|
|
63
|
+
| URLs de base, shardId, routes d'audience, codes HTTP & règles générales | [docs/inwink-api/README.md](docs/inwink-api/README.md) |
|
|
63
64
|
| Lire / lister des entités (query, count, pagination, projection, joins) | [docs/inwink-api/query.md](docs/inwink-api/query.md) |
|
|
64
65
|
| Construire un filtre / tri / projection (langage de requête JSON) | [docs/inwink-api/query-language.md](docs/inwink-api/query-language.md) |
|
|
66
|
+
| Regrouper / agréger (GROUP BY, statistiques, facettes) | [docs/inwink-api/aggregate.md](docs/inwink-api/aggregate.md) |
|
|
65
67
|
| Créer une entité | [docs/inwink-api/create.md](docs/inwink-api/create.md) |
|
|
66
68
|
| Modifier (patch / massupdate) | [docs/inwink-api/update.md](docs/inwink-api/update.md) |
|
|
67
69
|
| Supprimer (delete / massdelete) | [docs/inwink-api/delete.md](docs/inwink-api/delete.md) |
|
|
68
70
|
| Import en masse (bulk create/update) | [docs/inwink-api/import.md](docs/inwink-api/import.md) |
|
|
71
|
+
| Versions passées et entités supprimées | [docs/inwink-api/history.md](docs/inwink-api/history.md) |
|
|
72
|
+
| Contrats d'entités, endpoints divers | [docs/inwink-api/entities.md](docs/inwink-api/entities.md) |
|
|
73
|
+
| Déclarer un champ custom (template d'entité) | [docs/inwink-api/entity-template.md](docs/inwink-api/entity-template.md) |
|
|
69
74
|
| Champs de type `file` (upload / download) | [docs/inwink-api/files.md](docs/inwink-api/files.md) |
|
|
70
|
-
|
|
|
71
|
-
| Codes HTTP & règles générales | [docs/inwink-api/README.md](docs/inwink-api/README.md) |
|
|
75
|
+
| Manipuler les `assets` (galerie media) | [docs/inwink-api/assets.md](docs/inwink-api/assets.md) |
|
|
72
76
|
|
|
73
77
|
## Appeler l'API inwink
|
|
74
78
|
|
|
@@ -90,7 +94,7 @@ function MonComposant() {
|
|
|
90
94
|
}
|
|
91
95
|
```
|
|
92
96
|
|
|
93
|
-
Méthodes disponibles sur chaque hook : `request<T>(path, init?)`, `get<T>`, `post<T>(path, body?)`, `put<T>(path, body?)`, `del<T>`.
|
|
97
|
+
Méthodes disponibles sur chaque hook : `request<T>(path, init?)`, `get<T>`, `post<T>(path, body?)`, `put<T>(path, body?)`, `patch<T>(path, body?)`, `del<T>`.
|
|
94
98
|
|
|
95
99
|
Pour appeler un **hôte externe déclaré** (hors API inwink), utilise le hook générique `useInwinkApi(baseUrl)`.
|
|
96
100
|
|
|
@@ -130,10 +134,12 @@ import { useInwinkContext } from './hooks/useInwinkContext';
|
|
|
130
134
|
function MonComposant() {
|
|
131
135
|
const { scope, eventId, communityId, tenantId } = useInwinkContext();
|
|
132
136
|
// scope vaut 'event' | 'community' | 'tenant' | null (priorité event > community > tenant)
|
|
133
|
-
//
|
|
137
|
+
// En scope 'event' ou 'community', l'id correspondant est le shardId des chemins d'API.
|
|
134
138
|
}
|
|
135
139
|
```
|
|
136
140
|
|
|
141
|
+
En scope `tenant`, le `tenantId` **n'est pas un shardId** : les entités d'audience (`authtenant*`) vivent sous `{customerId}/audience/{tenantId}/{entity}/...` sur l'API Customer, où le shardId reste le `customerId` (cf. [docs/inwink-api/README.md](docs/inwink-api/README.md#audience-scoped-entities-customerapi)).
|
|
142
|
+
|
|
137
143
|
Pour laisser l'utilisateur **choisir** un contexte, monte un picker (`EventPicker`, `CommunityPicker`, `AudiencePicker`) — typiquement en **surchargeant le `Header`** (le `Header` par défaut est minimal et prévu pour ça). Les pickers ne sont pas montés par défaut : décide selon le besoin de l'extension.
|
|
138
144
|
|
|
139
145
|
## Recettes
|
|
@@ -217,6 +223,7 @@ Packaging et vérification via la CLI helper :
|
|
|
217
223
|
```powershell
|
|
218
224
|
npx @inwink-mcp/backoffice-extension-helpers@latest package # build + zip + vérification de conformité
|
|
219
225
|
npx @inwink-mcp/backoffice-extension-helpers@latest verify # vérification de conformité seule
|
|
226
|
+
npx @inwink-mcp/backoffice-extension-helpers@latest update-docs # met à jour docs/inwink-api/
|
|
220
227
|
```
|
|
221
228
|
|
|
222
229
|
## Conventions de code
|
|
@@ -232,5 +239,6 @@ npx @inwink-mcp/backoffice-extension-helpers@latest verify # vérification de
|
|
|
232
239
|
|
|
233
240
|
> Ces consignes s'appliquent à **tout** assistant IA / outil agentique qui manipule cette extension, quel qu'il soit.
|
|
234
241
|
|
|
242
|
+
- **Langue de l'extension.** Ce guide est rédigé en français et la référence API (`docs/inwink-api/`) en anglais : cela ne dit rien de la langue de l'extension. Échange avec l'utilisateur dans sa langue. Pour le code, les commentaires et les textes d'interface, utilise la langue qu'il demande ou, à défaut, celle dans laquelle il t'écrit — pas celle de cette documentation ni des commentaires du squelette.
|
|
235
243
|
- **Ne jamais publier sans validation explicite de l'utilisateur.** Le build, le packaging et la vérification **en local** sont autorisés sans demander. En revanche, toute étape qui **expose l'extension côté inwink** (upload d'une version, `publish`, promotion en `latest`) doit être **confirmée explicitement par l'utilisateur à chaque fois**. En cas de doute, demande avant d'agir.
|
|
236
244
|
- **Tester dans un vrai navigateur, pas dans un aperçu intégré.** Ne lance pas l'extension dans un panneau d'aperçu ou une webview embarquée de l'outil agentique : le flux OIDC y échoue. L'authentification exige que l'origine soit le serveur de dev local — le `redirect_uri` vaut `${window.location.origin}${window.location.pathname}` et n'est enregistré côté IdP que pour `http://localhost:<port>`, alors qu'un aperçu intégré sert l'application depuis une autre origine. **Recommandé** : lancer `npm run dev` et ouvrir un **vrai onglet de navigateur** (Chrome de préférence) sur l'URL locale affichée (ex. `http://localhost:5173`).
|
|
@@ -1,43 +1,84 @@
|
|
|
1
|
-
# inwink API
|
|
1
|
+
# inwink API reference
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Reference for consuming the inwink APIs from the extension. Calls always go through the `useInwink*Api` hooks, which set the Bearer token and the base URL: the paths below are relative to that base.
|
|
4
4
|
|
|
5
|
-
> **
|
|
5
|
+
> **Keep this folder current.** It is a snapshot of the helper CLI taken when the extension was generated, and it is never edited by hand. Refresh it once per working session, not before each read, with `npx @inwink-mcp/backoffice-extension-helpers@latest update-docs` (rewrites `docs/inwink-api/` only).
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
> **Dynamic discovery first.** This documentation describes call *patterns* (verbs, payload shapes, query language). The **exact endpoints, fields and entities** are discovered with the MCP tools (`inwink_mcp_find_api_endpoint`, `inwink_mcp_get_entity_description`) — never guess a route or a schema.
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
## Contents
|
|
10
|
+
|
|
11
|
+
| Task | File |
|
|
10
12
|
|---|---|
|
|
11
|
-
|
|
|
12
|
-
|
|
|
13
|
-
|
|
|
14
|
-
|
|
|
15
|
-
|
|
|
16
|
-
|
|
|
17
|
-
|
|
|
18
|
-
|
|
|
19
|
-
|
|
|
13
|
+
| Read / list entities (query, count, pagination, projection, joins) | [query.md](./query.md) |
|
|
14
|
+
| Build a filter / sort / projection (JSON query language) | [query-language.md](./query-language.md) |
|
|
15
|
+
| Group / aggregate (GROUP BY, statistics, facets) | [aggregate.md](./aggregate.md) |
|
|
16
|
+
| Create an entity | [create.md](./create.md) |
|
|
17
|
+
| Edit an entity (patch / massupdate) | [update.md](./update.md) |
|
|
18
|
+
| Delete an entity (delete / massdelete) | [delete.md](./delete.md) |
|
|
19
|
+
| Bulk import (bulk create/update) | [import.md](./import.md) |
|
|
20
|
+
| Past versions and deleted entities | [history.md](./history.md) |
|
|
21
|
+
| Entity contracts, other endpoints | [entities.md](./entities.md) |
|
|
22
|
+
| Declare a custom field (entity template) | [entity-template.md](./entity-template.md) |
|
|
23
|
+
| `file` fields (upload / download) | [files.md](./files.md) |
|
|
24
|
+
| Handle `assets` (media library) | [assets.md](./assets.md) |
|
|
20
25
|
|
|
21
26
|
---
|
|
22
27
|
|
|
23
|
-
##
|
|
28
|
+
## API Base URLs
|
|
29
|
+
|
|
30
|
+
| API | Base URL | Hook | Scope |
|
|
31
|
+
|---|---|---|---|
|
|
32
|
+
| EventAPI | `https://api-event.inwink.com` | `useInwinkEventApi` | Entities tied to an event (persons, sessions, exhibitors…) |
|
|
33
|
+
| CommunityAPI | `https://api-community.inwink.com` | `useInwinkCommunityApi` | Entities tied to a community (members, forums, forumsubjects…) |
|
|
34
|
+
| CustomerAPI | `https://api-customer.inwink.com` | `useInwinkCustomerApi` | Entities tied to the customer (events, contacts…) |
|
|
35
|
+
|
|
36
|
+
## URL pattern
|
|
37
|
+
|
|
38
|
+
- EventAPI: `{eventAPI}/{eventId}/{entity}/...`
|
|
39
|
+
- CommunityAPI: `{communityAPI}/{communityId}/{entity}/...`
|
|
40
|
+
- CustomerAPI: `{customerAPI}/{customerId}/{entity}/...`
|
|
41
|
+
|
|
42
|
+
The `{eventId}`, `{communityId}` or `{customerId}` is called the **shardId** — it scopes each request to one specific event, community or customer.
|
|
43
|
+
|
|
44
|
+
### Audience-scoped entities (CustomerAPI)
|
|
45
|
+
|
|
46
|
+
A customer holds one or more **audiences** (also called *tenants*). Entities that belong to an audience rather than to the whole customer have a name starting with `authtenant` (`authtenantcommunity`, `authtenantcontact`, `authtenantcompany`, `authtenantmailtemplate`…) and live on a two-segment route:
|
|
47
|
+
|
|
48
|
+
```
|
|
49
|
+
{customerAPI}/{customerId}/audience/{tenantId}/{entity}/...
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
The `{customerId}` remains the shardId; the `{tenantId}` identifies the audience. On these entities, `{shardId}` reads `{customerId}/audience/{tenantId}` in every route of this documentation. An `authtenant*` entity called without the audience segment returns 404.
|
|
53
|
+
|
|
54
|
+
## Common headers
|
|
55
|
+
|
|
56
|
+
Every request requires (set by the hooks):
|
|
57
|
+
|
|
58
|
+
```
|
|
59
|
+
Authorization: Bearer {access_token}
|
|
60
|
+
Content-Type: application/json
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
---
|
|
64
|
+
|
|
65
|
+
## General rules
|
|
24
66
|
|
|
25
67
|
- Property names are **case-insensitive**.
|
|
26
68
|
- Enum values are **case-insensitive**.
|
|
27
69
|
- All IDs are UUIDs.
|
|
28
70
|
- Dates are ISO 8601 / UTC.
|
|
29
|
-
-
|
|
30
|
-
|
|
31
|
-
---
|
|
71
|
+
- Write operations on entities (create, edit, delete, import) use the **POST** method. Only the partial edit of an entity's template uses `PATCH` (see [entity-template.md](./entity-template.md)).
|
|
32
72
|
|
|
33
|
-
## HTTP
|
|
73
|
+
## HTTP codes
|
|
34
74
|
|
|
35
75
|
| Code | Meaning |
|
|
36
76
|
|---|---|
|
|
37
|
-
| 200 | Success — parse body as JSON |
|
|
77
|
+
| 200 | Success — parse the body as JSON (some writes, such as entity template writes, return 200 with no body) |
|
|
38
78
|
| 204 | Success — no content |
|
|
39
|
-
| 400 | Bad request — body
|
|
40
|
-
| 401 | Unauthorized — token expired or invalid |
|
|
41
|
-
| 403 | Forbidden — missing rights, contact CSM |
|
|
42
|
-
|
|
|
79
|
+
| 400 | Bad request — the body holds the error details |
|
|
80
|
+
| 401 | Unauthorized — token expired or invalid (handled by the hooks) |
|
|
81
|
+
| 403 | Forbidden — missing rights, contact the CSM |
|
|
82
|
+
| 404 | Entity not found, empty body: entity name does not exist, entity exists but under another name on another API, or `authtenant*` entity called without the audience segment. Do not guess another name: run `inwink_mcp_find_api_endpoint` again with a natural-language description of the need |
|
|
83
|
+
| 500 | Server error — possibly transient, contact support if it persists. Also returned for a `shardId` that does not exist: check the identifier before concluding to an outage |
|
|
43
84
|
| 503 | Transient infrastructure error — retry later |
|