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
package/bin/dev.cmd ADDED
@@ -0,0 +1,3 @@
1
+ @echo off
2
+
3
+ node --loader ts-node/esm --no-warnings=ExperimentalWarning "%~dp0\dev" %*
package/bin/dev.js ADDED
@@ -0,0 +1,5 @@
1
+ #!/usr/bin/env -S node --loader ts-node/esm --disable-warning=ExperimentalWarning
2
+
3
+ import {execute} from '@oclif/core'
4
+
5
+ await execute({development: true, dir: import.meta.url})
package/bin/run.cmd ADDED
@@ -0,0 +1,3 @@
1
+ @echo off
2
+
3
+ node "%~dp0\run" %*
package/bin/run.js ADDED
@@ -0,0 +1,7 @@
1
+ #!/usr/bin/env node
2
+
3
+ import {enableCompileCache} from 'node:module'
4
+ enableCompileCache()
5
+
6
+ const {execute} = await import('@oclif/core')
7
+ await execute({dir: import.meta.url})
@@ -0,0 +1,74 @@
1
+ import { wikiLink } from "../utils/confluence-url.js";
2
+ import ky from "ky";
3
+ //#region src/clients/confluence/clients/attachments-client.ts
4
+ var ConfluenceAttachmentsClient = class {
5
+ http;
6
+ constructor(http) {
7
+ this.http = http;
8
+ }
9
+ /**
10
+ * Recherche une pièce jointe par son nom.
11
+ */
12
+ async find(pageId, filename) {
13
+ const hit = (await this.http.v1.get(`content/${pageId}/child/attachment`, { searchParams: {
14
+ filename,
15
+ expand: "version"
16
+ } }).json()).results?.[0];
17
+ if (!hit) return;
18
+ if (!hit._links?.download) return;
19
+ return {
20
+ id: String(hit.id),
21
+ filename,
22
+ downloadPath: hit._links.download
23
+ };
24
+ }
25
+ /**
26
+ * Télécharge directement via _links.download (API v1).
27
+ */
28
+ async downloadV1(downloadPath) {
29
+ return this.http.v1.get(wikiLink(this.http.baseUrl, downloadPath), {
30
+ prefixUrl: "",
31
+ headers: { Accept: "*/*" }
32
+ }).arrayBuffer();
33
+ }
34
+ /**
35
+ * Retourne le downloadLink signé (API v2).
36
+ */
37
+ async getDownloadLink(attachmentId) {
38
+ return (await this.http.v2.get(`attachments/${attachmentId}`).json()).downloadLink;
39
+ }
40
+ /**
41
+ * Télécharge un attachment via un downloadLink v2 (absolu OU relatif au wiki).
42
+ * Le lien étant déjà pré-signé, on n'envoie pas d'en-tête d'authentification.
43
+ */
44
+ async downloadV2(signedUrl) {
45
+ const url = signedUrl.startsWith("http") ? signedUrl : wikiLink(this.http.baseUrl, signedUrl);
46
+ return ky.get(url, { headers: { Accept: "*/*" } }).arrayBuffer();
47
+ }
48
+ /**
49
+ * Upload ou remplace
50
+ * une pièce jointe.
51
+ */
52
+ async upload(pageId, filename, blob) {
53
+ const existing = await this.find(pageId, filename);
54
+ const form = new FormData();
55
+ form.append("file", blob, filename);
56
+ const endpoint = existing ? `content/${pageId}/child/attachment/${existing.id}/data` : `content/${pageId}/child/attachment`;
57
+ await this.http.v1.post(endpoint, {
58
+ body: form,
59
+ headers: { "X-Atlassian-Token": "no-check" }
60
+ });
61
+ }
62
+ /**
63
+ * Télécharge directement
64
+ * le texte d'un attachment.
65
+ */
66
+ async downloadTextV1(downloadPath) {
67
+ return this.http.v1.get(wikiLink(this.http.baseUrl, downloadPath), {
68
+ prefixUrl: "",
69
+ headers: { Accept: "*/*" }
70
+ }).text();
71
+ }
72
+ };
73
+ //#endregion
74
+ export { ConfluenceAttachmentsClient };
@@ -0,0 +1,111 @@
1
+ import { titleVariants } from "../utils/confluence-url.js";
2
+ //#region src/clients/confluence/clients/folders-client.ts
3
+ /** Extrait le curseur de pagination de `_links.next` (API v2, pagination par curseur). */
4
+ function extractCursor(nextLink) {
5
+ if (!nextLink) return void 0;
6
+ const query = nextLink.split("?")[1];
7
+ if (!query) return void 0;
8
+ return new URLSearchParams(query).get("cursor") ?? void 0;
9
+ }
10
+ /** Échappe une valeur pour l'insérer dans une clause CQL entre guillemets doubles. */
11
+ function escapeCql(value) {
12
+ return value.replace(/\\/g, "\\\\").replace(/"/g, "\\\"");
13
+ }
14
+ var ConfluenceFoldersClient = class {
15
+ http;
16
+ constructor(http) {
17
+ this.http = http;
18
+ }
19
+ async findByTitleUnderParent(parentId, title) {
20
+ for (const variant of titleVariants(title)) {
21
+ const cql = `type = folder and parent = ${parentId} and title = "${escapeCql(variant)}"`;
22
+ const results = (await this.http.v1.get("search", { searchParams: {
23
+ cql,
24
+ limit: 2
25
+ } }).json()).results ?? [];
26
+ if (results.length === 1) return {
27
+ id: String(results[0].content.id),
28
+ title: results[0].content.title
29
+ };
30
+ }
31
+ }
32
+ async childrenFolders(parentId) {
33
+ const results = [];
34
+ const cql = `type = folder and parent = ${parentId}`;
35
+ const limit = 100;
36
+ let start = 0;
37
+ for (let page = 0; page < 1e3; page++) {
38
+ const batch = (await this.http.v1.get("search", { searchParams: {
39
+ cql,
40
+ start,
41
+ limit
42
+ } }).json()).results ?? [];
43
+ results.push(...batch.map((r) => ({
44
+ id: String(r.content.id),
45
+ title: r.content.title
46
+ })));
47
+ if (batch.length < limit) break;
48
+ start += limit;
49
+ }
50
+ return results;
51
+ }
52
+ async exists(id) {
53
+ try {
54
+ await this.http.v2.get(`folders/${id}`);
55
+ return true;
56
+ } catch {}
57
+ return false;
58
+ }
59
+ async get(folderId) {
60
+ try {
61
+ const data = await this.http.v2.get(`folders/${folderId}`).json();
62
+ return {
63
+ id: String(data.id),
64
+ title: data.title,
65
+ parentId: data.parentId ? String(data.parentId) : void 0
66
+ };
67
+ } catch {
68
+ return;
69
+ }
70
+ }
71
+ /** Suit la pagination par curseur de l'API v2 - sinon les dossiers de plus de 250 enfants étaient tronqués silencieusement. */
72
+ async children(folderId) {
73
+ const results = [];
74
+ let cursor;
75
+ for (let page = 0; page < 1e3; page++) {
76
+ const data = await this.http.v2.get(`folders/${folderId}/direct-children`, { searchParams: cursor ? {
77
+ limit: 250,
78
+ cursor
79
+ } : { limit: 250 } }).json();
80
+ results.push(...(data.results ?? []).map((child) => ({
81
+ id: String(child.id),
82
+ title: child.title,
83
+ type: child.type
84
+ })));
85
+ cursor = extractCursor(data._links?.next);
86
+ if (!cursor) break;
87
+ }
88
+ return results;
89
+ }
90
+ async delete(folderId) {
91
+ await this.http.v2.delete(`folders/${folderId}`);
92
+ }
93
+ async create(title, spaceId, parentId) {
94
+ const payload = {
95
+ title,
96
+ spaceId
97
+ };
98
+ if (parentId) payload.parentId = String(parentId);
99
+ return this.http.v2.post("folders", { json: payload }).json();
100
+ }
101
+ async getSpaceId(spaceKey) {
102
+ const id = (await this.http.v2.get("spaces", { searchParams: {
103
+ keys: spaceKey,
104
+ limit: 1
105
+ } }).json()).results?.[0]?.id;
106
+ if (!id) throw new Error(`Space "${spaceKey}" introuvable.`);
107
+ return String(id);
108
+ }
109
+ };
110
+ //#endregion
111
+ export { ConfluenceFoldersClient };
@@ -0,0 +1,128 @@
1
+ import { titleVariants } from "../utils/confluence-url.js";
2
+ //#region src/clients/confluence/clients/pages-client.ts
3
+ var ConfluencePagesClient = class {
4
+ http;
5
+ configuration;
6
+ constructor(http, configuration) {
7
+ this.http = http;
8
+ this.configuration = configuration;
9
+ }
10
+ async exists(id) {
11
+ try {
12
+ await this.http.v1.get(`content/${id}`).json();
13
+ return true;
14
+ } catch {
15
+ return false;
16
+ }
17
+ }
18
+ async testConnection() {
19
+ try {
20
+ await this.http.v1.get("content", { searchParams: { limit: 1 } });
21
+ return true;
22
+ } catch {
23
+ return false;
24
+ }
25
+ }
26
+ async fetchPage(pageId) {
27
+ const data = await this.http.v1.get(`content/${pageId}`, { searchParams: { expand: "body.storage,ancestors" } }).json();
28
+ const storageXml = data.body?.storage?.value ?? "";
29
+ const parent = data.ancestors?.[data.ancestors.length - 1];
30
+ return {
31
+ id: String(data.id),
32
+ title: data.title,
33
+ storageXml,
34
+ parentId: parent?.id ? parent.id : void 0,
35
+ hasImages: /ri:attachment/i.test(storageXml) || /ri:url/i.test(storageXml)
36
+ };
37
+ }
38
+ async fetchAncestors(pageId) {
39
+ const data = await this.http.v1.get(`content/${pageId}`, { searchParams: { expand: "ancestors" } }).json();
40
+ return [...(data.ancestors ?? []).map((a) => ({
41
+ id: String(a.id),
42
+ title: a.title ?? String(a.id)
43
+ })), {
44
+ id: String(data.id),
45
+ title: data.title
46
+ }];
47
+ }
48
+ /** Suit la pagination `start`/`limit` de l'API v1 - sinon les dossiers de plus de 200 pages étaient tronqués silencieusement. */
49
+ async fetchChildren(pageId) {
50
+ const results = [];
51
+ const limit = 200;
52
+ let start = 0;
53
+ for (let page = 0; page < 1e3; page++) {
54
+ const batch = (await this.http.v1.get(`content/${pageId}/child/page`, { searchParams: {
55
+ start,
56
+ limit,
57
+ expand: "version"
58
+ } }).json()).results ?? [];
59
+ results.push(...batch.map((p) => ({
60
+ id: String(p.id),
61
+ title: p.title
62
+ })));
63
+ if (batch.length < limit) break;
64
+ start += limit;
65
+ }
66
+ return results;
67
+ }
68
+ async findByTitle(title) {
69
+ for (const variant of titleVariants(title)) {
70
+ const data = await this.http.v1.get("content", { searchParams: {
71
+ title: variant,
72
+ spaceKey: this.configuration.spaceKey,
73
+ expand: "version"
74
+ } }).json();
75
+ if (data.results?.[0]) return data.results[0];
76
+ }
77
+ }
78
+ async getVersion(pageId) {
79
+ return this.http.v1.get(`content/${pageId}`, { searchParams: { expand: "version" } }).json();
80
+ }
81
+ async delete(pageId) {
82
+ await this.http.v1.delete(`content/${pageId}`);
83
+ }
84
+ /** Ajoute des labels à une page (n'écrase ni ne retire les existants - API additive). */
85
+ async addLabels(pageId, labels) {
86
+ if (!labels.length) return;
87
+ await this.http.v1.post(`content/${pageId}/label`, { json: labels.map((name) => ({
88
+ prefix: "global",
89
+ name
90
+ })) });
91
+ }
92
+ async create(title, storageContent, parentId) {
93
+ const effectiveParent = parentId ?? this.configuration.defaultParentPageId;
94
+ const payload = {
95
+ type: "page",
96
+ title,
97
+ space: { key: this.configuration.spaceKey },
98
+ body: { storage: {
99
+ value: storageContent,
100
+ representation: "storage"
101
+ } }
102
+ };
103
+ if (effectiveParent) payload.ancestors = [{ id: String(effectiveParent) }];
104
+ return this.http.v1.post("content", { json: payload }).json();
105
+ }
106
+ async update(pageId, title, storageContent, parentId) {
107
+ const version = await this.getVersion(pageId);
108
+ const effectiveParent = parentId ?? this.configuration.defaultParentPageId;
109
+ const payload = {
110
+ id: pageId,
111
+ type: "page",
112
+ title,
113
+ version: { number: version.version.number + 1 },
114
+ body: { storage: {
115
+ value: storageContent,
116
+ representation: "storage"
117
+ } }
118
+ };
119
+ if (effectiveParent) payload.ancestors = [{ id: String(effectiveParent) }];
120
+ return this.http.v1.put(`content/${pageId}`, { json: payload }).json();
121
+ }
122
+ async updateContent(pageId, storageContent) {
123
+ const page = await this.getVersion(pageId);
124
+ return this.update(pageId, page.title, storageContent);
125
+ }
126
+ };
127
+ //#endregion
128
+ export { ConfluencePagesClient };
@@ -0,0 +1,44 @@
1
+ import { wikiBase } from "./utils/confluence-url.js";
2
+ import { ConfluenceAttachmentsClient } from "./clients/attachments-client.js";
3
+ import { ConfluenceFoldersClient } from "./clients/folders-client.js";
4
+ import { ConfluencePagesClient } from "./clients/pages-client.js";
5
+ import ky from "ky";
6
+ //#region src/clients/confluence/confluence-client.ts
7
+ function createConfluenceClient(configuration) {
8
+ const http = createConfluenceHttp(configuration);
9
+ const attachments = new ConfluenceAttachmentsClient(http);
10
+ return {
11
+ pages: new ConfluencePagesClient(http, configuration),
12
+ folders: new ConfluenceFoldersClient(http),
13
+ attachments,
14
+ configuration,
15
+ foldersV2Unavailable: false
16
+ };
17
+ }
18
+ function basicAuth(username, apiToken) {
19
+ return `Basic ${Buffer.from(`${username}:${apiToken}`).toString("base64")}`;
20
+ }
21
+ const CONFLUENCE_TIMEOUT_MS = 6e4;
22
+ function createConfluenceHttp(conf) {
23
+ return {
24
+ baseUrl: conf.baseUrl,
25
+ v1: ky.create({
26
+ prefixUrl: `${wikiBase(conf.baseUrl)}/rest/api`,
27
+ timeout: CONFLUENCE_TIMEOUT_MS,
28
+ headers: {
29
+ Authorization: basicAuth(conf.username, conf.apiToken),
30
+ Accept: "application/json"
31
+ }
32
+ }),
33
+ v2: ky.create({
34
+ prefixUrl: `${wikiBase(conf.baseUrl)}/api/v2`,
35
+ timeout: CONFLUENCE_TIMEOUT_MS,
36
+ headers: {
37
+ Authorization: basicAuth(conf.username, conf.apiToken),
38
+ Accept: "application/json"
39
+ }
40
+ })
41
+ };
42
+ }
43
+ //#endregion
44
+ export { createConfluenceClient, createConfluenceHttp };
@@ -0,0 +1,25 @@
1
+ //#region src/clients/confluence/utils/confluence-url.ts
2
+ function wikiBase(baseUrl) {
3
+ return `${baseUrl.replace(/\/$/, "")}/wiki`;
4
+ }
5
+ function wikiLink(baseUrl, link) {
6
+ if (link.startsWith("http")) return link;
7
+ const base = baseUrl.replace(/\/$/, "");
8
+ return link.startsWith("/wiki") ? `${base}${link}` : `${base}/wiki${link}`;
9
+ }
10
+ function pageUrl(baseUrl, spaceKey, pageId) {
11
+ return `${wikiBase(baseUrl)}/spaces/${spaceKey}/pages/${pageId}`;
12
+ }
13
+ /**
14
+ * Variantes d'un titre à essayer lors d'une recherche : Confluence peut stocker
15
+ * une apostrophe droite (') ou typographique (’) selon la source.
16
+ */
17
+ function titleVariants(title) {
18
+ return [.../* @__PURE__ */ new Set([
19
+ title,
20
+ title.replace(/'/g, "’"),
21
+ title.replace(/’/g, "'")
22
+ ])];
23
+ }
24
+ //#endregion
25
+ export { pageUrl, titleVariants, wikiBase, wikiLink };
@@ -0,0 +1,20 @@
1
+ //#region src/clients/confluence/utils/confluence-util.ts
2
+ /** Extrait un libellé d'erreur lisible (statut HTTP s'il existe, sinon message). */
3
+ async function describeError(err) {
4
+ const e = err;
5
+ if (e?.response) {
6
+ const status = e.response.status;
7
+ let body = "";
8
+ try {
9
+ body = (await e.response.text()).slice(0, 120);
10
+ } catch {}
11
+ return `HTTP ${status}${body ? ` - ${body.replace(/\s+/g, " ").trim()}` : ""}`;
12
+ }
13
+ return e?.message ?? e?.name ?? String(err);
14
+ }
15
+ /** Type-guard : vrai si le téléchargement a réussi. */
16
+ function isAttachmentDownload(r) {
17
+ return "data" in r;
18
+ }
19
+ //#endregion
20
+ export { describeError, isAttachmentDownload };
@@ -0,0 +1,29 @@
1
+ import { initAgentMdOnly, initCodoc } from "../use-cases/init/init.js";
2
+ import { printAgentMdSummary, printInitSummary } from "../services/log/init-printer.js";
3
+ import { Command, Flags } from "@oclif/core";
4
+ //#region src/commands/init.ts
5
+ var Init = class Init extends Command {
6
+ static description = "Initialise la configuration codoc : codoc.yaml, .env-codoc, guide de démarrage. Avec --agent-md, ne génère que le guide agent IA (rien d'autre).";
7
+ static examples = ["<%= config.bin %> <%= command.id %>", "<%= config.bin %> <%= command.id %> --agent-md"];
8
+ static flags = {
9
+ "agent-md": Flags.boolean({
10
+ default: false,
11
+ description: "Ne génère QUE le guide agent IA basé sur `codoc.yaml` (qui doit déjà exister et être rempli) - au choix (via --agent-target, sinon celui déjà en place, sinon demandé) dans `.github/agents/codoc-agent.md` (fichier dédié) ou injecté en bloc dans `.github/copilot-instructions.md`. N'affecte aucun autre fichier."
12
+ }),
13
+ "agent-target": Flags.string({
14
+ options: ["agent", "copilot"],
15
+ description: "Avec --agent-md : destination du guide (fichier dédié ou bloc copilot-instructions.md). Par défaut : celle déjà en place, sinon demandé."
16
+ })
17
+ };
18
+ async run() {
19
+ const { flags } = await this.parse(Init);
20
+ if (flags["agent-md"]) {
21
+ printAgentMdSummary(await initAgentMdOnly(flags["agent-target"]));
22
+ return;
23
+ }
24
+ const { docRelPath, created, skipped, warnings } = initCodoc();
25
+ printInitSummary(created, skipped, warnings, docRelPath);
26
+ }
27
+ };
28
+ //#endregion
29
+ export { Init as default };
@@ -0,0 +1,34 @@
1
+ import { confluenceEnvRequirements } from "../config/codoc-config-atlassian.js";
2
+ import { loadRawConfig } from "../config/codoc-config-raw.js";
3
+ import { ensureEnvVars } from "../services/ensure-env.js";
4
+ import { publish } from "../use-cases/publish/publish.js";
5
+ import { Args, Command, Flags } from "@oclif/core";
6
+ //#region src/commands/publish.ts
7
+ var Publish = class Publish extends Command {
8
+ static description = "Publie une doc locale (fichier ou dossier) vers Confluence, avec ajout optionnel à codoc.yaml. Toute information non fournie en flag (ni déjà présente dans codoc.yaml) est demandée en console.";
9
+ static args = { path: Args.string({
10
+ description: "Chemin local relatif de la doc .md ou du dossier à publier",
11
+ required: false
12
+ }) };
13
+ static flags = {
14
+ env: Flags.string({ description: "Environnement Confluence cible (clé). Par défaut : celui de la config existante, sinon auto/prompt." }),
15
+ "in-config": Flags.boolean({
16
+ allowNo: true,
17
+ description: "Ajoute (ou met à jour) l'entrée codoc.yaml de cette doc. --no-in-config publie sans toucher ni à codoc.yaml ni à codoc.lock (publication ponctuelle, non suivie par `codoc sync`). Non fourni : demande en fin de commande."
18
+ }),
19
+ "parent-page-id": Flags.string({ description: "parentPageId Confluence cible. Par défaut : celui de la config existante ou defaultParentPageId de l’env." }),
20
+ title: Flags.string({ description: "Titre de la page Confluence (fichier unique uniquement). Par défaut : celui de la config existante, sinon le H1 du fichier." })
21
+ };
22
+ async run() {
23
+ const { args, flags } = await this.parse(Publish);
24
+ await ensureEnvVars(confluenceEnvRequirements(loadRawConfig().atlassian?.environments));
25
+ await publish(args.path ?? "", {
26
+ env: flags.env,
27
+ inConfig: flags["in-config"],
28
+ parentPageId: flags["parent-page-id"],
29
+ title: flags.title
30
+ });
31
+ }
32
+ };
33
+ //#endregion
34
+ export { Publish as default };
@@ -0,0 +1,54 @@
1
+ import { confluenceEnvRequirements } from "../config/codoc-config-atlassian.js";
2
+ import { loadRawConfig } from "../config/codoc-config-raw.js";
3
+ import { ensureEnvVars } from "../services/ensure-env.js";
4
+ import { pull } from "../use-cases/pull/pull.js";
5
+ import { Args, Command, Flags } from "@oclif/core";
6
+ //#region src/commands/pull.ts
7
+ var Pull = class Pull extends Command {
8
+ static description = "Importe une page Confluence en local, avec ajout optionnel à codoc.yaml. Toute information non fournie en flag (ni déjà présente dans codoc.yaml) est demandée en console.";
9
+ static args = { page: Args.string({
10
+ description: "URL ou ID de la page Confluence à importer",
11
+ required: false
12
+ }) };
13
+ static flags = {
14
+ env: Flags.string({ description: "Environnement Confluence où chercher la page. Par défaut : déduit de l’URL, sinon auto/prompt." }),
15
+ "as-folder": Flags.boolean({
16
+ allowNo: true,
17
+ description: "Si la page a des sous-pages/sous-dossiers, importe tout le dossier (--as-folder) ou seulement la page (--no-as-folder). Sans sous-élément, ignoré. Non fourni : demande le cas échéant."
18
+ }),
19
+ "keep-existing": Flags.boolean({
20
+ allowNo: true,
21
+ description: "Si un import précédent est détecté (même ID/titre/config), le remplace (--keep-existing, valeurs existantes comme défauts) ou crée un import séparé (--no-keep-existing). Non fourni : demande le cas échéant."
22
+ }),
23
+ "in-config": Flags.boolean({
24
+ allowNo: true,
25
+ description: "Ajoute (ou met à jour) l'entrée codoc.yaml de cette doc. --no-in-config importe sans toucher ni à codoc.yaml ni à codoc.lock (import ponctuel, non suivi par `codoc sync`). Non fourni : demande en fin de commande."
26
+ }),
27
+ "local-path": Flags.string({ description: "Chemin local du fichier .md (page unique) ou du dossier de destination (import dossier)." }),
28
+ title: Flags.string({ description: "Titre de la page Confluence (page unique). Par défaut : celui de la config existante, sinon le titre Confluence." }),
29
+ "parent-page-id": Flags.string({ description: "parentPageId Confluence. Par défaut : celui de la config existante, sinon le parent réel de la page." }),
30
+ "maintained-in": Flags.string({
31
+ options: ["code", "confluence"],
32
+ description: "code → le .md local fait foi ; confluence → la page Confluence fait foi. Par défaut : confluence."
33
+ }),
34
+ "images-dir": Flags.string({ description: "Dossier local pour les images (\"\" pour désactiver). Par défaut : doc/img." })
35
+ };
36
+ async run() {
37
+ const { args, flags } = await this.parse(Pull);
38
+ await ensureEnvVars(confluenceEnvRequirements(loadRawConfig().atlassian?.environments));
39
+ await pull({
40
+ page: args.page,
41
+ env: flags.env,
42
+ asFolder: flags["as-folder"],
43
+ keepExisting: flags["keep-existing"],
44
+ inConfig: flags["in-config"],
45
+ localPath: flags["local-path"],
46
+ title: flags.title,
47
+ parentPageId: flags["parent-page-id"],
48
+ maintainedIn: flags["maintained-in"],
49
+ imagesDir: flags["images-dir"]
50
+ });
51
+ }
52
+ };
53
+ //#endregion
54
+ export { Pull as default };
@@ -0,0 +1,27 @@
1
+ import { confluenceEnvRequirements } from "../config/codoc-config-atlassian.js";
2
+ import { loadRawConfig } from "../config/codoc-config-raw.js";
3
+ import { ensureEnvVars } from "../services/ensure-env.js";
4
+ import { sync } from "../use-cases/sync/sync.js";
5
+ import { Command, Flags } from "@oclif/core";
6
+ //#region src/commands/sync.ts
7
+ var Sync = class Sync extends Command {
8
+ static description = "Synchronise chaque doc locale et Confluence";
9
+ static flags = {
10
+ env: Flags.string({ description: "Ne synchronise que cet environnement Confluence (clé). Par défaut : tous." }),
11
+ confirm: Flags.boolean({
12
+ char: "f",
13
+ allowNo: true,
14
+ description: "Répond automatiquement aux suppressions de pages et adoptions de page en conflit de titre : --confirm accepte tout (CI), --no-confirm refuse tout (rien de risqué ne se fait). Non fourni : demande à chaque cas, comme en local."
15
+ })
16
+ };
17
+ async run() {
18
+ const { flags } = await this.parse(Sync);
19
+ await ensureEnvVars(confluenceEnvRequirements(loadRawConfig().atlassian?.environments));
20
+ await sync({
21
+ env: flags.env,
22
+ confirm: flags.confirm
23
+ });
24
+ }
25
+ };
26
+ //#endregion
27
+ export { Sync as default };
@@ -0,0 +1,17 @@
1
+ import { confluenceEnvRequirements } from "../config/codoc-config-atlassian.js";
2
+ import { loadRawConfig } from "../config/codoc-config-raw.js";
3
+ import { ensureEnvVars } from "../services/ensure-env.js";
4
+ import { printDocsTree } from "../use-cases/tree/docs-tree.js";
5
+ import { Command, Flags } from "@oclif/core";
6
+ //#region src/commands/tree.ts
7
+ var Tree = class Tree extends Command {
8
+ static description = "Affiche l’arborescence des documents";
9
+ static flags = { env: Flags.string({ description: "N’affiche que cet environnement Confluence (clé). Par défaut : tous." }) };
10
+ async run() {
11
+ const { flags } = await this.parse(Tree);
12
+ await ensureEnvVars(confluenceEnvRequirements(loadRawConfig().atlassian?.environments));
13
+ await printDocsTree({ env: flags.env });
14
+ }
15
+ };
16
+ //#endregion
17
+ export { Tree as default };