codoc-cli 0.1.2

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.
Files changed (92) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +104 -0
  3. package/assets/drawio-viewer/README.md +13 -0
  4. package/assets/drawio-viewer/viewer-static.min.js +8640 -0
  5. package/bin/dev.cmd +3 -0
  6. package/bin/dev.js +5 -0
  7. package/bin/run.cmd +3 -0
  8. package/bin/run.js +7 -0
  9. package/dist/clients/confluence/clients/attachments-client.js +74 -0
  10. package/dist/clients/confluence/clients/folders-client.js +111 -0
  11. package/dist/clients/confluence/clients/pages-client.js +128 -0
  12. package/dist/clients/confluence/confluence-client.js +44 -0
  13. package/dist/clients/confluence/utils/confluence-url.js +25 -0
  14. package/dist/clients/confluence/utils/confluence-util.js +20 -0
  15. package/dist/commands/init.js +29 -0
  16. package/dist/commands/publish.js +34 -0
  17. package/dist/commands/pull.js +54 -0
  18. package/dist/commands/sync.js +27 -0
  19. package/dist/commands/tree.js +17 -0
  20. package/dist/config/codoc-config-atlassian.js +89 -0
  21. package/dist/config/codoc-config-raw.js +21 -0
  22. package/dist/config/codoc-config.js +50 -0
  23. package/dist/config/codoc-paths.js +9 -0
  24. package/dist/hooks/command_not_found.js +7 -0
  25. package/dist/hooks/init/check-for-update.js +68 -0
  26. package/dist/hooks/init/load-env.js +15 -0
  27. package/dist/index.js +2 -0
  28. package/dist/services/codoc-id.js +7 -0
  29. package/dist/services/confluence/attachments.js +56 -0
  30. package/dist/services/confluence/folders.js +42 -0
  31. package/dist/services/confluence/labels.js +17 -0
  32. package/dist/services/confluence/pages.js +28 -0
  33. package/dist/services/conversion/confluenceToMarkdown/diagrams/drawio-diagrams.js +77 -0
  34. package/dist/services/conversion/confluenceToMarkdown/diagrams/drawio-to-image.js +131 -0
  35. package/dist/services/conversion/confluenceToMarkdown/index.js +17 -0
  36. package/dist/services/conversion/confluenceToMarkdown/preprocess/macro-converters.js +68 -0
  37. package/dist/services/conversion/confluenceToMarkdown/preprocess/preprocess.js +113 -0
  38. package/dist/services/conversion/confluenceToMarkdown/turndown.js +71 -0
  39. package/dist/services/conversion/markdownToConfluence/conversion-state.js +18 -0
  40. package/dist/services/conversion/markdownToConfluence/diagrams/mermaid-diagrams.js +56 -0
  41. package/dist/services/conversion/markdownToConfluence/diagrams/mermaid-to-drawio.js +331 -0
  42. package/dist/services/conversion/markdownToConfluence/index.js +48 -0
  43. package/dist/services/conversion/markdownToConfluence/languages.js +66 -0
  44. package/dist/services/conversion/markdownToConfluence/links.js +40 -0
  45. package/dist/services/conversion/markdownToConfluence/raw-html/raw-html.js +86 -0
  46. package/dist/services/conversion/markdownToConfluence/raw-html/task-lists.js +59 -0
  47. package/dist/services/conversion/markdownToConfluence/render/blocks.js +171 -0
  48. package/dist/services/conversion/markdownToConfluence/render/inline.js +44 -0
  49. package/dist/services/conversion/markdownToConfluence/render/page.js +31 -0
  50. package/dist/services/conversion/shared/confluence-macro-builder.js +11 -0
  51. package/dist/services/conversion/shared/gitlab-url.js +16 -0
  52. package/dist/services/conversion/shared/image-attachments.js +57 -0
  53. package/dist/services/conversion/shared/jira.js +7 -0
  54. package/dist/services/conversion/shared/macro-types.js +12 -0
  55. package/dist/services/conversion/shared/preserved-macros.js +8 -0
  56. package/dist/services/conversion/shared/read-storage-format.js +29 -0
  57. package/dist/services/conversion/shared/regex-cache.js +14 -0
  58. package/dist/services/conversion/shared/xml-escaping.js +19 -0
  59. package/dist/services/ensure-env.js +59 -0
  60. package/dist/services/files-service.js +74 -0
  61. package/dist/services/git/default-branch.js +33 -0
  62. package/dist/services/init-templates/agent-md-init.js +55 -0
  63. package/dist/services/init-templates/doc-guide-init.js +131 -0
  64. package/dist/services/init-templates/env-init.js +12 -0
  65. package/dist/services/init-templates/yaml-init.js +56 -0
  66. package/dist/services/lock/lock-file.js +61 -0
  67. package/dist/services/log/init-printer.js +50 -0
  68. package/dist/services/log/logger.js +131 -0
  69. package/dist/services/prompt.js +32 -0
  70. package/dist/services/slugify.js +6 -0
  71. package/dist/types/codoc-types.js +1 -0
  72. package/dist/use-cases/init/init.js +150 -0
  73. package/dist/use-cases/publish/publish.js +166 -0
  74. package/dist/use-cases/pull/parse-page-input.js +22 -0
  75. package/dist/use-cases/pull/pull.js +337 -0
  76. package/dist/use-cases/shared/codoc-yaml.js +45 -0
  77. package/dist/use-cases/shared/confluence-client-registry.js +27 -0
  78. package/dist/use-cases/shared/env-select.js +23 -0
  79. package/dist/use-cases/shared/fetch-page.js +11 -0
  80. package/dist/use-cases/shared/list-documents.js +172 -0
  81. package/dist/use-cases/shared/pull-folder.js +90 -0
  82. package/dist/use-cases/shared/render-remote-page.js +71 -0
  83. package/dist/use-cases/sync/attachment-upload.js +31 -0
  84. package/dist/use-cases/sync/sync-actions.js +170 -0
  85. package/dist/use-cases/sync/sync-associate.js +41 -0
  86. package/dist/use-cases/sync/sync-entries.js +36 -0
  87. package/dist/use-cases/sync/sync-handlers.js +140 -0
  88. package/dist/use-cases/sync/sync-helpers.js +8 -0
  89. package/dist/use-cases/sync/sync.js +94 -0
  90. package/dist/use-cases/tree/docs-tree.js +141 -0
  91. package/oclif.manifest.json +255 -0
  92. package/package.json +98 -0
@@ -0,0 +1,8 @@
1
+ import { escapeXml } from "./xml-escaping.js";
2
+ //#region src/services/conversion/shared/preserved-macros.ts
3
+ const PRESERVED_MACRO_ATTR = "data-confluence-macro-preserved";
4
+ function preserveAsSentinel(xml) {
5
+ return `<div ${PRESERVED_MACRO_ATTR}="1">${escapeXml(xml)}</div>`;
6
+ }
7
+ //#endregion
8
+ export { PRESERVED_MACRO_ATTR, preserveAsSentinel };
@@ -0,0 +1,29 @@
1
+ import { memoizedRegex } from "./regex-cache.js";
2
+ //#region src/services/conversion/shared/read-storage-format.ts
3
+ const macroParamRegexFor = memoizedRegex((name) => new RegExp(`<ac:parameter[^>]*ac:name="${name}"[^>]*>([\\s\\S]*?)<\\/ac:parameter>`));
4
+ const macroRegexFor = memoizedRegex((name) => new RegExp(`<ac:structured-macro[^>]*ac:name="${name}"[^>]*>[\\s\\S]*?<\\/ac:structured-macro>`, "g"));
5
+ /** Valeur (trimée) d'un `<ac:parameter ac:name="NAME">…</ac:parameter>`, ou undefined.
6
+ * `name` est inséré tel quel dans la regex (les motifs comme "colou?r" sont donc supportés). */
7
+ function macroParam(inner, name) {
8
+ return inner.match(macroParamRegexFor(name))?.[1]?.trim();
9
+ }
10
+ /** Regex globale matchant toutes les `<ac:structured-macro ac:name="NAME">…</ac:structured-macro>`. */
11
+ function macroRegex(name) {
12
+ return macroRegexFor(name);
13
+ }
14
+ /** Position juste après le `</div>` fermant le `<div` ouvert à `start` (gère l'imbrication). */
15
+ function findDivEnd(s, start) {
16
+ let depth = 0;
17
+ let i = start;
18
+ while (i < s.length) if (s.startsWith("<div", i) && /[\s>]/.test(s[i + 4] ?? "")) {
19
+ depth++;
20
+ i += 4;
21
+ } else if (s.startsWith("</div>", i)) {
22
+ depth--;
23
+ if (depth === 0) return i + 6;
24
+ i += 6;
25
+ } else i++;
26
+ return s.length;
27
+ }
28
+ //#endregion
29
+ export { findDivEnd, macroParam, macroRegex };
@@ -0,0 +1,14 @@
1
+ //#region src/services/conversion/shared/regex-cache.ts
2
+ function memoizedRegex(build) {
3
+ const cache = /* @__PURE__ */ new Map();
4
+ return (key) => {
5
+ let re = cache.get(key);
6
+ if (!re) {
7
+ re = build(key);
8
+ cache.set(key, re);
9
+ }
10
+ return re;
11
+ };
12
+ }
13
+ //#endregion
14
+ export { memoizedRegex };
@@ -0,0 +1,19 @@
1
+ //#region src/services/conversion/shared/xml-escaping.ts
2
+ /** Échappe le contenu XML : `&` `<` `>`. */
3
+ function escapeXml(text) {
4
+ return text.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
5
+ }
6
+ /** Échappe une valeur d'attribut XML : `&` `<` `>` `"`. */
7
+ function escapeAttr(text) {
8
+ return escapeXml(text).replace(/"/g, "&quot;");
9
+ }
10
+ /** Inverse exact d'{@link escapeXml} : `&lt;` `&gt;` `&amp;`. */
11
+ function unescapeXml(text) {
12
+ return text.replace(/&lt;/g, "<").replace(/&gt;/g, ">").replace(/&amp;/g, "&");
13
+ }
14
+ /** Neutralise les séquences de fin de CDATA `]]>` dans un contenu destiné à `<![CDATA[…]]>`. */
15
+ function escapeCdata(text) {
16
+ return text.replace(/]]>/g, "]]]]><![CDATA[>");
17
+ }
18
+ //#endregion
19
+ export { escapeAttr, escapeCdata, escapeXml, unescapeXml };
@@ -0,0 +1,59 @@
1
+ import { fileExists, readFile, writeFile } from "./files-service.js";
2
+ import { CODOC_ENV_FILE } from "../config/codoc-paths.js";
3
+ import { log } from "./log/logger.js";
4
+ import { confirm, input, password } from "@inquirer/prompts";
5
+ //#region src/services/ensure-env.ts
6
+ async function ensureEnvVars(requirements) {
7
+ for (const { name, satisfiedBy, ...opts } of requirements) {
8
+ if (satisfiedBy?.some((n) => process.env[n]?.trim())) continue;
9
+ await ensureEnvVar(name, opts);
10
+ }
11
+ }
12
+ async function ensureEnvVar(name, opts) {
13
+ const existing = process.env[name];
14
+ if (existing && existing.trim()) return existing.trim();
15
+ log.blank();
16
+ log.warning0(`${name} n'est pas défini.`);
17
+ log.raw(` ${opts.hint}`);
18
+ if (opts.generateUrl) log.raw(` Pour le générer : ${opts.generateUrl}`);
19
+ const secret = opts.secret !== false;
20
+ let value;
21
+ try {
22
+ value = secret ? await password({
23
+ message: ` Saisis ${name} (Entrée vide pour skip) :`,
24
+ mask: "*"
25
+ }) : await input({ message: ` Saisis ${name} (Entrée vide pour skip) :` });
26
+ } catch {
27
+ return;
28
+ }
29
+ value = value.trim();
30
+ if (!value) return void 0;
31
+ process.env[name] = value;
32
+ if (await confirm({
33
+ message: ` Sauvegarder ${name} dans .env-codoc pour les prochains runs ?`,
34
+ default: true
35
+ }).catch(() => false)) {
36
+ upsertEnvLine(name, value);
37
+ log.success1(`[UPDATED] ${name} sauvegardé dans .env-codoc`);
38
+ }
39
+ return value;
40
+ }
41
+ function upsertEnvLine(name, value) {
42
+ const lineRe = new RegExp(`^\\s*${escapeRegex(name)}\\s*=.*$`, "m");
43
+ const newLine = `${name}=${value}`;
44
+ if (!fileExists(CODOC_ENV_FILE)) {
45
+ writeFile(CODOC_ENV_FILE, `${newLine}\n`);
46
+ return;
47
+ }
48
+ const content = readFile(CODOC_ENV_FILE);
49
+ if (lineRe.test(content)) writeFile(CODOC_ENV_FILE, content.replace(lineRe, newLine));
50
+ else {
51
+ const sep = content.endsWith("\n") ? "" : "\n";
52
+ writeFile(CODOC_ENV_FILE, `${content}${sep}${newLine}\n`);
53
+ }
54
+ }
55
+ function escapeRegex(s) {
56
+ return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
57
+ }
58
+ //#endregion
59
+ export { ensureEnvVar, ensureEnvVars };
@@ -0,0 +1,74 @@
1
+ import path from "node:path";
2
+ import fs from "node:fs";
3
+ import fsPromises from "node:fs/promises";
4
+ import os from "node:os";
5
+ //#region src/services/files-service.ts
6
+ const DEFAULT_SKIP_DIRS = [
7
+ "node_modules",
8
+ ".git",
9
+ "target",
10
+ "build",
11
+ "generated-sources"
12
+ ];
13
+ /** Lit le contenu texte d'un fichier (UTF-8). */
14
+ function readFile(absolutePath) {
15
+ return fs.readFileSync(absolutePath, "utf8");
16
+ }
17
+ /** Vrai si le chemin existe (fichier ou dossier). */
18
+ function fileExists(absolutePath) {
19
+ return fs.existsSync(absolutePath);
20
+ }
21
+ /** Convertit les séparateurs Windows (`\`) en séparateurs POSIX (`/`), sans autre normalisation. */
22
+ function toPosixPath(p) {
23
+ return p.replace(/\\/g, "/");
24
+ }
25
+ /** Crée un dossier (et ses parents) s'il n'existe pas déjà. */
26
+ function ensureDir(absoluteDirectoryPath) {
27
+ fs.mkdirSync(absoluteDirectoryPath, { recursive: true });
28
+ }
29
+ /** Vrai si le chemin existe et est un dossier. */
30
+ function isDirectory(absolutePath) {
31
+ return fs.existsSync(absolutePath) && fs.statSync(absolutePath).isDirectory();
32
+ }
33
+ /**
34
+ * Aligne les fins de ligne du contenu sur celles de la plateforme d'exécution
35
+ * (CRLF sous Windows, LF ailleurs) - le code du projet écrit toujours en `\n`,
36
+ * sans ça les fichiers générés diffèrent systématiquement de la convention du
37
+ * dépôt cible sous Windows (diffs entiers dus uniquement aux fins de ligne).
38
+ */
39
+ function normalizeEol(content) {
40
+ return content.replace(/\r\n/g, "\n").replace(/\n/g, os.EOL);
41
+ }
42
+ /** Écrit un fichier texte (UTF-8), en créant le dossier parent au besoin. Fins de ligne alignées sur `os.EOL`. */
43
+ function writeFile(absolutePath, content) {
44
+ ensureDir(path.dirname(absolutePath));
45
+ fs.writeFileSync(absolutePath, normalizeEol(content), "utf8");
46
+ }
47
+ /** Écrit un fichier binaire (Buffer), en créant le dossier parent au besoin. */
48
+ function writeBinaryFile(absolutePath, content) {
49
+ ensureDir(path.dirname(absolutePath));
50
+ fs.writeFileSync(absolutePath, content);
51
+ }
52
+ /** Supprime un fichier ou dossier (récursif), sans erreur s'il est déjà absent. */
53
+ function removePath(absolutePath) {
54
+ fs.rmSync(absolutePath, {
55
+ recursive: true,
56
+ force: true
57
+ });
58
+ }
59
+ /** Liste récursivement les fichiers dont l'extension figure dans `exts` (dossiers de build ignorés). */
60
+ async function collectFiles(dir, exts, skipDirs = DEFAULT_SKIP_DIRS) {
61
+ const skip = new Set(skipDirs);
62
+ const entries = await fsPromises.readdir(dir, { withFileTypes: true });
63
+ const files = [];
64
+ for (const entry of entries) {
65
+ const fullPath = path.join(dir, entry.name);
66
+ if (entry.isDirectory()) {
67
+ if (skip.has(entry.name)) continue;
68
+ files.push(...await collectFiles(fullPath, exts, skipDirs));
69
+ } else if (entry.isFile() && exts.some((e) => entry.name.endsWith(e))) files.push(fullPath);
70
+ }
71
+ return files;
72
+ }
73
+ //#endregion
74
+ export { collectFiles, ensureDir, fileExists, isDirectory, readFile, removePath, toPosixPath, writeBinaryFile, writeFile };
@@ -0,0 +1,33 @@
1
+ import { PROJECT_ROOT } from "../../config/codoc-paths.js";
2
+ import { getGitlabConfig } from "../../config/codoc-config.js";
3
+ import { simpleGit } from "simple-git";
4
+ //#region src/services/git/default-branch.ts
5
+ let cached;
6
+ async function getDefaultBranch() {
7
+ if (cached) return cached;
8
+ const fromConfig = getGitlabConfig().defaultBranch;
9
+ if (fromConfig) {
10
+ cached = fromConfig;
11
+ return cached;
12
+ }
13
+ if (process.env.CI_DEFAULT_BRANCH) {
14
+ cached = process.env.CI_DEFAULT_BRANCH;
15
+ return cached;
16
+ }
17
+ try {
18
+ cached = (await simpleGit(PROJECT_ROOT).raw([
19
+ "symbolic-ref",
20
+ "--short",
21
+ "refs/remotes/origin/HEAD"
22
+ ])).trim().replace(/^origin\//, "");
23
+ if (cached) return cached;
24
+ } catch {}
25
+ cached = "main";
26
+ return cached;
27
+ }
28
+ /** Utilitaire pour les tests : réinitialise le cache. */
29
+ function resetDefaultBranchCache() {
30
+ cached = void 0;
31
+ }
32
+ //#endregion
33
+ export { getDefaultBranch, resetDefaultBranchCache };
@@ -0,0 +1,55 @@
1
+ //#region src/services/init-templates/agent-md-init.ts
2
+ function docRules(d) {
3
+ const overrides = (d.keywordOverrides ?? []).map((o) => ({
4
+ keywords: o.keywords,
5
+ location: o.path
6
+ }));
7
+ const baseKeywords = d.keywords?.length ? d.keywords : [d.confluence.title ?? d.path];
8
+ return [...overrides, {
9
+ keywords: baseKeywords,
10
+ location: d.path
11
+ }];
12
+ }
13
+ function renderRule(r) {
14
+ return `- when:\n contains_any: [${r.keywords.map((k) => JSON.stringify(k)).join(", ")}]\n use: "${r.location}"`;
15
+ }
16
+ function agentMdContent(config) {
17
+ const rules = config.docs.flatMap(docRules);
18
+ const routing = rules.length ? rules.map(renderRule).join("\n\n") : "# (aucune documentation indexée pour ce projet)";
19
+ const hasConfluenceDocs = config.docs.some((d) => d.maintainedIn === "confluence");
20
+ const envsLines = config.atlassian.environments.length ? config.atlassian.environments.map((e) => `- \`${e.key}\` → baseUrl \`${e.baseUrl}\`, espace \`${e.spaceKey}\``).join("\n") : "_Aucun environnement Confluence configuré._";
21
+ return `# Documentation du projet - index pour agents IA
22
+
23
+ ## RÈGLES OBLIGATOIRES
24
+
25
+ 1. Avant de répondre, de proposer du code, ou de décrire un comportement existant : vérifie la section ROUTAGE.
26
+ 2. Une règle \`contains_any\` correspond au sujet → base ta réponse sur le fichier \`use\`, jamais sur ta mémoire générale ni une recherche libre dans le code. Plusieurs règles correspondent → privilégie celle dont le \`use\` est le plus spécifique (chemin le plus précis).
27
+ 3. Aucune règle ne correspond → dis-le explicitement, ne présume pas d'une source, puis appuie-toi sur une lecture directe du code.
28
+ 4. N'invente jamais un fichier ou une page Confluence absent de ce document.
29
+ 5. Termine toujours ta réponse au format défini dans FORMAT DE RÉPONSE.
30
+
31
+ ## ROUTAGE
32
+
33
+ \`\`\`yaml
34
+ ${routing}
35
+ \`\`\`
36
+ ${hasConfluenceDocs ? `
37
+ ## CONFLUENCE
38
+
39
+ ${envsLines}
40
+
41
+ Pour une doc \`confluence\`, la page fait foi et \`codoc.lock\` (racine du projet) donne son URL :
42
+ 1. Cherche l'entrée dont \`sourceFile\` correspond au fichier \`use\` de la règle appliquée.
43
+ 2. \`confluenceUrl\` présent → lien direct.
44
+ 3. Absent (page pas encore publiée) → reconstruit avec \`{baseUrl}/wiki/spaces/{spaceKey}/pages/{confluencePageId}\`.
45
+ ` : ""}
46
+ ## FORMAT DE RÉPONSE
47
+
48
+ \`\`\`
49
+ SOURCE: <fichier, code direct et/ou page Confluence associée dans le lock>
50
+ RÉPONSE: <ta réponse>
51
+ \`\`\`
52
+ `;
53
+ }
54
+ //#endregion
55
+ export { agentMdContent };
@@ -0,0 +1,131 @@
1
+ //#region src/services/init-templates/doc-guide-init.ts
2
+ function docguideContent(id) {
3
+ return `> **Page d'exemple** - générée par \`codoc init\`. Supprime l'entrée de \`codoc.yaml\` puis relance \`codoc sync\` pour la retirer.
4
+
5
+ # Guide codoc - ${id}
6
+
7
+ ## Rôle
8
+
9
+ \`codoc\` synchronise des \`.md\` locaux avec des pages Confluence, dans les deux sens.
10
+
11
+ Chaque doc déclarée dans \`codoc.yaml\` a une "source de vérité" mentionnée :
12
+
13
+ | \`maintainedIn\` | Source de vérité | Action de \`sync\` |
14
+ |---|---|---|
15
+ | \`code\` | Le \`.md\` local | Publie / met à jour la page Confluence |
16
+ | \`confluence\` | La page Confluence | Régénère le \`.md\` local |
17
+
18
+ ## Commandes
19
+
20
+ Résolution des informations dans l'ordre **flag CLI** → **valeur déjà présente dans \`codoc.yaml\`** → **prompt console** (pas de mode CI détecté automatiquement - un pipeline doit fournir tout ce qu'il faut pour rester silencieux).
21
+
22
+ ### \`codoc init\`
23
+
24
+ Génère la config initiale (codoc.yaml, .env-codoc, guide).
25
+
26
+ | Flag | Rôle |
27
+ |---|---|
28
+ | \`--agent-md\` | Ne génère QUE le guide agent IA (basé sur \`codoc.yaml\` déjà rempli) - rien d'autre |
29
+ | \`--agent-target <agent\\|copilot>\` | Avec \`--agent-md\` : destination du guide. Défaut : celle déjà en place, sinon demandé |
30
+
31
+ ### \`codoc sync\`
32
+
33
+ Synchronise chaque doc dans les deux sens selon son \`maintainedIn\`.
34
+
35
+ | Flag | Rôle |
36
+ |---|---|
37
+ | \`--env <clé>\` | Ne synchronise que cet environnement Confluence. Défaut : tous |
38
+ | \`--confirm\` / \`--no-confirm\` | Répond automatiquement aux suppressions/adoptions en conflit de titre (accepte/refuse tout). Non fourni : demande à chaque cas |
39
+
40
+ ### \`codoc pull [url\\|id]\`
41
+
42
+ Importe une page Confluence existante en local (interactif).
43
+
44
+ | Flag | Rôle |
45
+ |---|---|
46
+ | \`--env <clé>\` | Environnement où chercher la page. Défaut : déduit de l'URL, sinon auto/prompt |
47
+ | \`--as-folder\` / \`--no-as-folder\` | Sous-éléments détectés : importe tout le dossier, ou seulement la page. Non fourni : demande le cas échéant |
48
+ | \`--keep-existing\` / \`--no-keep-existing\` | Import précédent détecté : le remplace, ou en crée un séparé. Non fourni : demande le cas échéant |
49
+ | \`--in-config\` / \`--no-in-config\` | Ajoute/met à jour l'entrée \`codoc.yaml\`. \`--no-in-config\` : import ponctuel, hors suivi (yaml et lock non touchés). Non fourni : demande en fin de commande |
50
+ | \`--local-path <chemin>\` | Chemin local du \`.md\` (page unique) ou dossier de destination (import dossier) |
51
+ | \`--title <titre>\` | Titre de la page Confluence (page unique). Défaut : config existante, sinon le titre Confluence |
52
+ | \`--parent-page-id <id>\` | parentPageId Confluence. Défaut : config existante, sinon le parent réel de la page |
53
+ | \`--maintained-in <code\\|confluence>\` | \`code\` → le \`.md\` fait foi ; \`confluence\` → la page fait foi. Défaut : \`confluence\` |
54
+ | \`--images-dir <chemin>\` | Dossier local pour les images (\`""\` pour désactiver). Défaut : \`doc/img\` |
55
+
56
+ ### \`codoc publish <chemin>\`
57
+
58
+ Publie un \`.md\` local vers Confluence (interactif).
59
+
60
+ | Flag | Rôle |
61
+ |---|---|
62
+ | \`--env <clé>\` | Environnement Confluence cible. Défaut : config existante, sinon auto/prompt |
63
+ | \`--in-config\` / \`--no-in-config\` | Ajoute/met à jour l'entrée \`codoc.yaml\`. \`--no-in-config\` : publication ponctuelle, hors suivi (yaml et lock non touchés). Non fourni : demande en fin de commande |
64
+ | \`--parent-page-id <id>\` | parentPageId Confluence cible. Défaut : config existante, sinon \`defaultParentPageId\` de l'env |
65
+ | \`--title <titre>\` | Titre de la page Confluence (fichier unique). Défaut : config existante, sinon le H1 du fichier |
66
+
67
+ ### \`codoc tree\`
68
+
69
+ Affiche l'arborescence des docs déployées.
70
+
71
+ | Flag | Rôle |
72
+ |---|---|
73
+ | \`--env <clé>\` | N'affiche que cet environnement Confluence. Défaut : tous |
74
+
75
+ \`pull\` et \`publish\` demandent en fin de commande s'il faut ajouter le document à \`codoc.yaml\` (défaut : oui) - répondre non (ou \`--no-in-config\`) permet un import/publication ponctuel, sans toucher ni à \`codoc.yaml\` ni à \`codoc.lock\`, non suivi par les prochains \`sync\`.
76
+
77
+ ## Configuration - \`codoc.yaml\`
78
+
79
+ | Champ | Rôle |
80
+ |---|---|
81
+ | \`atlassian.environments.<clé>.baseUrl\` | URL Confluence |
82
+ | \`atlassian.environments.<clé>.spaceKey\` | Clé de l'espace |
83
+ | \`atlassian.environments.<clé>.defaultParentPageId\` | Page parente par défaut |
84
+ | \`drawio.*\` | Réglages des diagrammes générés depuis les blocs \`\`\`mermaid (un seul jeu de valeurs, partagé par tous les environnements) |
85
+ | \`gitlab.baseUrl\` / \`gitlab.defaultBranch\` | Réécrit les liens vers du code en URLs GitLab dans les pages publiées (optionnel) |
86
+ | \`jira.serverId\` / \`jira.server\` | Rend les liens de tickets Jira sous forme de macro Confluence native (optionnel) |
87
+ | \`docs[].codocId\` | Lien stable yaml ↔ lock |
88
+ | \`docs[].path\` | Chemin du \`.md\` (ou glob \`/**\`) |
89
+ | \`docs[].maintainedIn\` | \`code\` ou \`confluence\` |
90
+ | \`docs[].generateSummary\` | Génère le sommaire (macro toc) en haut de page - \`maintainedIn: code\` uniquement (défaut : \`true\`) |
91
+ | \`docs[].confluence.title\` | Titre de la page (défaut : \`# H1\` du fichier) |
92
+
93
+ ## Identifiants - \`.env-codoc\`
94
+
95
+ Ne pas committer (ajouté à \`.gitignore\` par \`init\`).
96
+
97
+ | Variable | Usage |
98
+ |---|---|
99
+ | \`CONFLUENCE_<CLÉ>_USERNAME\` | Email de compte Atlassian pour l'environnement \`<clé>\` |
100
+ | \`CONFLUENCE_<CLÉ>_API_TOKEN\` | Token Atlassian pour l'environnement \`<clé>\` |
101
+
102
+ Toujours préfixées par la clé de l'environnement (\`atlassian.environments.<clé>\` dans \`codoc.yaml\`) - même s'il n'y en a qu'un seul, ex. \`CONFLUENCE_DEFAULT_USERNAME\`. Pas de variable générique partagée entre environnements.
103
+
104
+ ## Workflow type
105
+
106
+ \`\`\`
107
+ 1. codoc init → génère yaml, env, guide
108
+ 2. Remplir codoc.yaml + .env-codoc
109
+ 3. codoc sync → publie le guide sur Confluence
110
+ \`\`\`
111
+
112
+ ## Format Markdown supporté
113
+
114
+ | Markdown | Rendu Confluence |
115
+ |---|---|
116
+ | \`# Titre\` | Titre de section |
117
+ | \`**gras**\` / \`*italique*\` | Gras / italique |
118
+ | \`\`\`code\`\`\` | Code inline |
119
+ | Bloc \`\`\`lang | Bloc de code avec coloration |
120
+ | Bloc \`\`\`mermaid | Diagramme draw.io (converti + upload en pièce jointe) |
121
+ | \`- [ ] tâche\` | Liste de tâches Confluence |
122
+ | \`📅 2024-01-15\` | \`<time datetime="2024-01-15"/>\` |
123
+ | \`<span data-confluence-status="Green">Livré</span>\` | Macro Status |
124
+
125
+ ## \`codoc.lock\`
126
+
127
+ Généré par \`sync\` / \`pull\`. **À committer**. Trace les IDs Confluence pour les mises à jour idempotentes.
128
+ `;
129
+ }
130
+ //#endregion
131
+ export { docguideContent };
@@ -0,0 +1,12 @@
1
+ //#region src/services/init-templates/env-init.ts
2
+ function envTemplate() {
3
+ return `# Identifiants - NE PAS COMMITTER
4
+ # Toujours préfixés par la clé de l'environnement Confluence (atlassian.environments.<clé> dans
5
+ # codoc.yaml) - même s'il n'y en a qu'un seul, jamais de variable générique partagée.
6
+
7
+ CONFLUENCE_DEFAULT_USERNAME=prenom.nom@entreprise.com
8
+ CONFLUENCE_DEFAULT_API_TOKEN=votre_token_api_confluence
9
+ `;
10
+ }
11
+ //#endregion
12
+ export { envTemplate };
@@ -0,0 +1,56 @@
1
+ //#region src/services/init-templates/yaml-init.ts
2
+ function yamlTemplate(docRelPath, codocId) {
3
+ return `# Configuration codoc - généré par codoc init
4
+ # Remplissez les champs marqués TODO puis lancez : codoc sync
5
+
6
+ # Environnements Atlassian (Confluence/Jira) cibles. Au moins un. Une seule clé = la
7
+ # référence par défaut côté docs. Identifiants toujours préfixés par la clé de l'env,
8
+ # CONFLUENCE_<KEY>_USERNAME / CONFLUENCE_<KEY>_API_TOKEN dans .env-codoc.
9
+ atlassian:
10
+ environments:
11
+ default:
12
+ baseUrl: https://TODO.atlassian.net # URL de l'instance Confluence Cloud
13
+ spaceKey: TODO # Clé de l'espace Confluence cible (ex. DA)
14
+ defaultParentPageId: "TODO" # ID de la page parente par défaut (optionnel)
15
+
16
+ # Configuration draw.io (optionnel - blocs \`\`\`mermaid convertis en diagrammes). Un seul jeu de
17
+ # réglages, partagé par tous les environnements Atlassian ci-dessus.
18
+ # drawio:
19
+ # macroName: drawio
20
+ # width: 900
21
+ # edgeStyle: curved # tracé : curved | orthogonal | straight
22
+ # edgeAnchor: side # ancrage des flèches : side | auto (flottant)
23
+
24
+ # Configuration GitLab (optionnel - réécrit les liens vers du code en URLs GitLab dans les pages publiées)
25
+ # gitlab:
26
+ # baseUrl: https://TODO/mon-groupe/mon-repo
27
+ # defaultBranch: main # branche cible des liens
28
+
29
+ # Identifiants Jira (optionnel - liens tickets cliquables dans les pages Confluence publiées, sous
30
+ # forme de macro Jira native).
31
+ # jira:
32
+ # serverId: ec6d1637-f9d6-3ae4-9d5e-9dce283383ea
33
+ # server: System Jira
34
+
35
+ docs:
36
+ - codocId: ${codocId} # identifiant stable (lien yaml ↔ codoc.lock) - unique, ne pas réutiliser
37
+ path: ${docRelPath}
38
+ maintainedIn: code # code → .md publie sur Confluence | confluence → Confluence publie en local
39
+ # generateSummary: false # désactive le sommaire (macro toc) en haut de page - défaut : true (maintainedIn: code uniquement)
40
+ keywords: ["codoc", "comment fonctionne la synchro Confluence"] # mots-clés de routage - alimentent codoc-agent.md
41
+ # Pour une entrée dossier/glob (path se terminant par /* ou /**), affine le routage par
42
+ # sous-chemin - les keywords ci-dessus restent le repli pour tout le reste du dossier :
43
+ # keywordOverrides:
44
+ # - path: doc/model/endpoints/clients
45
+ # keywords: ["clients", "endpoint clients", "API clients"]
46
+ # - path: doc/model/diagrammes/admin/compte
47
+ # keywords: ["compte", "diagramme compte"]
48
+ confluence:
49
+ title: Guide codoc
50
+ # parentPageId: "TODO" # surcharge defaultParentPageId pour cette page uniquement
51
+ # titlePrefix: "[DRAFT] "
52
+ # titleSuffix: " (auto)"
53
+ `;
54
+ }
55
+ //#endregion
56
+ export { yamlTemplate };
@@ -0,0 +1,61 @@
1
+ import { fileExists, readFile, writeFile } from "../files-service.js";
2
+ import { STATE_FILE } from "../../config/codoc-paths.js";
3
+ import { log } from "../log/logger.js";
4
+ //#region src/services/lock/lock-file.ts
5
+ let cache;
6
+ function loadPublishState() {
7
+ if (cache) return cache;
8
+ if (!fileExists(STATE_FILE)) {
9
+ cache = {
10
+ lastPublished: "",
11
+ pages: {}
12
+ };
13
+ return cache;
14
+ }
15
+ try {
16
+ const raw = readFile(STATE_FILE);
17
+ cache = JSON.parse(raw);
18
+ return cache;
19
+ } catch {
20
+ log.warning0("codoc.lock illisible - état précédent ignoré.");
21
+ cache = {
22
+ lastPublished: "",
23
+ pages: {}
24
+ };
25
+ return cache;
26
+ }
27
+ }
28
+ function savePublishState(state) {
29
+ cache = state;
30
+ writeFile(STATE_FILE, JSON.stringify(state, null, 2) + "\n");
31
+ }
32
+ function findLockedRef(lock, envKey, sourceFile) {
33
+ for (const p of Object.values(lock.pages)) {
34
+ if (p.environment !== envKey) continue;
35
+ if (p.sourceFile === sourceFile) return {
36
+ confluencePageId: p.confluencePageId,
37
+ confluenceUrl: p.confluenceUrl,
38
+ title: p.title
39
+ };
40
+ const child = p.children?.find((c) => c.sourceFile === sourceFile);
41
+ if (child) return {
42
+ confluencePageId: child.confluencePageId,
43
+ confluenceUrl: child.confluenceUrl,
44
+ title: child.title
45
+ };
46
+ }
47
+ }
48
+ function makePageState(p) {
49
+ return {
50
+ confluencePageId: p.pageId,
51
+ environment: p.environment,
52
+ title: p.title,
53
+ confluenceUrl: p.url,
54
+ sourceFile: p.sourceFile,
55
+ publishedAt: p.publishedAt,
56
+ maintainedIn: p.maintainedIn,
57
+ ...p.isFolder ? { isFolder: true } : {}
58
+ };
59
+ }
60
+ //#endregion
61
+ export { findLockedRef, loadPublishState, makePageState, savePublishState };
@@ -0,0 +1,50 @@
1
+ import { ansi, log } from "./logger.js";
2
+ //#region src/services/log/init-printer.ts
3
+ function printInitSummary(created, skipped, warnings, docRelPath) {
4
+ log.startProcess("Initialisation de codoc");
5
+ if (created.length) {
6
+ log.info0("Fichiers générés :");
7
+ for (const f of created) log.success2(`[CREATED] ${f}`);
8
+ }
9
+ if (skipped.length) {
10
+ log.blank();
11
+ log.info0("Déjà présents (non modifiés) :");
12
+ for (const f of skipped) log.info2(`[UNCHANGED] ${f}`);
13
+ }
14
+ if (warnings.length) {
15
+ log.blank();
16
+ log.warning0("À regarder :");
17
+ for (const w of warnings) log.warning2(w);
18
+ }
19
+ log.blank();
20
+ log.info0(ansi.bold("Prochaines étapes :"));
21
+ log.blank();
22
+ log.raw(` ${ansi.bold("1.")} Remplis ${ansi.cyan("codoc.yaml")}`);
23
+ log.raw(" • atlassian.environments.<key>.baseUrl + spaceKey + defaultParentPageId");
24
+ log.raw(" • gitlab.baseUrl / jira.serverId+server (optionnels - liens code et tickets dans les pages publiées)");
25
+ log.blank();
26
+ log.raw(` ${ansi.bold("2.")} Renseigne tes identifiants dans ${ansi.cyan(".env-codoc")} (déjà ignoré par git si tu as un .gitignore standard)`);
27
+ log.raw(" • CONFLUENCE_<CLÉ>_USERNAME, CONFLUENCE_<CLÉ>_API_TOKEN (préfixées par la clé de l'env, ex. CONFLUENCE_DEFAULT_USERNAME)");
28
+ log.blank();
29
+ log.raw(` ${ansi.bold("3.")} Publie ton premier document :`);
30
+ log.raw(` ${ansi.dim("$")} codoc sync`);
31
+ log.raw(` → publie ${ansi.cyan(docRelPath)} sur Confluence`);
32
+ log.blank();
33
+ log.info0(ansi.dim(`Guide complet généré dans ${docRelPath}`));
34
+ log.blank();
35
+ }
36
+ function printAgentMdSummary(result) {
37
+ log.startProcess("Génération de l'agent-md");
38
+ if (result.created.length) {
39
+ log.info0("Fichier généré :");
40
+ for (const f of result.created) log.success2(`[CREATED] ${f}`);
41
+ }
42
+ if (result.warnings.length) {
43
+ log.blank();
44
+ log.warning0("À regarder :");
45
+ for (const w of result.warnings) log.warning2(w);
46
+ }
47
+ log.blank();
48
+ }
49
+ //#endregion
50
+ export { printAgentMdSummary, printInitSummary };