diff --git a/apps/obsidian/AGENTS.md b/apps/obsidian/AGENTS.md index 87514d5b30..33e1d32b49 100644 --- a/apps/obsidian/AGENTS.md +++ b/apps/obsidian/AGENTS.md @@ -91,6 +91,8 @@ for (let leaf of app.workspace.getActiveLeavesOfType(MY_VIEW_TYPE)) { - Node.js and Electron APIs (`fs`, `crypto`, `os`) are unavailable on mobile - If the plugin targets mobile, use web API equivalents: `SubtleCrypto` instead of `crypto`, `navigator.clipboard` for clipboard access - Regex lookbehind assertions are not supported on some mobile — avoid them if possible +- The plugin ships with `isDesktopOnly: false`, so it must load on mobile +- Node builtins and `electron` are deliberately not in the esbuild `external` array in `scripts/compile.ts`. Marking them external lets a dependency's `require("fs")` reach the bundle and throw on mobile; leaving them out fails the build instead. Replace the dependency rather than re-adding the external ### Code quality diff --git a/apps/obsidian/manifest.json b/apps/obsidian/manifest.json index 142ec82d9d..9d7e4c0672 100644 --- a/apps/obsidian/manifest.json +++ b/apps/obsidian/manifest.json @@ -6,5 +6,5 @@ "description": "Add semantic structure to your notes with the Discourse Graph protocol.", "author": "Discourse Graphs", "authorUrl": "https://discoursegraphs.com", - "isDesktopOnly": true + "isDesktopOnly": false } diff --git a/apps/obsidian/package.json b/apps/obsidian/package.json index 474f3dfa4d..8ff3795090 100644 --- a/apps/obsidian/package.json +++ b/apps/obsidian/package.json @@ -22,12 +22,10 @@ "@octokit/core": "^6.1.2", "@repo/eslint-config": "workspace:*", "@repo/typescript-config": "workspace:*", - "@types/mime-types": "3.0.1", "@types/node": "catalog:", "@types/react": "catalog:obsidian", "@types/react-dom": "catalog:obsidian", "autoprefixer": "^10.4.21", - "builtin-modules": "3.3.0", "dotenv": "^16.4.5", "esbuild": "0.17.3", "eslint": "catalog:", @@ -39,6 +37,7 @@ "typescript": "5.5.4", "uuidv7": "1.1.0", "vitest": "catalog:", + "yaml": "^2.8.1", "zod": "^3.24.1" }, "dependencies": { @@ -48,8 +47,6 @@ "@repo/utils": "workspace:*", "@supabase/supabase-js": "catalog:", "date-fns": "^4.1.0", - "gray-matter": "^4.0.3", - "mime-types": "^3.0.1", "nanoid": "^4.0.2", "react": "catalog:obsidian", "react-dom": "catalog:obsidian", diff --git a/apps/obsidian/scripts/compile.ts b/apps/obsidian/scripts/compile.ts index 84326b12d7..be07ac8a4b 100644 --- a/apps/obsidian/scripts/compile.ts +++ b/apps/obsidian/scripts/compile.ts @@ -2,7 +2,6 @@ import esbuild from "esbuild"; import fs from "fs"; import path from "path"; import { z } from "zod"; -import builtins from "builtin-modules"; import dotenv from "dotenv"; import postcss from "postcss"; import tailwindcss from "tailwindcss"; @@ -43,9 +42,11 @@ export const args = { format: "cjs", root: ".", mirror: process.env.OBSIDIAN_PLUGIN_PATH, + // Node builtins and "electron" are deliberately NOT external. Marking them external lets a + // dependency's `require("fs")` survive into the bundle, which throws at runtime on Obsidian + // mobile; leaving them out fails the build instead. Replace the dependency, do not re-add these. external: [ "obsidian", - "electron", "@codemirror/autocomplete", "@codemirror/collab", "@codemirror/commands", @@ -58,7 +59,6 @@ export const args = { "@lezer/highlight", "@lezer/lr", "tslib=window.TSLib", - ...builtins, ], } as CliOpts; @@ -115,6 +115,7 @@ export const compile = ({ outdir, bundle: true, format, + platform: "browser", sourcemap: isProd ? undefined : "inline", minify: isProd, entryNames: out, diff --git a/apps/obsidian/src/utils/__tests__/mimeType.test.ts b/apps/obsidian/src/utils/__tests__/mimeType.test.ts new file mode 100644 index 0000000000..8e9022fc4d --- /dev/null +++ b/apps/obsidian/src/utils/__tests__/mimeType.test.ts @@ -0,0 +1,49 @@ +import { describe, expect, it } from "vitest"; +import { DEFAULT_MIME_TYPE, getMimeTypeForPath } from "~/utils/mimeType"; + +describe("getMimeTypeForPath", () => { + it("types the attachment kinds Obsidian accepts", () => { + expect(getMimeTypeForPath("a/b.png")).toBe("image/png"); + expect(getMimeTypeForPath("a/b.pdf")).toBe("application/pdf"); + expect(getMimeTypeForPath("a/b.mp4")).toBe("video/mp4"); + expect(getMimeTypeForPath("a/b.mp3")).toBe("audio/mpeg"); + }); + + it("ignores extension case", () => { + expect(getMimeTypeForPath("A.PNG")).toBe("image/png"); + expect(getMimeTypeForPath("A.JpEg")).toBe("image/jpeg"); + }); + + // The caller skips every `text/` attachment, so an extension missing from the + // table would start being uploaded. Transcluded notes are the common case. + it("keeps transcluded text files under a text/ type", () => { + for (const path of ["note.md", "note.markdown", "a.mdx", "a.txt", "a.csv"]) + expect(getMimeTypeForPath(path).startsWith("text/")).toBe(true); + }); + + it("does not classify xml as text, which would stop it being uploaded", () => { + expect(getMimeTypeForPath("a.xml")).toBe("application/xml"); + }); + + it("uses the last extension of a multi-dot name", () => { + expect(getMimeTypeForPath("archive.tar.png")).toBe("image/png"); + }); + + it("ignores dots in parent directories", () => { + expect(getMimeTypeForPath("folder.v2/asset.png")).toBe("image/png"); + }); + + // A plain object literal would return an inherited member here, and the + // caller's `.startsWith` on it aborts the node's whole asset sync. + it("falls back for an extension that names an Object prototype member", () => { + for (const path of ["a.constructor", "a.__proto__", "a.CONSTRUCTOR"]) + expect(getMimeTypeForPath(path)).toBe(DEFAULT_MIME_TYPE); + }); + + it("falls back for unknown, extensionless and dotfile paths", () => { + expect(getMimeTypeForPath("a.unknownext")).toBe(DEFAULT_MIME_TYPE); + expect(getMimeTypeForPath("noextension")).toBe(DEFAULT_MIME_TYPE); + expect(getMimeTypeForPath("dir/.config")).toBe(DEFAULT_MIME_TYPE); + expect(getMimeTypeForPath("trailing.")).toBe(DEFAULT_MIME_TYPE); + }); +}); diff --git a/apps/obsidian/src/utils/__tests__/parseFrontmatter.test.ts b/apps/obsidian/src/utils/__tests__/parseFrontmatter.test.ts new file mode 100644 index 0000000000..321ff8ae9c --- /dev/null +++ b/apps/obsidian/src/utils/__tests__/parseFrontmatter.test.ts @@ -0,0 +1,42 @@ +import { describe, expect, it } from "vitest"; +import { parseFrontmatter } from "~/utils/importNodes"; + +describe("parseFrontmatter", () => { + it("reads the fields callers depend on", () => { + const { frontmatter } = parseFrontmatter( + "---\nnodeTypeId: 7f3a9c12-4b8e\nimportedFromRid: rid://abc-123\n---\nbody", + ); + expect(frontmatter.nodeTypeId).toBe("7f3a9c12-4b8e"); + expect(frontmatter.importedFromRid).toBe("rid://abc-123"); + }); + + it("returns empty frontmatter when the file has no block", () => { + expect(parseFrontmatter("just body")).toEqual({ + frontmatter: {}, + body: "just body", + }); + }); + + // `gray-matter` threw here. Obsidian tolerates bad YAML, so the import keeps + // going and the node type falls back to the one the import supplies. + it("treats unparseable yaml as absent instead of throwing", () => { + expect(() => + parseFrontmatter("---\na: [unclosed\n---\nbody"), + ).not.toThrow(); + expect( + parseFrontmatter("---\na: [unclosed\n---\nbody").frontmatter, + ).toEqual({}); + }); + + it("strips the block from the body it returns, even for bad yaml", () => { + expect(parseFrontmatter("---\na: [unclosed\n---\nbody").body).toBe("body"); + }); + + // Obsidian's parseYaml is the `yaml` package, so an unquoted date stays a + // string. No caller reads a date field; this pins the engine's behaviour. + it("leaves an unquoted date as a string", () => { + expect( + parseFrontmatter("---\ncreated: 2026-08-07\n---\n").frontmatter, + ).toEqual({ created: "2026-08-07" }); + }); +}); diff --git a/apps/obsidian/src/utils/__tests__/splitFrontmatter.test.ts b/apps/obsidian/src/utils/__tests__/splitFrontmatter.test.ts new file mode 100644 index 0000000000..02158f4892 --- /dev/null +++ b/apps/obsidian/src/utils/__tests__/splitFrontmatter.test.ts @@ -0,0 +1,63 @@ +import { describe, expect, it } from "vitest"; +import { splitFrontmatter } from "~/utils/splitFrontmatter"; + +describe("splitFrontmatter", () => { + it("splits a standard block", () => { + expect(splitFrontmatter("---\na: 1\n---\nbody\n")).toEqual({ + yaml: "a: 1", + body: "body\n", + }); + }); + + it("handles CRLF line endings", () => { + expect(splitFrontmatter("---\r\na: 1\r\n---\r\nbody")).toEqual({ + yaml: "a: 1", + body: "body", + }); + }); + + it("reports an empty block as empty yaml, not as absent", () => { + expect(splitFrontmatter("---\n---\nbody")).toEqual({ + yaml: "", + body: "body", + }); + }); + + it("returns null yaml when the file has no block", () => { + expect(splitFrontmatter("just body\n")).toEqual({ + yaml: null, + body: "just body\n", + }); + }); + + it("leaves a horizontal rule in the body alone", () => { + const content = "intro\n\n---\n\nafter the rule\n"; + expect(splitFrontmatter(content)).toEqual({ yaml: null, body: content }); + }); + + it("treats a file that is only frontmatter as having an empty body", () => { + expect(splitFrontmatter("---\na: 1\n---\n")).toEqual({ + yaml: "a: 1", + body: "", + }); + }); + + it("keeps multi-line yaml values intact", () => { + const { yaml } = splitFrontmatter( + "---\ntags:\n - one\n - two\n---\nbody", + ); + expect(yaml).toBe("tags:\n - one\n - two"); + }); + + it("tolerates trailing spaces on the fences", () => { + expect(splitFrontmatter("--- \na: 1\n--- \nbody")).toEqual({ + yaml: "a: 1", + body: "body", + }); + }); + + it("ignores a block that does not start at the first character", () => { + const content = "\n---\na: 1\n---\nbody"; + expect(splitFrontmatter(content)).toEqual({ yaml: null, body: content }); + }); +}); diff --git a/apps/obsidian/src/utils/importNodes.ts b/apps/obsidian/src/utils/importNodes.ts index dbc9429108..f236eb1fee 100644 --- a/apps/obsidian/src/utils/importNodes.ts +++ b/apps/obsidian/src/utils/importNodes.ts @@ -1,5 +1,4 @@ -import matter from "gray-matter"; -import { App, Notice, TFile } from "obsidian"; +import { App, Notice, TFile, parseYaml } from "obsidian"; import type { DGSupabaseClient } from "@repo/database/lib/client"; import { listGroupSharedNodes } from "@repo/database/lib/sharedNodes"; import type DiscourseGraphPlugin from "~/index"; @@ -39,6 +38,7 @@ import { } from "./importedNodeContent"; import { decorateTitle } from "@repo/database/lib/decorateTitle"; import { buildSchemaRid, findLocalNodeTypeMatch } from "./schemaMatching"; +import { splitFrontmatter } from "~/utils/splitFrontmatter"; type PublishedNode = { source_local_id: string; @@ -697,12 +697,18 @@ const updateMarkdownAssetLinks = ({ }, ); - // Match markdown links (non-image): [text](path) — internal paths resolved like wikilinks, href kept URL-encoded - const markdownLinkRegex = /(? { - if (!linkPath) return match; + // A rest parameter keeps this within the arrow-function parameter limit. + (match, ...groups: string[]) => { + const [imagePrefix, linkText, rawLinkPath] = groups; + // An `!` prefix makes this an image embed, handled by the image pass below. + if (imagePrefix) return match; + if (!rawLinkPath) return match; + let linkPath = rawLinkPath; // Resolve by row before looking at the shape of the link: a Roam-origin asset is // referenced by its storage URL. Matched on the raw link, because the decoding // below would mangle the percent-escaping a storage URL carries. @@ -1176,14 +1182,19 @@ type ParsedFrontmatter = { [key: string]: unknown; }; +// Unparseable frontmatter is treated as absent rather than thrown, matching how +// Obsidian itself tolerates bad YAML. `gray-matter` threw here instead. export const parseFrontmatter = ( content: string, ): { frontmatter: ParsedFrontmatter; body: string } => { - const { data, content: body } = matter(content); - return { - frontmatter: (data ?? {}) as ParsedFrontmatter, - body: body ?? "", - }; + const { yaml, body } = splitFrontmatter(content); + if (yaml === null) return { frontmatter: {}, body }; + try { + const data = parseYaml(yaml) as ParsedFrontmatter | null; + return { frontmatter: data ?? {}, body }; + } catch { + return { frontmatter: {}, body }; + } }; /** diff --git a/apps/obsidian/src/utils/mimeType.ts b/apps/obsidian/src/utils/mimeType.ts new file mode 100644 index 0000000000..2069a1f499 --- /dev/null +++ b/apps/obsidian/src/utils/mimeType.ts @@ -0,0 +1,151 @@ +// Replaces the `mime-types` package, which requires Node's `path` module and so +// cannot load on Obsidian mobile. Values are copied from mime-db so the type +// stored against an uploaded asset does not change. +// Null-prototyped so an attachment named `x.constructor` or `x.__proto__` misses +// rather than returning an inherited member, which is not a string. +const MIME_TYPES_BY_EXTENSION: Record = Object.assign( + Object.create(null) as Record, + { + // Text. Embeds include transcluded notes, and the caller skips every `text/` + // attachment, so an extension missing here would start being uploaded. + "3dml": "text/vnd.in3d.3dml", + appcache: "text/cache-manifest", + asm: "text/x-asm", + c: "text/x-c", + cc: "text/x-c", + coffee: "text/coffeescript", + conf: "text/plain", + cpp: "text/x-c", + css: "text/css", + csv: "text/csv", + curl: "text/vnd.curl", + cxx: "text/x-c", + dcurl: "text/vnd.curl.dcurl", + def: "text/plain", + dic: "text/x-c", + dsc: "text/prs.lines.tag", + etx: "text/x-setext", + f: "text/x-fortran", + f77: "text/x-fortran", + f90: "text/x-fortran", + flx: "text/vnd.fmi.flexstor", + fly: "text/vnd.fly", + for: "text/x-fortran", + ged: "text/vnd.familysearch.gedcom", + gv: "text/vnd.graphviz", + h: "text/x-c", + hbs: "text/x-handlebars-template", + hh: "text/x-c", + htc: "text/x-component", + htm: "text/html", + html: "text/html", + ics: "text/calendar", + ifb: "text/calendar", + in: "text/plain", + ini: "text/plain", + jad: "text/vnd.sun.j2me.app-descriptor", + jade: "text/jade", + java: "text/x-java-source", + js: "text/javascript", + jsx: "text/jsx", + less: "text/less", + list: "text/plain", + litcoffee: "text/coffeescript", + log: "text/plain", + lua: "text/x-lua", + man: "text/troff", + manifest: "text/cache-manifest", + markdown: "text/markdown", + mcurl: "text/vnd.curl.mcurl", + md: "text/markdown", + mdx: "text/mdx", + me: "text/troff", + mjs: "text/javascript", + mkd: "text/x-markdown", + mml: "text/mathml", + ms: "text/troff", + n3: "text/n3", + nfo: "text/x-nfo", + opml: "text/x-opml", + p: "text/x-pascal", + pas: "text/x-pascal", + pde: "text/x-processing", + roff: "text/troff", + rtx: "text/richtext", + s: "text/x-asm", + sass: "text/x-sass", + scss: "text/x-scss", + scurl: "text/vnd.curl.scurl", + sfv: "text/x-sfv", + sgm: "text/sgml", + sgml: "text/sgml", + shex: "text/shex", + shtml: "text/html", + slim: "text/slim", + slm: "text/slim", + spdx: "text/spdx", + spot: "text/vnd.in3d.spot", + styl: "text/stylus", + stylus: "text/stylus", + sub: "text/vnd.dvb.subtitle", + t: "text/troff", + text: "text/plain", + tr: "text/troff", + tsv: "text/tab-separated-values", + ttl: "text/turtle", + txt: "text/plain", + uri: "text/uri-list", + uris: "text/uri-list", + urls: "text/uri-list", + uu: "text/x-uuencode", + vcard: "text/vcard", + vcf: "text/x-vcard", + vcs: "text/x-vcalendar", + vtt: "text/vtt", + wgsl: "text/wgsl", + wml: "text/vnd.wap.wml", + wmls: "text/vnd.wap.wmlscript", + yaml: "text/yaml", + yml: "text/yaml", + ymp: "text/x-suse-ymp", + // Attachment types Obsidian accepts. + png: "image/png", + webp: "image/webp", + jpg: "image/jpeg", + jpeg: "image/jpeg", + gif: "image/gif", + bmp: "image/bmp", + svg: "image/svg+xml", + avif: "image/avif", + ico: "image/vnd.microsoft.icon", + tif: "image/tiff", + tiff: "image/tiff", + mp3: "audio/mpeg", + webm: "video/webm", + wav: "audio/wav", + m4a: "audio/mp4", + ogg: "audio/ogg", + oga: "audio/ogg", + "3gp": "video/3gpp", + flac: "audio/x-flac", + aac: "audio/aac", + mp4: "video/mp4", + ogv: "video/ogg", + mov: "video/quicktime", + mkv: "video/x-matroska", + pdf: "application/pdf", + json: "application/json", + xml: "application/xml", + }, +); + +export const DEFAULT_MIME_TYPE = "application/octet-stream"; + +export const getMimeTypeForPath = (filePath: string): string => { + const fileName = filePath.split("/").pop() ?? ""; + const lastDotIndex = fileName.lastIndexOf("."); + // A leading dot is a dotfile, not an extension. + if (lastDotIndex <= 0) return DEFAULT_MIME_TYPE; + const extension = fileName.slice(lastDotIndex + 1).toLowerCase(); + return MIME_TYPES_BY_EXTENSION[extension] ?? DEFAULT_MIME_TYPE; +}; diff --git a/apps/obsidian/src/utils/splitFrontmatter.ts b/apps/obsidian/src/utils/splitFrontmatter.ts new file mode 100644 index 0000000000..f474870791 --- /dev/null +++ b/apps/obsidian/src/utils/splitFrontmatter.ts @@ -0,0 +1,20 @@ +// Replaces the frontmatter splitting `gray-matter` did. That package requires +// Node's `fs` at module load, so it cannot run on Obsidian mobile. Kept free of +// any `obsidian` import so it stays a pure, directly testable function; callers +// pass the returned YAML to Obsidian's own `parseYaml`. +const FRONTMATTER_BLOCK = + /^---[ \t]*\r?\n([\s\S]*?)\r?\n?^---[ \t]*(?:\r?\n|$)/m; + +export type SplitFrontmatter = { + /** Raw YAML source, or null when the content has no frontmatter block. */ + yaml: string | null; + body: string; +}; + +export const splitFrontmatter = (content: string): SplitFrontmatter => { + const match = FRONTMATTER_BLOCK.exec(content); + // The block only counts as frontmatter at the very start of the file; + // a `---` further down is a horizontal rule. + if (!match || match.index !== 0) return { yaml: null, body: content }; + return { yaml: match[1] ?? "", body: content.slice(match[0].length) }; +}; diff --git a/apps/obsidian/src/utils/syncDgNodesToSupabase.ts b/apps/obsidian/src/utils/syncDgNodesToSupabase.ts index 40880fc696..4098f9f0b3 100644 --- a/apps/obsidian/src/utils/syncDgNodesToSupabase.ts +++ b/apps/obsidian/src/utils/syncDgNodesToSupabase.ts @@ -1,7 +1,7 @@ import { Notice, TFile } from "obsidian"; import { addFile } from "@repo/database/lib/files"; import { isAssetTooLarge } from "@repo/database/lib/assetLimits"; -import mime from "mime-types"; +import { getMimeTypeForPath } from "~/utils/mimeType"; import { ensureNodeInstanceId } from "~/utils/nodeInstanceId"; import type { DGSupabaseClient } from "@repo/database/lib/client"; import type { Json } from "@repo/database/dbTypes"; @@ -939,7 +939,7 @@ export const syncPublishedNodeAssets = async ({ for (const { link, file: attachment } of attachments) { // The extension comes from the resolved file: a link may be written without one. - const mimetype = mime.lookup(attachment.path) || "application/octet-stream"; + const mimetype = getMimeTypeForPath(attachment.path); if (mimetype.startsWith("text/")) continue; // Do not use standard upload for large files if (isAssetTooLarge(attachment.stat.size)) { diff --git a/apps/obsidian/test/obsidianStub.conformance.ts b/apps/obsidian/test/obsidianStub.conformance.ts index a6d1b6d79b..b148b78cee 100644 --- a/apps/obsidian/test/obsidianStub.conformance.ts +++ b/apps/obsidian/test/obsidianStub.conformance.ts @@ -20,6 +20,7 @@ const conformant = { Platform: stub.Platform satisfies typeof ObsidianApi.Platform, normalizePath: stub.normalizePath satisfies typeof ObsidianApi.normalizePath, parseLinktext: stub.parseLinktext satisfies typeof ObsidianApi.parseLinktext, + parseYaml: stub.parseYaml satisfies typeof ObsidianApi.parseYaml, setIcon: stub.setIcon satisfies typeof ObsidianApi.setIcon, setTooltip: stub.setTooltip satisfies typeof ObsidianApi.setTooltip, debounce: stub.debounce satisfies typeof ObsidianApi.debounce, diff --git a/apps/obsidian/test/obsidianStub.ts b/apps/obsidian/test/obsidianStub.ts index 2a22a0ea77..88c997de7d 100644 --- a/apps/obsidian/test/obsidianStub.ts +++ b/apps/obsidian/test/obsidianStub.ts @@ -7,6 +7,7 @@ * "Keeping the stub honest" in AGENTS.md before adding one. */ +import { parse as parseYamlUpstream } from "yaml"; import type { Debouncer, FileStats, @@ -95,6 +96,12 @@ export const normalizePath = (path: string): string => { .normalize("NFC"); }; +/** + * Obsidian's `parseYaml` is `YAML.parse` from the `yaml` package, not js-yaml, + * so the stub delegates to that package rather than reimplementing it. + */ +export const parseYaml = (yaml: string): unknown => parseYamlUpstream(yaml); + export const parseLinktext = ( linktext: string, ): { path: string; subpath: string } => { diff --git a/apps/website/content/obsidian/welcome/installation.md b/apps/website/content/obsidian/welcome/installation.md index 35bc0f7513..e5f23b3e58 100644 --- a/apps/website/content/obsidian/welcome/installation.md +++ b/apps/website/content/obsidian/welcome/installation.md @@ -27,3 +27,14 @@ Once Datacore is installed and enabled, add the Discourse Graphs plugin from the 3. Search for "Discourse Graphs", then install and enable it Or install directly from the [community plugin page](https://community.obsidian.md/plugins/discourse-graphs). + +## Mobile support + +The plugin installs and runs on Obsidian mobile, but support there is limited. Prefer the desktop app for day-to-day work. + +On mobile: + +- Run features from the command palette. Several actions are only reachable by hover, right-click, drag, or a keyboard shortcut on desktop, and have no touch equivalent yet. +- Use the desktop app for advanced features. Sync, the admin panel, and features released as beta are built and tested against desktop, and are not supported on mobile. +- Exporting and importing a discourse graph schema is unavailable, because it needs a desktop file dialog. +- The help menu is unavailable, because mobile has no status bar. diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index eebdf276ac..f1223942ad 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -139,12 +139,6 @@ importers: date-fns: specifier: ^4.1.0 version: 4.1.0 - gray-matter: - specifier: ^4.0.3 - version: 4.0.3 - mime-types: - specifier: ^3.0.1 - version: 3.0.2 nanoid: specifier: ^4.0.2 version: 4.0.2 @@ -170,9 +164,6 @@ importers: '@repo/typescript-config': specifier: workspace:* version: link:../../packages/typescript-config - '@types/mime-types': - specifier: 3.0.1 - version: 3.0.1 '@types/node': specifier: 'catalog:' version: 22.20.0 @@ -185,9 +176,6 @@ importers: autoprefixer: specifier: ^10.4.21 version: 10.4.21(postcss@8.5.6) - builtin-modules: - specifier: 3.3.0 - version: 3.3.0 dotenv: specifier: ^16.4.5 version: 16.6.1 @@ -221,6 +209,9 @@ importers: vitest: specifier: 'catalog:' version: 4.1.6(@edge-runtime/vm@3.2.0)(@opentelemetry/api@1.9.0)(@types/node@22.20.0)(jsdom@20.0.3)(msw@2.11.1(@types/node@22.20.0)(typescript@5.5.4))(vite@7.3.3(@types/node@22.20.0)(jiti@1.21.7)(tsx@4.20.5)(yaml@2.8.2)) + yaml: + specifier: ^2.8.1 + version: 2.8.2 zod: specifier: ^3.24.1 version: 3.25.76 @@ -5083,9 +5074,6 @@ packages: '@types/mime-db@1.43.6': resolution: {integrity: sha512-r2cqxAt/Eo5yWBOQie1lyM1JZFCiORa5xtLlhSZI0w8RJggBPKw8c4g/fgQCzWydaDR5bL4imnmix2d1n52iBw==} - '@types/mime-types@3.0.1': - resolution: {integrity: sha512-xRMsfuQbnRq1Ef+C+RKaENOxXX87Ygl38W1vDfPHRku02TgQr+Qd8iivLtAMcR0KF5/29xlnFihkTlbqFrGOVQ==} - '@types/minimatch@6.0.0': resolution: {integrity: sha512-zmPitbQ8+6zNutpwgcQuLcsEpn/Cj54Kbn7L5pX0Os5kdWplB7xPgEh/g+SWOB/qmows2gpuCaPyduq8ZZRnxA==} deprecated: This is a stub types definition. minimatch provides its own type definitions, so you do not need this installed. @@ -5805,10 +5793,6 @@ packages: buffer@6.0.3: resolution: {integrity: sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==} - builtin-modules@3.3.0: - resolution: {integrity: sha512-zhaCDicdLuWN5UbN5IMnFqNMhNfo919sH85y2/ea+5Yg9TsTkeZxpL+JLbp6cgYFS4sRLp3YV4S6yDuqVWHYOw==} - engines: {node: '>=6'} - bytes@3.1.0: resolution: {integrity: sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg==} engines: {node: '>= 0.8'} @@ -11260,11 +11244,6 @@ packages: resolution: {integrity: sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==} engines: {node: '>= 6'} - yaml@2.8.1: - resolution: {integrity: sha512-lcYcMxX2PO9XMGvAJkJ3OsNMw+/7FKes7/hgerGUYWIoWu5j/+YQqcZr5JnPZWzOsEBgMbSbiSTn/dv/69Mkpw==} - engines: {node: '>= 14.6'} - hasBin: true - yaml@2.8.2: resolution: {integrity: sha512-mplynKqc1C2hTVYxd0PU2xQAc22TI1vShAYGksCCfxbn/dFwnHTNi1bvYsBTkhdUNtGIf5xNOg938rrSSYvS9A==} engines: {node: '>= 14.6'} @@ -12261,7 +12240,7 @@ snapshots: supports-color: 8.1.1 type-fest: 4.41.0 util-arity: 1.1.0 - yaml: 2.8.1 + yaml: 2.8.2 yup: 1.7.1 '@cucumber/gherkin-streams@6.0.0(@cucumber/gherkin@38.0.0)(@cucumber/message-streams@4.0.1(@cucumber/messages@32.0.1))(@cucumber/messages@32.0.1)': @@ -16640,8 +16619,6 @@ snapshots: '@types/mime-db@1.43.6': {} - '@types/mime-types@3.0.1': {} - '@types/minimatch@6.0.0': dependencies: minimatch: 10.2.6 @@ -17670,8 +17647,6 @@ snapshots: base64-js: 1.5.1 ieee754: 1.2.1 - builtin-modules@3.3.0: {} - bytes@3.1.0: {} bytes@3.1.2: {} @@ -21888,7 +21863,7 @@ snapshots: postcss-load-config@4.0.2(postcss@8.5.6)(ts-node@10.9.2(@types/node@22.20.0)(typescript@5.5.4)): dependencies: lilconfig: 3.1.3 - yaml: 2.8.1 + yaml: 2.8.2 optionalDependencies: postcss: 8.5.6 ts-node: 10.9.2(@types/node@22.20.0)(typescript@5.5.4) @@ -21896,7 +21871,7 @@ snapshots: postcss-load-config@4.0.2(postcss@8.5.6)(ts-node@10.9.2(@types/node@22.20.0)(typescript@5.9.3)): dependencies: lilconfig: 3.1.3 - yaml: 2.8.1 + yaml: 2.8.2 optionalDependencies: postcss: 8.5.6 ts-node: 10.9.2(@types/node@22.20.0)(typescript@5.9.3) @@ -24728,8 +24703,6 @@ snapshots: yaml@1.10.2: {} - yaml@2.8.1: {} - yaml@2.8.2: {} yargs-parser@20.2.9: {}