yanki 0.8.6 → 0.9.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,5 +1,5 @@
1
1
  #!/usr/bin/env node
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-BpGql9Vs.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"slash";import G from"node:os";import K from"yargs";import{hideBin as Q}from"yargs/helpers";import"rehype-mathjax";import"rehype-parse";import"node:crypto";var V="0.8.6";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=G.homedir();function X(e){if(typeof e!="string")throw new TypeError(`Expected a string, got ${typeof e}`);return E?e.replace(/^~(?=$|\/|\\)/,E):e}u(X,"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"),$=K(Q(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=B(X(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(`
2
+ var J=Object.defineProperty;var u=(e,t)=>J(e,"name",{value:t,configurable:!0});import{w as h,v 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-oN-EsBHo.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"slash";import G from"node:os";import K from"yargs";import{hideBin as Q}from"yargs/helpers";import"rehype-mathjax";import"rehype-parse";import"node:crypto";var V="0.9.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=G.homedir();function X(e){if(typeof e!="string")throw new TypeError(`Expected a string, got ${typeof e}`);return E?e.replace(/^~(?=$|\/|\\)/,E):e}u(X,"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"),$=K(Q(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=B(X(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
3
  `)):(process.stderr.write(I(N,p)),process.stderr.write(`
4
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
5
  `)):(process.stdout.write(U(n)),process.stdout.write(`
@@ -960,15 +960,24 @@ type SyncedNote = {
960
960
  action: 'ankiUnreachable' | 'created' | 'deleted' | 'recreated' | 'unchanged' | 'updated';
961
961
  note: YankiNote;
962
962
  };
963
- type SyncOptions = Pick<GlobalOptions, 'ankiConnectOptions' | 'ankiWeb' | 'dryRun' | 'namespace'>;
963
+ type SyncNotesOptions = Pick<GlobalOptions, 'ankiConnectOptions' | 'ankiWeb' | 'dryRun' | 'namespace'>;
964
+ declare const defaultSyncNotesOptions: SyncNotesOptions;
964
965
  type SyncNotesResult = Simplify<{
965
966
  deletedDecks: string[];
966
967
  deletedMedia: string[];
967
968
  duration: number;
968
969
  synced: SyncedNote[];
969
970
  } & Pick<GlobalOptions, 'ankiWeb' | 'dryRun' | 'namespace'>>;
971
+ /**
972
+ * Syncs local notes to Anki.
973
+ *
974
+ * @param allLocalNotes All the YankiNotes to sync
975
+ * @returns The synced notes (with new IDs where applicable), plus some stats
976
+ * about the sync @throws
977
+ */
978
+ declare function syncNotes(allLocalNotes: YankiNote[], options?: PartialDeep<SyncNotesOptions>): Promise<SyncNotesResult>;
970
979
 
971
- type SyncFilesOptions = Simplify<Pick<GlobalOptions, 'basePath' | 'fetchAdapter' | 'fileAdapter' | 'manageFilenames' | 'maxFilenameLength' | 'namespace' | 'obsidianVault' | 'syncMediaAssets'> & SyncOptions>;
980
+ type SyncFilesOptions = Simplify<Pick<GlobalOptions, 'basePath' | 'fetchAdapter' | 'fileAdapter' | 'manageFilenames' | 'maxFilenameLength' | 'namespace' | 'obsidianVault' | 'syncMediaAssets'> & SyncNotesOptions>;
972
981
  declare const defaultSyncFilesOptions: SyncFilesOptions;
973
982
  type SyncedFile = Simplify<{
974
983
  filePath: string | undefined;
@@ -1009,4 +1018,4 @@ declare function urlToHostAndPort(url: string): {
1009
1018
  };
1010
1019
  declare function hostAndPortToUrl(host: string, port: number): string;
1011
1020
 
1012
- 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 };
1021
+ 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 SyncNotesOptions, type SyncNotesResult, type YankiNote, cleanNotes, defaultCleanOptions, defaultGetNoteFromMarkdownOptions, defaultGetStyleOptions, defaultListOptions, defaultRenameFilesOptions, defaultSetStyleOptions, defaultSyncFilesOptions, defaultSyncNotesOptions, formatCleanResult, formatListResult, formatSetStyleResult, formatSyncFilesResult, getNoteFromMarkdown, getStyle, hostAndPortToUrl, listNotes, renameFiles, setStyle, syncFiles, syncNotes, urlToHostAndPort };
package/dist/lib/index.js CHANGED
@@ -1 +1 @@
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-BpGql9Vs.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};
1
+ import{c as n,d as i,q as r,g as f,a as m,e as p,h as u,k as d,o as y,f as S,b as O,i as c,m as F,t as N,j as R,u as g,l as k,r as h,s as w,n as A,p as C,v as G}from"../sync-files-oN-EsBHo.js";import"rehype-mathjax";import"rehype-parse";import"node:path";import"node:crypto";export{n as cleanNotes,i as defaultCleanOptions,r as defaultGetNoteFromMarkdownOptions,f as defaultGetStyleOptions,m as defaultListOptions,p as defaultRenameFilesOptions,u as defaultSetStyleOptions,d as defaultSyncFilesOptions,y as defaultSyncNotesOptions,S as formatCleanResult,O as formatListResult,c as formatSetStyleResult,F as formatSyncFilesResult,N as getNoteFromMarkdown,R as getStyle,g as hostAndPortToUrl,k as listNotes,h as renameFiles,w as setStyle,A as syncFiles,C as syncNotes,G as urlToHostAndPort};