@floh-solutions/pharos-cli 0.4.0 → 0.13.0

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 (67) hide show
  1. package/dist/cli.d.ts.map +1 -1
  2. package/dist/cli.js +418 -7
  3. package/dist/cli.js.map +1 -1
  4. package/dist/commands/attach.d.ts +30 -0
  5. package/dist/commands/attach.d.ts.map +1 -0
  6. package/dist/commands/attach.js +55 -0
  7. package/dist/commands/attach.js.map +1 -0
  8. package/dist/commands/create.d.ts +35 -0
  9. package/dist/commands/create.d.ts.map +1 -0
  10. package/dist/commands/create.js +82 -0
  11. package/dist/commands/create.js.map +1 -0
  12. package/dist/commands/detach.d.ts +34 -0
  13. package/dist/commands/detach.d.ts.map +1 -0
  14. package/dist/commands/detach.js +117 -0
  15. package/dist/commands/detach.js.map +1 -0
  16. package/dist/commands/discover.d.ts +66 -0
  17. package/dist/commands/discover.d.ts.map +1 -0
  18. package/dist/commands/discover.js +128 -0
  19. package/dist/commands/discover.js.map +1 -0
  20. package/dist/commands/hooks.d.ts +24 -0
  21. package/dist/commands/hooks.d.ts.map +1 -0
  22. package/dist/commands/hooks.js +184 -0
  23. package/dist/commands/hooks.js.map +1 -0
  24. package/dist/commands/image.d.ts +44 -0
  25. package/dist/commands/image.d.ts.map +1 -0
  26. package/dist/commands/image.js +114 -0
  27. package/dist/commands/image.js.map +1 -0
  28. package/dist/commands/import.d.ts +51 -0
  29. package/dist/commands/import.d.ts.map +1 -0
  30. package/dist/commands/import.js +190 -0
  31. package/dist/commands/import.js.map +1 -0
  32. package/dist/commands/link.d.ts +40 -0
  33. package/dist/commands/link.d.ts.map +1 -0
  34. package/dist/commands/link.js +150 -0
  35. package/dist/commands/link.js.map +1 -0
  36. package/dist/commands/people.d.ts +39 -0
  37. package/dist/commands/people.d.ts.map +1 -0
  38. package/dist/commands/people.js +64 -0
  39. package/dist/commands/people.js.map +1 -0
  40. package/dist/commands/query.d.ts +43 -0
  41. package/dist/commands/query.d.ts.map +1 -0
  42. package/dist/commands/query.js +202 -0
  43. package/dist/commands/query.js.map +1 -0
  44. package/dist/commands/recycle.d.ts +10 -0
  45. package/dist/commands/recycle.d.ts.map +1 -0
  46. package/dist/commands/recycle.js +118 -0
  47. package/dist/commands/recycle.js.map +1 -0
  48. package/dist/commands/task.d.ts +13 -0
  49. package/dist/commands/task.d.ts.map +1 -1
  50. package/dist/commands/task.js +106 -4
  51. package/dist/commands/task.js.map +1 -1
  52. package/dist/commands/update.d.ts +52 -0
  53. package/dist/commands/update.d.ts.map +1 -0
  54. package/dist/commands/update.js +181 -0
  55. package/dist/commands/update.js.map +1 -0
  56. package/dist/commands/wiki.d.ts.map +1 -1
  57. package/dist/commands/wiki.js +133 -2
  58. package/dist/commands/wiki.js.map +1 -1
  59. package/dist/output.d.ts.map +1 -1
  60. package/dist/output.js +32 -2
  61. package/dist/output.js.map +1 -1
  62. package/dist/wiql-build.d.ts +92 -0
  63. package/dist/wiql-build.d.ts.map +1 -0
  64. package/dist/wiql-build.js +147 -0
  65. package/dist/wiql-build.js.map +1 -0
  66. package/package.json +2 -2
  67. package/skill/SKILL.md +388 -62
@@ -0,0 +1,184 @@
1
+ import { requiredSubscriptions, subscriptionStatus, } from "@floh-solutions/ado-core";
2
+ import { emit, usageError } from "../output.js";
3
+ import { approve } from "../session.js";
4
+ export async function runHooks(io, session, positionals, input) {
5
+ const verb = positionals[0] ?? "list";
6
+ switch (verb) {
7
+ case "list":
8
+ return list(io, session);
9
+ case "check":
10
+ return check(io, session, input);
11
+ case "create":
12
+ return create(io, session, input);
13
+ case "repoint":
14
+ return repoint(io, session, input);
15
+ case "delete":
16
+ return remove(io, session, positionals[1]);
17
+ default:
18
+ throw usageError(`Unknown hooks verb "${verb}". Try list, check, create, repoint or delete.`);
19
+ }
20
+ }
21
+ async function list(io, session) {
22
+ const subscriptions = await session.client.hooks.list();
23
+ return emit(io, subscriptions.map(describe));
24
+ }
25
+ /**
26
+ * What is set up, what is not, and what is pointing somewhere else.
27
+ *
28
+ * Read-only, so it needs no confirmation and no write budget — the point is
29
+ * that somebody can run it on a real organisation without consequences.
30
+ */
31
+ async function check(io, session, input) {
32
+ const { statuses, hub } = await assess(session, input);
33
+ const missing = statuses.filter((s) => s.state === "missing");
34
+ const elsewhere = statuses.filter((s) => s.state === "elsewhere");
35
+ return emit(io, {
36
+ hub,
37
+ ok: missing.length === 0 && elsewhere.length === 0,
38
+ subscriptions: statuses.map(summarise),
39
+ // Separated because they need different verbs, and conflating them is how
40
+ // somebody ends up with two subscriptions delivering the same event twice.
41
+ missing: missing.length,
42
+ pointingElsewhere: elsewhere.length,
43
+ ...(missing.length > 0 ? { hint: "pharos hooks create --hub <url> --user <u> --password <p> --yes" } : {}),
44
+ ...(elsewhere.length > 0
45
+ ? { repointHint: "pharos hooks repoint --hub <url> --user <u> --password <p> --yes" }
46
+ : {}),
47
+ });
48
+ }
49
+ async function create(io, session, input) {
50
+ const { statuses, hub, projectId, credentials } = await assess(session, input, {
51
+ needCredentials: true,
52
+ });
53
+ const missing = statuses.filter((s) => s.state === "missing");
54
+ if (missing.length === 0) {
55
+ return emit(io, { applied: false, action: "nothingMissing", hub });
56
+ }
57
+ const stop = approve(io, session, {
58
+ what: `create ${missing.length} service hook subscription(s) pointing at ${hub}`,
59
+ would: {
60
+ action: "createSubscriptions",
61
+ hub,
62
+ subscriptions: missing.map((s) => s.required.label),
63
+ },
64
+ });
65
+ if (stop !== undefined)
66
+ return stop;
67
+ const created = [];
68
+ for (const status of missing) {
69
+ const subscription = await session.client.hooks.create({
70
+ eventType: status.required.eventType,
71
+ projectId,
72
+ url: hub,
73
+ basicAuthUsername: credentials.user,
74
+ basicAuthPassword: credentials.password,
75
+ ...(status.required.repositoryId === undefined
76
+ ? {}
77
+ : { repositoryId: status.required.repositoryId }),
78
+ });
79
+ created.push(subscription.id);
80
+ }
81
+ return emit(io, { applied: true, action: "created", hub, count: created.length, ids: created });
82
+ }
83
+ /**
84
+ * Move existing subscriptions to a new URL.
85
+ *
86
+ * The verb a hostname change needs. Moving a hub means re-pointing every
87
+ * subscription, and there are six — which is also why doing it by hand is where
88
+ * one gets forgotten and realtime half-works.
89
+ */
90
+ async function repoint(io, session, input) {
91
+ const { statuses, hub, credentials } = await assess(session, input, { needCredentials: true });
92
+ const moving = statuses.filter((s) => s.state === "elsewhere" && s.existing !== undefined);
93
+ if (moving.length === 0) {
94
+ return emit(io, { applied: false, action: "nothingToRepoint", hub });
95
+ }
96
+ const stop = approve(io, session, {
97
+ what: `re-point ${moving.length} subscription(s) to ${hub}`,
98
+ would: {
99
+ action: "repointSubscriptions",
100
+ to: hub,
101
+ subscriptions: moving.map((s) => ({ event: s.required.label, from: s.currentUrl })),
102
+ },
103
+ });
104
+ if (stop !== undefined)
105
+ return stop;
106
+ const moved = [];
107
+ for (const status of moving) {
108
+ // The password is re-sent, never carried over: Azure DevOps returns it
109
+ // masked as `********`, so a read-modify-write would send that literal
110
+ // string and every delivery would 401 while the subscription still read
111
+ // as enabled.
112
+ const updated = await session.client.hooks.repoint(status.existing, hub, credentials.user, credentials.password);
113
+ moved.push(updated.id);
114
+ }
115
+ return emit(io, { applied: true, action: "repointed", hub, count: moved.length, ids: moved });
116
+ }
117
+ async function remove(io, session, id) {
118
+ if (id === undefined || id === "") {
119
+ throw usageError("Which subscription? `pharos hooks delete <id>` — ids come from `hooks list`.");
120
+ }
121
+ const stop = approve(io, session, {
122
+ what: `delete service hook subscription ${id}`,
123
+ would: { action: "deleteSubscription", id },
124
+ });
125
+ if (stop !== undefined)
126
+ return stop;
127
+ await session.client.hooks.remove(id);
128
+ return emit(io, { applied: true, action: "deleted", id });
129
+ }
130
+ // MARK: - Shared assessment
131
+ /**
132
+ * Resolve the project GUID, the wiki's repository GUID, and the current state.
133
+ *
134
+ * The wiki repository GUID is the whole reason this is not a browser job, and
135
+ * it comes from the Wiki API rather than the git one — the repositories
136
+ * endpoint does not list wiki repos at all.
137
+ */
138
+ async function assess(session, input, options = {}) {
139
+ const hub = input.hub ?? "";
140
+ if (hub === "") {
141
+ throw usageError("Which hub? Pass --hub https://your-hub.example/hooks/ado — the URL Azure DevOps should POST to.");
142
+ }
143
+ const credentials = { user: input.user ?? "", password: input.password ?? "" };
144
+ if (options.needCredentials === true && (credentials.user === "" || credentials.password === "")) {
145
+ throw usageError("A subscription needs the hub's webhook credentials — pass --user and --password. They must "
146
+ + "match what the hub is configured with (PHAROS_WEBHOOK_USER / PHAROS_WEBHOOK_PASSWORD), "
147
+ + "or Azure DevOps will deliver and the hub will answer 401.");
148
+ }
149
+ const wiki = await session.wikiForLinking().catch(() => undefined);
150
+ const wikis = await session.wiki.listWikis().catch(() => []);
151
+ const repositoryId = wikis.find((w) => w.name === wiki?.name)?.repositoryId;
152
+ const projectId = wiki?.projectId ?? wikis[0]?.projectId ?? "";
153
+ if (projectId === "") {
154
+ throw usageError("Could not resolve the project GUID — a subscription cannot be scoped without it.");
155
+ }
156
+ const existing = await session.client.hooks.list();
157
+ const statuses = subscriptionStatus(existing, requiredSubscriptions(repositoryId), {
158
+ projectId,
159
+ url: hub,
160
+ });
161
+ return { statuses, hub, projectId, credentials };
162
+ }
163
+ function summarise(status) {
164
+ return {
165
+ event: status.required.eventType,
166
+ what: status.required.label,
167
+ state: status.state,
168
+ ...(status.existing === undefined ? {} : { id: status.existing.id }),
169
+ ...(status.currentUrl === undefined ? {} : { currentUrl: status.currentUrl }),
170
+ };
171
+ }
172
+ function describe(subscription) {
173
+ return {
174
+ id: subscription.id,
175
+ eventType: subscription.eventType,
176
+ status: subscription.status,
177
+ url: subscription.consumerInputs?.["url"],
178
+ projectId: subscription.publisherInputs?.["projectId"],
179
+ // Present only on git.push, and its absence there means `[Any]` — every
180
+ // push in the project, which is the wrong default rather than a detail.
181
+ repository: subscription.publisherInputs?.["repository"],
182
+ };
183
+ }
184
+ //# sourceMappingURL=hooks.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hooks.js","sourceRoot":"","sources":["../../src/commands/hooks.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,44 @@
1
+ import { type ExitCode, type Io } from "../output.js";
2
+ import type { Session } from "../session.js";
3
+ /**
4
+ * `pharos image <file>` and `pharos wiki image <file>` — put a picture INSIDE
5
+ * the text.
6
+ *
7
+ * ## Why this is not `pharos attach`
8
+ *
9
+ * They look like the same verb and they are not, and the difference is the
10
+ * reason this exists.
11
+ *
12
+ * `attach` uploads bytes **and adds an `AttachedFile` relation**, so the file
13
+ * appears in the work item's Attachments list. An inline image has **no
14
+ * relation at all** — measured on #333: two screenshots pasted into a
15
+ * description gave `relations: 0` and `attachments: []`. It is an `![](…)` in
16
+ * the field text and nothing else, which is why the Attachments list is right
17
+ * to show nothing and why every piece of machinery that works from relations
18
+ * cannot see it.
19
+ *
20
+ * So: `attach` for a file somebody will open, `image` for a picture somebody
21
+ * will look at while reading. Using the wrong one is not fatal, but it puts a
22
+ * screenshot in the file list or a spreadsheet in the middle of a sentence.
23
+ *
24
+ * ## Two endpoints, and they are not interchangeable
25
+ *
26
+ * A **work item** image goes to `POST wit/attachments` and comes back with a
27
+ * GUID url, so two files called `image.png` are simply two attachments.
28
+ *
29
+ * A **wiki** image goes to `PUT wiki/wikis/{wiki}/attachments?name=`, whose body
30
+ * must be BASE64 (Microsoft's spec says stream; the server disagrees with a 500),
31
+ * and which writes a file into a git repository — so the NAME is the identity
32
+ * and a second `image.png` overwrites the first. The name is made unique here
33
+ * before it is sent.
34
+ */
35
+ export interface ImageCommandOptions {
36
+ /** Alt text. Defaults to the file's own name, which is what a screenshot has. */
37
+ alt: string | undefined;
38
+ allowOversize: boolean;
39
+ }
40
+ /** `pharos image <file>` — for a work item description or comment. */
41
+ export declare function runImage(io: Io, session: Session, cwd: string, positionals: readonly string[], options: ImageCommandOptions): Promise<ExitCode>;
42
+ /** `pharos wiki image <file>` — for a wiki page or a wiki page comment. */
43
+ export declare function runWikiImage(io: Io, session: Session, cwd: string, positionals: readonly string[], options: ImageCommandOptions): Promise<ExitCode>;
44
+ //# sourceMappingURL=image.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"image.d.ts","sourceRoot":"","sources":["../../src/commands/image.ts"],"names":[],"mappings":"AAKA,OAAO,EAA8B,KAAK,QAAQ,EAAE,KAAK,EAAE,EAAE,MAAM,cAAc,CAAC;AAClF,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,eAAe,CAAC;AAE7C;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AAEH,MAAM,WAAW,mBAAmB;IAClC,iFAAiF;IACjF,GAAG,EAAE,MAAM,GAAG,SAAS,CAAC;IACxB,aAAa,EAAE,OAAO,CAAC;CACxB;AAED,sEAAsE;AACtE,wBAAsB,QAAQ,CAC5B,EAAE,EAAE,EAAE,EACN,OAAO,EAAE,OAAO,EAChB,GAAG,EAAE,MAAM,EACX,WAAW,EAAE,SAAS,MAAM,EAAE,EAC9B,OAAO,EAAE,mBAAmB,GAC3B,OAAO,CAAC,QAAQ,CAAC,CA6BnB;AAED,2EAA2E;AAC3E,wBAAsB,YAAY,CAChC,EAAE,EAAE,EAAE,EACN,OAAO,EAAE,OAAO,EAChB,GAAG,EAAE,MAAM,EACX,WAAW,EAAE,SAAS,MAAM,EAAE,EAC9B,OAAO,EAAE,mBAAmB,GAC3B,OAAO,CAAC,QAAQ,CAAC,CAyCnB"}
@@ -0,0 +1,114 @@
1
+ import { readFile } from "node:fs/promises";
2
+ import { basename, resolve } from "node:path";
3
+ import { uniqueWikiAttachmentName } from "@floh-solutions/ado-core";
4
+ import { emit, emitText, usageError } from "../output.js";
5
+ /** `pharos image <file>` — for a work item description or comment. */
6
+ export async function runImage(io, session, cwd, positionals, options) {
7
+ const { path, bytes, name } = await readImage(cwd, positionals[0], "pharos image ./shot.png");
8
+ const alt = options.alt ?? altTextFor(name);
9
+ if (session.previewOnly) {
10
+ return report(io, session, {
11
+ applied: false,
12
+ dryRun: true,
13
+ target: "workItem",
14
+ name,
15
+ bytes: bytes.length,
16
+ markdown: `![${alt}](…)`,
17
+ path,
18
+ });
19
+ }
20
+ const uploaded = await session.client.attachments.upload(name, bytes, {
21
+ allowOversize: options.allowOversize,
22
+ });
23
+ return report(io, session, {
24
+ applied: true,
25
+ target: "workItem",
26
+ name,
27
+ bytes: bytes.length,
28
+ url: uploaded.url,
29
+ markdown: `![${alt}](${uploaded.url})`,
30
+ path,
31
+ });
32
+ }
33
+ /** `pharos wiki image <file>` — for a wiki page or a wiki page comment. */
34
+ export async function runWikiImage(io, session, cwd, positionals, options) {
35
+ const { path, bytes, name } = await readImage(cwd, positionals[0], "pharos wiki image ./diagram.png");
36
+ const alt = options.alt ?? altTextFor(name);
37
+ // **Unique before it is sent.** A wiki attachment is a file in a git repo, so
38
+ // the name IS the identity: a second `image.png` lands on the first and every
39
+ // page pointing at it silently changes picture.
40
+ const unique = uniqueWikiAttachmentName(name, Date.now());
41
+ if (session.previewOnly) {
42
+ return report(io, session, {
43
+ applied: false,
44
+ dryRun: true,
45
+ target: "wiki",
46
+ name: unique,
47
+ bytes: bytes.length,
48
+ markdown: `![${alt}](/.attachments/${unique})`,
49
+ path,
50
+ });
51
+ }
52
+ const wiki = await session.wikiIdentifier();
53
+ const created = await session.wiki.createAttachment(wiki, unique, bytes);
54
+ return report(io, session, {
55
+ applied: true,
56
+ target: "wiki",
57
+ wiki,
58
+ name: created.name,
59
+ bytes: bytes.length,
60
+ // Repo-relative, NOT an absolute url. A page referencing an absolute
61
+ // `wit/attachments` url renders for anyone with a session and breaks for
62
+ // everybody else.
63
+ attachmentPath: created.path,
64
+ markdown: `![${alt}](${created.path})`,
65
+ path,
66
+ });
67
+ }
68
+ async function readImage(cwd, given, example) {
69
+ if (given === undefined) {
70
+ throw usageError(`Which file? \`${example}\`.`);
71
+ }
72
+ const path = resolve(cwd, given);
73
+ let bytes;
74
+ try {
75
+ bytes = await readFile(path);
76
+ }
77
+ catch (error) {
78
+ // Exit 2, not 1: a missing file is the call being wrong, and repeating it
79
+ // unchanged will never work.
80
+ throw usageError(`Cannot read "${given}": ${error instanceof Error ? error.message : error}`, {
81
+ path,
82
+ });
83
+ }
84
+ return { path, bytes, name: basename(path) };
85
+ }
86
+ /**
87
+ * Alt text worth reading, from a file name.
88
+ *
89
+ * `Screenshot 2026-08-06 at 14.02.11.png` → `Screenshot 2026-08-06 at 14.02.11`.
90
+ * Not brilliant, and better than `image.png` or nothing: alt text is what
91
+ * somebody using a screen reader gets instead of the picture, and it is what
92
+ * every renderer shows when the image fails to load.
93
+ */
94
+ function altTextFor(fileName) {
95
+ const dot = fileName.lastIndexOf(".");
96
+ const stem = dot > 0 ? fileName.slice(0, dot) : fileName;
97
+ return stem.trim() === "" ? "image" : stem;
98
+ }
99
+ function report(io, session, result) {
100
+ if (session.pretty) {
101
+ const where = result.target === "wiki" ? "the wiki" : "this project's work item attachments";
102
+ emitText(io, result.dryRun === true
103
+ ? `Would upload ${result.name} (${result.bytes} bytes) to ${where}.`
104
+ : `Uploaded ${result.name} (${result.bytes} bytes) to ${where}.`);
105
+ emitText(io, "");
106
+ // The markdown is the ANSWER, not a detail of it — the whole point of the
107
+ // verb is the line you paste into a description, a comment or a page.
108
+ emitText(io, result.markdown);
109
+ return 0;
110
+ }
111
+ emit(io, result);
112
+ return 0;
113
+ }
114
+ //# sourceMappingURL=image.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"image.js","sourceRoot":"","sources":["../../src/commands/image.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,51 @@
1
+ import { type ExitCode, type Io } from "../output.js";
2
+ import type { Session } from "../session.js";
3
+ /**
4
+ * `pharos wiki import <file…>` — documents in, wiki pages out.
5
+ *
6
+ * ## What it is for
7
+ *
8
+ * The macOS app grew this in #641: pick files, name them, choose one parent, and
9
+ * each becomes a page. Everything about it that is a RULE rather than a window —
10
+ * how a file name becomes a page name, what happens when two files want the same
11
+ * name, what happens when the page already exists — is the same here, because
12
+ * getting those wrong loses somebody's work either way.
13
+ *
14
+ * ## Where the conversion comes from
15
+ *
16
+ * `.md` and `.txt` are copied VERBATIM. A markdown file is already the target
17
+ * format and anything done to it on the way in would be reformatting a document
18
+ * somebody wrote deliberately.
19
+ *
20
+ * `.docx`, `.pdf`, `.rtf`, `.html` need converting, and that converter is Swift:
21
+ * it lives in `PharosCore`, has thirty-odd tests behind it and was measured
22
+ * against 246 real `.docx` files. **This shells out to it rather than
23
+ * reimplementing it**, because a second converter in TypeScript would be a
24
+ * second set of answers to "what does this document say", and the one that
25
+ * drifts is the one nobody is looking at.
26
+ *
27
+ * The helper is `pharos-convert`. When it is not on PATH, `.md` and `.txt`
28
+ * still import and everything else is refused **by name, with the reason** —
29
+ * rather than half-importing, which is the failure that would be worth
30
+ * complaining about.
31
+ *
32
+ * ## The rules that stop it losing work
33
+ *
34
+ * - **A collision SKIPS and says so.** A wiki page write with an empty version
35
+ * is a create, so writing over an existing page is silent data loss.
36
+ * - **Names are settled against the batch as well as the wiki.** Importing
37
+ * `Notes.docx` and `Notes.pdf` together is two files wanting one page name;
38
+ * checking only the server would make that one page written twice, inside a
39
+ * single command, faster than anybody could see.
40
+ * - **An empty document is refused.** A scanned PDF carries no text at all —
41
+ * PDFKit hands back an empty string and no error — and an empty page would
42
+ * look like a successful import until somebody opened it.
43
+ */
44
+ export interface ImportCommandOptions {
45
+ /** The wiki path everything lands under. Empty is the top level. */
46
+ under: string | undefined;
47
+ /** Override the page name. Only meaningful for a single file. */
48
+ as: string | undefined;
49
+ }
50
+ export declare function runWikiImport(io: Io, session: Session, cwd: string, positionals: readonly string[], options: ImportCommandOptions): Promise<ExitCode>;
51
+ //# sourceMappingURL=import.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"import.d.ts","sourceRoot":"","sources":["../../src/commands/import.ts"],"names":[],"mappings":"AAOA,OAAO,EAA8B,KAAK,QAAQ,EAAE,KAAK,EAAE,EAAE,MAAM,cAAc,CAAC;AAClF,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,eAAe,CAAC;AAI7C;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAwCG;AAEH,MAAM,WAAW,oBAAoB;IACnC,oEAAoE;IACpE,KAAK,EAAE,MAAM,GAAG,SAAS,CAAC;IAC1B,iEAAiE;IACjE,EAAE,EAAE,MAAM,GAAG,SAAS,CAAC;CACxB;AASD,wBAAsB,aAAa,CACjC,EAAE,EAAE,EAAE,EACN,OAAO,EAAE,OAAO,EAChB,GAAG,EAAE,MAAM,EACX,WAAW,EAAE,SAAS,MAAM,EAAE,EAC9B,OAAO,EAAE,oBAAoB,GAC5B,OAAO,CAAC,QAAQ,CAAC,CA6GnB"}
@@ -0,0 +1,190 @@
1
+ import { execFile } from "node:child_process";
2
+ import { readFile } from "node:fs/promises";
3
+ import { basename, extname, resolve } from "node:path";
4
+ import { promisify } from "node:util";
5
+ import { AdoNotFoundError } from "@floh-solutions/ado-core";
6
+ import { emit, emitText, usageError } from "../output.js";
7
+ const run = promisify(execFile);
8
+ export async function runWikiImport(io, session, cwd, positionals, options) {
9
+ if (positionals.length === 0) {
10
+ throw usageError("Which files? `pharos wiki import ./notes.md ./report.docx --under /Guides`.");
11
+ }
12
+ if (options.as !== undefined && positionals.length > 1) {
13
+ throw usageError("`--as` names ONE page, so it cannot be used with several files.");
14
+ }
15
+ const wiki = await session.wikiIdentifier();
16
+ const parent = normalisePath(options.under ?? "");
17
+ // Every path already in the wiki, for the collision check. One call, and the
18
+ // check is repeated per file against the growing set so the batch cannot
19
+ // collide with itself.
20
+ const taken = new Set();
21
+ for (const path of await existingPaths(session, wiki)) {
22
+ taken.add(path.toLowerCase());
23
+ }
24
+ const outcomes = [];
25
+ for (const given of positionals) {
26
+ const path = resolve(cwd, given);
27
+ const file = basename(path);
28
+ let converted;
29
+ try {
30
+ converted = await convert(path, file);
31
+ }
32
+ catch (error) {
33
+ outcomes.push({ file, skipped: error instanceof Error ? error.message : String(error) });
34
+ continue;
35
+ }
36
+ // **A NAME SOMEBODY TYPED IS NOT RENAMED.** A name DERIVED from a file name
37
+ // is counted up when it is taken — that is the same thing the app's staging
38
+ // list does, and importing `Notes.docx` beside `Notes.pdf` should give two
39
+ // pages rather than one refusal. But `--as` is an instruction, and quietly
40
+ // turning it into "… 2" writes a page nobody asked for under a name nobody
41
+ // chose. So an explicit name is allowed to collide, and collisions skip.
42
+ const explicit = options.as !== undefined;
43
+ const leaf = sanitise(options.as ?? converted.name);
44
+ const target = joinPath(parent, explicit ? leaf : freeName(leaf, taken, parent));
45
+ if (taken.has(target.toLowerCase())) {
46
+ outcomes.push({
47
+ file,
48
+ page: target,
49
+ skipped: `There is already a page at ${target}. It was left alone.`,
50
+ });
51
+ continue;
52
+ }
53
+ if (session.previewOnly) {
54
+ outcomes.push({ file, page: target, created: false });
55
+ taken.add(target.toLowerCase());
56
+ continue;
57
+ }
58
+ // ANCESTORS ARE NOT CREATED FOR YOU — writing `/A/B` when `/A` does not
59
+ // exist is refused by Azure DevOps. Made once per missing ancestor, empty,
60
+ // which is what the website does when you add a page under a folder.
61
+ for (const ancestor of ancestorsOf(target)) {
62
+ if (taken.has(ancestor.toLowerCase()))
63
+ continue;
64
+ try {
65
+ await session.wiki.writePage(wiki, ancestor, "");
66
+ }
67
+ catch {
68
+ // Already there, or refused for a reason the child's own write will
69
+ // report properly. Either way it is not this loop's business.
70
+ }
71
+ taken.add(ancestor.toLowerCase());
72
+ }
73
+ await session.wiki.writePage(wiki, target, converted.markdown);
74
+ taken.add(target.toLowerCase());
75
+ outcomes.push({ file, page: target, created: true });
76
+ }
77
+ const created = outcomes.filter((outcome) => outcome.created === true).length;
78
+ const skipped = outcomes.filter((outcome) => outcome.skipped !== undefined).length;
79
+ if (session.pretty) {
80
+ for (const outcome of outcomes) {
81
+ emitText(io, outcome.skipped !== undefined
82
+ ? `skipped ${outcome.file} — ${outcome.skipped}`
83
+ : `${outcome.created === true ? "created" : "would create"} ${outcome.page}`);
84
+ }
85
+ emitText(io, "");
86
+ emitText(io, `${created} created, ${skipped} skipped.`);
87
+ return skipped > 0 && created === 0 ? 3 : 0;
88
+ }
89
+ emit(io, {
90
+ applied: !session.previewOnly,
91
+ dryRun: session.previewOnly ? true : undefined,
92
+ wiki,
93
+ under: parent === "" ? "/" : parent,
94
+ created,
95
+ skipped,
96
+ pages: outcomes,
97
+ });
98
+ // Exit 3 — a guard here refused — only when NOTHING landed. A partial batch
99
+ // did real work and reporting it as a failure would invite a blind retry that
100
+ // collides with the pages it just made.
101
+ return skipped > 0 && created === 0 ? 3 : 0;
102
+ }
103
+ /** A document, as markdown. */
104
+ async function convert(path, file) {
105
+ const extension = extname(file).toLowerCase();
106
+ const stem = file.slice(0, file.length - extension.length) || file;
107
+ if (extension === ".md" || extension === ".markdown" || extension === ".txt") {
108
+ const markdown = await readFile(path, "utf8");
109
+ if (markdown.trim() === "") {
110
+ throw new Error(`${file} has no text in it, so there is nothing to make a page from.`);
111
+ }
112
+ return { name: stem, markdown };
113
+ }
114
+ try {
115
+ const { stdout } = await run("pharos-convert", [path], { maxBuffer: 32 * 1024 * 1024 });
116
+ const parsed = JSON.parse(stdout);
117
+ return parsed;
118
+ }
119
+ catch (error) {
120
+ const failure = error;
121
+ if (failure.code === "ENOENT") {
122
+ throw new Error(`${file} needs \`pharos-convert\`, which is not on PATH. `
123
+ + "Markdown and text import without it; Word documents and PDFs need it because the "
124
+ + "converter is shared with the macOS app rather than written twice.");
125
+ }
126
+ // The helper's own sentence, forwarded verbatim — it already says what is
127
+ // wrong and what to do, including the scanned-PDF case.
128
+ const said = (failure.stderr ?? "").trim();
129
+ throw new Error(said === "" ? `${file} could not be converted.` : said);
130
+ }
131
+ }
132
+ /** Every page path already in the wiki. */
133
+ async function existingPaths(session, wiki) {
134
+ try {
135
+ const ids = await session.wiki.pageIdsByPath(wiki);
136
+ return [...ids.keys()];
137
+ }
138
+ catch (error) {
139
+ if (error instanceof AdoNotFoundError)
140
+ return [];
141
+ throw error;
142
+ }
143
+ }
144
+ /** Leading slash, no trailing one. The API is inconsistent about both. */
145
+ function normalisePath(path) {
146
+ let trimmed = path.trim();
147
+ while (trimmed.endsWith("/"))
148
+ trimmed = trimmed.slice(0, -1);
149
+ if (trimmed === "")
150
+ return "";
151
+ return trimmed.startsWith("/") ? trimmed : `/${trimmed}`;
152
+ }
153
+ function joinPath(parent, leaf) {
154
+ const head = normalisePath(parent);
155
+ const tail = leaf.replace(/^\/+|\/+$/g, "");
156
+ return head === "" ? `/${tail}` : `${head}/${tail}`;
157
+ }
158
+ /**
159
+ * A page name a wiki path can hold.
160
+ *
161
+ * **The slash is the one that matters**: a path is slash-separated, so a file
162
+ * called `Q1/Q2 review` would otherwise create a page called `Q2 review` nested
163
+ * under a brand-new `Q1`, in a place nobody chose and with `--under` silently
164
+ * overruled.
165
+ */
166
+ function sanitise(name) {
167
+ const cleaned = name
168
+ .replace(/[/\\:*?"<>|#\t\n\r]+/g, "-")
169
+ .replace(/^[\s\-.]+|[\s\-.]+$/g, "");
170
+ return cleaned === "" ? "Untitled" : cleaned;
171
+ }
172
+ /** A name not already taken, counting from 2 because the unnumbered one is first. */
173
+ function freeName(name, taken, parent) {
174
+ if (!taken.has(joinPath(parent, name).toLowerCase()))
175
+ return name;
176
+ let suffix = 2;
177
+ while (taken.has(joinPath(parent, `${name} ${suffix}`).toLowerCase()))
178
+ suffix += 1;
179
+ return `${name} ${suffix}`;
180
+ }
181
+ /** `/A/B/C` → `["/A", "/A/B"]`, outermost first. */
182
+ function ancestorsOf(path) {
183
+ const parts = normalisePath(path).split("/").filter((part) => part !== "");
184
+ const out = [];
185
+ for (let index = 1; index < parts.length; index += 1) {
186
+ out.push(`/${parts.slice(0, index).join("/")}`);
187
+ }
188
+ return out;
189
+ }
190
+ //# sourceMappingURL=import.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"import.js","sourceRoot":"","sources":["../../src/commands/import.ts"],"names":[],"mappings":"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"}