@getfamiliar/shared 0.1.4 → 0.1.7
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/build/EventBus.d.ts +2 -2
- package/build/EventBus.js +2 -2
- package/build/Mail.d.ts +18 -2
- package/build/Plugin.d.ts +23 -11
- package/build/Storage.d.ts +263 -0
- package/build/Storage.js +33 -0
- package/build/ToolRunner.d.ts +13 -2
- package/build/ToolRunner.js +28 -12
- package/build/index.d.ts +3 -1
- package/build/index.js +2 -0
- package/build/markdownOutput.d.ts +39 -0
- package/build/markdownOutput.js +50 -0
- package/build/markdownTerminal.js +7 -2
- package/build/testing/FakeStorageProvider.d.ts +133 -0
- package/build/testing/FakeStorageProvider.js +459 -0
- package/build/testing/StorageProviderContract.d.ts +32 -0
- package/build/testing/StorageProviderContract.js +168 -0
- package/build/testing/index.d.ts +2 -0
- package/build/testing/index.js +2 -0
- package/package.json +1 -1
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Escape a single value for use as a GitHub-flavoured-markdown table cell:
|
|
3
|
+
* backslashes stay literal, `|` no longer splits the column, and any
|
|
4
|
+
* newline collapses to a space so the row stays on one line. Backticks are
|
|
5
|
+
* left untouched so inline-code spans in a cell survive. Mirrors the
|
|
6
|
+
* escaping the `tools list --raw` renderer needs; that renderer's
|
|
7
|
+
* `escapeCell` delegates here so there is a single implementation.
|
|
8
|
+
*
|
|
9
|
+
* @param text - raw cell text
|
|
10
|
+
* @returns the text safe to drop between two `|` pipes
|
|
11
|
+
*/
|
|
12
|
+
export declare function escapeTableCell(text: string): string;
|
|
13
|
+
/**
|
|
14
|
+
* Build a GitHub-flavoured-markdown table from a header row and body rows.
|
|
15
|
+
* Every cell is run through {@link escapeTableCell}. The result is meant to
|
|
16
|
+
* be handed to {@link writeMarkdown}, which renders it (cli-table3) on a TTY
|
|
17
|
+
* or emits it verbatim when piped.
|
|
18
|
+
*
|
|
19
|
+
* @param headers - column header labels
|
|
20
|
+
* @param rows - one string tuple per row; each row should have `headers.length` cells
|
|
21
|
+
* @returns a GFM table string terminated by a single newline
|
|
22
|
+
*/
|
|
23
|
+
export declare function markdownTable(headers: readonly string[], rows: readonly (readonly string[])[]): string;
|
|
24
|
+
/**
|
|
25
|
+
* Write a markdown string to stdout as the single human-facing CLI/plugin
|
|
26
|
+
* output sink: ANSI-styled via {@link renderMarkdown} on a TTY, or the raw
|
|
27
|
+
* markdown verbatim when stdout is piped/redirected.
|
|
28
|
+
*
|
|
29
|
+
* The TTY check lives *inside* this function on purpose — no call site
|
|
30
|
+
* should read `process.stdout.isTTY` itself. A caller can only ever *force*
|
|
31
|
+
* raw output (e.g. to honour a `--raw` flag); it can never accidentally
|
|
32
|
+
* render box-drawing tables and ANSI escapes into a pipe.
|
|
33
|
+
*
|
|
34
|
+
* @param markdown - markdown source to emit
|
|
35
|
+
* @param opts - `raw: true` forces raw output even on a TTY; omitted/false lets the TTY check decide
|
|
36
|
+
*/
|
|
37
|
+
export declare function writeMarkdown(markdown: string, opts?: {
|
|
38
|
+
readonly raw?: boolean;
|
|
39
|
+
}): void;
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { renderMarkdown } from "./markdownTerminal.js";
|
|
2
|
+
/**
|
|
3
|
+
* Escape a single value for use as a GitHub-flavoured-markdown table cell:
|
|
4
|
+
* backslashes stay literal, `|` no longer splits the column, and any
|
|
5
|
+
* newline collapses to a space so the row stays on one line. Backticks are
|
|
6
|
+
* left untouched so inline-code spans in a cell survive. Mirrors the
|
|
7
|
+
* escaping the `tools list --raw` renderer needs; that renderer's
|
|
8
|
+
* `escapeCell` delegates here so there is a single implementation.
|
|
9
|
+
*
|
|
10
|
+
* @param text - raw cell text
|
|
11
|
+
* @returns the text safe to drop between two `|` pipes
|
|
12
|
+
*/
|
|
13
|
+
export function escapeTableCell(text) {
|
|
14
|
+
return text.replace(/\\/g, "\\\\").replace(/\|/g, "\\|").replace(/\r?\n/g, " ");
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Build a GitHub-flavoured-markdown table from a header row and body rows.
|
|
18
|
+
* Every cell is run through {@link escapeTableCell}. The result is meant to
|
|
19
|
+
* be handed to {@link writeMarkdown}, which renders it (cli-table3) on a TTY
|
|
20
|
+
* or emits it verbatim when piped.
|
|
21
|
+
*
|
|
22
|
+
* @param headers - column header labels
|
|
23
|
+
* @param rows - one string tuple per row; each row should have `headers.length` cells
|
|
24
|
+
* @returns a GFM table string terminated by a single newline
|
|
25
|
+
*/
|
|
26
|
+
export function markdownTable(headers, rows) {
|
|
27
|
+
const headerLine = `| ${headers.map(escapeTableCell).join(" | ")} |`;
|
|
28
|
+
const separator = `| ${headers.map(() => "---").join(" | ")} |`;
|
|
29
|
+
const body = rows.map((row) => `| ${row.map(escapeTableCell).join(" | ")} |`).join("\n");
|
|
30
|
+
return body.length > 0
|
|
31
|
+
? `${headerLine}\n${separator}\n${body}\n`
|
|
32
|
+
: `${headerLine}\n${separator}\n`;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Write a markdown string to stdout as the single human-facing CLI/plugin
|
|
36
|
+
* output sink: ANSI-styled via {@link renderMarkdown} on a TTY, or the raw
|
|
37
|
+
* markdown verbatim when stdout is piped/redirected.
|
|
38
|
+
*
|
|
39
|
+
* The TTY check lives *inside* this function on purpose — no call site
|
|
40
|
+
* should read `process.stdout.isTTY` itself. A caller can only ever *force*
|
|
41
|
+
* raw output (e.g. to honour a `--raw` flag); it can never accidentally
|
|
42
|
+
* render box-drawing tables and ANSI escapes into a pipe.
|
|
43
|
+
*
|
|
44
|
+
* @param markdown - markdown source to emit
|
|
45
|
+
* @param opts - `raw: true` forces raw output even on a TTY; omitted/false lets the TTY check decide
|
|
46
|
+
*/
|
|
47
|
+
export function writeMarkdown(markdown, opts) {
|
|
48
|
+
const raw = opts?.raw === true || !process.stdout.isTTY;
|
|
49
|
+
process.stdout.write(raw ? markdown : renderMarkdown(markdown));
|
|
50
|
+
}
|
|
@@ -39,7 +39,7 @@ import wrapAnsi from "wrap-ansi";
|
|
|
39
39
|
*/
|
|
40
40
|
/** Upper bound on render width; full-width prose is hard to read on ultra-wide terminals. */
|
|
41
41
|
const MAX_WIDTH = 180;
|
|
42
|
-
/** Fallback width when stdout
|
|
42
|
+
/** Fallback width when stdout reports no usable `columns` (not a TTY, or a degenerate 0-width pty). */
|
|
43
43
|
const FALLBACK_WIDTH = 100;
|
|
44
44
|
/** Floor on the content width at deep nesting, so wrapping never degenerates. */
|
|
45
45
|
const MIN_CONTENT_WIDTH = 12;
|
|
@@ -369,7 +369,12 @@ function buildRenderer(width) {
|
|
|
369
369
|
* @returns ANSI-styled terminal output
|
|
370
370
|
*/
|
|
371
371
|
export function renderMarkdown(input) {
|
|
372
|
-
|
|
372
|
+
// `columns` is undefined when stdout is not a TTY and can be a
|
|
373
|
+
// degenerate 0 under a zero-width pty (e.g. `script`); either way fall
|
|
374
|
+
// back to a healthy default rather than rendering into no space.
|
|
375
|
+
const columns = process.stdout.columns;
|
|
376
|
+
const usableWidth = typeof columns === "number" && columns > 0 ? columns : FALLBACK_WIDTH;
|
|
377
|
+
const width = Math.min(usableWidth, MAX_WIDTH);
|
|
373
378
|
const marked = new Marked();
|
|
374
379
|
marked.use({ renderer: buildRenderer(width) });
|
|
375
380
|
return marked.parse(input);
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
import { type StorageCapabilities, type StorageConflict, type StorageDownloadResult, type StorageDrive, type StorageItem, type StorageItemKind, type StorageListOrder, type StoragePage, type StoragePageOptions, type StorageProvider, type StorageSearchHit, type StorageSearchQuery, type StorageUploadSource } from "../Storage.js";
|
|
2
|
+
/** Which real provider the fake imitates. */
|
|
3
|
+
export type FakeStorageFlavor = "onedrive" | "gdrive" | "dropbox";
|
|
4
|
+
/** Google-style native doc mime types and their export mapping. */
|
|
5
|
+
export declare const FAKE_NATIVE_EXPORTS: Readonly<Record<string, {
|
|
6
|
+
mime: string;
|
|
7
|
+
ext: string;
|
|
8
|
+
}>>;
|
|
9
|
+
/** Behaviour knobs for tests. */
|
|
10
|
+
export interface FakeStorageOptions {
|
|
11
|
+
readonly pluginId?: string;
|
|
12
|
+
readonly flavor?: FakeStorageFlavor;
|
|
13
|
+
/** Accounts and their drives (selector `null` = primary). Default: one account `a@x` with a primary drive. */
|
|
14
|
+
readonly accounts?: Readonly<Record<string, readonly {
|
|
15
|
+
selector: string | null;
|
|
16
|
+
name: string;
|
|
17
|
+
}[]>>;
|
|
18
|
+
/** Accounts whose calls throw `AuthExpired`. */
|
|
19
|
+
readonly expiredAccounts?: readonly string[];
|
|
20
|
+
/** Accounts whose `listDrives` throws `ProviderUnavailable`. */
|
|
21
|
+
readonly brokenAccounts?: readonly string[];
|
|
22
|
+
/** Artificial search latency (honours the abort signal). */
|
|
23
|
+
readonly searchDelayMs?: number;
|
|
24
|
+
/** Make every search throw this error. */
|
|
25
|
+
readonly searchError?: Error;
|
|
26
|
+
/** Override `contentSearch`. */
|
|
27
|
+
readonly contentSearch?: boolean;
|
|
28
|
+
/** Override `trash`. */
|
|
29
|
+
readonly trash?: boolean;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* In-memory {@link StorageProvider} for service, tool, CLI and contract
|
|
33
|
+
* tests. Imitates OneDrive (path addressing, unique names), Google
|
|
34
|
+
* Drive (id addressing, duplicate names, native docs) or Dropbox
|
|
35
|
+
* (path addressing, no content search). Content lives in memory;
|
|
36
|
+
* download / upload still go through real host files so the byte paths
|
|
37
|
+
* are exercised.
|
|
38
|
+
*/
|
|
39
|
+
export declare class FakeStorageProvider implements StorageProvider {
|
|
40
|
+
readonly pluginId: string;
|
|
41
|
+
readonly flavor: FakeStorageFlavor;
|
|
42
|
+
private readonly drives;
|
|
43
|
+
private readonly options;
|
|
44
|
+
private nextId;
|
|
45
|
+
private clock;
|
|
46
|
+
/**
|
|
47
|
+
* @param options - Behaviour knobs; see {@link FakeStorageOptions}.
|
|
48
|
+
*/
|
|
49
|
+
constructor(options?: FakeStorageOptions);
|
|
50
|
+
/** @returns The capabilities of the imitated provider. */
|
|
51
|
+
capabilities(_drive: StorageDrive): StorageCapabilities;
|
|
52
|
+
/** @returns All configured accounts. */
|
|
53
|
+
listAccounts(): Promise<readonly string[]>;
|
|
54
|
+
/** @returns A fake login command. */
|
|
55
|
+
loginCommand(account: string): string;
|
|
56
|
+
/** @returns The account's drives, filtered and capped. */
|
|
57
|
+
listDrives(account: string, options: {
|
|
58
|
+
readonly limit: number;
|
|
59
|
+
readonly search?: string;
|
|
60
|
+
}): Promise<readonly StorageDrive[]>;
|
|
61
|
+
/** @returns The drive for `selector`. */
|
|
62
|
+
openDrive(account: string, selector: string | null): Promise<StorageDrive>;
|
|
63
|
+
/** @returns The root folder id. */
|
|
64
|
+
rootRealId(drive: StorageDrive): Promise<string>;
|
|
65
|
+
/** @returns The item or `null`. */
|
|
66
|
+
stat(drive: StorageDrive, realId: string): Promise<StorageItem | null>;
|
|
67
|
+
/** Native path resolution (only for path-addressed flavors). */
|
|
68
|
+
get resolvePath(): ((drive: StorageDrive, path: string) => Promise<readonly StorageItem[]>) | undefined;
|
|
69
|
+
/** @returns One page of children. */
|
|
70
|
+
list(drive: StorageDrive, folderRealId: string, options: StoragePageOptions & {
|
|
71
|
+
readonly order?: StorageListOrder;
|
|
72
|
+
}): Promise<StoragePage<StorageItem>>;
|
|
73
|
+
/** @returns One page of name / content hits. */
|
|
74
|
+
search(drive: StorageDrive, query: StorageSearchQuery, options: StoragePageOptions): Promise<StoragePage<StorageSearchHit>>;
|
|
75
|
+
/** Write the bytes (or the native export) to `destPath`. */
|
|
76
|
+
download(drive: StorageDrive, realId: string, destPath: string): Promise<StorageDownloadResult>;
|
|
77
|
+
/** Upload from a host file with conflict / revision semantics. */
|
|
78
|
+
upload(drive: StorageDrive, parentRealId: string, name: string, source: StorageUploadSource, options: {
|
|
79
|
+
readonly conflict: StorageConflict;
|
|
80
|
+
readonly replaceRealId?: string;
|
|
81
|
+
readonly ifRevision?: string;
|
|
82
|
+
}): Promise<StorageItem>;
|
|
83
|
+
/** Create a folder. */
|
|
84
|
+
createFolder(drive: StorageDrive, parentRealId: string, name: string, options: {
|
|
85
|
+
readonly conflict: "fail" | "rename" | "reuse";
|
|
86
|
+
}): Promise<StorageItem>;
|
|
87
|
+
/** Move / rename. */
|
|
88
|
+
move(drive: StorageDrive, realId: string, target: {
|
|
89
|
+
readonly parentRealId?: string;
|
|
90
|
+
readonly name?: string;
|
|
91
|
+
}, options: {
|
|
92
|
+
readonly ifRevision?: string;
|
|
93
|
+
}): Promise<StorageItem>;
|
|
94
|
+
/** Server-side copy (files only in the fake). */
|
|
95
|
+
copy(drive: StorageDrive, realId: string, target: {
|
|
96
|
+
readonly parentRealId: string;
|
|
97
|
+
readonly name?: string;
|
|
98
|
+
}, options: {
|
|
99
|
+
readonly conflict: "fail" | "rename";
|
|
100
|
+
}): Promise<StorageItem>;
|
|
101
|
+
/** Soft delete (recursive). */
|
|
102
|
+
trash(drive: StorageDrive, realId: string, options: {
|
|
103
|
+
readonly ifRevision?: string;
|
|
104
|
+
}): Promise<void>;
|
|
105
|
+
/**
|
|
106
|
+
* Seed an item under `parentPath` (folders are created as needed).
|
|
107
|
+
* Duplicates are allowed for the gdrive flavor.
|
|
108
|
+
*
|
|
109
|
+
* @returns The seeded item's realId.
|
|
110
|
+
*/
|
|
111
|
+
seed(account: string, selector: string | null, path: string, options?: {
|
|
112
|
+
kind?: StorageItemKind;
|
|
113
|
+
content?: string;
|
|
114
|
+
mimeType?: string;
|
|
115
|
+
}): string;
|
|
116
|
+
/** Test helper: raw content of an item, or `null`. */
|
|
117
|
+
contentOf(account: string, selector: string | null, realId: string): string | null;
|
|
118
|
+
/** Test helper: whether an item is in the trash. */
|
|
119
|
+
isTrashed(account: string, selector: string | null, realId: string): boolean;
|
|
120
|
+
private state;
|
|
121
|
+
private assertAccount;
|
|
122
|
+
private requireNode;
|
|
123
|
+
private requireFolder;
|
|
124
|
+
private checkRevision;
|
|
125
|
+
private children;
|
|
126
|
+
private childNamed;
|
|
127
|
+
private freeName;
|
|
128
|
+
private pathOf;
|
|
129
|
+
private toItem;
|
|
130
|
+
private makeNode;
|
|
131
|
+
private newId;
|
|
132
|
+
private tick;
|
|
133
|
+
}
|