yanki 0.7.1 → 0.8.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.
package/dist/bin/cli.js CHANGED
@@ -1,10 +1,10 @@
1
1
  #!/usr/bin/env node
2
- var Y=Object.defineProperty;var m=(e,t)=>Y(e,"name",{value:t,configurable:!0});import{y as J,u as h,k as T,j as D,l as I,b as M,c as U,f as L,s as z,h as j}from"../sync-B_u0fKEU.js";import s from"chalk";import{globby as H}from"globby";import W from"node:fs/promises";import q from"node:path";import B from"node:os";import F from"yargs";import{hideBin as G}from"yargs/helpers";import"rehype-mathjax";import"rehype-parse";import"node:crypto";var K="0.7.1";const N=process?.versions?.node!==void 0,d={verbose:!1,log(...e){if(!this.verbose)return;const t=s.gray("[Log]");N?console.warn(t,...e):console.log(t,...e)},logPrefixed(e,...t){this.info(s.blue(`[${e}]`),...t)},info(...e){if(!this.verbose)return;const t=s.green("[Info]");N?console.warn(t,...e):console.info(t,...e)},infoPrefixed(e,...t){this.info(s.blue(`[${e}]`),...t)},warn(...e){console.warn(s.yellow("[Warning]"),...e)},warnPrefixed(e,...t){this.warn(s.blue(`[${e}]`),...t)},error(...e){console.error(s.red("[Error]"),...e)},errorPrefixed(e,...t){this.error(s.blue(`[${e}]`),...t)}},y={"anki-auto-launch":{alias:"l",default:!1,describe:"Attempt to open the Anki desktop app if it's not already running. (Experimental, macOS only.)",type:"boolean"}},g={"anki-web":{alias:"w",default:!0,describe:'Automatically sync any changes to AnkiWeb after Yanki has finished syncing locally. If false, only local Anki data is updated and you must manually invoke a sync to AnkiWeb. This is the equivalent of pushing the "sync" button in the Anki app.',type:"boolean"}},w={"anki-connect":{default:"http://127.0.0.1:8765",describe:"Host and port of the Anki-Connect server. The default is usually fine. See the Anki-Connect documentation for more information.",type:"string"}},v={verbose:{default:!1,describe:"Enable verbose logging.",type:"boolean"}};function k(e){return{json:{default:!1,describe:e,type:"boolean"}}}m(k,"jsonOption");const S={"dry-run":{alias:"d",default:!1,describe:"Run without making any changes to the Anki database. See a report of what would have been done.",type:"boolean"}};function A(e){return{namespace:{alias:"n",default:J,describe:e,type:"string"}}}m(A,"namespaceOption");const x=B.homedir();function Q(e){if(typeof e!="string")throw new TypeError(`Expected a string, got ${typeof e}`);return x?e.replace(/^~(?=$|\/|\\)/,x):e}m(Q,"untildify");const b=m(e=>{const{code:t}=e.cause;throw t==="ECONNREFUSED"&&(d.error("Failed to connect to Anki. Make sure Anki is running and AnkiConnect is installed."),process.exitCode=1,process.exit()),e instanceof Error?e:new Error("Unknown error")},"ankiNotRunningErrorHandler"),R=F(G(process.argv));await R.scriptName("yanki").usage("$0 [command]","Run a Yanki command. Defaults to `sync` if a command is not provided.").command(["$0 <directory> [options]","sync <directory> [options]"],"Perform a one-way synchronization from a local directory of Markdown files to the Anki database. Any Markdown files in subdirectories are included as well.",e=>e.positional("directory",{demandOption:!0,describe:"The path to the local directory of Markdown files to sync.",type:"string"}).option(S).option(A("Advanced option for managing multiple Yanki synchronization groups. Case insensitive. See the readme for more information.")).option(w).option(y).option(g).option("manage-filenames",{alias:"m",choices:["off","prompt","response"],default:"off",describe:'Rename local note files to match their content. Useful if you want to feel have semantically reasonable note file names without managing them by hand. The `"prompt"` option will attempt to create the filename based on the "front" of the card, while `"response"` will prioritize the "back", "Cloze", or "type in the answer" portions of the card. Truncation, sanitization, and deduplication are taken care of.',type:"string"}).option("max-filename-length",{default:void 0,defaultDescription:"60",describe:"If `manage-filenames` is enabled, this option specifies the maximum length of the filename in characters.",type:"number"}).option(k("Output the sync report as JSON.")).option(v),async({ankiAutoLaunch:e,ankiConnect:t,ankiWeb:i,directory:n,dryRun:a,json:r,manageFilenames:o,maxFilenameLength:p,namespace:f,recursive:c=!0,verbose:u})=>{d.verbose=u;const l=Q(n),E=c?`${l}/**/*.md`:`${l}/*.md`,C=await H(E,{absolute:!0});if(C.length===0){d.error(`No Markdown files found in "${l}".`),process.exitCode=1;return}o==="off"&&p!==void 0&&d.warn("Ignoring `max-filename-length` option because `manage-filenames` is not enabled.");const{host:$,port:P}=h(t),O=await T(C,{ankiConnectOptions:{autoLaunch:e,host:$,port:P},ankiWeb:i,dryRun:a,filenameMode:o==="off"?void 0:o,manageFilenames:o!=="off",maxFilenameLength:p,namespace:f}).catch(b);r?(process.stdout.write(JSON.stringify(O,void 0,2)),process.stdout.write(`
3
- `)):(process.stderr.write(D(O,u)),process.stderr.write(`
4
- `))}).command("list [options]","Utility command to list Yanki-created notes in the Anki database.",e=>e.option(A("Advanced option to list notes in a specific namespace. Case insensitive. Notes from the default internal namespace are listed by default. Pass `'*'` to list all Yanki-created notes in the Anki database.")).options(w).options(y).option(k("Output the list of notes as JSON to stdout.")),async({ankiAutoLaunch:e,ankiConnect:t,json:i,namespace:n})=>{const{host:a,port:r}=h(t),o=await I({ankiConnectOptions:{autoLaunch:e,host:a,port:r},namespace:n}).catch(b);i?(process.stdout.write(JSON.stringify(o,void 0,2)),process.stdout.write(`
5
- `)):(process.stdout.write(M(o)),process.stdout.write(`
6
- `))}).command("delete [options]","Utility command to manually delete Yanki-created notes in the Anki database. This is for advanced use cases, usually the `sync` command takes care of deleting files from Anki Database once they're removed from the local file system.",e=>e.option(S).option(A("Advanced option to list notes in a specific namespace. Case insensitive. Notes from the default internal namespace are listed by default. If you've synced notes to multiple namespaces, Pass `'*'` to delete all Yanki-created notes in the Anki database.")).options(w).options(y).option(g).option(k("Output the list of deleted notes as JSON to stdout.")).option(v),async({ankiAutoLaunch:e,ankiConnect:t,ankiWeb:i,dryRun:n,json:a,namespace:r,verbose:o})=>{const{host:p,port:f}=h(t),c=await U({ankiConnectOptions:{autoLaunch:e,host:p,port:f},ankiWeb:i,dryRun:n,namespace:r}).catch(b);a?(process.stdout.write(JSON.stringify(c,void 0,2)),process.stdout.write(`
7
- `)):(process.stderr.write(L(c,o)),process.stderr.write(`
8
- `))}).command("style [options]","Utility command to set the CSS stylesheet for all present and future Yanki-created notes.",e=>e.option(S).option("css",{alias:"c",default:void 0,describe:"Path to the CSS stylesheet to set for all Yanki-created notes. If not provided, the default Anki stylesheet is used.",type:"string"}).options(w).options(y).option(g).option(k("Output the list of updated note types / models as JSON to stdout.")).option(v),async({ankiAutoLaunch:e,ankiConnect:t,ankiWeb:i,css:n,dryRun:a,json:r,verbose:o})=>{const{host:p,port:f}=h(t);let c;if(n!==void 0){if(q.extname(n)!==".css"){d.error("The provided CSS file must have a .css extension."),process.exitCode=1;return}try{c=await W.readFile(n,"utf8")}catch(l){l instanceof Error?d.error(`Error loading CSS file: ${l.message}`):d.error(`Unknown error loading CSS file: ${String(l)}`),process.exitCode=1;return}}const u=await z({ankiConnectOptions:{autoLaunch:e,host:p,port:f},ankiWeb:i,css:c??void 0,dryRun:a}).catch(b);r?(process.stdout.write(JSON.stringify(u,void 0,2)),process.stdout.write(`
9
- `)):(process.stderr.write(j(u,o)),process.stderr.write(`
10
- `))}).demandCommand(1).alias("h","help").version(K).alias("v","version").help().wrap(process.stdout.isTTY?Math.min(120,R.terminalWidth()):0).parse();
2
+ var J=Object.defineProperty;var u=(e,t)=>J(e,"name",{value:t,configurable:!0});import{t as h,u as y,n as M,m as I,l as L,b as U,c as D,f as z,s as F,i as H}from"../sync-files-norUev7R.js";import s from"chalk";import{globby as j}from"globby";import W from"node:fs/promises";import q from"node:path";import B from"node:os";import G from"yargs";import{hideBin as K}from"yargs/helpers";import"rehype-mathjax";import"rehype-parse";import"node:crypto";var Q="0.8.0";const R=process?.versions?.node!==void 0,c={verbose:!1,log(...e){if(!this.verbose)return;const t=s.gray("[Log]");R?console.warn(t,...e):console.log(t,...e)},logPrefixed(e,...t){this.info(s.blue(`[${e}]`),...t)},info(...e){if(!this.verbose)return;const t=s.green("[Info]");R?console.warn(t,...e):console.info(t,...e)},infoPrefixed(e,...t){this.info(s.blue(`[${e}]`),...t)},warn(...e){console.warn(s.yellow("[Warning]"),...e)},warnPrefixed(e,...t){this.warn(s.blue(`[${e}]`),...t)},error(...e){console.error(s.red("[Error]"),...e)},errorPrefixed(e,...t){this.error(s.blue(`[${e}]`),...t)}},g={"anki-auto-launch":{alias:"l",default:!1,describe:"Attempt to open the Anki desktop app if it's not already running. (Experimental, macOS only.)",type:"boolean"}},A={"anki-web":{alias:"w",default:!0,describe:'Automatically sync any changes to AnkiWeb after Yanki has finished syncing locally. If false, only local Anki data is updated and you must manually invoke a sync to AnkiWeb. This is the equivalent of pushing the "sync" button in the Anki app.',type:"boolean"}},w={"anki-connect":{default:"http://127.0.0.1:8765",describe:"Host and port of the Anki-Connect server. The default is usually fine. See the Anki-Connect documentation for more information.",type:"string"}},S={verbose:{default:!1,describe:"Enable verbose logging.",type:"boolean"}};function b(e){return{json:{default:!1,describe:e,type:"boolean"}}}u(b,"jsonOption");const C={"dry-run":{alias:"d",default:!1,describe:"Run without making any changes to the Anki database. See a report of what would have been done.",type:"boolean"}};function O(e){return{namespace:{alias:"n",default:h.namespace,describe:e,type:"string"}}}u(O,"namespaceOption");const E=B.homedir();function V(e){if(typeof e!="string")throw new TypeError(`Expected a string, got ${typeof e}`);return E?e.replace(/^~(?=$|\/|\\)/,E):e}u(V,"untildify");const k=u(e=>{const{code:t}=e.cause;throw t==="ECONNREFUSED"&&(c.error("Failed to connect to Anki. Make sure Anki is running and AnkiConnect is installed."),process.exitCode=1,process.exit()),e instanceof Error?e:new Error("Unknown error")},"ankiNotRunningErrorHandler"),$=G(K(process.argv));await $.scriptName("yanki").usage("$0 [command]","Run a Yanki command. Defaults to `sync` if a command is not provided.").command(["$0 <directory> [options]","sync <directory> [options]"],"Perform a one-way synchronization from a local directory of Markdown files to the Anki database. Any Markdown files in subdirectories are included as well.",e=>e.positional("directory",{demandOption:!0,describe:"The path to the local directory of Markdown files to sync.",type:"string"}).option(C).option(O("Advanced option for managing multiple Yanki synchronization groups. Case insensitive. See the readme for more information.")).option(w).option(g).option(A).option("manage-filenames",{alias:"m",choices:["off","prompt","response"],default:h.manageFilenames,describe:'Rename local note files to match their content. Useful if you want to feel have semantically reasonable note file names without managing them by hand. The `"prompt"` option will attempt to create the filename based on the "front" of the card, while `"response"` will prioritize the "back", "Cloze", or "type in the answer" portions of the card. Truncation, sanitization, and deduplication are taken care of.',type:"string"}).option("max-filename-length",{default:void 0,defaultDescription:String(h.maxFilenameLength),describe:"If `manage-filenames` is enabled, this option specifies the maximum length of the filename in characters.",type:"number"}).option("sync-media",{alias:"s",choices:["off","all","local","remote"],default:h.syncMediaAssets,describe:"Sync image, video, and audio assets to Anki's media storage system. Clean up is managed automatically. The `all` argument will save both local and remote assets to Anki, while `local` will only save local assets, `remote` will only save remote assets, and `off` will not save any assets.",type:"string"}).option(b("Output the sync report as JSON.")).option(S),async({ankiAutoLaunch:e,ankiConnect:t,ankiWeb:i,directory:o,dryRun:a,json:r,manageFilenames:n,maxFilenameLength:d,namespace:f,recursive:l=!0,syncMedia:m,verbose:p})=>{c.verbose=p;const v=V(o),P=l?`${v}/**/*.md`:`${v}/*.md`,x=await j(P,{absolute:!0});if(x.length===0){c.error(`No Markdown files found in "${v}".`),process.exitCode=1;return}n==="off"&&d!==void 0&&c.warn("Ignoring `max-filename-length` option because `manage-filenames` is not enabled.");const{host:Y,port:T}=y(t),N=await M(x,{ankiConnectOptions:{autoLaunch:e,host:Y,port:T},ankiWeb:i,dryRun:a,manageFilenames:n,maxFilenameLength:d,namespace:f,syncMediaAssets:m}).catch(k);r?(process.stdout.write(JSON.stringify(N,void 0,2)),process.stdout.write(`
3
+ `)):(process.stderr.write(I(N,p)),process.stderr.write(`
4
+ `))}).command("list [options]","Utility command to list Yanki-created notes in the Anki database.",e=>e.option(O("Advanced option to list notes in a specific namespace. Case insensitive. Notes from the default internal namespace are listed by default. Pass `'*'` to list all Yanki-created notes in the Anki database.")).options(w).options(g).option(b("Output the list of notes as JSON to stdout.")),async({ankiAutoLaunch:e,ankiConnect:t,json:i,namespace:o})=>{const{host:a,port:r}=y(t),n=await L({ankiConnectOptions:{autoLaunch:e,host:a,port:r},namespace:o}).catch(k);i?(process.stdout.write(JSON.stringify(n,void 0,2)),process.stdout.write(`
5
+ `)):(process.stdout.write(U(n)),process.stdout.write(`
6
+ `))}).command("delete [options]","Utility command to manually delete Yanki-created notes in the Anki database. This is for advanced use cases, usually the `sync` command takes care of deleting files from Anki Database once they're removed from the local file system.",e=>e.option(C).option(O("Advanced option to list notes in a specific namespace. Case insensitive. Notes from the default internal namespace are listed by default. If you've synced notes to multiple namespaces, Pass `'*'` to delete all Yanki-created notes in the Anki database.")).options(w).options(g).option(A).option(b("Output the list of deleted notes as JSON to stdout.")).option(S),async({ankiAutoLaunch:e,ankiConnect:t,ankiWeb:i,dryRun:o,json:a,namespace:r,verbose:n})=>{const{host:d,port:f}=y(t),l=await D({ankiConnectOptions:{autoLaunch:e,host:d,port:f},ankiWeb:i,dryRun:o,namespace:r}).catch(k);a?(process.stdout.write(JSON.stringify(l,void 0,2)),process.stdout.write(`
7
+ `)):(process.stderr.write(z(l,n)),process.stderr.write(`
8
+ `))}).command("style [options]","Utility command to set the CSS stylesheet for all present and future Yanki-created notes.",e=>e.option(C).option("css",{alias:"c",default:void 0,describe:"Path to the CSS stylesheet to set for all Yanki-created notes. If not provided, the default Anki stylesheet is used.",type:"string"}).options(w).options(g).option(A).option(b("Output the list of updated note types / models as JSON to stdout.")).option(S),async({ankiAutoLaunch:e,ankiConnect:t,ankiWeb:i,css:o,dryRun:a,json:r,verbose:n})=>{const{host:d,port:f}=y(t);let l;if(o!==void 0){if(q.extname(o)!==".css"){c.error("The provided CSS file must have a .css extension."),process.exitCode=1;return}try{l=await W.readFile(o,"utf8")}catch(p){p instanceof Error?c.error(`Error loading CSS file: ${p.message}`):c.error(`Unknown error loading CSS file: ${String(p)}`),process.exitCode=1;return}}const m=await F({ankiConnectOptions:{autoLaunch:e,host:d,port:f},ankiWeb:i,css:l??void 0,dryRun:a}).catch(k);r?(process.stdout.write(JSON.stringify(m,void 0,2)),process.stdout.write(`
9
+ `)):(process.stderr.write(H(m,n)),process.stderr.write(`
10
+ `))}).demandCommand(1).alias("h","help").version(Q).alias("v","version").help().wrap(process.stdout.isTTY?Math.min(120,$.terminalWidth()):0).parse();
@@ -140,10 +140,11 @@ type DeckRequests = Request<'changeDeck', 6, {
140
140
 
141
141
  type NoteModel = 'Basic (and reversed card)' | 'Basic (type in the answer)' | 'Basic' | 'Cloze' | ({} & string);
142
142
  type NoteMedia = {
143
+ data?: string;
143
144
  fields: string[];
144
- filename: string;
145
+ path?: string;
145
146
  skipHash?: false;
146
- url: string;
147
+ url?: string;
147
148
  };
148
149
  type Note = {
149
150
  audio?: NoteMedia[];
@@ -266,11 +267,11 @@ type GraphicalRequests = Request<'guiBrowse', 6, {
266
267
  type MediaRequests = Request<'retrieveMediaFile', 6, {
267
268
  filename: string;
268
269
  }, false | string> | Request<'storeMediaFile', 6, {
269
- data: string;
270
- deleteExisting: boolean;
270
+ data?: string;
271
+ deleteExisting?: boolean;
271
272
  filename: string;
272
- path: string;
273
- url: string;
273
+ path?: string;
274
+ url?: string;
274
275
  }, string> | Request<'deleteMediaFile', 6, {
275
276
  filename: string;
276
277
  }> | Request<'getMediaDirPath', 6, never, string> | Request<'getMediaFilesNames', 6, {
@@ -527,12 +528,13 @@ type StatisticRequests = Request<'cardReviews', 6, {
527
528
  * external re-implementation when passing a custom fetch function to
528
529
  * YankiClient.
529
530
  */
530
- type YankiFetch = (input: string, init: {
531
- body: string;
532
- headers: Record<string, string>;
533
- method: string;
534
- mode: RequestMode;
531
+ type YankiFetchAdapter = (input: string, init?: {
532
+ body?: string;
533
+ headers?: Record<string, string>;
534
+ method?: string;
535
+ mode?: RequestMode;
535
536
  }) => Promise<{
537
+ headers: Headers | Record<string, string>;
536
538
  json(): Promise<any>;
537
539
  status: number;
538
540
  } | undefined>;
@@ -567,7 +569,7 @@ type YankiConnectOptions = {
567
569
  *
568
570
  * @default fetch
569
571
  */
570
- customFetch: YankiFetch | undefined;
572
+ fetchAdapter: YankiFetchAdapter | undefined;
571
573
  /**
572
574
  * Host where the Anki-Connect service is running.
573
575
  *
@@ -840,8 +842,21 @@ type YankiNote = Simplify<{
840
842
  noteId: number | undefined;
841
843
  } & Omit<ParamsForAction<'addNote'>['note'], 'fields' | 'modelName' | 'options'>>;
842
844
 
843
- declare const defaultCleanOptions: CleanOptions;
844
- type CleanOptions = {
845
+ type FetchAdapter = YankiFetchAdapter;
846
+ type ManageFilenames = 'off' | 'prompt' | 'response';
847
+ type SyncMediaAssets = 'all' | 'local' | 'off' | 'remote';
848
+ type FileAdapter = {
849
+ readFile(filePath: string): Promise<string>;
850
+ readFileBuffer(filePath: string): Promise<Uint8Array>;
851
+ rename(oldPath: string, newPath: string): Promise<void>;
852
+ stat(filePath: string): Promise<{
853
+ ctimeMs: number;
854
+ mtimeMs: number;
855
+ size: number;
856
+ }>;
857
+ writeFile(filePath: string, data: string): Promise<void>;
858
+ };
859
+ type GlobalOptions = {
845
860
  ankiConnectOptions: YankiConnectOptions;
846
861
  /**
847
862
  * Automatically sync any changes to AnkiWeb after Yanki has finished syncing
@@ -850,17 +865,34 @@ type CleanOptions = {
850
865
  * button in the Anki app.
851
866
  */
852
867
  ankiWeb: boolean;
868
+ cwd: string;
853
869
  dryRun: boolean;
870
+ /**
871
+ * Exposed for Obsidian, currently only used for getting URL content hashes
872
+ * and inferring MIME types of URLs without extensions.
873
+ * Note that ankiConnectOptions ALSO has a fetch adapter option specifically
874
+ * for communicating with Anki-Connect.
875
+ */
876
+ fetchAdapter: FetchAdapter | undefined;
877
+ fileAdapter: FileAdapter | undefined;
878
+ manageFilenames: ManageFilenames;
879
+ /** Only applies if manageFilenames is `true`. Will _not_ truncate user-specified file names in other cases. */
880
+ maxFilenameLength: number;
854
881
  namespace: string;
882
+ /** Ensures that wiki-style links work correctly */
883
+ obsidianVault: string | undefined;
884
+ /** Sync image, video, and audio assets to Anki's media storage system */
885
+ syncMediaAssets: SyncMediaAssets;
855
886
  };
856
- type CleanReport = {
857
- ankiWeb: boolean;
858
- decks: string[];
859
- deleted: YankiNote[];
860
- dryRun: boolean;
887
+
888
+ type CleanOptions = Pick<GlobalOptions, 'ankiConnectOptions' | 'ankiWeb' | 'dryRun' | 'namespace'>;
889
+ declare const defaultCleanOptions: CleanOptions;
890
+ type CleanResult = Simplify<{
891
+ deletedDecks: string[];
892
+ deletedMedia: string[];
893
+ deletedNotes: YankiNote[];
861
894
  duration: number;
862
- namespace: string;
863
- };
895
+ } & Pick<GlobalOptions, 'ankiWeb' | 'dryRun' | 'namespace'>>;
864
896
  /**
865
897
  * Deletes all remote notes in Anki associated with the given namespace.
866
898
  *
@@ -870,15 +902,12 @@ type CleanReport = {
870
902
  * @param options
871
903
  * @throws
872
904
  */
873
- declare function cleanNotes(options?: PartialDeep<CleanOptions>): Promise<CleanReport>;
874
- declare function formatCleanReport(report: CleanReport, verbose?: boolean): string;
905
+ declare function cleanNotes(options?: PartialDeep<CleanOptions>): Promise<CleanResult>;
906
+ declare function formatCleanResult(result: CleanResult, verbose?: boolean): string;
875
907
 
876
- type ListOptions = {
877
- ankiConnectOptions: YankiConnectOptions;
878
- namespace: string;
879
- };
908
+ type ListOptions = Pick<GlobalOptions, 'ankiConnectOptions' | 'namespace'>;
880
909
  declare const defaultListOptions: ListOptions;
881
- type ListReport = {
910
+ type ListResult = {
882
911
  duration: number;
883
912
  namespace: string;
884
913
  notes: YankiNote[];
@@ -888,107 +917,64 @@ type ListReport = {
888
917
  * @param options
889
918
  * @returns
890
919
  */
891
- declare function listNotes(options?: PartialDeep<ListOptions>): Promise<ListReport>;
892
- declare function formatListReport(report: ListReport): string;
893
-
894
- type RenameFilesReport = {
895
- dryRun: boolean;
896
- notes: Array<{
897
- filePath: string;
898
- filePathOriginal: string;
899
- markdown: string;
900
- note: YankiNote;
901
- }>;
920
+ declare function listNotes(options?: PartialDeep<ListOptions>): Promise<ListResult>;
921
+ declare function formatListResult(result: ListResult): string;
922
+
923
+ type LoadOptions = Pick<GlobalOptions, 'fetchAdapter' | 'fileAdapter' | 'namespace' | 'obsidianVault' | 'syncMediaAssets'>;
924
+ type LocalNote = {
925
+ filePath: string;
926
+ filePathOriginal: string;
927
+ markdown: string;
928
+ note: YankiNote;
902
929
  };
903
- type FilenameMode = 'prompt' | 'response';
904
- type RenameFilesOptions = {
930
+
931
+ type RenameNotesOptions = Pick<GlobalOptions, 'dryRun' | 'fileAdapter' | 'manageFilenames' | 'maxFilenameLength'>;
932
+ type RenameFilesResult = {
905
933
  dryRun: boolean;
906
- filenameMode: FilenameMode;
907
- manageFilenames: boolean;
908
- maxFilenameLength: number;
909
- namespace: string;
910
- obsidianVault: string | undefined;
934
+ notes: LocalNote[];
911
935
  };
936
+ type RenameFilesOptions = Simplify<LoadOptions & RenameNotesOptions>;
912
937
  declare const defaultRenameFilesOptions: RenameFilesOptions;
913
- /**
914
- * Also loads the notes from markdown and sets deck names...
915
- * @param allLocalFilePaths
916
- * @param options
917
- * @param readFile
918
- * @param writeFile
919
- * @param rename
920
- */
921
- declare function renameFiles(allLocalFilePaths: string[], options: Partial<RenameFilesOptions>, readFile?: (filePath: string) => Promise<string>, writeFile?: (filePath: string, data: string) => Promise<void>, // Not used, yet
922
- rename?: (oldPath: string, newPath: string) => Promise<void>): Promise<RenameFilesReport>;
938
+ declare function renameFiles(allLocalFilePaths: string[], options: Partial<RenameFilesOptions>): Promise<RenameFilesResult>;
923
939
 
924
- type StyleOptions = {
925
- ankiConnectOptions: YankiConnectOptions;
926
- /**
927
- * Automatically sync any changes to AnkiWeb after Yanki has finished syncing
928
- * locally. If false, only local Anki data is updated and you must manually
929
- * invoke a sync to AnkiWeb. This is the equivalent of pushing the "sync"
930
- * button in the Anki app.
931
- */
932
- ankiWeb: boolean;
940
+ type SetStyleOptions = {
933
941
  css: string;
934
- dryRun: boolean;
935
- };
936
- declare const defaultStyleOptions: StyleOptions;
937
- type StyleReport = {
938
- ankiWeb: boolean;
939
- dryRun: boolean;
942
+ } & Pick<GlobalOptions, 'ankiConnectOptions' | 'ankiWeb' | 'dryRun'>;
943
+ declare const defaultSetStyleOptions: SetStyleOptions;
944
+ type SetStyleResult = Simplify<{
940
945
  duration: number;
941
946
  models: Array<{
942
947
  action: 'unchanged' | 'updated';
943
948
  name: string;
944
949
  }>;
945
- };
946
- declare function setStyle(options: PartialDeep<StyleOptions>): Promise<StyleReport>;
947
- declare function formatStyleReport(report: StyleReport, verbose?: boolean): string;
950
+ } & Pick<GlobalOptions, 'ankiWeb' | 'dryRun'>>;
951
+ type GetStyleOptions = Pick<GlobalOptions, 'ankiConnectOptions'>;
952
+ declare const defaultGetStyleOptions: GetStyleOptions;
953
+ declare function getStyle(options: PartialDeep<GetStyleOptions>): Promise<string>;
954
+ declare function setStyle(options?: PartialDeep<SetStyleOptions>): Promise<SetStyleResult>;
955
+ declare function formatSetStyleResult(result: SetStyleResult, verbose?: boolean): string;
948
956
 
949
957
  type SyncedNote = {
950
958
  action: 'ankiUnreachable' | 'created' | 'deleted' | 'recreated' | 'unchanged' | 'updated';
951
- filePath?: string;
952
- filePathOriginal?: string;
953
959
  note: YankiNote;
954
960
  };
955
- type SyncOptions = {
956
- ankiConnectOptions: YankiConnectOptions;
957
- /**
958
- * Automatically sync any changes to AnkiWeb after Yanki has finished syncing
959
- * locally. If false, only local Anki data is updated and you must manually
960
- * invoke a sync to AnkiWeb. This is the equivalent of pushing the "sync"
961
- * button in the Anki app.
962
- */
963
- ankiWeb: boolean;
964
- defaultDeckName: string;
965
- dryRun: boolean;
966
- filenameMode: FilenameMode;
967
- /** Only applies to syncFiles */
968
- manageFilenames: boolean;
969
- /** Only applies if manageFilenames is not `false`. Will _not_ truncate user-specified file names in other cases. */
970
- maxFilenameLength: number;
971
- namespace: string;
972
- /** Ensures that wiki-style links work correctly */
973
- obsidianVault: string | undefined;
974
- };
975
- declare const defaultSyncOptions: SyncOptions;
976
- type SyncReport = {
977
- ankiWeb: boolean;
961
+ type SyncOptions = Pick<GlobalOptions, 'ankiConnectOptions' | 'ankiWeb' | 'dryRun' | 'namespace'>;
962
+ type SyncNotesResult = Simplify<{
978
963
  deletedDecks: string[];
979
- dryRun: boolean;
964
+ deletedMedia: string[];
980
965
  duration: number;
981
- namespace: string;
982
966
  synced: SyncedNote[];
983
- };
984
- /**
985
- * Syncs local notes to Anki.
986
- *
987
- * @param allLocalNotes All the YankiNotes to sync
988
- * @returns The synced notes (with new IDs where applicable), plus some stats
989
- * about the sync @throws
990
- */
991
- declare function syncNotes(allLocalNotes: YankiNote[], options?: PartialDeep<SyncOptions>): Promise<SyncReport>;
967
+ } & Pick<GlobalOptions, 'ankiWeb' | 'dryRun' | 'namespace'>>;
968
+
969
+ type SyncFilesOptions = Simplify<Pick<GlobalOptions, 'fetchAdapter' | 'fileAdapter' | 'manageFilenames' | 'maxFilenameLength' | 'namespace' | 'obsidianVault' | 'syncMediaAssets'> & SyncOptions>;
970
+ declare const defaultSyncFilesOptions: SyncFilesOptions;
971
+ type SyncedFile = Simplify<{
972
+ filePath: string | undefined;
973
+ filePathOriginal: string | undefined;
974
+ } & SyncedNote>;
975
+ type SyncFilesResult = Simplify<{
976
+ synced: SyncedFile[];
977
+ } & Omit<SyncNotesResult, 'synced'>>;
992
978
  /**
993
979
  * Sync a list of local yanki files to Anki.
994
980
  *
@@ -1001,25 +987,19 @@ declare function syncNotes(allLocalNotes: YankiNote[], options?: PartialDeep<Syn
1001
987
  * @returns The synced files (with new IDs where applicable), plus some stats
1002
988
  * about the sync @throws
1003
989
  */
1004
- declare function syncFiles(allLocalFilePaths: string[], options?: PartialDeep<SyncOptions>, readFile?: (filePath: string) => Promise<string>, writeFile?: (filePath: string, data: string) => Promise<void>, rename?: (oldPath: string, newPath: string) => Promise<void>): Promise<SyncReport>;
1005
- declare function formatSyncReport(report: SyncReport, verbose?: boolean): string;
1006
-
1007
- /**
1008
- * Helpers for working with the Markdown AST.
1009
- */
1010
-
1011
- type AstFromMarkdownOptions = {
1012
- obsidianVault?: string;
1013
- };
990
+ declare function syncFiles(allLocalFilePaths: string[], options?: PartialDeep<SyncFilesOptions>): Promise<SyncFilesResult>;
991
+ declare function formatSyncFilesResult(result: SyncFilesResult, verbose?: boolean): string;
1014
992
 
1015
993
  /**
1016
994
  * Turns a markdown string into a YankiNote object.
1017
995
  */
1018
996
 
1019
- type NoteFromMarkdownOptions = {
1020
- namespace: string;
1021
- } & AstFromMarkdownOptions;
1022
- declare function getNoteFromMarkdown(markdown: string, options: NoteFromMarkdownOptions): Promise<YankiNote>;
997
+ type GetNoteFromMarkdownOptions = {
998
+ /** Needed for the public API, but optional for more efficient use internally when the namespace is already validated. */
999
+ namespaceValidationAndSanitization: boolean;
1000
+ } & Pick<GlobalOptions, 'cwd' | 'fetchAdapter' | 'fileAdapter' | 'namespace' | 'obsidianVault' | 'syncMediaAssets'>;
1001
+ declare const defaultGetNoteFromMarkdownOptions: GetNoteFromMarkdownOptions;
1002
+ declare function getNoteFromMarkdown(markdown: string, options?: Partial<GetNoteFromMarkdownOptions>): Promise<YankiNote>;
1023
1003
 
1024
1004
  declare function urlToHostAndPort(url: string): {
1025
1005
  host: string;
@@ -1027,4 +1007,4 @@ declare function urlToHostAndPort(url: string): {
1027
1007
  };
1028
1008
  declare function hostAndPortToUrl(host: string, port: number): string;
1029
1009
 
1030
- export { type CleanOptions, type CleanReport, type ListOptions, type RenameFilesOptions, type RenameFilesReport, type StyleOptions, type StyleReport, type SyncOptions, type SyncReport, type YankiNote, cleanNotes, defaultCleanOptions, defaultListOptions, defaultRenameFilesOptions, defaultStyleOptions, defaultSyncOptions, formatCleanReport, formatListReport, formatStyleReport, formatSyncReport, getNoteFromMarkdown, hostAndPortToUrl, listNotes, renameFiles, setStyle, syncFiles, syncNotes, urlToHostAndPort };
1010
+ export { type CleanOptions, type CleanResult, type FetchAdapter, type FileAdapter, type GetNoteFromMarkdownOptions, type GetStyleOptions, type ListOptions, type RenameFilesOptions, type RenameFilesResult, type SetStyleOptions, type SetStyleResult, type SyncFilesOptions, type SyncFilesResult, type YankiNote, cleanNotes, defaultCleanOptions, defaultGetNoteFromMarkdownOptions, defaultGetStyleOptions, defaultListOptions, defaultRenameFilesOptions, defaultSetStyleOptions, defaultSyncFilesOptions, formatCleanResult, formatListResult, formatSetStyleResult, formatSyncFilesResult, getNoteFromMarkdown, getStyle, hostAndPortToUrl, listNotes, renameFiles, setStyle, syncFiles, urlToHostAndPort };
package/dist/lib/index.js CHANGED
@@ -1 +1 @@
1
- import{c as l,d as n,a as i,e as p,g as m,i as f,f as d,b as u,h as y,j as c,n as O,o as R,l as S,r as F,s as N,k as g,m as h,u as k}from"../sync-B_u0fKEU.js";import"rehype-mathjax";import"rehype-parse";import"node:path";import"node:crypto";export{l as cleanNotes,n as defaultCleanOptions,i as defaultListOptions,p as defaultRenameFilesOptions,m as defaultStyleOptions,f as defaultSyncOptions,d as formatCleanReport,u as formatListReport,y as formatStyleReport,c as formatSyncReport,O as getNoteFromMarkdown,R as hostAndPortToUrl,S as listNotes,F as renameFiles,N as setStyle,g as syncFiles,h as syncNotes,k as urlToHostAndPort};
1
+ import{c as i,d as n,o as r,g as m,a as f,e as p,h as u,k as d,f as S,b as y,i as F,m as O,p as c,j as R,q as N,l as g,r as k,s as h,n as w,u as A}from"../sync-files-norUev7R.js";import"rehype-mathjax";import"rehype-parse";import"node:path";import"node:crypto";export{i as cleanNotes,n as defaultCleanOptions,r as defaultGetNoteFromMarkdownOptions,m as defaultGetStyleOptions,f as defaultListOptions,p as defaultRenameFilesOptions,u as defaultSetStyleOptions,d as defaultSyncFilesOptions,S as formatCleanResult,y as formatListResult,F as formatSetStyleResult,O as formatSyncFilesResult,c as getNoteFromMarkdown,R as getStyle,N as hostAndPortToUrl,g as listNotes,k as renameFiles,h as setStyle,w as syncFiles,A as urlToHostAndPort};