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.
- package/LICENSE +21 -0
- package/README.md +104 -0
- package/assets/drawio-viewer/README.md +13 -0
- package/assets/drawio-viewer/viewer-static.min.js +8640 -0
- package/bin/dev.cmd +3 -0
- package/bin/dev.js +5 -0
- package/bin/run.cmd +3 -0
- package/bin/run.js +7 -0
- package/dist/clients/confluence/clients/attachments-client.js +74 -0
- package/dist/clients/confluence/clients/folders-client.js +111 -0
- package/dist/clients/confluence/clients/pages-client.js +128 -0
- package/dist/clients/confluence/confluence-client.js +44 -0
- package/dist/clients/confluence/utils/confluence-url.js +25 -0
- package/dist/clients/confluence/utils/confluence-util.js +20 -0
- package/dist/commands/init.js +29 -0
- package/dist/commands/publish.js +34 -0
- package/dist/commands/pull.js +54 -0
- package/dist/commands/sync.js +27 -0
- package/dist/commands/tree.js +17 -0
- package/dist/config/codoc-config-atlassian.js +89 -0
- package/dist/config/codoc-config-raw.js +21 -0
- package/dist/config/codoc-config.js +50 -0
- package/dist/config/codoc-paths.js +9 -0
- package/dist/hooks/command_not_found.js +7 -0
- package/dist/hooks/init/check-for-update.js +68 -0
- package/dist/hooks/init/load-env.js +15 -0
- package/dist/index.js +2 -0
- package/dist/services/codoc-id.js +7 -0
- package/dist/services/confluence/attachments.js +56 -0
- package/dist/services/confluence/folders.js +42 -0
- package/dist/services/confluence/labels.js +17 -0
- package/dist/services/confluence/pages.js +28 -0
- package/dist/services/conversion/confluenceToMarkdown/diagrams/drawio-diagrams.js +77 -0
- package/dist/services/conversion/confluenceToMarkdown/diagrams/drawio-to-image.js +131 -0
- package/dist/services/conversion/confluenceToMarkdown/index.js +17 -0
- package/dist/services/conversion/confluenceToMarkdown/preprocess/macro-converters.js +68 -0
- package/dist/services/conversion/confluenceToMarkdown/preprocess/preprocess.js +113 -0
- package/dist/services/conversion/confluenceToMarkdown/turndown.js +71 -0
- package/dist/services/conversion/markdownToConfluence/conversion-state.js +18 -0
- package/dist/services/conversion/markdownToConfluence/diagrams/mermaid-diagrams.js +56 -0
- package/dist/services/conversion/markdownToConfluence/diagrams/mermaid-to-drawio.js +331 -0
- package/dist/services/conversion/markdownToConfluence/index.js +48 -0
- package/dist/services/conversion/markdownToConfluence/languages.js +66 -0
- package/dist/services/conversion/markdownToConfluence/links.js +40 -0
- package/dist/services/conversion/markdownToConfluence/raw-html/raw-html.js +86 -0
- package/dist/services/conversion/markdownToConfluence/raw-html/task-lists.js +59 -0
- package/dist/services/conversion/markdownToConfluence/render/blocks.js +171 -0
- package/dist/services/conversion/markdownToConfluence/render/inline.js +44 -0
- package/dist/services/conversion/markdownToConfluence/render/page.js +31 -0
- package/dist/services/conversion/shared/confluence-macro-builder.js +11 -0
- package/dist/services/conversion/shared/gitlab-url.js +16 -0
- package/dist/services/conversion/shared/image-attachments.js +57 -0
- package/dist/services/conversion/shared/jira.js +7 -0
- package/dist/services/conversion/shared/macro-types.js +12 -0
- package/dist/services/conversion/shared/preserved-macros.js +8 -0
- package/dist/services/conversion/shared/read-storage-format.js +29 -0
- package/dist/services/conversion/shared/regex-cache.js +14 -0
- package/dist/services/conversion/shared/xml-escaping.js +19 -0
- package/dist/services/ensure-env.js +59 -0
- package/dist/services/files-service.js +74 -0
- package/dist/services/git/default-branch.js +33 -0
- package/dist/services/init-templates/agent-md-init.js +55 -0
- package/dist/services/init-templates/doc-guide-init.js +131 -0
- package/dist/services/init-templates/env-init.js +12 -0
- package/dist/services/init-templates/yaml-init.js +56 -0
- package/dist/services/lock/lock-file.js +61 -0
- package/dist/services/log/init-printer.js +50 -0
- package/dist/services/log/logger.js +131 -0
- package/dist/services/prompt.js +32 -0
- package/dist/services/slugify.js +6 -0
- package/dist/types/codoc-types.js +1 -0
- package/dist/use-cases/init/init.js +150 -0
- package/dist/use-cases/publish/publish.js +166 -0
- package/dist/use-cases/pull/parse-page-input.js +22 -0
- package/dist/use-cases/pull/pull.js +337 -0
- package/dist/use-cases/shared/codoc-yaml.js +45 -0
- package/dist/use-cases/shared/confluence-client-registry.js +27 -0
- package/dist/use-cases/shared/env-select.js +23 -0
- package/dist/use-cases/shared/fetch-page.js +11 -0
- package/dist/use-cases/shared/list-documents.js +172 -0
- package/dist/use-cases/shared/pull-folder.js +90 -0
- package/dist/use-cases/shared/render-remote-page.js +71 -0
- package/dist/use-cases/sync/attachment-upload.js +31 -0
- package/dist/use-cases/sync/sync-actions.js +170 -0
- package/dist/use-cases/sync/sync-associate.js +41 -0
- package/dist/use-cases/sync/sync-entries.js +36 -0
- package/dist/use-cases/sync/sync-handlers.js +140 -0
- package/dist/use-cases/sync/sync-helpers.js +8 -0
- package/dist/use-cases/sync/sync.js +94 -0
- package/dist/use-cases/tree/docs-tree.js +141 -0
- package/oclif.manifest.json +255 -0
- 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, "&").replace(/</g, "<").replace(/>/g, ">");
|
|
5
|
+
}
|
|
6
|
+
/** Échappe une valeur d'attribut XML : `&` `<` `>` `"`. */
|
|
7
|
+
function escapeAttr(text) {
|
|
8
|
+
return escapeXml(text).replace(/"/g, """);
|
|
9
|
+
}
|
|
10
|
+
/** Inverse exact d'{@link escapeXml} : `<` `>` `&`. */
|
|
11
|
+
function unescapeXml(text) {
|
|
12
|
+
return text.replace(/</g, "<").replace(/>/g, ">").replace(/&/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 };
|