diff --git a/.forgejo/workflows/pdfexport.yml b/.forgejo/workflows/pdfexport.yml new file mode 100644 index 0000000000..833c5e08d1 --- /dev/null +++ b/.forgejo/workflows/pdfexport.yml @@ -0,0 +1,20 @@ +name: pdfexport + +on: + push: + pull_request: + +jobs: + pdfexport-worker-fixtures: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Build worker image + run: docker build -t forgejo/pdf-worker:v0.1 ./worker/pdf + - name: Run fixtures (no network) + run: | + docker run --rm --network=none \ + -v "$PWD/tests/fixtures/pdfexport:/fixtures:ro" \ + forgejo/pdf-worker:v0.1 \ + node scripts/test-fixtures.js --fixtures /fixtures + diff --git a/modules/setting/pdf.go b/modules/setting/pdf.go new file mode 100644 index 0000000000..c3a78037c6 --- /dev/null +++ b/modules/setting/pdf.go @@ -0,0 +1,51 @@ +// SPDX-License-Identifier: MIT + +package setting + +import "strings" + +// PDF holds server-side configuration for Markdown → PDF export. +// +// NOTE: The worker-facing config surface is represented in services/pdfexport and +// is always marshaled with the exact JSON keys required by the worker contract. +var PDF = struct { + Enabled bool + Determinism string + Timestamp string + Typography string + + OrphansWidowsEnforce bool `ini:"ORPHANS_WIDOWS_ENFORCE"` + FooterEnabled bool `ini:"FOOTER_ENABLED"` + + Mermaid struct { + Strategy string + Caption bool + } + + // ContainerRuntime is the executable used to run the worker container. + // Typical values: docker, podman. + ContainerRuntime string `ini:"CONTAINER_RUNTIME"` + // WorkerImage is the container image reference to run. + WorkerImage string `ini:"WORKER_IMAGE"` +}{ + Enabled: false, + Determinism: "strict", + Timestamp: "commit_time", + Typography: "professional", + OrphansWidowsEnforce: true, + FooterEnabled: true, + ContainerRuntime: "podman", + WorkerImage: "localhost/forgejo/pdf-worker:v0.1", + Mermaid: struct{ Strategy string; Caption bool }{Strategy: "balanced", Caption: false}, +} + +func loadPDFFrom(rootCfg ConfigProvider) { + mustMapSetting(rootCfg, "pdf", &PDF) + // Allow nested mermaid configuration under [pdf.mermaid]. + mustMapSetting(rootCfg, "pdf.mermaid", &PDF.Mermaid) + + PDF.Determinism = strings.ToLower(PDF.Determinism) + PDF.Timestamp = strings.ToLower(PDF.Timestamp) + PDF.Typography = strings.ToLower(PDF.Typography) + PDF.Mermaid.Strategy = strings.ToLower(PDF.Mermaid.Strategy) +} diff --git a/modules/setting/setting.go b/modules/setting/setting.go index c9d30836ac..9590ba96c7 100644 --- a/modules/setting/setting.go +++ b/modules/setting/setting.go @@ -155,6 +155,7 @@ func loadCommonSettingsFrom(cfg ConfigProvider) error { loadGitFrom(cfg) loadMirrorFrom(cfg) loadMarkupFrom(cfg) + loadPDFFrom(cfg) loadQuotaFrom(cfg) loadOtherFrom(cfg) return nil diff --git a/options/locale/locale_en-US.ini b/options/locale/locale_en-US.ini index 53a47f0c17..268ab40012 100644 --- a/options/locale/locale_en-US.ini +++ b/options/locale/locale_en-US.ini @@ -1315,6 +1315,7 @@ n_release_few = %s releases released_this = released this file.title = %s at %s file_raw = Raw +file_export_pdf = Export PDF file_follow = Follow symlink file_history = History file_view_source = View source @@ -3925,4 +3926,3 @@ filepreview.truncated = Preview has been truncated [translation_meta] test = This is a test string. It is not displayed in Forgejo UI but is used for testing purposes. Feel free to enter "ok" to save time (or a fun fact of your choice) to hit that sweet 100% completion mark :) - diff --git a/routers/web/repo/download.go b/routers/web/repo/download.go index 1e87bbf015..22ff2a4641 100644 --- a/routers/web/repo/download.go +++ b/routers/web/repo/download.go @@ -6,6 +6,7 @@ package repo import ( "path" + "strings" "time" git_model "code.gitea.io/gitea/models/git" @@ -17,6 +18,7 @@ import ( "code.gitea.io/gitea/modules/storage" "code.gitea.io/gitea/routers/common" "code.gitea.io/gitea/services/context" + "code.gitea.io/gitea/services/pdfexport" ) // ServeBlobOrLFS download a git.Blob redirecting to LFS if necessary @@ -115,6 +117,11 @@ func getBlobForEntry(ctx *context.Context) (blob *git.Blob, lastModified *time.T // SingleDownload download a file by repos path func SingleDownload(ctx *context.Context) { + if strings.EqualFold(ctx.FormString("format"), "pdf") { + pdfexport.ServeMarkdownPDF(ctx) + return + } + blob, lastModified := getBlobForEntry(ctx) if blob == nil { return diff --git a/routers/web/repo/view.go b/routers/web/repo/view.go index fd8c1da058..4056c0ab4c 100644 --- a/routers/web/repo/view.go +++ b/routers/web/repo/view.go @@ -393,6 +393,10 @@ func renderFile(ctx *context.Context, entry *git.TreeEntry) { ctx.Data["FileIsSymlink"] = entry.IsLink() ctx.Data["FileName"] = blob.Name() ctx.Data["RawFileLink"] = ctx.Repo.RepoLink + "/raw/" + ctx.Repo.BranchNameSubURL() + "/" + util.PathEscapeSegments(ctx.Repo.TreePath) + ctx.Data["PDFExportLink"] = "" + if setting.PDF.Enabled && markup.IsMarkupFile(blob.Name(), "markdown") { + ctx.Data["PDFExportLink"] = ctx.Repo.RepoLink + "/raw/" + util.PathEscapeSegments(ctx.Repo.TreePath) + "?ref=" + url.QueryEscape(ctx.Repo.CommitID) + "&format=pdf" + } if entry.IsLink() { _, link, err := entry.FollowLinks() diff --git a/services/pdfexport/config.go b/services/pdfexport/config.go new file mode 100644 index 0000000000..05c7f5cfa7 --- /dev/null +++ b/services/pdfexport/config.go @@ -0,0 +1,65 @@ +// SPDX-License-Identifier: MIT + +package pdfexport + +import ( + "encoding/json" + "strings" + + "code.gitea.io/gitea/modules/setting" +) + +func configFromSetting() (pdfConfig, userError) { + cfg := pdfConfig{ + Determinism: strings.ToLower(setting.PDF.Determinism), + Timestamp: strings.ToLower(setting.PDF.Timestamp), + Typography: strings.ToLower(setting.PDF.Typography), + } + cfg.Mermaid.Strategy = strings.ToLower(setting.PDF.Mermaid.Strategy) + cfg.Mermaid.Caption = setting.PDF.Mermaid.Caption + cfg.OrphansWidows.Enforce = setting.PDF.OrphansWidowsEnforce + cfg.Footer.Enabled = setting.PDF.FooterEnabled + + // Defaults if empty/misconfigured. + if cfg.Determinism == "" { + cfg.Determinism = "strict" + } + if cfg.Timestamp == "" { + cfg.Timestamp = "commit_time" + } + if cfg.Typography == "" { + cfg.Typography = "professional" + } + if cfg.Mermaid.Strategy == "" { + cfg.Mermaid.Strategy = "balanced" + } + + if cfg.Determinism != "strict" && cfg.Determinism != "relaxed" { + return pdfConfig{}, errBadRequest("ERR_PDF_CONFIG_INVALID", "Invalid PDF configuration.") + } + if cfg.Timestamp != "commit_time" && cfg.Timestamp != "render_time" { + return pdfConfig{}, errBadRequest("ERR_PDF_CONFIG_INVALID", "Invalid PDF configuration.") + } + if cfg.Typography != "basic" && cfg.Typography != "professional" { + return pdfConfig{}, errBadRequest("ERR_PDF_CONFIG_INVALID", "Invalid PDF configuration.") + } + if cfg.Mermaid.Strategy != "fast" && cfg.Mermaid.Strategy != "balanced" && cfg.Mermaid.Strategy != "prestige" { + return pdfConfig{}, errBadRequest("ERR_PDF_CONFIG_INVALID", "Invalid PDF configuration.") + } + + // Strict implies timestamp=commit_time only. + if cfg.Determinism == "strict" && cfg.Timestamp != "commit_time" { + return pdfConfig{}, errBadRequest("ERR_PDF_CONFIG_INVALID", "Invalid PDF configuration.") + } + + return cfg, userError{} +} + +func configHash(cfg pdfConfig) (string, error) { + b, err := json.Marshal(cfg) + if err != nil { + return "", err + } + return sha256Hex(b), nil +} + diff --git a/services/pdfexport/errors.go b/services/pdfexport/errors.go new file mode 100644 index 0000000000..5ca86f603c --- /dev/null +++ b/services/pdfexport/errors.go @@ -0,0 +1,28 @@ +// SPDX-License-Identifier: MIT + +package pdfexport + +import "fmt" + +type userError struct { + Status int + ErrorID string + Message string +} + +func (e userError) Error() string { + return fmt.Sprintf("%s: %s", e.ErrorID, e.Message) +} + +func errBadRequest(errorID, message string) userError { + return userError{Status: 400, ErrorID: errorID, Message: message} +} + +func errNotFound(errorID, message string) userError { + return userError{Status: 404, ErrorID: errorID, Message: message} +} + +func errInternal(errorID, message string) userError { + return userError{Status: 500, ErrorID: errorID, Message: message} +} + diff --git a/services/pdfexport/runner.go b/services/pdfexport/runner.go new file mode 100644 index 0000000000..c32c6f4b85 --- /dev/null +++ b/services/pdfexport/runner.go @@ -0,0 +1,148 @@ +// SPDX-License-Identifier: MIT + +package pdfexport + +import ( + "bytes" + gocontext "context" + "encoding/json" + "errors" + "fmt" + "io" + "os" + "os/exec" + "path/filepath" + "strings" + "time" + + "code.gitea.io/gitea/modules/log" + "code.gitea.io/gitea/modules/setting" +) + +const ( + defaultWorkerTimeout = 60 * time.Second + maxWorkerStderrBytes = 256 * 1024 +) + +func runWorker(input workerInput) ([]byte, userError) { + if !setting.PDF.Enabled { + return nil, errBadRequest("ERR_PDF_DISABLED", "PDF export is disabled.") + } + + jobDir, err := os.MkdirTemp("", "forgejo-pdfexport-job-*") + if err != nil { + return nil, errInternal("ERR_PDF_INTERNAL", "Failed to export PDF.") + } + defer func() { + if err := os.RemoveAll(jobDir); err != nil { + log.Error("pdfexport: remove job dir: %v", err) + } + }() + + inPath := filepath.Join(jobDir, "input.json") + outPath := filepath.Join(jobDir, "output.pdf") + + inBytes, err := json.Marshal(input) + if err != nil { + return nil, errInternal("ERR_PDF_INTERNAL", "Failed to export PDF.") + } + if err := os.WriteFile(inPath, inBytes, 0o600); err != nil { + return nil, errInternal("ERR_PDF_INTERNAL", "Failed to export PDF.") + } + + runtime := setting.PDF.ContainerRuntime + if runtime == "" { + runtime = "podman" + } + image := setting.PDF.WorkerImage + if image == "" { + image = "localhost/forgejo/pdf-worker:v0.1" + } + + ctx, cancel := gocontext.WithTimeout(gocontext.Background(), defaultWorkerTimeout) + defer cancel() + + args := []string{ + "run", "--rm", + "--network=none", + "--read-only", + "--cap-drop=ALL", + "--security-opt=no-new-privileges", + "--tmpfs", "/tmp:rw,noexec,nosuid,size=1024m", + "--volume", fmt.Sprintf("%s:/job:rw", jobDir), + "--memory", "2g", + "--cpus", "2", + image, + "node", "src/index.js", + "--in", "/job/input.json", + "--out", "/job/output.pdf", + } + if strings.Contains(runtime, "podman") { + // Proxmox/LXC deployments often confine AppArmor and can block the default container profile load. + args = append(args[:6], append([]string{"--security-opt=apparmor=unconfined"}, args[6:]...)...) + } + + cmd := exec.CommandContext(ctx, runtime, args...) + cmd.Stdout = io.Discard + var stderr bytes.Buffer + cmd.Stderr = &limitedWriter{W: &stderr, N: maxWorkerStderrBytes} + + if err := cmd.Run(); err != nil { + if errors.Is(ctx.Err(), gocontext.DeadlineExceeded) { + return nil, errInternal("ERR_PDF_TIMEOUT", "PDF export timed out.") + } + // Best-effort parse of worker error envelope from stderr JSONL. + if ue, ok := parseWorkerError(stderr.String()); ok { + return nil, ue + } + log.Error("pdfexport: worker failed: %v", err) + return nil, errInternal("ERR_PDF_WORKER_FAILED", "Failed to export PDF.") + } + + pdf, err := os.ReadFile(outPath) + if err != nil || len(pdf) == 0 { + return nil, errInternal("ERR_PDF_WORKER_NO_OUTPUT", "Failed to export PDF.") + } + return pdf, userError{} +} + +type limitedWriter struct { + W io.Writer + N int +} + +func (w *limitedWriter) Write(p []byte) (int, error) { + if w.N <= 0 { + return len(p), nil + } + if len(p) > w.N { + p = p[:w.N] + } + n, err := w.W.Write(p) + w.N -= n + return n, err +} + +func parseWorkerError(stderr string) (userError, bool) { + type workerErr struct { + ErrorID string `json:"error_id"` + Message string `json:"message"` + } + + lines := strings.Split(stderr, "\n") + for i := len(lines) - 1; i >= 0; i-- { + line := strings.TrimSpace(lines[i]) + if line == "" { + continue + } + var we workerErr + if err := json.Unmarshal([]byte(line), &we); err != nil { + continue + } + if we.ErrorID == "" || we.Message == "" { + continue + } + return errInternal(we.ErrorID, we.Message), true + } + return userError{}, false +} diff --git a/services/pdfexport/service.go b/services/pdfexport/service.go new file mode 100644 index 0000000000..0e88a0cabd --- /dev/null +++ b/services/pdfexport/service.go @@ -0,0 +1,235 @@ +// SPDX-License-Identifier: MIT + +package pdfexport + +import ( + "crypto/sha256" + "encoding/hex" + "encoding/json" + "fmt" + "io" + "net/http" + "os" + "path" + "path/filepath" + "strings" + "time" + + "code.gitea.io/gitea/modules/git" + "code.gitea.io/gitea/modules/log" + "code.gitea.io/gitea/modules/markup" + "code.gitea.io/gitea/modules/setting" + "code.gitea.io/gitea/services/context" +) + +const ( + maxMarkdownBytes = 4 << 20 + + // ManifestSHA must be kept in sync with worker/pdf/manifest.json for cache key stability. + // It is used as part of the worker input and cache key (v0.1 determinism requirement). + ManifestSHA = "e0e9ebe129de8fabcf2aab0a9509f248cd907d3684542e8d42e7e45b747cc956" +) + +// ServeMarkdownPDF handles GET /{owner}/{repo}/raw/{path}?ref=&format=pdf +// It returns either PDF bytes or a safe JSON error envelope. +func ServeMarkdownPDF(ctx *context.Context) { + if strings.ToLower(ctx.FormString("format")) != "pdf" { + ctx.Status(http.StatusNotFound) + return + } + + // Determine effective file path and commit. + effectivePath, commit, ue := resolvePathAndCommit(ctx) + if ue.ErrorID != "" { + writeUserError(ctx, ue) + return + } + + if !markup.IsMarkupFile(path.Base(effectivePath), "markdown") { + writeUserError(ctx, errBadRequest("ERR_PDF_NOT_MARKDOWN", "PDF export is only supported for Markdown files.")) + return + } + + entry, err := commit.GetTreeEntryByPath(effectivePath) + if err != nil { + if git.IsErrNotExist(err) { + writeUserError(ctx, errNotFound("ERR_PDF_NOT_FOUND", "File not found.")) + return + } + log.Error("pdfexport: GetTreeEntryByPath: %v", err) + writeUserError(ctx, errInternal("ERR_PDF_INTERNAL", "Failed to export PDF.")) + return + } + if entry.IsDir() || entry.IsSubModule() { + writeUserError(ctx, errBadRequest("ERR_PDF_NOT_MARKDOWN", "PDF export is only supported for Markdown files.")) + return + } + + blob := entry.Blob() + rc, err := blob.DataAsync() + if err != nil { + log.Error("pdfexport: blob.DataAsync: %v", err) + writeUserError(ctx, errInternal("ERR_PDF_INTERNAL", "Failed to export PDF.")) + return + } + defer func() { _ = rc.Close() }() + + md, ue := readBounded(rc, maxMarkdownBytes) + if ue.ErrorID != "" { + writeUserError(ctx, ue) + return + } + + cfg, ue := configFromSetting() + if ue.ErrorID != "" { + writeUserError(ctx, ue) + return + } + cfgHash, err := configHash(cfg) + if err != nil { + log.Error("pdfexport: config hash: %v", err) + writeUserError(ctx, errInternal("ERR_PDF_INTERNAL", "Failed to export PDF.")) + return + } + + cacheKey := fmt.Sprintf("%d|%s|%s|%s|%s", ctx.Repo.Repository.ID, commit.ID.String(), effectivePath, cfgHash, ManifestSHA) + pdf, cacheHit, ue := getOrGeneratePDF(cacheKey, workerInput{ + Markdown: string(md), + RepoMeta: workerRepoMeta{ + Owner: ctx.Repo.Repository.OwnerName, + Repo: ctx.Repo.Repository.Name, + Path: effectivePath, + RepoID: ctx.Repo.Repository.ID, + CommitSHA: commit.ID.String(), + CommitTimeRFC3339: commit.Committer.When.UTC().Format(time.RFC3339), + }, + Config: workerConfigWrap{PDF: cfg}, + ManifestSHA: ManifestSHA, + }) + if ue.ErrorID != "" { + writeUserError(ctx, ue) + return + } + + filename := path.Base(effectivePath) + if strings.HasSuffix(strings.ToLower(filename), ".md") { + filename = filename[:len(filename)-3] + } + if filename == "" { + filename = "document" + } + filename += ".pdf" + + ctx.Resp.Header().Set("Content-Type", "application/pdf") + ctx.Resp.Header().Set("X-Content-Type-Options", "nosniff") + ctx.Resp.Header().Set("Content-Disposition", fmt.Sprintf("inline; filename=%q", filename)) + if cacheHit { + ctx.Resp.Header().Set("X-Forgejo-PDF-Cache", "HIT") + } else { + ctx.Resp.Header().Set("X-Forgejo-PDF-Cache", "MISS") + } + ctx.Resp.WriteHeader(http.StatusOK) + _, _ = ctx.Resp.Write(pdf) +} + +func resolvePathAndCommit(ctx *context.Context) (string, *git.Commit, userError) { + if ctx.Repo.Repository == nil || ctx.Repo.GitRepo == nil { + return "", nil, errInternal("ERR_PDF_INTERNAL", "Failed to export PDF.") + } + + ref := strings.TrimSpace(ctx.FormString("ref")) + commit := ctx.Repo.Commit + if ref != "" { + c, err := ctx.Repo.GitRepo.GetCommit(ref) + if err != nil { + if git.IsErrNotExist(err) { + return "", nil, errNotFound("ERR_PDF_REF_NOT_FOUND", "Commit not found.") + } + log.Error("pdfexport: GetCommit(ref): %v", err) + return "", nil, errInternal("ERR_PDF_INTERNAL", "Failed to export PDF.") + } + commit = c + } + if commit == nil { + return "", nil, errInternal("ERR_PDF_INTERNAL", "Failed to export PDF.") + } + + // For the legacy /raw/* route with an explicit ref query parameter, treat the wildcard as the filepath + // (avoid legacy ref guessing for deterministic cache keys). + effectivePath := ctx.Repo.TreePath + if ref != "" && isRawLegacyPath(ctx) { + effectivePath = strings.TrimPrefix(ctx.Params("*"), "/") + } + if effectivePath == "" { + return "", nil, errBadRequest("ERR_PDF_BAD_REQUEST", "Invalid request.") + } + return effectivePath, commit, userError{} +} + +func isRawLegacyPath(ctx *context.Context) bool { + p := ctx.Req.URL.Path + idx := strings.Index(p, "/raw/") + if idx < 0 { + return false + } + after := p[idx+len("/raw/"):] + return !(strings.HasPrefix(after, "branch/") || strings.HasPrefix(after, "tag/") || strings.HasPrefix(after, "commit/") || strings.HasPrefix(after, "blob/")) +} + +func readBounded(r io.Reader, limit int64) ([]byte, userError) { + lr := io.LimitReader(r, limit+1) + b, err := io.ReadAll(lr) + if err != nil { + return nil, errInternal("ERR_PDF_INTERNAL", "Failed to export PDF.") + } + if int64(len(b)) > limit { + return nil, errBadRequest("ERR_PDF_TOO_LARGE", "File is too large to export.") + } + return b, userError{} +} + +func getOrGeneratePDF(cacheKey string, input workerInput) ([]byte, bool, userError) { + cacheDir := filepath.Join(setting.AppDataPath, "pdfexport", "cache") + if err := os.MkdirAll(cacheDir, 0o750); err != nil { + log.Error("pdfexport: mkdir cache: %v", err) + return nil, false, errInternal("ERR_PDF_INTERNAL", "Failed to export PDF.") + } + cacheFile := filepath.Join(cacheDir, sha256Hex([]byte(cacheKey))+".pdf") + + if b, err := os.ReadFile(cacheFile); err == nil && len(b) > 0 { + return b, true, userError{} + } + + pdf, ue := runWorker(input) + if ue.ErrorID != "" { + return nil, false, ue + } + + tmp := cacheFile + ".tmp" + if err := os.WriteFile(tmp, pdf, 0o640); err == nil { + _ = os.Rename(tmp, cacheFile) + } else { + log.Error("pdfexport: write cache: %v", err) + } + + return pdf, false, userError{} +} + +func sha256Hex(b []byte) string { + h := sha256.Sum256(b) + return hex.EncodeToString(h[:]) +} + +func writeUserError(ctx *context.Context, ue userError) { + status := ue.Status + if status == 0 { + status = http.StatusInternalServerError + } + ctx.Resp.Header().Set("Content-Type", "application/json; charset=utf-8") + ctx.Resp.Header().Set("X-Content-Type-Options", "nosniff") + ctx.Resp.WriteHeader(status) + _ = json.NewEncoder(ctx.Resp).Encode(map[string]string{ + "error_id": ue.ErrorID, + "message": ue.Message, + }) +} diff --git a/services/pdfexport/types.go b/services/pdfexport/types.go new file mode 100644 index 0000000000..6155109435 --- /dev/null +++ b/services/pdfexport/types.go @@ -0,0 +1,45 @@ +// SPDX-License-Identifier: MIT + +package pdfexport + +type workerInput struct { + Markdown string `json:"markdown"` + RepoMeta workerRepoMeta `json:"repoMeta"` + Config workerConfigWrap `json:"config"` + + ManifestSHA string `json:"manifestSHA"` +} + +type workerRepoMeta struct { + Owner string `json:"owner"` + Repo string `json:"repo"` + Path string `json:"path"` + RepoID interface{} `json:"repoID"` + CommitSHA string `json:"commitSHA"` + CommitTimeRFC3339 string `json:"commitTimeRFC3339"` +} + +type workerConfigWrap struct { + PDF pdfConfig `json:"pdf"` +} + +// pdfConfig matches the required worker config surface (JSON keys and values). +type pdfConfig struct { + Determinism string `json:"determinism"` // strict|relaxed + Timestamp string `json:"timestamp"` // commit_time|render_time + Typography string `json:"typography"` // basic|professional + + Mermaid struct { + Strategy string `json:"strategy"` // fast|balanced|prestige + Caption bool `json:"caption"` + } `json:"mermaid"` + + OrphansWidows struct { + Enforce bool `json:"enforce"` + } `json:"orphansWidows"` + + Footer struct { + Enabled bool `json:"enabled"` + } `json:"footer"` +} + diff --git a/templates/repo/view_file.tmpl b/templates/repo/view_file.tmpl index b2443e82c4..40fa1a42a6 100644 --- a/templates/repo/view_file.tmpl +++ b/templates/repo/view_file.tmpl @@ -47,6 +47,9 @@ {{ctx.Locale.Tr "repo.file_follow"}} {{end}} {{ctx.Locale.Tr "repo.file_raw"}} + {{if and $.PDFExportLink .IsMarkup (eq .MarkupType "markdown") .IsDisplayingRendered}} + {{ctx.Locale.Tr "repo.file_export_pdf"}} + {{end}} {{if not .IsViewCommit}} {{ctx.Locale.Tr "repo.file_permalink"}} {{end}} diff --git a/tests/fixtures/pdfexport/basic.md b/tests/fixtures/pdfexport/basic.md new file mode 100644 index 0000000000..b9a61caef1 --- /dev/null +++ b/tests/fixtures/pdfexport/basic.md @@ -0,0 +1,17 @@ +# PDF Export Fixture — Basic + +KNOWN_UNIQUE_STRING: PDFX_BASIC_6d7d6fdd + +This fixture ensures the PDF contains selectable text, including headings, paragraphs, lists and code. + +## List + +- Alpha +- Beta +- Gamma + +## Code + +```bash +echo "hello" +``` diff --git a/tests/fixtures/pdfexport/external_resources.md b/tests/fixtures/pdfexport/external_resources.md new file mode 100644 index 0000000000..3f4f53fb08 --- /dev/null +++ b/tests/fixtures/pdfexport/external_resources.md @@ -0,0 +1,11 @@ +# PDF Export Fixture — External Resources Blocked + +KNOWN_UNIQUE_STRING: PDFX_EXT_25d1c31b + +This fixture contains external resources which must not be fetched in v0.1. + +![external-image](https://example.com/forbidden.png) + +[external-link](https://example.com/) + + diff --git a/tests/fixtures/pdfexport/mermaid.md b/tests/fixtures/pdfexport/mermaid.md new file mode 100644 index 0000000000..38fe0c0a7c --- /dev/null +++ b/tests/fixtures/pdfexport/mermaid.md @@ -0,0 +1,12 @@ +# PDF Export Fixture — Mermaid + +KNOWN_UNIQUE_STRING: PDFX_MERMAID_2a2dbf1d + +```mermaid +flowchart LR + A[Alpha] --> B[Beta] + B --> C[Gamma] + C --> D[Delta] +``` + +The diagram labels must remain selectable PDF text. diff --git a/tests/fixtures/pdfexport/mermaid_wide.md b/tests/fixtures/pdfexport/mermaid_wide.md new file mode 100644 index 0000000000..a96f45d96d --- /dev/null +++ b/tests/fixtures/pdfexport/mermaid_wide.md @@ -0,0 +1,16 @@ +# PDF Export Fixture — Mermaid Wide + +KNOWN_UNIQUE_STRING: PDFX_WIDE_bf3a6c2e + +```mermaid +flowchart LR + A[Start] --> B[Step 1] + B --> C[Step 2] + C --> D[Step 3] + D --> E[Step 4] + E --> F[Step 5] + F --> G[Step 6] + G --> H[Step 7] + H --> I[Step 8] + I --> J[Finish] +``` diff --git a/worker/pdf/Dockerfile b/worker/pdf/Dockerfile new file mode 100644 index 0000000000..c5f012eec2 --- /dev/null +++ b/worker/pdf/Dockerfile @@ -0,0 +1,49 @@ +FROM node:20-bookworm-slim + +ENV NODE_ENV=production \ + TZ=UTC \ + PUPPETEER_CACHE_DIR=/opt/puppeteer \ + PUPPETEER_SKIP_DOWNLOAD=0 + +RUN apt-get update -y && apt-get install -y --no-install-recommends \ + ca-certificates \ + dumb-init \ + qpdf \ + poppler-utils \ + libasound2 \ + libatk-bridge2.0-0 \ + libatk1.0-0 \ + libcups2 \ + libdrm2 \ + libgbm1 \ + libgtk-3-0 \ + libnss3 \ + libpango-1.0-0 \ + libpangocairo-1.0-0 \ + libx11-6 \ + libx11-xcb1 \ + libxcb1 \ + libxcomposite1 \ + libxdamage1 \ + libxext6 \ + libxfixes3 \ + libxrandr2 \ + libxrender1 \ + libxshmfence1 \ + libxkbcommon0 \ + && rm -rf /var/lib/apt/lists/* + +WORKDIR /opt/forgejo-pdf + +COPY package.json package-lock.json ./ +RUN npm ci --omit=dev + +COPY src ./src +COPY scripts ./scripts +COPY assets ./assets + +RUN node scripts/copy-assets.js \ + && node scripts/generate-manifest.js + +ENTRYPOINT ["dumb-init", "--"] +CMD ["node", "src/index.js"] diff --git a/worker/pdf/assets/css/basic.css b/worker/pdf/assets/css/basic.css new file mode 100644 index 0000000000..a75ec939d5 --- /dev/null +++ b/worker/pdf/assets/css/basic.css @@ -0,0 +1,66 @@ +@font-face { + font-family: "IBM Plex Sans"; + font-style: normal; + font-weight: 400; + src: url("../fonts/ibm-plex-sans-latin-400-normal.woff2") format("woff2"); + font-display: swap; +} + +@font-face { + font-family: "IBM Plex Mono"; + font-style: normal; + font-weight: 400; + src: url("../fonts/ibm-plex-mono-latin-400-normal.woff2") format("woff2"); + font-display: swap; +} + +@page { + size: A4; + margin: 20mm; +} + +html, body { + font-family: "IBM Plex Sans", system-ui, -apple-system, "Segoe UI", Roboto, Arial, sans-serif; + color: #111827; + background: #ffffff; +} + +p, li, a, h1, h2, h3, h4, h5, h6 { + overflow-wrap: anywhere; + word-break: break-word; +} + +h1 { break-before: page; } +p { orphans: 3; widows: 3; } +pre, table, figure, blockquote { break-inside: avoid; } + +pre, code { + font-family: "IBM Plex Mono", ui-monospace, SFMono-Regular, Menlo, Monaco, "Liberation Mono", monospace; +} + +pre { + background: #f3f4f6; + padding: 10px 12px; + border-radius: 6px; + overflow-wrap: anywhere; + white-space: pre-wrap; +} + +.mermaid-figure { + margin: 12px 0; +} + +.mermaid-figure svg { + max-width: 100%; + height: auto; +} + +.mermaid-landscape { + break-before: page; + page: mermaidLandscape; +} + +@page mermaidLandscape { + size: A4 landscape; + margin: 20mm; +} diff --git a/worker/pdf/assets/css/professional.css b/worker/pdf/assets/css/professional.css new file mode 100644 index 0000000000..19b4d57021 --- /dev/null +++ b/worker/pdf/assets/css/professional.css @@ -0,0 +1,98 @@ +@font-face { + font-family: "IBM Plex Sans"; + font-style: normal; + font-weight: 400; + src: url("../fonts/ibm-plex-sans-latin-400-normal.woff2") format("woff2"); + font-display: swap; +} + +@font-face { + font-family: "IBM Plex Sans"; + font-style: normal; + font-weight: 600; + src: url("../fonts/ibm-plex-sans-latin-600-normal.woff2") format("woff2"); + font-display: swap; +} + +@font-face { + font-family: "IBM Plex Mono"; + font-style: normal; + font-weight: 400; + src: url("../fonts/ibm-plex-mono-latin-400-normal.woff2") format("woff2"); + font-display: swap; +} + +@page { + size: A4; + margin: 18mm 18mm 22mm 18mm; +} + +html, body { + font-family: "IBM Plex Sans", system-ui, -apple-system, "Segoe UI", Roboto, Arial, sans-serif; + color: #0f172a; + background: #ffffff; + font-size: 11.5pt; + line-height: 1.45; +} + +p, li, a, h1, h2, h3, h4, h5, h6 { + overflow-wrap: anywhere; + word-break: break-word; +} + +h1, h2, h3, h4, h5, h6 { + font-weight: 600; + color: #0b1220; +} + +h1 { break-before: page; font-size: 20pt; margin: 0 0 10pt; } +h2 { font-size: 15pt; margin: 18pt 0 8pt; } +h3 { font-size: 12.5pt; margin: 14pt 0 6pt; } + +p { orphans: 3; widows: 3; margin: 0 0 10pt; } +pre, table, figure, blockquote { break-inside: avoid; } + +pre, code { + font-family: "IBM Plex Mono", ui-monospace, SFMono-Regular, Menlo, Monaco, "Liberation Mono", monospace; +} + +pre { + background: #f1f5f9; + border: 1px solid #e2e8f0; + padding: 10px 12px; + border-radius: 8px; + overflow-wrap: anywhere; + white-space: pre-wrap; +} + +blockquote { + margin: 12pt 0; + padding: 0 0 0 10pt; + border-left: 3px solid #cbd5e1; + color: #334155; +} + +hr { + border: 0; + border-top: 1px solid #e2e8f0; + margin: 14pt 0; +} + +.mermaid-figure { + margin: 12pt 0; +} + +.mermaid-figure svg { + max-width: 100%; + height: auto; +} + +.mermaid-landscape { + break-before: page; + page: mermaidLandscape; +} + +@page mermaidLandscape { + size: A4 landscape; + margin: 20mm; +} diff --git a/worker/pdf/assets/js/runtime.js b/worker/pdf/assets/js/runtime.js new file mode 100644 index 0000000000..6e3f56c3d4 --- /dev/null +++ b/worker/pdf/assets/js/runtime.js @@ -0,0 +1,4 @@ +// This file is intentionally tiny; the heavy dependencies are loaded as file:// assets. +// It serves as a stable entrypoint for in-page render orchestration. +// (The actual work is executed by src/render_pdf.js via page.evaluate().) + diff --git a/worker/pdf/manifest.json b/worker/pdf/manifest.json new file mode 100644 index 0000000000..de97e4663d --- /dev/null +++ b/worker/pdf/manifest.json @@ -0,0 +1,38 @@ +{ + "versions": { + "worker": "0.1.0", + "node": "v20.19.6", + "puppeteer": "23.11.1", + "mermaid": "10.9.1", + "pagedjs": "0.4.3", + "markdownIt": "14.1.0", + "sanitizeHtml": "2.14.0", + "ibmPlexSans": "5.2.6", + "ibmPlexMono": "5.2.6", + "lockfileVersion": 3 + }, + "chromium": { + "sha256": "8b60627021064a6a1ecc3cf7f41d528747ea7f95713ca72b369833fa89638b0a", + "version": null + }, + "code": { + "src/index.js": "e0add4a9e0adba7752b6a70b41dc9592aee937045ceba4e81b4ef2c2d5d6f935", + "src/render_pdf.js": "915fadbb9934fddf8f842d28459375e2b693eb6c646507cbeb7db05387038eb1", + "src/validate.js": "42fcc411306036168ad6ae68626d07cb6b5175814559f4055f98fc2925bfdb55", + "src/errors.js": "4afcb10eb0b5cfeaba69599e3d0d2d42cdc68655a93d20dad3a1508585df1529", + "src/logger.js": "7ec580c7154036fda4fa9617442a5d9ae5e1839139010c78e555e0c37b7f11a2", + "scripts/test-fixtures.js": "4694d98297ae73189b602c2d2d7941b8354e92e17ce4ba3c71f72db1a4408dd7", + "scripts/copy-assets.js": "e0b9075ebef4962bc36738136d9a5f99b6b58e959234e944fd4b4f429971fbac", + "scripts/generate-manifest.js": "711a9a1677728b44aeb80cb7f87c29ef04f1a74e9a46e1028b76878fbe25906b" + }, + "assets": { + "assets/js/mermaid.min.js": "61b335a46df05a7ce1c98378f60e5f3e77a7fb608a1056997e8a649304a936d6", + "assets/js/paged.polyfill.js": "f59f361802416c770d549a647958649af2cf6601999924bc00e4f507dad5269f", + "assets/css/basic.css": "efcf1befdb7d9708981530cc0ec685154551712dbf369f05c3dbd61627315230", + "assets/css/professional.css": "02bd01ad088ba4d07e9a343823406e7a787222f12fbe6c528026a2f80c8106cc", + "assets/fonts/ibm-plex-sans-latin-400-normal.woff2": "3b646991d30055a93a4ecc499713d4347953a74a947ecab435ab72070cbdab0e", + "assets/fonts/ibm-plex-sans-latin-600-normal.woff2": "8960851d691c054ed38e259bdcf1a6190d157b4203ed5bb32c632a863fb8ec2f", + "assets/fonts/ibm-plex-mono-latin-400-normal.woff2": "3c5a451f9ec27a354b0c2bcca636c6ec17a651281aabf29f8427e210a1d31e85" + }, + "manifest_sha": "e0e9ebe129de8fabcf2aab0a9509f248cd907d3684542e8d42e7e45b747cc956" +} \ No newline at end of file diff --git a/worker/pdf/package-lock.json b/worker/pdf/package-lock.json new file mode 100644 index 0000000000..9512806bbc --- /dev/null +++ b/worker/pdf/package-lock.json @@ -0,0 +1,2950 @@ +{ + "name": "forgejo-pdf-worker", + "version": "0.1.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "forgejo-pdf-worker", + "version": "0.1.0", + "dependencies": { + "@fontsource/ibm-plex-mono": "5.2.6", + "@fontsource/ibm-plex-sans": "5.2.6", + "markdown-it": "14.1.0", + "mermaid": "10.9.1", + "pagedjs": "0.4.3", + "puppeteer": "23.11.1", + "sanitize-html": "2.14.0" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz", + "integrity": "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==", + "license": "MIT", + "dependencies": { + "@babel/helper-validator-identifier": "^7.27.1", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", + "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/polyfill": { + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/polyfill/-/polyfill-7.12.1.tgz", + "integrity": "sha512-X0pi0V6gxLi6lFZpGmeNa4zxtwEmCs42isWLNjZZDE0Y8yVfgu0T2OAHlzBbdYlqbW/YXVvoBHpATEM+goCj8g==", + "deprecated": "🚨 This package has been deprecated in favor of separate inclusion of a polyfill and regenerator-runtime (when needed). See the @babel/polyfill docs (https://babeljs.io/docs/en/babel-polyfill) for more information.", + "license": "MIT", + "dependencies": { + "core-js": "^2.6.5", + "regenerator-runtime": "^0.13.4" + } + }, + "node_modules/@babel/runtime": { + "version": "7.28.4", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.28.4.tgz", + "integrity": "sha512-Q/N6JNWvIvPnLDvjlE1OUBLPQHH6l3CltCEsHIujp45zQUSSh8K+gHnaEX45yAT1nyngnINhvWtzN+Nb9D8RAQ==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@braintree/sanitize-url": { + "version": "6.0.4", + "resolved": "https://registry.npmjs.org/@braintree/sanitize-url/-/sanitize-url-6.0.4.tgz", + "integrity": "sha512-s3jaWicZd0pkP0jf5ysyHUI/RE7MHos6qlToFcGWXVp+ykHOy77OUMrfbgJ9it2C5bow7OIQwYYaHjk9XlBQ2A==", + "license": "MIT" + }, + "node_modules/@fontsource/ibm-plex-mono": { + "version": "5.2.6", + "resolved": "https://registry.npmjs.org/@fontsource/ibm-plex-mono/-/ibm-plex-mono-5.2.6.tgz", + "integrity": "sha512-LTZJNTcpoT19fmwERZNMDI+ljHueuyhF2Qn+bICJ4Y4hxBLAAoJ2MRsGnyp0QNutW6t/25eyZpvaUK1LDrCo7Q==", + "license": "OFL-1.1", + "funding": { + "url": "https://github.com/sponsors/ayuhito" + } + }, + "node_modules/@fontsource/ibm-plex-sans": { + "version": "5.2.6", + "resolved": "https://registry.npmjs.org/@fontsource/ibm-plex-sans/-/ibm-plex-sans-5.2.6.tgz", + "integrity": "sha512-yclktpagJncROwdWHHEfPsrIfoo3uiIeDFchvmjNmTW/YMBfLYWJnUv19EAqPNhGizdtmcH9FjzOR2094Z2uPg==", + "license": "OFL-1.1", + "funding": { + "url": "https://github.com/sponsors/ayuhito" + } + }, + "node_modules/@puppeteer/browsers": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/@puppeteer/browsers/-/browsers-2.6.1.tgz", + "integrity": "sha512-aBSREisdsGH890S2rQqK82qmQYU3uFpSH8wcZWHgHzl3LfzsxAKbLNiAG9mO8v1Y0UICBeClICxPJvyr0rcuxg==", + "license": "Apache-2.0", + "dependencies": { + "debug": "^4.4.0", + "extract-zip": "^2.0.1", + "progress": "^2.0.3", + "proxy-agent": "^6.5.0", + "semver": "^7.6.3", + "tar-fs": "^3.0.6", + "unbzip2-stream": "^1.4.3", + "yargs": "^17.7.2" + }, + "bin": { + "browsers": "lib/cjs/main-cli.js" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@tootallnate/quickjs-emscripten": { + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/@tootallnate/quickjs-emscripten/-/quickjs-emscripten-0.23.0.tgz", + "integrity": "sha512-C5Mc6rdnsaJDjO3UpGW/CQTHtCKaYlScZTly4JIu97Jxo/odCiH0ITnDXSJPTOrEKk/ycSZ0AOgTmkDtkOsvIA==", + "license": "MIT" + }, + "node_modules/@types/d3-scale": { + "version": "4.0.9", + "resolved": "https://registry.npmjs.org/@types/d3-scale/-/d3-scale-4.0.9.tgz", + "integrity": "sha512-dLmtwB8zkAeO/juAMfnV+sItKjlsw2lKdZVVy6LRr0cBmegxSABiLEpGVmSJJ8O08i4+sGR6qQtb6WtuwJdvVw==", + "license": "MIT", + "dependencies": { + "@types/d3-time": "*" + } + }, + "node_modules/@types/d3-scale-chromatic": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@types/d3-scale-chromatic/-/d3-scale-chromatic-3.1.0.tgz", + "integrity": "sha512-iWMJgwkK7yTRmWqRB5plb1kadXyQ5Sj8V/zYlFGMUBbIPKQScw+Dku9cAAMgJG+z5GYDoMjWGLVOvjghDEFnKQ==", + "license": "MIT" + }, + "node_modules/@types/d3-time": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/d3-time/-/d3-time-3.0.4.tgz", + "integrity": "sha512-yuzZug1nkAAaBlBBikKZTgzCeA+k1uy4ZFwWANOfKw5z5LRhV0gNA7gNkKm7HoK+HRN0wX3EkxGk0fpbWhmB7g==", + "license": "MIT" + }, + "node_modules/@types/debug": { + "version": "4.1.12", + "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.12.tgz", + "integrity": "sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==", + "license": "MIT", + "dependencies": { + "@types/ms": "*" + } + }, + "node_modules/@types/mdast": { + "version": "3.0.15", + "resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-3.0.15.tgz", + "integrity": "sha512-LnwD+mUEfxWMa1QpDraczIn6k0Ee3SMicuYSSzS6ZYl2gKS09EClnJYGd8Du6rfc5r/GZEk5o1mRb8TaTj03sQ==", + "license": "MIT", + "dependencies": { + "@types/unist": "^2" + } + }, + "node_modules/@types/ms": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@types/ms/-/ms-2.1.0.tgz", + "integrity": "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==", + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "25.0.2", + "resolved": "https://registry.npmjs.org/@types/node/-/node-25.0.2.tgz", + "integrity": "sha512-gWEkeiyYE4vqjON/+Obqcoeffmk0NF15WSBwSs7zwVA2bAbTaE0SJ7P0WNGoJn8uE7fiaV5a7dKYIJriEqOrmA==", + "license": "MIT", + "optional": true, + "dependencies": { + "undici-types": "~7.16.0" + } + }, + "node_modules/@types/trusted-types": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.7.tgz", + "integrity": "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==", + "license": "MIT", + "optional": true + }, + "node_modules/@types/unist": { + "version": "2.0.11", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.11.tgz", + "integrity": "sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA==", + "license": "MIT" + }, + "node_modules/@types/yauzl": { + "version": "2.10.3", + "resolved": "https://registry.npmjs.org/@types/yauzl/-/yauzl-2.10.3.tgz", + "integrity": "sha512-oJoftv0LSuaDZE3Le4DbKX+KS9G36NzOeSap90UIK0yMA/NhKJhqlSGtNDORNRaIbQfzjXDrQa0ytJ6mNRGz/Q==", + "license": "MIT", + "optional": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/agent-base": { + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz", + "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==", + "license": "MIT", + "engines": { + "node": ">= 14" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "license": "Python-2.0" + }, + "node_modules/ast-types": { + "version": "0.13.4", + "resolved": "https://registry.npmjs.org/ast-types/-/ast-types-0.13.4.tgz", + "integrity": "sha512-x1FCFnFifvYDDzTaLII71vG5uvDwgtmDTEVWAxrgeiR8VjMONcCXJx7E+USjDtHlwFmt9MysbqgF9b9Vjr6w+w==", + "license": "MIT", + "dependencies": { + "tslib": "^2.0.1" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/b4a": { + "version": "1.7.3", + "resolved": "https://registry.npmjs.org/b4a/-/b4a-1.7.3.tgz", + "integrity": "sha512-5Q2mfq2WfGuFp3uS//0s6baOJLMoVduPYVeNmDYxu5OUA1/cBfvr2RIS7vi62LdNj/urk1hfmj867I3qt6uZ7Q==", + "license": "Apache-2.0", + "peerDependencies": { + "react-native-b4a": "*" + }, + "peerDependenciesMeta": { + "react-native-b4a": { + "optional": true + } + } + }, + "node_modules/bare-events": { + "version": "2.8.2", + "resolved": "https://registry.npmjs.org/bare-events/-/bare-events-2.8.2.tgz", + "integrity": "sha512-riJjyv1/mHLIPX4RwiK+oW9/4c3TEUeORHKefKAKnZ5kyslbN+HXowtbaVEqt4IMUB7OXlfixcs6gsFeo/jhiQ==", + "license": "Apache-2.0", + "peerDependencies": { + "bare-abort-controller": "*" + }, + "peerDependenciesMeta": { + "bare-abort-controller": { + "optional": true + } + } + }, + "node_modules/bare-fs": { + "version": "4.5.2", + "resolved": "https://registry.npmjs.org/bare-fs/-/bare-fs-4.5.2.tgz", + "integrity": "sha512-veTnRzkb6aPHOvSKIOy60KzURfBdUflr5VReI+NSaPL6xf+XLdONQgZgpYvUuZLVQ8dCqxpBAudaOM1+KpAUxw==", + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "bare-events": "^2.5.4", + "bare-path": "^3.0.0", + "bare-stream": "^2.6.4", + "bare-url": "^2.2.2", + "fast-fifo": "^1.3.2" + }, + "engines": { + "bare": ">=1.16.0" + }, + "peerDependencies": { + "bare-buffer": "*" + }, + "peerDependenciesMeta": { + "bare-buffer": { + "optional": true + } + } + }, + "node_modules/bare-os": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/bare-os/-/bare-os-3.6.2.tgz", + "integrity": "sha512-T+V1+1srU2qYNBmJCXZkUY5vQ0B4FSlL3QDROnKQYOqeiQR8UbjNHlPa+TIbM4cuidiN9GaTaOZgSEgsvPbh5A==", + "license": "Apache-2.0", + "optional": true, + "engines": { + "bare": ">=1.14.0" + } + }, + "node_modules/bare-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/bare-path/-/bare-path-3.0.0.tgz", + "integrity": "sha512-tyfW2cQcB5NN8Saijrhqn0Zh7AnFNsnczRcuWODH0eYAXBsJ5gVxAUuNr7tsHSC6IZ77cA0SitzT+s47kot8Mw==", + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "bare-os": "^3.0.1" + } + }, + "node_modules/bare-stream": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/bare-stream/-/bare-stream-2.7.0.tgz", + "integrity": "sha512-oyXQNicV1y8nc2aKffH+BUHFRXmx6VrPzlnaEvMhram0nPBrKcEdcyBg5r08D0i8VxngHFAiVyn1QKXpSG0B8A==", + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "streamx": "^2.21.0" + }, + "peerDependencies": { + "bare-buffer": "*", + "bare-events": "*" + }, + "peerDependenciesMeta": { + "bare-buffer": { + "optional": true + }, + "bare-events": { + "optional": true + } + } + }, + "node_modules/bare-url": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/bare-url/-/bare-url-2.3.2.tgz", + "integrity": "sha512-ZMq4gd9ngV5aTMa5p9+UfY0b3skwhHELaDkhEHetMdX0LRkW9kzaym4oo/Eh+Ghm0CCDuMTsRIGM/ytUc1ZYmw==", + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "bare-path": "^3.0.0" + } + }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/basic-ftp": { + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/basic-ftp/-/basic-ftp-5.0.5.tgz", + "integrity": "sha512-4Bcg1P8xhUuqcii/S0Z9wiHIrQVPMermM1any+MX5GeGD7faD3/msQUDGLol9wOcz4/jbg/WJnGqoJF6LiBdtg==", + "license": "MIT", + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/buffer": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", + "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.1.13" + } + }, + "node_modules/buffer-crc32": { + "version": "0.2.13", + "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", + "integrity": "sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==", + "license": "MIT", + "engines": { + "node": "*" + } + }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/character-entities": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/character-entities/-/character-entities-2.0.2.tgz", + "integrity": "sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/chromium-bidi": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/chromium-bidi/-/chromium-bidi-0.11.0.tgz", + "integrity": "sha512-6CJWHkNRoyZyjV9Rwv2lYONZf1Xm0IuDyNq97nwSsxxP3wf5Bwy15K5rOvVKMtJ127jJBmxFUanSAOjgFRxgrA==", + "license": "Apache-2.0", + "dependencies": { + "mitt": "3.0.1", + "zod": "3.23.8" + }, + "peerDependencies": { + "devtools-protocol": "*" + } + }, + "node_modules/clear-cut": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/clear-cut/-/clear-cut-2.0.2.tgz", + "integrity": "sha512-WVgn/gSejQ+0aoR8ucbKIdo6icduPZW6AbWwyUmAUgxy63rUYjwa5rj/HeoNPhf0/XPrl82X8bO/hwBkSmsFtg==", + "license": "MIT" + }, + "node_modules/cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "license": "ISC", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "license": "MIT" + }, + "node_modules/commander": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz", + "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==", + "license": "MIT", + "engines": { + "node": ">= 10" + } + }, + "node_modules/core-js": { + "version": "2.6.12", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.6.12.tgz", + "integrity": "sha512-Kb2wC0fvsWfQrgk8HU5lW6U/Lcs8+9aaYcy4ZFc6DDlo4nZ7n70dEgE5rtR0oG6ufKDUnrwfWL1mXR5ljDatrQ==", + "deprecated": "core-js@<3.23.3 is no longer maintained and not recommended for usage due to the number of issues. Because of the V8 engine whims, feature detection in old core-js versions could cause a slowdown up to 100x even if nothing is polyfilled. Some versions have web compatibility issues. Please, upgrade your dependencies to the actual version of core-js.", + "hasInstallScript": true, + "license": "MIT" + }, + "node_modules/cose-base": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/cose-base/-/cose-base-1.0.3.tgz", + "integrity": "sha512-s9whTXInMSgAp/NVXVNuVxVKzGH2qck3aQlVHxDCdAEPgtMKwc4Wq6/QKhgdEdgbLSi9rBTAcPoRa6JpiG4ksg==", + "license": "MIT", + "dependencies": { + "layout-base": "^1.0.0" + } + }, + "node_modules/cosmiconfig": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-9.0.0.tgz", + "integrity": "sha512-itvL5h8RETACmOTFc4UfIyB2RfEHi71Ax6E/PivVxq9NseKbOWpeyHEOIbmAw1rs8Ak0VursQNww7lf7YtUwzg==", + "license": "MIT", + "dependencies": { + "env-paths": "^2.2.1", + "import-fresh": "^3.3.0", + "js-yaml": "^4.1.0", + "parse-json": "^5.2.0" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/d-fischer" + }, + "peerDependencies": { + "typescript": ">=4.9.5" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/css-tree": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-1.1.3.tgz", + "integrity": "sha512-tRpdppF7TRazZrjJ6v3stzv93qxRcSsFmW6cX0Zm2NVKpxE1WV1HblnghVv9TreireHkqI/VDEsfolRF1p6y7Q==", + "license": "MIT", + "dependencies": { + "mdn-data": "2.0.14", + "source-map": "^0.6.1" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/cytoscape": { + "version": "3.33.1", + "resolved": "https://registry.npmjs.org/cytoscape/-/cytoscape-3.33.1.tgz", + "integrity": "sha512-iJc4TwyANnOGR1OmWhsS9ayRS3s+XQ185FmuHObThD+5AeJCakAAbWv8KimMTt08xCCLNgneQwFp+JRJOr9qGQ==", + "license": "MIT", + "engines": { + "node": ">=0.10" + } + }, + "node_modules/cytoscape-cose-bilkent": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/cytoscape-cose-bilkent/-/cytoscape-cose-bilkent-4.1.0.tgz", + "integrity": "sha512-wgQlVIUJF13Quxiv5e1gstZ08rnZj2XaLHGoFMYXz7SkNfCDOOteKBE6SYRfA9WxxI/iBc3ajfDoc6hb/MRAHQ==", + "license": "MIT", + "dependencies": { + "cose-base": "^1.0.0" + }, + "peerDependencies": { + "cytoscape": "^3.2.0" + } + }, + "node_modules/d": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/d/-/d-1.0.2.tgz", + "integrity": "sha512-MOqHvMWF9/9MX6nza0KgvFH4HpMU0EF5uUDXqX/BtxtU8NfB0QzRtJ8Oe/6SuS4kbhyzVJwjd97EA4PKrzJ8bw==", + "license": "ISC", + "dependencies": { + "es5-ext": "^0.10.64", + "type": "^2.7.2" + }, + "engines": { + "node": ">=0.12" + } + }, + "node_modules/d3": { + "version": "7.9.0", + "resolved": "https://registry.npmjs.org/d3/-/d3-7.9.0.tgz", + "integrity": "sha512-e1U46jVP+w7Iut8Jt8ri1YsPOvFpg46k+K8TpCb0P+zjCkjkPnV7WzfDJzMHy1LnA+wj5pLT1wjO901gLXeEhA==", + "license": "ISC", + "dependencies": { + "d3-array": "3", + "d3-axis": "3", + "d3-brush": "3", + "d3-chord": "3", + "d3-color": "3", + "d3-contour": "4", + "d3-delaunay": "6", + "d3-dispatch": "3", + "d3-drag": "3", + "d3-dsv": "3", + "d3-ease": "3", + "d3-fetch": "3", + "d3-force": "3", + "d3-format": "3", + "d3-geo": "3", + "d3-hierarchy": "3", + "d3-interpolate": "3", + "d3-path": "3", + "d3-polygon": "3", + "d3-quadtree": "3", + "d3-random": "3", + "d3-scale": "4", + "d3-scale-chromatic": "3", + "d3-selection": "3", + "d3-shape": "3", + "d3-time": "3", + "d3-time-format": "4", + "d3-timer": "3", + "d3-transition": "3", + "d3-zoom": "3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-array": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-3.2.4.tgz", + "integrity": "sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==", + "license": "ISC", + "dependencies": { + "internmap": "1 - 2" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-axis": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-axis/-/d3-axis-3.0.0.tgz", + "integrity": "sha512-IH5tgjV4jE/GhHkRV0HiVYPDtvfjHQlQfJHs0usq7M30XcSBvOotpmH1IgkcXsO/5gEQZD43B//fc7SRT5S+xw==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-brush": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-brush/-/d3-brush-3.0.0.tgz", + "integrity": "sha512-ALnjWlVYkXsVIGlOsuWH1+3udkYFI48Ljihfnh8FZPF2QS9o+PzGLBslO0PjzVoHLZ2KCVgAM8NVkXPJB2aNnQ==", + "license": "ISC", + "dependencies": { + "d3-dispatch": "1 - 3", + "d3-drag": "2 - 3", + "d3-interpolate": "1 - 3", + "d3-selection": "3", + "d3-transition": "3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-chord": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-chord/-/d3-chord-3.0.1.tgz", + "integrity": "sha512-VE5S6TNa+j8msksl7HwjxMHDM2yNK3XCkusIlpX5kwauBfXuyLAtNg9jCp/iHH61tgI4sb6R/EIMWCqEIdjT/g==", + "license": "ISC", + "dependencies": { + "d3-path": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-color": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-color/-/d3-color-3.1.0.tgz", + "integrity": "sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-contour": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/d3-contour/-/d3-contour-4.0.2.tgz", + "integrity": "sha512-4EzFTRIikzs47RGmdxbeUvLWtGedDUNkTcmzoeyg4sP/dvCexO47AaQL7VKy/gul85TOxw+IBgA8US2xwbToNA==", + "license": "ISC", + "dependencies": { + "d3-array": "^3.2.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-delaunay": { + "version": "6.0.4", + "resolved": "https://registry.npmjs.org/d3-delaunay/-/d3-delaunay-6.0.4.tgz", + "integrity": "sha512-mdjtIZ1XLAM8bm/hx3WwjfHt6Sggek7qH043O8KEjDXN40xi3vx/6pYSVTwLjEgiXQTbvaouWKynLBiUZ6SK6A==", + "license": "ISC", + "dependencies": { + "delaunator": "5" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-dispatch": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-dispatch/-/d3-dispatch-3.0.1.tgz", + "integrity": "sha512-rzUyPU/S7rwUflMyLc1ETDeBj0NRuHKKAcvukozwhshr6g6c5d8zh4c2gQjY2bZ0dXeGLWc1PF174P2tVvKhfg==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-drag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-drag/-/d3-drag-3.0.0.tgz", + "integrity": "sha512-pWbUJLdETVA8lQNJecMxoXfH6x+mO2UQo8rSmZ+QqxcbyA3hfeprFgIT//HW2nlHChWeIIMwS2Fq+gEARkhTkg==", + "license": "ISC", + "dependencies": { + "d3-dispatch": "1 - 3", + "d3-selection": "3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-dsv": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-dsv/-/d3-dsv-3.0.1.tgz", + "integrity": "sha512-UG6OvdI5afDIFP9w4G0mNq50dSOsXHJaRE8arAS5o9ApWnIElp8GZw1Dun8vP8OyHOZ/QJUKUJwxiiCCnUwm+Q==", + "license": "ISC", + "dependencies": { + "commander": "7", + "iconv-lite": "0.6", + "rw": "1" + }, + "bin": { + "csv2json": "bin/dsv2json.js", + "csv2tsv": "bin/dsv2dsv.js", + "dsv2dsv": "bin/dsv2dsv.js", + "dsv2json": "bin/dsv2json.js", + "json2csv": "bin/json2dsv.js", + "json2dsv": "bin/json2dsv.js", + "json2tsv": "bin/json2dsv.js", + "tsv2csv": "bin/dsv2dsv.js", + "tsv2json": "bin/dsv2json.js" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-ease": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-ease/-/d3-ease-3.0.1.tgz", + "integrity": "sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-fetch": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-fetch/-/d3-fetch-3.0.1.tgz", + "integrity": "sha512-kpkQIM20n3oLVBKGg6oHrUchHM3xODkTzjMoj7aWQFq5QEM+R6E4WkzT5+tojDY7yjez8KgCBRoj4aEr99Fdqw==", + "license": "ISC", + "dependencies": { + "d3-dsv": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-force": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-force/-/d3-force-3.0.0.tgz", + "integrity": "sha512-zxV/SsA+U4yte8051P4ECydjD/S+qeYtnaIyAs9tgHCqfguma/aAQDjo85A9Z6EKhBirHRJHXIgJUlffT4wdLg==", + "license": "ISC", + "dependencies": { + "d3-dispatch": "1 - 3", + "d3-quadtree": "1 - 3", + "d3-timer": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-format": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-format/-/d3-format-3.1.0.tgz", + "integrity": "sha512-YyUI6AEuY/Wpt8KWLgZHsIU86atmikuoOmCfommt0LYHiQSPjvX2AcFc38PX0CBpr2RCyZhjex+NS/LPOv6YqA==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-geo": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/d3-geo/-/d3-geo-3.1.1.tgz", + "integrity": "sha512-637ln3gXKXOwhalDzinUgY83KzNWZRKbYubaG+fGVuc/dxO64RRljtCTnf5ecMyE1RIdtqpkVcq0IbtU2S8j2Q==", + "license": "ISC", + "dependencies": { + "d3-array": "2.5.0 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-hierarchy": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/d3-hierarchy/-/d3-hierarchy-3.1.2.tgz", + "integrity": "sha512-FX/9frcub54beBdugHjDCdikxThEqjnR93Qt7PvQTOHxyiNCAlvMrHhclk3cD5VeAaq9fxmfRp+CnWw9rEMBuA==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-interpolate": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-interpolate/-/d3-interpolate-3.0.1.tgz", + "integrity": "sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==", + "license": "ISC", + "dependencies": { + "d3-color": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-path": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-path/-/d3-path-3.1.0.tgz", + "integrity": "sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-polygon": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-polygon/-/d3-polygon-3.0.1.tgz", + "integrity": "sha512-3vbA7vXYwfe1SYhED++fPUQlWSYTTGmFmQiany/gdbiWgU/iEyQzyymwL9SkJjFFuCS4902BSzewVGsHHmHtXg==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-quadtree": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-quadtree/-/d3-quadtree-3.0.1.tgz", + "integrity": "sha512-04xDrxQTDTCFwP5H6hRhsRcb9xxv2RzkcsygFzmkSIOJy3PeRJP7sNk3VRIbKXcog561P9oU0/rVH6vDROAgUw==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-random": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-random/-/d3-random-3.0.1.tgz", + "integrity": "sha512-FXMe9GfxTxqd5D6jFsQ+DJ8BJS4E/fT5mqqdjovykEB2oFbTMDVdg1MGFxfQW+FBOGoB++k8swBrgwSHT1cUXQ==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-sankey": { + "version": "0.12.3", + "resolved": "https://registry.npmjs.org/d3-sankey/-/d3-sankey-0.12.3.tgz", + "integrity": "sha512-nQhsBRmM19Ax5xEIPLMY9ZmJ/cDvd1BG3UVvt5h3WRxKg5zGRbvnteTyWAbzeSvlh3tW7ZEmq4VwR5mB3tutmQ==", + "license": "BSD-3-Clause", + "dependencies": { + "d3-array": "1 - 2", + "d3-shape": "^1.2.0" + } + }, + "node_modules/d3-sankey/node_modules/d3-array": { + "version": "2.12.1", + "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-2.12.1.tgz", + "integrity": "sha512-B0ErZK/66mHtEsR1TkPEEkwdy+WDesimkM5gpZr5Dsg54BiTA5RXtYW5qTLIAcekaS9xfZrzBLF/OAkB3Qn1YQ==", + "license": "BSD-3-Clause", + "dependencies": { + "internmap": "^1.0.0" + } + }, + "node_modules/d3-sankey/node_modules/d3-path": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/d3-path/-/d3-path-1.0.9.tgz", + "integrity": "sha512-VLaYcn81dtHVTjEHd8B+pbe9yHWpXKZUC87PzoFmsFrJqgFwDe/qxfp5MlfsfM1V5E/iVt0MmEbWQ7FVIXh/bg==", + "license": "BSD-3-Clause" + }, + "node_modules/d3-sankey/node_modules/d3-shape": { + "version": "1.3.7", + "resolved": "https://registry.npmjs.org/d3-shape/-/d3-shape-1.3.7.tgz", + "integrity": "sha512-EUkvKjqPFUAZyOlhY5gzCxCeI0Aep04LwIRpsZ/mLFelJiUfnK56jo5JMDSE7yyP2kLSb6LtF+S5chMk7uqPqw==", + "license": "BSD-3-Clause", + "dependencies": { + "d3-path": "1" + } + }, + "node_modules/d3-sankey/node_modules/internmap": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/internmap/-/internmap-1.0.1.tgz", + "integrity": "sha512-lDB5YccMydFBtasVtxnZ3MRBHuaoE8GKsppq+EchKL2U4nK/DmEpPHNH8MZe5HkMtpSiTSOZwfN0tzYjO/lJEw==", + "license": "ISC" + }, + "node_modules/d3-scale": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/d3-scale/-/d3-scale-4.0.2.tgz", + "integrity": "sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ==", + "license": "ISC", + "dependencies": { + "d3-array": "2.10.0 - 3", + "d3-format": "1 - 3", + "d3-interpolate": "1.2.0 - 3", + "d3-time": "2.1.1 - 3", + "d3-time-format": "2 - 4" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-scale-chromatic": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-scale-chromatic/-/d3-scale-chromatic-3.1.0.tgz", + "integrity": "sha512-A3s5PWiZ9YCXFye1o246KoscMWqf8BsD9eRiJ3He7C9OBaxKhAd5TFCdEx/7VbKtxxTsu//1mMJFrEt572cEyQ==", + "license": "ISC", + "dependencies": { + "d3-color": "1 - 3", + "d3-interpolate": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-selection": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-selection/-/d3-selection-3.0.0.tgz", + "integrity": "sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-shape": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/d3-shape/-/d3-shape-3.2.0.tgz", + "integrity": "sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA==", + "license": "ISC", + "dependencies": { + "d3-path": "^3.1.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-time": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-time/-/d3-time-3.1.0.tgz", + "integrity": "sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q==", + "license": "ISC", + "dependencies": { + "d3-array": "2 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-time-format": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/d3-time-format/-/d3-time-format-4.1.0.tgz", + "integrity": "sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg==", + "license": "ISC", + "dependencies": { + "d3-time": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-timer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-timer/-/d3-timer-3.0.1.tgz", + "integrity": "sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-transition": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-transition/-/d3-transition-3.0.1.tgz", + "integrity": "sha512-ApKvfjsSR6tg06xrL434C0WydLr7JewBB3V+/39RMHsaXTOG0zmt/OAXeng5M5LBm0ojmxJrpomQVZ1aPvBL4w==", + "license": "ISC", + "dependencies": { + "d3-color": "1 - 3", + "d3-dispatch": "1 - 3", + "d3-ease": "1 - 3", + "d3-interpolate": "1 - 3", + "d3-timer": "1 - 3" + }, + "engines": { + "node": ">=12" + }, + "peerDependencies": { + "d3-selection": "2 - 3" + } + }, + "node_modules/d3-zoom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-zoom/-/d3-zoom-3.0.0.tgz", + "integrity": "sha512-b8AmV3kfQaqWAuacbPuNbL6vahnOJflOhexLzMMNLga62+/nh0JzvJ0aO/5a5MVgUFGS7Hu1P9P03o3fJkDCyw==", + "license": "ISC", + "dependencies": { + "d3-dispatch": "1 - 3", + "d3-drag": "2 - 3", + "d3-interpolate": "1 - 3", + "d3-selection": "2 - 3", + "d3-transition": "2 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/dagre-d3-es": { + "version": "7.0.10", + "resolved": "https://registry.npmjs.org/dagre-d3-es/-/dagre-d3-es-7.0.10.tgz", + "integrity": "sha512-qTCQmEhcynucuaZgY5/+ti3X/rnszKZhEQH/ZdWdtP1tA/y3VoHJzcVrO9pjjJCNpigfscAtoUB5ONcd2wNn0A==", + "license": "MIT", + "dependencies": { + "d3": "^7.8.2", + "lodash-es": "^4.17.21" + } + }, + "node_modules/data-uri-to-buffer": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-6.0.2.tgz", + "integrity": "sha512-7hvf7/GW8e86rW0ptuwS3OcBGDjIi6SZva7hCyWC0yYry2cOPmLIjXAUHI6DK2HsnwJd9ifmt57i8eV2n4YNpw==", + "license": "MIT", + "engines": { + "node": ">= 14" + } + }, + "node_modules/dayjs": { + "version": "1.11.19", + "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.19.tgz", + "integrity": "sha512-t5EcLVS6QPBNqM2z8fakk/NKel+Xzshgt8FFKAn+qwlD1pzZWxh0nVCrvFK7ZDb6XucZeF9z8C7CBWTRIVApAw==", + "license": "MIT" + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/decode-named-character-reference": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/decode-named-character-reference/-/decode-named-character-reference-1.2.0.tgz", + "integrity": "sha512-c6fcElNV6ShtZXmsgNgFFV5tVX2PaV4g+MOAkb8eXHvn6sryJBrZa9r0zV6+dtTyoCKxtDy5tyQ5ZwQuidtd+Q==", + "license": "MIT", + "dependencies": { + "character-entities": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/deepmerge": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", + "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/degenerator": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/degenerator/-/degenerator-5.0.1.tgz", + "integrity": "sha512-TllpMR/t0M5sqCXfj85i4XaAzxmS5tVA16dqvdkMwGmzI+dXLXnw3J+3Vdv7VKw+ThlTMboK6i9rnZ6Nntj5CQ==", + "license": "MIT", + "dependencies": { + "ast-types": "^0.13.4", + "escodegen": "^2.1.0", + "esprima": "^4.0.1" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/delaunator": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/delaunator/-/delaunator-5.0.1.tgz", + "integrity": "sha512-8nvh+XBe96aCESrGOqMp/84b13H9cdKbG5P2ejQCh4d4sK9RL4371qou9drQjMhvnPmhWl5hnmqbEE0fXr9Xnw==", + "license": "ISC", + "dependencies": { + "robust-predicates": "^3.0.2" + } + }, + "node_modules/dequal": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", + "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/devtools-protocol": { + "version": "0.0.1367902", + "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1367902.tgz", + "integrity": "sha512-XxtPuC3PGakY6PD7dG66/o8KwJ/LkH2/EKe19Dcw58w53dv4/vSQEkn/SzuyhHE2q4zPgCkxQBxus3VV4ql+Pg==", + "license": "BSD-3-Clause" + }, + "node_modules/diff": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-5.2.0.tgz", + "integrity": "sha512-uIFDxqpRZGZ6ThOk84hEfqWoHx2devRFvpTZcTHur85vImfaxUbTW9Ryh4CpCuDnToOP1CEtXKIgytHBPVff5A==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.3.1" + } + }, + "node_modules/dom-serializer": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz", + "integrity": "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==", + "license": "MIT", + "dependencies": { + "domelementtype": "^2.3.0", + "domhandler": "^5.0.2", + "entities": "^4.2.0" + }, + "funding": { + "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1" + } + }, + "node_modules/domelementtype": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz", + "integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ], + "license": "BSD-2-Clause" + }, + "node_modules/domhandler": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz", + "integrity": "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==", + "license": "BSD-2-Clause", + "dependencies": { + "domelementtype": "^2.3.0" + }, + "engines": { + "node": ">= 4" + }, + "funding": { + "url": "https://github.com/fb55/domhandler?sponsor=1" + } + }, + "node_modules/dompurify": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.3.1.tgz", + "integrity": "sha512-qkdCKzLNtrgPFP1Vo+98FRzJnBRGe4ffyCea9IwHB1fyxPOeNTHpLKYGd4Uk9xvNoH0ZoOjwZxNptyMwqrId1Q==", + "license": "(MPL-2.0 OR Apache-2.0)", + "optionalDependencies": { + "@types/trusted-types": "^2.0.7" + } + }, + "node_modules/domutils": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.2.2.tgz", + "integrity": "sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw==", + "license": "BSD-2-Clause", + "dependencies": { + "dom-serializer": "^2.0.0", + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3" + }, + "funding": { + "url": "https://github.com/fb55/domutils?sponsor=1" + } + }, + "node_modules/elkjs": { + "version": "0.9.3", + "resolved": "https://registry.npmjs.org/elkjs/-/elkjs-0.9.3.tgz", + "integrity": "sha512-f/ZeWvW/BCXbhGEf1Ujp29EASo/lk1FDnETgNKwJrsVvGZhUWCZyg3xLJjAsxfOmt8KjswHmI5EwCQcPMpOYhQ==", + "license": "EPL-2.0" + }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "license": "MIT" + }, + "node_modules/end-of-stream": { + "version": "1.4.5", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.5.tgz", + "integrity": "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==", + "license": "MIT", + "dependencies": { + "once": "^1.4.0" + } + }, + "node_modules/entities": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/env-paths": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz", + "integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/error-ex": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.4.tgz", + "integrity": "sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ==", + "license": "MIT", + "dependencies": { + "is-arrayish": "^0.2.1" + } + }, + "node_modules/es5-ext": { + "version": "0.10.64", + "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.64.tgz", + "integrity": "sha512-p2snDhiLaXe6dahss1LddxqEm+SkuDvV8dnIQG0MWjyHpcMNfXKPE+/Cc0y+PhxJX3A4xGNeFCj5oc0BUh6deg==", + "hasInstallScript": true, + "license": "ISC", + "dependencies": { + "es6-iterator": "^2.0.3", + "es6-symbol": "^3.1.3", + "esniff": "^2.0.1", + "next-tick": "^1.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/es6-iterator": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/es6-iterator/-/es6-iterator-2.0.3.tgz", + "integrity": "sha512-zw4SRzoUkd+cl+ZoE15A9o1oQd920Bb0iOJMQkQhl3jNc03YqVjAhG7scf9C5KWRU/R13Orf588uCC6525o02g==", + "license": "MIT", + "dependencies": { + "d": "1", + "es5-ext": "^0.10.35", + "es6-symbol": "^3.1.1" + } + }, + "node_modules/es6-symbol": { + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/es6-symbol/-/es6-symbol-3.1.4.tgz", + "integrity": "sha512-U9bFFjX8tFiATgtkJ1zg25+KviIXpgRvRHS8sau3GfhVzThRQrOeksPeT0BWW2MNZs1OEWJ1DPXOQMn0KKRkvg==", + "license": "ISC", + "dependencies": { + "d": "^1.0.2", + "ext": "^1.7.0" + }, + "engines": { + "node": ">=0.12" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/escodegen": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-2.1.0.tgz", + "integrity": "sha512-2NlIDTwUWJN0mRPQOdtQBzbUHvdGY2P1VXSyU83Q3xKxM7WHX2Ql8dKq782Q9TgQUNOLEzEYu9bzLNj1q88I5w==", + "license": "BSD-2-Clause", + "dependencies": { + "esprima": "^4.0.1", + "estraverse": "^5.2.0", + "esutils": "^2.0.2" + }, + "bin": { + "escodegen": "bin/escodegen.js", + "esgenerate": "bin/esgenerate.js" + }, + "engines": { + "node": ">=6.0" + }, + "optionalDependencies": { + "source-map": "~0.6.1" + } + }, + "node_modules/esniff": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/esniff/-/esniff-2.0.1.tgz", + "integrity": "sha512-kTUIGKQ/mDPFoJ0oVfcmyJn4iBDRptjNVIzwIFR7tqWXdVI9xfA2RMwY/gbSpJG3lkdWNEjLap/NqVHZiJsdfg==", + "license": "ISC", + "dependencies": { + "d": "^1.0.1", + "es5-ext": "^0.10.62", + "event-emitter": "^0.3.5", + "type": "^2.7.2" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "license": "BSD-2-Clause", + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/event-emitter": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/event-emitter/-/event-emitter-0.3.5.tgz", + "integrity": "sha512-D9rRn9y7kLPnJ+hMq7S/nhvoKwwvVJahBi2BPmx3bvbsEdK3W9ii8cBSGjP+72/LnM4n6fo3+dkCX5FeTQruXA==", + "license": "MIT", + "dependencies": { + "d": "1", + "es5-ext": "~0.10.14" + } + }, + "node_modules/events-universal": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/events-universal/-/events-universal-1.0.1.tgz", + "integrity": "sha512-LUd5euvbMLpwOF8m6ivPCbhQeSiYVNb8Vs0fQ8QjXo0JTkEHpz8pxdQf0gStltaPpw0Cca8b39KxvK9cfKRiAw==", + "license": "Apache-2.0", + "dependencies": { + "bare-events": "^2.7.0" + } + }, + "node_modules/ext": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/ext/-/ext-1.7.0.tgz", + "integrity": "sha512-6hxeJYaL110a9b5TEJSj0gojyHQAmA2ch5Os+ySCiA1QGdS697XWY1pzsrSjqA9LDEEgdB/KypIlR59RcLuHYw==", + "license": "ISC", + "dependencies": { + "type": "^2.7.2" + } + }, + "node_modules/extract-zip": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extract-zip/-/extract-zip-2.0.1.tgz", + "integrity": "sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg==", + "license": "BSD-2-Clause", + "dependencies": { + "debug": "^4.1.1", + "get-stream": "^5.1.0", + "yauzl": "^2.10.0" + }, + "bin": { + "extract-zip": "cli.js" + }, + "engines": { + "node": ">= 10.17.0" + }, + "optionalDependencies": { + "@types/yauzl": "^2.9.1" + } + }, + "node_modules/fast-fifo": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/fast-fifo/-/fast-fifo-1.3.2.tgz", + "integrity": "sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ==", + "license": "MIT" + }, + "node_modules/fd-slicer": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz", + "integrity": "sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g==", + "license": "MIT", + "dependencies": { + "pend": "~1.2.0" + } + }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "license": "ISC", + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/get-stream": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", + "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", + "license": "MIT", + "dependencies": { + "pump": "^3.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/get-uri": { + "version": "6.0.5", + "resolved": "https://registry.npmjs.org/get-uri/-/get-uri-6.0.5.tgz", + "integrity": "sha512-b1O07XYq8eRuVzBNgJLstU6FYc1tS6wnMtF1I1D9lE8LxZSOGZ7LhxN54yPP6mGw5f2CkXY2BQUL9Fx41qvcIg==", + "license": "MIT", + "dependencies": { + "basic-ftp": "^5.0.2", + "data-uri-to-buffer": "^6.0.2", + "debug": "^4.3.4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/htmlparser2": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-8.0.2.tgz", + "integrity": "sha512-GYdjWKDkbRLkZ5geuHs5NY1puJ+PXwP7+fHPRz06Eirsb9ugf6d8kkXav6ADhcODhFFPMIXyxkxSuMf3D6NCFA==", + "funding": [ + "https://github.com/fb55/htmlparser2?sponsor=1", + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ], + "license": "MIT", + "dependencies": { + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3", + "domutils": "^3.0.1", + "entities": "^4.4.0" + } + }, + "node_modules/http-proxy-agent": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", + "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==", + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.0", + "debug": "^4.3.4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/https-proxy-agent": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", + "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.2", + "debug": "4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "BSD-3-Clause" + }, + "node_modules/import-fresh": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", + "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", + "license": "MIT", + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/internmap": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/internmap/-/internmap-2.0.3.tgz", + "integrity": "sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/ip-address": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.1.0.tgz", + "integrity": "sha512-XXADHxXmvT9+CRxhXg56LJovE+bmWnEWB78LB83VZTprKTmaC5QfruXocxzTZ2Kl0DNwKuBdlIhjL8LeY8Sf8Q==", + "license": "MIT", + "engines": { + "node": ">= 12" + } + }, + "node_modules/is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", + "license": "MIT" + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-plain-object": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-5.0.0.tgz", + "integrity": "sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "license": "MIT" + }, + "node_modules/js-yaml": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", + "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/json-parse-even-better-errors": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", + "license": "MIT" + }, + "node_modules/katex": { + "version": "0.16.27", + "resolved": "https://registry.npmjs.org/katex/-/katex-0.16.27.tgz", + "integrity": "sha512-aeQoDkuRWSqQN6nSvVCEFvfXdqo1OQiCmmW1kc9xSdjutPv7BGO7pqY9sQRJpMOGrEdfDgF2TfRXe5eUAD2Waw==", + "funding": [ + "https://opencollective.com/katex", + "https://github.com/sponsors/katex" + ], + "license": "MIT", + "dependencies": { + "commander": "^8.3.0" + }, + "bin": { + "katex": "cli.js" + } + }, + "node_modules/katex/node_modules/commander": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-8.3.0.tgz", + "integrity": "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==", + "license": "MIT", + "engines": { + "node": ">= 12" + } + }, + "node_modules/khroma": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/khroma/-/khroma-2.1.0.tgz", + "integrity": "sha512-Ls993zuzfayK269Svk9hzpeGUKob/sIgZzyHYdjQoAdQetRKpOLj+k/QQQ/6Qi0Yz65mlROrfd+Ev+1+7dz9Kw==" + }, + "node_modules/kleur": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/kleur/-/kleur-4.1.5.tgz", + "integrity": "sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/layout-base": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/layout-base/-/layout-base-1.0.2.tgz", + "integrity": "sha512-8h2oVEZNktL4BH2JCOI90iD1yXwL6iNW7KcCKT2QZgQJR2vbqDsldCTPRU9NifTCqHZci57XvQQ15YTu+sTYPg==", + "license": "MIT" + }, + "node_modules/lines-and-columns": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", + "license": "MIT" + }, + "node_modules/linkify-it": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-5.0.0.tgz", + "integrity": "sha512-5aHCbzQRADcdP+ATqnDuhhJ/MRIqDkZX5pyjFHRRysS8vZ5AbqGEoFIb6pYHPZ+L/OC2Lc+xT8uHVVR5CAK/wQ==", + "license": "MIT", + "dependencies": { + "uc.micro": "^2.0.0" + } + }, + "node_modules/lodash-es": { + "version": "4.17.22", + "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.22.tgz", + "integrity": "sha512-XEawp1t0gxSi9x01glktRZ5HDy0HXqrM0x5pXQM98EaI0NxO6jVM7omDOxsuEo5UIASAnm2bRp1Jt/e0a2XU8Q==", + "license": "MIT" + }, + "node_modules/lru-cache": { + "version": "7.18.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz", + "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/markdown-it": { + "version": "14.1.0", + "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-14.1.0.tgz", + "integrity": "sha512-a54IwgWPaeBCAAsv13YgmALOF1elABB08FxO9i+r4VFk5Vl4pKokRPeX8u5TCgSsPi6ec1otfLjdOpVcgbpshg==", + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1", + "entities": "^4.4.0", + "linkify-it": "^5.0.0", + "mdurl": "^2.0.0", + "punycode.js": "^2.3.1", + "uc.micro": "^2.1.0" + }, + "bin": { + "markdown-it": "bin/markdown-it.mjs" + } + }, + "node_modules/mdast-util-from-markdown": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/mdast-util-from-markdown/-/mdast-util-from-markdown-1.3.1.tgz", + "integrity": "sha512-4xTO/M8c82qBcnQc1tgpNtubGUW/Y1tBQ1B0i5CtSoelOLKFYlElIr3bvgREYYO5iRqbMY1YuqZng0GVOI8Qww==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^3.0.0", + "@types/unist": "^2.0.0", + "decode-named-character-reference": "^1.0.0", + "mdast-util-to-string": "^3.1.0", + "micromark": "^3.0.0", + "micromark-util-decode-numeric-character-reference": "^1.0.0", + "micromark-util-decode-string": "^1.0.0", + "micromark-util-normalize-identifier": "^1.0.0", + "micromark-util-symbol": "^1.0.0", + "micromark-util-types": "^1.0.0", + "unist-util-stringify-position": "^3.0.0", + "uvu": "^0.5.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-to-string": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/mdast-util-to-string/-/mdast-util-to-string-3.2.0.tgz", + "integrity": "sha512-V4Zn/ncyN1QNSqSBxTrMOLpjr+IKdHl2v3KVLoWmDPscP4r9GcCi71gjgvUV1SFSKh92AjAG4peFuBl2/YgCJg==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdn-data": { + "version": "2.0.14", + "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.14.tgz", + "integrity": "sha512-dn6wd0uw5GsdswPFfsgMp5NSB0/aDe6fK94YJV/AJDYXL6HVLWBsxeq7js7Ad+mU2K9LAlwpk6kN2D5mwCPVow==", + "license": "CC0-1.0" + }, + "node_modules/mdurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mdurl/-/mdurl-2.0.0.tgz", + "integrity": "sha512-Lf+9+2r+Tdp5wXDXC4PcIBjTDtq4UKjCPMQhKIuzpJNW0b96kVqSwW0bT7FhRSfmAiFYgP+SCRvdrDozfh0U5w==", + "license": "MIT" + }, + "node_modules/mermaid": { + "version": "10.9.1", + "resolved": "https://registry.npmjs.org/mermaid/-/mermaid-10.9.1.tgz", + "integrity": "sha512-Mx45Obds5W1UkW1nv/7dHRsbfMM1aOKA2+Pxs/IGHNonygDHwmng8xTHyS9z4KWVi0rbko8gjiBmuwwXQ7tiNA==", + "license": "MIT", + "dependencies": { + "@braintree/sanitize-url": "^6.0.1", + "@types/d3-scale": "^4.0.3", + "@types/d3-scale-chromatic": "^3.0.0", + "cytoscape": "^3.28.1", + "cytoscape-cose-bilkent": "^4.1.0", + "d3": "^7.4.0", + "d3-sankey": "^0.12.3", + "dagre-d3-es": "7.0.10", + "dayjs": "^1.11.7", + "dompurify": "^3.0.5", + "elkjs": "^0.9.0", + "katex": "^0.16.9", + "khroma": "^2.0.0", + "lodash-es": "^4.17.21", + "mdast-util-from-markdown": "^1.3.0", + "non-layered-tidy-tree-layout": "^2.0.2", + "stylis": "^4.1.3", + "ts-dedent": "^2.2.0", + "uuid": "^9.0.0", + "web-worker": "^1.2.0" + } + }, + "node_modules/micromark": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/micromark/-/micromark-3.2.0.tgz", + "integrity": "sha512-uD66tJj54JLYq0De10AhWycZWGQNUvDI55xPgk2sQM5kn1JYlhbCMTtEeT27+vAhW2FBQxLlOmS3pmA7/2z4aA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "@types/debug": "^4.0.0", + "debug": "^4.0.0", + "decode-named-character-reference": "^1.0.0", + "micromark-core-commonmark": "^1.0.1", + "micromark-factory-space": "^1.0.0", + "micromark-util-character": "^1.0.0", + "micromark-util-chunked": "^1.0.0", + "micromark-util-combine-extensions": "^1.0.0", + "micromark-util-decode-numeric-character-reference": "^1.0.0", + "micromark-util-encode": "^1.0.0", + "micromark-util-normalize-identifier": "^1.0.0", + "micromark-util-resolve-all": "^1.0.0", + "micromark-util-sanitize-uri": "^1.0.0", + "micromark-util-subtokenize": "^1.0.0", + "micromark-util-symbol": "^1.0.0", + "micromark-util-types": "^1.0.1", + "uvu": "^0.5.0" + } + }, + "node_modules/micromark-core-commonmark": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-core-commonmark/-/micromark-core-commonmark-1.1.0.tgz", + "integrity": "sha512-BgHO1aRbolh2hcrzL2d1La37V0Aoz73ymF8rAcKnohLy93titmv62E0gP8Hrx9PKcKrqCZ1BbLGbP3bEhoXYlw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "decode-named-character-reference": "^1.0.0", + "micromark-factory-destination": "^1.0.0", + "micromark-factory-label": "^1.0.0", + "micromark-factory-space": "^1.0.0", + "micromark-factory-title": "^1.0.0", + "micromark-factory-whitespace": "^1.0.0", + "micromark-util-character": "^1.0.0", + "micromark-util-chunked": "^1.0.0", + "micromark-util-classify-character": "^1.0.0", + "micromark-util-html-tag-name": "^1.0.0", + "micromark-util-normalize-identifier": "^1.0.0", + "micromark-util-resolve-all": "^1.0.0", + "micromark-util-subtokenize": "^1.0.0", + "micromark-util-symbol": "^1.0.0", + "micromark-util-types": "^1.0.1", + "uvu": "^0.5.0" + } + }, + "node_modules/micromark-factory-destination": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-factory-destination/-/micromark-factory-destination-1.1.0.tgz", + "integrity": "sha512-XaNDROBgx9SgSChd69pjiGKbV+nfHGDPVYFs5dOoDd7ZnMAE+Cuu91BCpsY8RT2NP9vo/B8pds2VQNCLiu0zhg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^1.0.0", + "micromark-util-symbol": "^1.0.0", + "micromark-util-types": "^1.0.0" + } + }, + "node_modules/micromark-factory-label": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-factory-label/-/micromark-factory-label-1.1.0.tgz", + "integrity": "sha512-OLtyez4vZo/1NjxGhcpDSbHQ+m0IIGnT8BoPamh+7jVlzLJBH98zzuCoUeMxvM6WsNeh8wx8cKvqLiPHEACn0w==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^1.0.0", + "micromark-util-symbol": "^1.0.0", + "micromark-util-types": "^1.0.0", + "uvu": "^0.5.0" + } + }, + "node_modules/micromark-factory-space": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-1.1.0.tgz", + "integrity": "sha512-cRzEj7c0OL4Mw2v6nwzttyOZe8XY/Z8G0rzmWQZTBi/jjwyw/U4uqKtUORXQrR5bAZZnbTI/feRV/R7hc4jQYQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^1.0.0", + "micromark-util-types": "^1.0.0" + } + }, + "node_modules/micromark-factory-title": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-factory-title/-/micromark-factory-title-1.1.0.tgz", + "integrity": "sha512-J7n9R3vMmgjDOCY8NPw55jiyaQnH5kBdV2/UXCtZIpnHH3P6nHUKaH7XXEYuWwx/xUJcawa8plLBEjMPU24HzQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-factory-space": "^1.0.0", + "micromark-util-character": "^1.0.0", + "micromark-util-symbol": "^1.0.0", + "micromark-util-types": "^1.0.0" + } + }, + "node_modules/micromark-factory-whitespace": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-factory-whitespace/-/micromark-factory-whitespace-1.1.0.tgz", + "integrity": "sha512-v2WlmiymVSp5oMg+1Q0N1Lxmt6pMhIHD457whWM7/GUlEks1hI9xj5w3zbc4uuMKXGisksZk8DzP2UyGbGqNsQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-factory-space": "^1.0.0", + "micromark-util-character": "^1.0.0", + "micromark-util-symbol": "^1.0.0", + "micromark-util-types": "^1.0.0" + } + }, + "node_modules/micromark-util-character": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-1.2.0.tgz", + "integrity": "sha512-lXraTwcX3yH/vMDaFWCQJP1uIszLVebzUa3ZHdrgxr7KEU/9mL4mVgCpGbyhvNLNlauROiNUq7WN5u7ndbY6xg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^1.0.0", + "micromark-util-types": "^1.0.0" + } + }, + "node_modules/micromark-util-chunked": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-chunked/-/micromark-util-chunked-1.1.0.tgz", + "integrity": "sha512-Ye01HXpkZPNcV6FiyoW2fGZDUw4Yc7vT0E9Sad83+bEDiCJ1uXu0S3mr8WLpsz3HaG3x2q0HM6CTuPdcZcluFQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^1.0.0" + } + }, + "node_modules/micromark-util-classify-character": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-classify-character/-/micromark-util-classify-character-1.1.0.tgz", + "integrity": "sha512-SL0wLxtKSnklKSUplok1WQFoGhUdWYKggKUiqhX+Swala+BtptGCu5iPRc+xvzJ4PXE/hwM3FNXsfEVgoZsWbw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^1.0.0", + "micromark-util-symbol": "^1.0.0", + "micromark-util-types": "^1.0.0" + } + }, + "node_modules/micromark-util-combine-extensions": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-combine-extensions/-/micromark-util-combine-extensions-1.1.0.tgz", + "integrity": "sha512-Q20sp4mfNf9yEqDL50WwuWZHUrCO4fEyeDCnMGmG5Pr0Cz15Uo7KBs6jq+dq0EgX4DPwwrh9m0X+zPV1ypFvUA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-chunked": "^1.0.0", + "micromark-util-types": "^1.0.0" + } + }, + "node_modules/micromark-util-decode-numeric-character-reference": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-decode-numeric-character-reference/-/micromark-util-decode-numeric-character-reference-1.1.0.tgz", + "integrity": "sha512-m9V0ExGv0jB1OT21mrWcuf4QhP46pH1KkfWy9ZEezqHKAxkj4mPCy3nIH1rkbdMlChLHX531eOrymlwyZIf2iw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^1.0.0" + } + }, + "node_modules/micromark-util-decode-string": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-decode-string/-/micromark-util-decode-string-1.1.0.tgz", + "integrity": "sha512-YphLGCK8gM1tG1bd54azwyrQRjCFcmgj2S2GoJDNnh4vYtnL38JS8M4gpxzOPNyHdNEpheyWXCTnnTDY3N+NVQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "decode-named-character-reference": "^1.0.0", + "micromark-util-character": "^1.0.0", + "micromark-util-decode-numeric-character-reference": "^1.0.0", + "micromark-util-symbol": "^1.0.0" + } + }, + "node_modules/micromark-util-encode": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-encode/-/micromark-util-encode-1.1.0.tgz", + "integrity": "sha512-EuEzTWSTAj9PA5GOAs992GzNh2dGQO52UvAbtSOMvXTxv3Criqb6IOzJUBCmEqrrXSblJIJBbFFv6zPxpreiJw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromark-util-html-tag-name": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/micromark-util-html-tag-name/-/micromark-util-html-tag-name-1.2.0.tgz", + "integrity": "sha512-VTQzcuQgFUD7yYztuQFKXT49KghjtETQ+Wv/zUjGSGBioZnkA4P1XXZPT1FHeJA6RwRXSF47yvJ1tsJdoxwO+Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromark-util-normalize-identifier": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-normalize-identifier/-/micromark-util-normalize-identifier-1.1.0.tgz", + "integrity": "sha512-N+w5vhqrBihhjdpM8+5Xsxy71QWqGn7HYNUvch71iV2PM7+E3uWGox1Qp90loa1ephtCxG2ftRV/Conitc6P2Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^1.0.0" + } + }, + "node_modules/micromark-util-resolve-all": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-resolve-all/-/micromark-util-resolve-all-1.1.0.tgz", + "integrity": "sha512-b/G6BTMSg+bX+xVCshPTPyAu2tmA0E4X98NSR7eIbeC6ycCqCeE7wjfDIgzEbkzdEVJXRtOG4FbEm/uGbCRouA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-types": "^1.0.0" + } + }, + "node_modules/micromark-util-sanitize-uri": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/micromark-util-sanitize-uri/-/micromark-util-sanitize-uri-1.2.0.tgz", + "integrity": "sha512-QO4GXv0XZfWey4pYFndLUKEAktKkG5kZTdUNaTAkzbuJxn2tNBOr+QtxR2XpWaMhbImT2dPzyLrPXLlPhph34A==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^1.0.0", + "micromark-util-encode": "^1.0.0", + "micromark-util-symbol": "^1.0.0" + } + }, + "node_modules/micromark-util-subtokenize": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-subtokenize/-/micromark-util-subtokenize-1.1.0.tgz", + "integrity": "sha512-kUQHyzRoxvZO2PuLzMt2P/dwVsTiivCK8icYTeR+3WgbuPqfHgPPy7nFKbeqRivBvn/3N3GBiNC+JRTMSxEC7A==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-chunked": "^1.0.0", + "micromark-util-symbol": "^1.0.0", + "micromark-util-types": "^1.0.0", + "uvu": "^0.5.0" + } + }, + "node_modules/micromark-util-symbol": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-1.1.0.tgz", + "integrity": "sha512-uEjpEYY6KMs1g7QfJ2eX1SQEV+ZT4rUD3UcF6l57acZvLNK7PBZL+ty82Z1qhK1/yXIY4bdx04FKMgR0g4IAag==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromark-util-types": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-types/-/micromark-util-types-1.1.0.tgz", + "integrity": "sha512-ukRBgie8TIAcacscVHSiddHjO4k/q3pnedmzMQ4iwDcK0FtFCohKOlFbaOL/mPgfnPsL3C1ZyxJa4sbWrBl3jg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/mitt": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/mitt/-/mitt-3.0.1.tgz", + "integrity": "sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==", + "license": "MIT" + }, + "node_modules/mri": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/mri/-/mri-1.2.0.tgz", + "integrity": "sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/nanoid": { + "version": "3.3.11", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/netmask": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/netmask/-/netmask-2.0.2.tgz", + "integrity": "sha512-dBpDMdxv9Irdq66304OLfEmQ9tbNRFnFTuZiLo+bD+r332bBmMJ8GBLXklIXXgxd3+v9+KUnZaUR5PJMa75Gsg==", + "license": "MIT", + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/next-tick": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/next-tick/-/next-tick-1.1.0.tgz", + "integrity": "sha512-CXdUiJembsNjuToQvxayPZF9Vqht7hewsvy2sOWafLvi2awflj9mOC6bHIg50orX8IJvWKY9wYQ/zB2kogPslQ==", + "license": "ISC" + }, + "node_modules/non-layered-tidy-tree-layout": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/non-layered-tidy-tree-layout/-/non-layered-tidy-tree-layout-2.0.2.tgz", + "integrity": "sha512-gkXMxRzUH+PB0ax9dUN0yYF0S25BqeAYqhgMaLUFmpXLEk7Fcu8f4emJuOAY0V8kjDICxROIKsTAKsV/v355xw==", + "license": "MIT" + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "license": "ISC", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/pac-proxy-agent": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/pac-proxy-agent/-/pac-proxy-agent-7.2.0.tgz", + "integrity": "sha512-TEB8ESquiLMc0lV8vcd5Ql/JAKAoyzHFXaStwjkzpOpC5Yv+pIzLfHvjTSdf3vpa2bMiUQrg9i6276yn8666aA==", + "license": "MIT", + "dependencies": { + "@tootallnate/quickjs-emscripten": "^0.23.0", + "agent-base": "^7.1.2", + "debug": "^4.3.4", + "get-uri": "^6.0.1", + "http-proxy-agent": "^7.0.0", + "https-proxy-agent": "^7.0.6", + "pac-resolver": "^7.0.1", + "socks-proxy-agent": "^8.0.5" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/pac-resolver": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/pac-resolver/-/pac-resolver-7.0.1.tgz", + "integrity": "sha512-5NPgf87AT2STgwa2ntRMr45jTKrYBGkVU36yT0ig/n/GMAa3oPqhZfIQ2kMEimReg0+t9kZViDVZ83qfVUlckg==", + "license": "MIT", + "dependencies": { + "degenerator": "^5.0.0", + "netmask": "^2.0.2" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/pagedjs": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/pagedjs/-/pagedjs-0.4.3.tgz", + "integrity": "sha512-YtAN9JAjsQw1142gxEjEAwXvOF5nYQuDwnQ67RW2HZDkMLI+b4RsBE37lULZa9gAr6kDAOGBOhXI4wGMoY3raw==", + "license": "MIT", + "dependencies": { + "@babel/polyfill": "^7.10.1", + "@babel/runtime": "^7.21.0", + "clear-cut": "^2.0.2", + "css-tree": "^1.1.3", + "event-emitter": "^0.3.5" + } + }, + "node_modules/parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "license": "MIT", + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/parse-json": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", + "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.0.0", + "error-ex": "^1.3.1", + "json-parse-even-better-errors": "^2.3.0", + "lines-and-columns": "^1.1.6" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/parse-srcset": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/parse-srcset/-/parse-srcset-1.0.2.tgz", + "integrity": "sha512-/2qh0lav6CmI15FzA3i/2Bzk2zCgQhGMkvhOhKNcBVQ1ldgpbfiNTVslmooUmWJcADi1f1kIeynbDRVzNlfR6Q==", + "license": "MIT" + }, + "node_modules/pend": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz", + "integrity": "sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==", + "license": "MIT" + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "license": "ISC" + }, + "node_modules/postcss": { + "version": "8.5.6", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", + "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.11", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/progress": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", + "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/proxy-agent": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/proxy-agent/-/proxy-agent-6.5.0.tgz", + "integrity": "sha512-TmatMXdr2KlRiA2CyDu8GqR8EjahTG3aY3nXjdzFyoZbmB8hrBsTyMezhULIXKnC0jpfjlmiZ3+EaCzoInSu/A==", + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.2", + "debug": "^4.3.4", + "http-proxy-agent": "^7.0.1", + "https-proxy-agent": "^7.0.6", + "lru-cache": "^7.14.1", + "pac-proxy-agent": "^7.1.0", + "proxy-from-env": "^1.1.0", + "socks-proxy-agent": "^8.0.5" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", + "license": "MIT" + }, + "node_modules/pump": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.3.tgz", + "integrity": "sha512-todwxLMY7/heScKmntwQG8CXVkWUOdYxIvY2s0VWAAMh/nd8SoYiRaKjlr7+iCs984f2P8zvrfWcDDYVb73NfA==", + "license": "MIT", + "dependencies": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "node_modules/punycode.js": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode.js/-/punycode.js-2.3.1.tgz", + "integrity": "sha512-uxFIHU0YlHYhDQtV4R9J6a52SLx28BCjT+4ieh7IGbgwVJWO+km431c4yRlREUAsAmt/uMjQUyQHNEPf0M39CA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/puppeteer": { + "version": "23.11.1", + "resolved": "https://registry.npmjs.org/puppeteer/-/puppeteer-23.11.1.tgz", + "integrity": "sha512-53uIX3KR5en8l7Vd8n5DUv90Ae9QDQsyIthaUFVzwV6yU750RjqRznEtNMBT20VthqAdemnJN+hxVdmMHKt7Zw==", + "deprecated": "< 24.15.0 is no longer supported", + "hasInstallScript": true, + "license": "Apache-2.0", + "dependencies": { + "@puppeteer/browsers": "2.6.1", + "chromium-bidi": "0.11.0", + "cosmiconfig": "^9.0.0", + "devtools-protocol": "0.0.1367902", + "puppeteer-core": "23.11.1", + "typed-query-selector": "^2.12.0" + }, + "bin": { + "puppeteer": "lib/cjs/puppeteer/node/cli.js" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/puppeteer-core": { + "version": "23.11.1", + "resolved": "https://registry.npmjs.org/puppeteer-core/-/puppeteer-core-23.11.1.tgz", + "integrity": "sha512-3HZ2/7hdDKZvZQ7dhhITOUg4/wOrDRjyK2ZBllRB0ZCOi9u0cwq1ACHDjBB+nX+7+kltHjQvBRdeY7+W0T+7Gg==", + "license": "Apache-2.0", + "dependencies": { + "@puppeteer/browsers": "2.6.1", + "chromium-bidi": "0.11.0", + "debug": "^4.4.0", + "devtools-protocol": "0.0.1367902", + "typed-query-selector": "^2.12.0", + "ws": "^8.18.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/regenerator-runtime": { + "version": "0.13.11", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz", + "integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==", + "license": "MIT" + }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/robust-predicates": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/robust-predicates/-/robust-predicates-3.0.2.tgz", + "integrity": "sha512-IXgzBWvWQwE6PrDI05OvmXUIruQTcoMDzRsOd5CDvHCVLcLHMTSYvOK5Cm46kWqlV3yAbuSpBZdJ5oP5OUoStg==", + "license": "Unlicense" + }, + "node_modules/rw": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/rw/-/rw-1.3.3.tgz", + "integrity": "sha512-PdhdWy89SiZogBLaw42zdeqtRJ//zFd2PgQavcICDUgJT5oW10QCRKbJ6bg4r0/UY2M6BWd5tkxuGFRvCkgfHQ==", + "license": "BSD-3-Clause" + }, + "node_modules/sade": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/sade/-/sade-1.8.1.tgz", + "integrity": "sha512-xal3CZX1Xlo/k4ApwCFrHVACi9fBqJ7V+mwhBsuf/1IOKbBy098Fex+Wa/5QMubw09pSZ/u8EY8PWgevJsXp1A==", + "license": "MIT", + "dependencies": { + "mri": "^1.1.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "license": "MIT" + }, + "node_modules/sanitize-html": { + "version": "2.14.0", + "resolved": "https://registry.npmjs.org/sanitize-html/-/sanitize-html-2.14.0.tgz", + "integrity": "sha512-CafX+IUPxZshXqqRaG9ZClSlfPVjSxI0td7n07hk8QO2oO+9JDnlcL8iM8TWeOXOIBFgIOx6zioTzM53AOMn3g==", + "license": "MIT", + "dependencies": { + "deepmerge": "^4.2.2", + "escape-string-regexp": "^4.0.0", + "htmlparser2": "^8.0.0", + "is-plain-object": "^5.0.0", + "parse-srcset": "^1.0.2", + "postcss": "^8.3.11" + } + }, + "node_modules/semver": { + "version": "7.7.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", + "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/smart-buffer": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz", + "integrity": "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==", + "license": "MIT", + "engines": { + "node": ">= 6.0.0", + "npm": ">= 3.0.0" + } + }, + "node_modules/socks": { + "version": "2.8.7", + "resolved": "https://registry.npmjs.org/socks/-/socks-2.8.7.tgz", + "integrity": "sha512-HLpt+uLy/pxB+bum/9DzAgiKS8CX1EvbWxI4zlmgGCExImLdiad2iCwXT5Z4c9c3Eq8rP2318mPW2c+QbtjK8A==", + "license": "MIT", + "dependencies": { + "ip-address": "^10.0.1", + "smart-buffer": "^4.2.0" + }, + "engines": { + "node": ">= 10.0.0", + "npm": ">= 3.0.0" + } + }, + "node_modules/socks-proxy-agent": { + "version": "8.0.5", + "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-8.0.5.tgz", + "integrity": "sha512-HehCEsotFqbPW9sJ8WVYB6UbmIMv7kUUORIF2Nncq4VQvBfNBLibW9YZR5dlYCSUhwcD628pRllm7n+E+YTzJw==", + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.2", + "debug": "^4.3.4", + "socks": "^2.8.3" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/streamx": { + "version": "2.23.0", + "resolved": "https://registry.npmjs.org/streamx/-/streamx-2.23.0.tgz", + "integrity": "sha512-kn+e44esVfn2Fa/O0CPFcex27fjIL6MkVae0Mm6q+E6f0hWv578YCERbv+4m02cjxvDsPKLnmxral/rR6lBMAg==", + "license": "MIT", + "dependencies": { + "events-universal": "^1.0.0", + "fast-fifo": "^1.3.2", + "text-decoder": "^1.1.0" + } + }, + "node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/stylis": { + "version": "4.3.6", + "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.3.6.tgz", + "integrity": "sha512-yQ3rwFWRfwNUY7H5vpU0wfdkNSnvnJinhF9830Swlaxl03zsOjCfmX0ugac+3LtK0lYSgwL/KXc8oYL3mG4YFQ==", + "license": "MIT" + }, + "node_modules/tar-fs": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-3.1.1.tgz", + "integrity": "sha512-LZA0oaPOc2fVo82Txf3gw+AkEd38szODlptMYejQUhndHMLQ9M059uXR+AfS7DNo0NpINvSqDsvyaCrBVkptWg==", + "license": "MIT", + "dependencies": { + "pump": "^3.0.0", + "tar-stream": "^3.1.5" + }, + "optionalDependencies": { + "bare-fs": "^4.0.1", + "bare-path": "^3.0.0" + } + }, + "node_modules/tar-stream": { + "version": "3.1.7", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-3.1.7.tgz", + "integrity": "sha512-qJj60CXt7IU1Ffyc3NJMjh6EkuCFej46zUqJ4J7pqYlThyd9bO0XBTmcOIhSzZJVWfsLks0+nle/j538YAW9RQ==", + "license": "MIT", + "dependencies": { + "b4a": "^1.6.4", + "fast-fifo": "^1.2.0", + "streamx": "^2.15.0" + } + }, + "node_modules/text-decoder": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/text-decoder/-/text-decoder-1.2.3.tgz", + "integrity": "sha512-3/o9z3X0X0fTupwsYvR03pJ/DjWuqqrfwBgTQzdWDiQSm9KitAyz/9WqsT2JQW7KV2m+bC2ol/zqpW37NHxLaA==", + "license": "Apache-2.0", + "dependencies": { + "b4a": "^1.6.4" + } + }, + "node_modules/through": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", + "integrity": "sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==", + "license": "MIT" + }, + "node_modules/ts-dedent": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/ts-dedent/-/ts-dedent-2.2.0.tgz", + "integrity": "sha512-q5W7tVM71e2xjHZTlgfTDoPF/SmqKG5hddq9SzR49CH2hayqRKJtQ4mtRlSxKaJlR/+9rEM+mnBHf7I2/BQcpQ==", + "license": "MIT", + "engines": { + "node": ">=6.10" + } + }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, + "node_modules/type": { + "version": "2.7.3", + "resolved": "https://registry.npmjs.org/type/-/type-2.7.3.tgz", + "integrity": "sha512-8j+1QmAbPvLZow5Qpi6NCaN8FB60p/6x8/vfNqOk/hC+HuvFZhL4+WfekuhQLiqFZXOgQdrs3B+XxEmCc6b3FQ==", + "license": "ISC" + }, + "node_modules/typed-query-selector": { + "version": "2.12.0", + "resolved": "https://registry.npmjs.org/typed-query-selector/-/typed-query-selector-2.12.0.tgz", + "integrity": "sha512-SbklCd1F0EiZOyPiW192rrHZzZ5sBijB6xM+cpmrwDqObvdtunOHHIk9fCGsoK5JVIYXoyEp4iEdE3upFH3PAg==", + "license": "MIT" + }, + "node_modules/uc.micro": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-2.1.0.tgz", + "integrity": "sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A==", + "license": "MIT" + }, + "node_modules/unbzip2-stream": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/unbzip2-stream/-/unbzip2-stream-1.4.3.tgz", + "integrity": "sha512-mlExGW4w71ebDJviH16lQLtZS32VKqsSfk80GCfUlwT/4/hNRFsoscrF/c++9xinkMzECL1uL9DDwXqFWkruPg==", + "license": "MIT", + "dependencies": { + "buffer": "^5.2.1", + "through": "^2.3.8" + } + }, + "node_modules/undici-types": { + "version": "7.16.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz", + "integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==", + "license": "MIT", + "optional": true + }, + "node_modules/unist-util-stringify-position": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/unist-util-stringify-position/-/unist-util-stringify-position-3.0.3.tgz", + "integrity": "sha512-k5GzIBZ/QatR8N5X2y+drfpWG8IDBzdnVj6OInRNWm1oXrzydiaAT2OQiA8DPRRZyAKb9b6I2a6PxYklZD0gKg==", + "license": "MIT", + "dependencies": { + "@types/unist": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/uuid": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", + "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "license": "MIT", + "bin": { + "uuid": "dist/bin/uuid" + } + }, + "node_modules/uvu": { + "version": "0.5.6", + "resolved": "https://registry.npmjs.org/uvu/-/uvu-0.5.6.tgz", + "integrity": "sha512-+g8ENReyr8YsOc6fv/NVJs2vFdHBnBNdfE49rshrTzDWOlUx4Gq7KOS2GD8eqhy2j+Ejq29+SbKH8yjkAqXqoA==", + "license": "MIT", + "dependencies": { + "dequal": "^2.0.0", + "diff": "^5.0.0", + "kleur": "^4.0.3", + "sade": "^1.7.3" + }, + "bin": { + "uvu": "bin.js" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/web-worker": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/web-worker/-/web-worker-1.5.0.tgz", + "integrity": "sha512-RiMReJrTAiA+mBjGONMnjVDP2u3p9R1vkcGz6gDIrOMT3oGuYwX2WRMYI9ipkphSuE5XKEhydbhNEJh4NY9mlw==", + "license": "Apache-2.0" + }, + "node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "license": "ISC" + }, + "node_modules/ws": { + "version": "8.18.3", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.3.tgz", + "integrity": "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==", + "license": "MIT", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "license": "ISC", + "engines": { + "node": ">=10" + } + }, + "node_modules/yargs": { + "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "license": "MIT", + "dependencies": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/yauzl": { + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.10.0.tgz", + "integrity": "sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g==", + "license": "MIT", + "dependencies": { + "buffer-crc32": "~0.2.3", + "fd-slicer": "~1.1.0" + } + }, + "node_modules/zod": { + "version": "3.23.8", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.23.8.tgz", + "integrity": "sha512-XBx9AXhXktjUqnepgTiE5flcKIYWi/rme0Eaj+5Y0lftuGBq+jyRu/md4WnuxqgP1ubdpNCsYEYPxrzVHD8d6g==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + } + } +} diff --git a/worker/pdf/package.json b/worker/pdf/package.json new file mode 100644 index 0000000000..708b038460 --- /dev/null +++ b/worker/pdf/package.json @@ -0,0 +1,24 @@ +{ + "name": "forgejo-pdf-worker", + "version": "0.1.0", + "private": true, + "type": "commonjs", + "engines": { + "node": ">=20" + }, + "scripts": { + "lint": "node -c src/index.js", + "copy-assets": "node scripts/copy-assets.js", + "generate-manifest": "node scripts/generate-manifest.js", + "test:fixtures": "node scripts/test-fixtures.js" + }, + "dependencies": { + "@fontsource/ibm-plex-mono": "5.2.6", + "@fontsource/ibm-plex-sans": "5.2.6", + "markdown-it": "14.1.0", + "mermaid": "10.9.1", + "pagedjs": "0.4.3", + "puppeteer": "23.11.1", + "sanitize-html": "2.14.0" + } +} diff --git a/worker/pdf/scripts/copy-assets.js b/worker/pdf/scripts/copy-assets.js new file mode 100644 index 0000000000..df89127aa8 --- /dev/null +++ b/worker/pdf/scripts/copy-assets.js @@ -0,0 +1,43 @@ +const fs = require("node:fs"); +const path = require("node:path"); + +function copyFile(src, dst) { + fs.mkdirSync(path.dirname(dst), { recursive: true }); + fs.copyFileSync(src, dst); +} + +function pkgRoot(pkg) { + try { + return path.dirname(require.resolve(`${pkg}/package.json`)); + } catch { + const entry = require.resolve(pkg); + let dir = path.dirname(entry); + for (let i = 0; i < 8; i++) { + if (fs.existsSync(path.join(dir, "package.json"))) return dir; + dir = path.dirname(dir); + } + throw new Error(`Unable to locate package root: ${pkg}`); + } +} + +function main() { + const root = path.resolve(__dirname, ".."); + const assets = path.join(root, "assets"); + + // Mermaid bundle (browser). + // Keep it local; no external fetches at runtime. + copyFile(path.join(pkgRoot("mermaid"), "dist/mermaid.min.js"), path.join(assets, "js/mermaid.min.js")); + + // Paged.js bundle. + copyFile(path.join(pkgRoot("pagedjs"), "dist/paged.polyfill.js"), path.join(assets, "js/paged.polyfill.js")); + + // Fonts: only include latin subsets required by the default CSS. + const sansDir = path.join(pkgRoot("@fontsource/ibm-plex-sans"), "files"); + const monoDir = path.join(pkgRoot("@fontsource/ibm-plex-mono"), "files"); + + copyFile(path.join(sansDir, "ibm-plex-sans-latin-400-normal.woff2"), path.join(assets, "fonts/ibm-plex-sans-latin-400-normal.woff2")); + copyFile(path.join(sansDir, "ibm-plex-sans-latin-600-normal.woff2"), path.join(assets, "fonts/ibm-plex-sans-latin-600-normal.woff2")); + copyFile(path.join(monoDir, "ibm-plex-mono-latin-400-normal.woff2"), path.join(assets, "fonts/ibm-plex-mono-latin-400-normal.woff2")); +} + +main(); diff --git a/worker/pdf/scripts/generate-manifest.js b/worker/pdf/scripts/generate-manifest.js new file mode 100644 index 0000000000..39f166e558 --- /dev/null +++ b/worker/pdf/scripts/generate-manifest.js @@ -0,0 +1,88 @@ +const crypto = require("node:crypto"); +const fs = require("node:fs"); +const path = require("node:path"); + +function sha256File(p) { + const h = crypto.createHash("sha256"); + h.update(fs.readFileSync(p)); + return h.digest("hex"); +} + +function sha256String(s) { + return crypto.createHash("sha256").update(s).digest("hex"); +} + +function tryReadJSON(p) { + try { + return JSON.parse(fs.readFileSync(p, "utf8")); + } catch { + return null; + } +} + +function main() { + const root = path.resolve(__dirname, ".."); + const pkg = tryReadJSON(path.join(root, "package.json")) || {}; + const lock = tryReadJSON(path.join(root, "package-lock.json")) || {}; + + const code = { + "src/index.js": sha256File(path.join(root, "src/index.js")), + "src/render_pdf.js": sha256File(path.join(root, "src/render_pdf.js")), + "src/validate.js": sha256File(path.join(root, "src/validate.js")), + "src/errors.js": sha256File(path.join(root, "src/errors.js")), + "src/logger.js": sha256File(path.join(root, "src/logger.js")), + "scripts/test-fixtures.js": sha256File(path.join(root, "scripts/test-fixtures.js")), + "scripts/copy-assets.js": sha256File(path.join(root, "scripts/copy-assets.js")), + "scripts/generate-manifest.js": sha256File(path.join(root, "scripts/generate-manifest.js")) + }; + + const assets = { + "assets/js/mermaid.min.js": sha256File(path.join(root, "assets/js/mermaid.min.js")), + "assets/js/paged.polyfill.js": sha256File(path.join(root, "assets/js/paged.polyfill.js")), + "assets/css/basic.css": sha256File(path.join(root, "assets/css/basic.css")), + "assets/css/professional.css": sha256File(path.join(root, "assets/css/professional.css")), + "assets/fonts/ibm-plex-sans-latin-400-normal.woff2": sha256File(path.join(root, "assets/fonts/ibm-plex-sans-latin-400-normal.woff2")), + "assets/fonts/ibm-plex-sans-latin-600-normal.woff2": sha256File(path.join(root, "assets/fonts/ibm-plex-sans-latin-600-normal.woff2")), + "assets/fonts/ibm-plex-mono-latin-400-normal.woff2": sha256File(path.join(root, "assets/fonts/ibm-plex-mono-latin-400-normal.woff2")) + }; + + let chromePath = null; + let chromeSha = null; + let chromeVersion = null; + try { + const puppeteer = require("puppeteer"); + chromePath = puppeteer.executablePath(); + chromeSha = sha256File(chromePath); + if (typeof puppeteer.browserVersion === "function") { + chromeVersion = puppeteer.browserVersion(); + } + } catch { + // noop + } + + const deps = (pkg && pkg.dependencies) || {}; + const manifestCore = { + versions: { + worker: pkg.version || "0.0.0", + node: process.version, + puppeteer: deps.puppeteer || null, + mermaid: deps.mermaid || null, + pagedjs: deps.pagedjs || null, + markdownIt: deps["markdown-it"] || null, + sanitizeHtml: deps["sanitize-html"] || null, + ibmPlexSans: deps["@fontsource/ibm-plex-sans"] || null, + ibmPlexMono: deps["@fontsource/ibm-plex-mono"] || null, + lockfileVersion: lock.lockfileVersion || null + }, + chromium: chromePath ? { sha256: chromeSha, version: chromeVersion || null } : null, + code, + assets + }; + + const canonical = JSON.stringify(manifestCore); + const manifest = { ...manifestCore, manifest_sha: sha256String(canonical) }; + + fs.writeFileSync(path.join(root, "manifest.json"), JSON.stringify(manifest, null, 2)); +} + +main(); diff --git a/worker/pdf/scripts/test-fixtures.js b/worker/pdf/scripts/test-fixtures.js new file mode 100644 index 0000000000..40ae85a7ad --- /dev/null +++ b/worker/pdf/scripts/test-fixtures.js @@ -0,0 +1,133 @@ +const fs = require("node:fs"); +const path = require("node:path"); +const { spawnSync, execFileSync } = require("node:child_process"); + +function parseArgs(argv) { + const out = { fixtures: "/fixtures", outDir: "/tmp/pdf-fixtures-out" }; + for (let i = 2; i < argv.length; i++) { + const a = argv[i]; + if (a === "--fixtures") out.fixtures = argv[++i]; + else if (a === "--out") out.outDir = argv[++i]; + } + return out; +} + +function readManifestSHA() { + const p = "/opt/forgejo-pdf/manifest.json"; + const b = fs.readFileSync(p, "utf8"); + const j = JSON.parse(b); + if (!j.manifest_sha || typeof j.manifest_sha !== "string") { + throw new Error("manifest.json missing manifest_sha"); + } + return j.manifest_sha; +} + +function extractKnownUniqueString(md) { + const m = md.match(/KNOWN_UNIQUE_STRING:\s*([A-Za-z0-9_\-\.]+)/); + return m ? m[1] : null; +} + +function containsMermaid(md) { + return /```mermaid[\s\S]*?```/m.test(md); +} + +function run(cmd, args, opts = {}) { + const res = spawnSync(cmd, args, { encoding: "utf8", ...opts }); + return res; +} + +function main() { + const { fixtures, outDir } = parseArgs(process.argv); + fs.mkdirSync(outDir, { recursive: true }); + + const manifestSHA = readManifestSHA(); + + const pdfConfig = { + pdf: { + determinism: "strict", + timestamp: "commit_time", + typography: "professional", + mermaid: { strategy: "balanced", caption: false }, + orphansWidows: { enforce: true }, + footer: { enabled: true } + } + }; + + const files = fs + .readdirSync(fixtures) + .filter((f) => f.endsWith(".md")) + .sort(); + + if (files.length === 0) { + throw new Error("no fixture markdown files found"); + } + + for (const f of files) { + const mdPath = path.join(fixtures, f); + const md = fs.readFileSync(mdPath, "utf8"); + const expected = extractKnownUniqueString(md); + if (!expected) throw new Error(`fixture missing KNOWN_UNIQUE_STRING: ${f}`); + + const input = { + markdown: md, + repoMeta: { + owner: "fixture", + repo: "forgejo-pdf", + path: f, + repoID: 1, + commitSHA: "0123456789abcdef0123456789abcdef01234567", + commitTimeRFC3339: "2020-01-02T03:04:05Z" + }, + config: pdfConfig, + manifestSHA + }; + + const jobDir = fs.mkdtempSync(path.join(outDir, "job-")); + const inPath = path.join(jobDir, "input.json"); + const outPath = path.join(jobDir, "output.pdf"); + fs.writeFileSync(inPath, JSON.stringify(input), "utf8"); + + const res = run("node", ["src/index.js", "--in", inPath, "--out", outPath], { + cwd: "/opt/forgejo-pdf" + }); + if (res.status !== 0) { + throw new Error(`worker failed for ${f}: ${res.stderr.trim() || res.stdout.trim()}`); + } + + const logs = res.stderr + .split("\n") + .map((l) => l.trim()) + .filter(Boolean) + .map((l) => { + try { + return JSON.parse(l); + } catch { + return null; + } + }) + .filter(Boolean); + + const done = logs.findLast ? logs.findLast((l) => l.event === "done") : logs.reverse().find((l) => l.event === "done"); + if (!done) throw new Error(`missing done log for ${f}`); + if (done.blocked_requests !== 0) throw new Error(`blocked_requests != 0 for ${f}`); + + const hasMermaid = containsMermaid(md); + if (hasMermaid && (!Number.isFinite(done.mermaid_count) || done.mermaid_count < 1)) { + throw new Error(`expected mermaid_count >= 1 for ${f}`); + } + if (!hasMermaid && done.mermaid_count !== 0) { + throw new Error(`expected mermaid_count == 0 for ${f}`); + } + + execFileSync("qpdf", ["--check", outPath], { stdio: "inherit" }); + + const text = execFileSync("pdftotext", [outPath, "-"], { encoding: "utf8" }); + const normalized = text.replace(/\s+/g, ""); + const expectedNorm = expected.replace(/\s+/g, ""); + if (!normalized.includes(expectedNorm)) { + throw new Error(`pdftotext missing expected marker for ${f}: ${expected}`); + } + } +} + +main(); diff --git a/worker/pdf/src/errors.js b/worker/pdf/src/errors.js new file mode 100644 index 0000000000..a4a40a5f3a --- /dev/null +++ b/worker/pdf/src/errors.js @@ -0,0 +1,20 @@ +const ERROR = Object.freeze({ + BAD_INPUT: { error_id: "ERR_BAD_INPUT", message: "Invalid input." }, + BAD_CONFIG: { error_id: "ERR_BAD_CONFIG", message: "Invalid configuration." }, + NETWORK_ATTEMPT: { error_id: "ERR_NETWORK_ATTEMPT", message: "External network access is not allowed." }, + MERMAID_NO_TEXT: { error_id: "ERR_MERMAID_NO_TEXT", message: "Mermaid output must contain selectable text." }, + MERMAID_FOREIGN_OBJECT: { error_id: "ERR_MERMAID_FOREIGN_OBJECT", message: "Mermaid output contains forbidden elements." }, + TIMEOUT: { error_id: "ERR_TIMEOUT", message: "Render timed out." }, + INTERNAL: { error_id: "ERR_INTERNAL", message: "Render failed." } +}); + +function asFailure(errorDef, detailsCode) { + return { + error_id: errorDef.error_id, + message: errorDef.message, + ...(detailsCode ? { details_code: detailsCode } : {}) + }; +} + +module.exports = { ERROR, asFailure }; + diff --git a/worker/pdf/src/index.js b/worker/pdf/src/index.js new file mode 100644 index 0000000000..a7c764365b --- /dev/null +++ b/worker/pdf/src/index.js @@ -0,0 +1,73 @@ +const fs = require("node:fs"); +const path = require("node:path"); + +const { log } = require("./logger"); +const { ERROR, asFailure } = require("./errors"); +const { validateInput } = require("./validate"); +const { renderToPDF } = require("./render_pdf"); + +function parseArgs(argv) { + const args = { inPath: null, outPath: null }; + for (let i = 2; i < argv.length; i++) { + const a = argv[i]; + if (a === "--in") args.inPath = argv[++i]; + else if (a === "--out") args.outPath = argv[++i]; + } + return args; +} + +async function main() { + const { inPath, outPath } = parseArgs(process.argv); + if (!inPath || !outPath) { + log(asFailure(ERROR.BAD_INPUT, "missing_cli_args")); + process.exitCode = 2; + return; + } + + let inputRaw; + try { + inputRaw = fs.readFileSync(inPath, "utf8"); + } catch { + log(asFailure(ERROR.BAD_INPUT, "read_input_failed")); + process.exitCode = 2; + return; + } + + let input; + try { + input = JSON.parse(inputRaw); + } catch { + log(asFailure(ERROR.BAD_INPUT, "parse_input_failed")); + process.exitCode = 2; + return; + } + + const bad = validateInput(input); + if (bad) { + log(asFailure(bad, "validate_input_failed")); + process.exitCode = 2; + return; + } + + const started = process.hrtime.bigint(); + log({ event: "start", repo: `${input.repoMeta.owner}/${input.repoMeta.repo}`, path: input.repoMeta.path }); + + try { + const { pdf, blockedRequests, mermaidCount } = await renderToPDF(input); + fs.mkdirSync(path.dirname(outPath), { recursive: true }); + fs.writeFileSync(outPath, pdf); + const elapsedMs = Number((process.hrtime.bigint() - started) / 1000000n); + log({ event: "done", bytes: pdf.length, ms: elapsedMs, blocked_requests: blockedRequests, mermaid_count: mermaidCount }); + } catch (e) { + const safe = e && typeof e === "object" && e.safeError ? e.safeError : null; + if (safe) { + log(safe); + process.exitCode = 1; + return; + } + log(asFailure(ERROR.INTERNAL, "unhandled_exception")); + process.exitCode = 1; + } +} + +main(); diff --git a/worker/pdf/src/logger.js b/worker/pdf/src/logger.js new file mode 100644 index 0000000000..f40c63c59a --- /dev/null +++ b/worker/pdf/src/logger.js @@ -0,0 +1,6 @@ +function log(obj) { + process.stderr.write(`${JSON.stringify(obj)}\n`); +} + +module.exports = { log }; + diff --git a/worker/pdf/src/render_pdf.js b/worker/pdf/src/render_pdf.js new file mode 100644 index 0000000000..329f6f5ab1 --- /dev/null +++ b/worker/pdf/src/render_pdf.js @@ -0,0 +1,405 @@ +const crypto = require("node:crypto"); +const fs = require("node:fs"); +const path = require("node:path"); + +const sanitizeHtml = require("sanitize-html"); +const MarkdownIt = require("markdown-it"); +const puppeteer = require("puppeteer"); + +const { ERROR, asFailure } = require("./errors"); + +function safeThrow(errorDef, detailsCode) { + const e = new Error(errorDef.error_id); + e.safeError = asFailure(errorDef, detailsCode); + throw e; +} + +function sha256Hex(text) { + return crypto.createHash("sha256").update(text).digest("hex"); +} + +function normalizeMarkdown(md) { + return md.replace(/\r\n/g, "\n").replace(/\r/g, "\n"); +} + +function markdownToHTML(markdown) { + const md = new MarkdownIt({ + html: false, + linkify: true + }); + + const html = md.render(markdown); + + // Sanitize and remove images/external resources for v0.1. + const cleaned = sanitizeHtml(html, { + allowedTags: sanitizeHtml.defaults.allowedTags.filter((t) => t !== "img"), + allowedAttributes: { + a: ["href", "title"], + code: ["class"], + pre: ["class"], + span: ["class"], + p: ["class"], + h1: ["id"], + h2: ["id"], + h3: ["id"], + h4: ["id"], + h5: ["id"], + h6: ["id"] + }, + allowVulnerableTags: false, + transformTags: { + a: (tagName, attribs) => { + const href = attribs.href || ""; + const lower = href.trim().toLowerCase(); + const forbidden = + lower.startsWith("http:") || + lower.startsWith("https:") || + lower.startsWith("ws:") || + lower.startsWith("wss:") || + lower.startsWith("mailto:") || + lower.startsWith("javascript:") || + lower.startsWith("data:") || + lower.startsWith("file:"); + if (forbidden) return { tagName: "a", attribs: { rel: "nofollow" } }; + return { tagName, attribs: { href, rel: "nofollow" } }; + } + } + }); + + return cleaned; +} + +function htmlTemplate({ bodyHTML, assetsDir, cssName, footerCSS }) { + const cssPath = `file://${path.join(assetsDir, "css", cssName)}`; + const runtimePath = `file://${path.join(assetsDir, "js", "runtime.js")}`; + const mermaidPath = `file://${path.join(assetsDir, "js", "mermaid.min.js")}`; + const pagedPath = `file://${path.join(assetsDir, "js", "paged.polyfill.js")}`; + + return ` + + + + + + + + +
${bodyHTML}
+ + + + + +`; +} + +function footerCSSFor(input) { + const pdf = input.config.pdf; + if (!pdf.footer.enabled) return ""; + + const repo = `${input.repoMeta.owner}/${input.repoMeta.repo}`; + const sha = input.repoMeta.commitSHA.slice(0, 12); + + let date = ""; + if (pdf.timestamp === "commit_time") { + date = input.repoMeta.commitTimeRFC3339; + } else { + date = new Date().toISOString(); + } + + const left = cssString(repo); + const center = cssString(`${sha} • ${date}`); + + return ` +@page { + @bottom-left { content: "${left}"; } + @bottom-center { content: "${center}"; } + @bottom-right { content: counter(page); } +}`; +} + +function cssString(s) { + return String(s).replace(/\\/g, "\\\\").replace(/"/g, '\\"'); +} + +async function renderToPDF(input) { + const assetsDir = "/opt/forgejo-pdf/assets"; + const pdf = input.config.pdf; + const cssName = pdf.typography === "basic" ? "basic.css" : "professional.css"; + + const bodyHTML = markdownToHTML(normalizeMarkdown(input.markdown)); + const html = htmlTemplate({ bodyHTML, assetsDir, cssName, footerCSS: footerCSSFor(input) }); + + const userDataDir = fs.mkdtempSync("/tmp/chrome-profile-"); + const pageDir = fs.mkdtempSync("/tmp/pdf-page-"); + const htmlPath = path.join(pageDir, "index.html"); + fs.writeFileSync(htmlPath, html, "utf8"); + + const browser = await puppeteer.launch({ + headless: "new", + args: [ + "--no-sandbox", + "--disable-dev-shm-usage", + "--allow-file-access-from-files", + `--user-data-dir=${userDataDir}` + ] + }); + + try { + const page = await browser.newPage(); + let lastPageError = null; + page.on("pageerror", (err) => { + lastPageError = err && typeof err.message === "string" ? err.message : String(err); + }); + page.on("console", (msg) => { + if (msg.type && msg.type() === "error") { + lastPageError = msg.text ? msg.text() : String(msg); + } + }); + await page.setRequestInterception(true); + let blockedRequests = 0; + let blockedURL = null; + page.on("request", (req) => { + const u = req.url(); + if (u.startsWith("file:")) return req.continue(); + blockedRequests++; + if (!blockedURL) blockedURL = u; + return req.abort(); + }); + + await page.goto(`file://${htmlPath}`, { waitUntil: "load" }); + if (blockedRequests > 0) safeThrow(ERROR.NETWORK_ATTEMPT, `blocked:${blockedURL || "non_file_request"}`); + + // Mermaid + Paged.js rendering happens in-page. + const renderResult = await page.evaluate(async (payload) => { + try { + // Minimal SHA-256 (hex) implementation for deterministic IDs. + // Avoids relying on SubtleCrypto availability for file:// contexts. + function sha256HexLocal(ascii) { + function rightRotate(value, amount) { + return (value >>> amount) | (value << (32 - amount)); + } + // Normalize to a byte-string (UTF-8) for consistent hashing across unicode input. + ascii = unescape(encodeURIComponent(ascii)); + const maxWord = Math.pow(2, 32); + let result = ""; + + const words = []; + const asciiBitLength = ascii.length * 8; + + let hash = sha256HexLocal.h || []; + let k = sha256HexLocal.k || []; + + let primeCounter = k.length; + const isComposite = {}; + for (let candidate = 2; primeCounter < 64; candidate++) { + if (!isComposite[candidate]) { + for (let i = 0; i < 313; i += candidate) isComposite[i] = candidate; + hash[primeCounter] = (Math.pow(candidate, 0.5) * maxWord) | 0; + k[primeCounter++] = (Math.pow(candidate, 1 / 3) * maxWord) | 0; + } + } + sha256HexLocal.h = hash; + sha256HexLocal.k = k; + + ascii += "\x80"; + while ((ascii.length % 64) - 56) ascii += "\x00"; + for (let i = 0; i < ascii.length; i++) { + const j = ascii.charCodeAt(i); + words[i >> 2] |= j << ((3 - i) % 4) * 8; + } + words[words.length] = (asciiBitLength / maxWord) | 0; + words[words.length] = asciiBitLength; + + for (let j = 0; j < words.length; ) { + const w = words.slice(j, (j += 16)); + const oldHash = hash.slice(0); + + for (let i = 0; i < 64; i++) { + const w15 = w[i - 15]; + const w2 = w[i - 2]; + + const a = hash[0]; + const e = hash[4]; + const temp1 = + hash[7] + + (rightRotate(e, 6) ^ rightRotate(e, 11) ^ rightRotate(e, 25)) + + ((e & hash[5]) ^ (~e & hash[6])) + + k[i] + + (w[i] = + i < 16 + ? w[i] + : (w[i - 16] + + (rightRotate(w15, 7) ^ rightRotate(w15, 18) ^ (w15 >>> 3)) + + w[i - 7] + + (rightRotate(w2, 17) ^ rightRotate(w2, 19) ^ (w2 >>> 10))) | + 0); + const temp2 = + (rightRotate(a, 2) ^ rightRotate(a, 13) ^ rightRotate(a, 22)) + + ((a & hash[1]) ^ (a & hash[2]) ^ (hash[1] & hash[2])); + + hash = [(temp1 + temp2) | 0].concat(hash); + hash[4] = (hash[4] + temp1) | 0; + hash.pop(); + } + + for (let i = 0; i < 8; i++) { + hash[i] = (hash[i] + oldHash[i]) | 0; + } + } + + for (let i = 0; i < 8; i++) { + for (let j = 3; j + 1; j--) { + const b = (hash[i] >> (j * 8)) & 255; + result += (b < 16 ? "0" : "") + b.toString(16); + } + } + return result; + } + + function sanitizeSVG(svgText) { + const parser = new DOMParser(); + const doc = parser.parseFromString(svgText, "image/svg+xml"); + const svg = doc.documentElement; + + const forbidden = svg.querySelector("foreignObject"); + if (forbidden) throw { error_id: "ERR_MERMAID_FOREIGN_OBJECT", message: "Mermaid output contains forbidden elements." }; + + const scripts = svg.querySelectorAll("script"); + for (const s of scripts) s.remove(); + + for (const el of svg.querySelectorAll("*")) { + for (const attr of Array.from(el.attributes)) { + const name = attr.name; + const value = attr.value || ""; + if (name.startsWith("on")) el.removeAttribute(name); + if ((name === "href" || name === "xlink:href") && !value.startsWith("#")) el.removeAttribute(name); + } + } + + const hasText = svg.querySelector("text, tspan"); + if (!hasText) throw { error_id: "ERR_MERMAID_NO_TEXT", message: "Mermaid output must contain selectable text." }; + + return svg.outerHTML; + } + + function shouldLandscape(svgEl) { + const vb = svgEl.getAttribute("viewBox"); + if (!vb) return false; + const parts = vb.trim().split(/\s+/).map(Number); + if (parts.length !== 4 || parts.some((n) => !Number.isFinite(n))) return false; + const w = parts[2]; + const h = parts[3]; + if (h <= 0) return false; + const aspect = w / h; + return aspect > 1.15; + } + + // Determinism guards. + Math.random = () => 0.5; + if (payload.pdf.determinism === "strict") { + const fixed = Date.parse(payload.repoMeta.commitTimeRFC3339); + if (Number.isFinite(fixed)) Date.now = () => fixed; + } + + // Render Mermaid fences found in code blocks. + const blocks = Array.from(document.querySelectorAll("pre > code")); + const mermaidBlocks = blocks.filter((c) => (c.className || "").includes("language-mermaid")); + + if (typeof mermaid === "undefined" || !mermaid) throw { error_id: "ERR_INTERNAL", message: "Render failed." }; + mermaid.initialize({ + startOnLoad: false, + securityLevel: "strict", + htmlLabels: false, + flowchart: { htmlLabels: false }, + sequence: { htmlLabels: false }, + state: { htmlLabels: false }, + class: { htmlLabels: false }, + fontFamily: "IBM Plex Sans", + theme: "base", + themeVariables: { + fontFamily: "IBM Plex Sans", + primaryColor: "#ffffff", + primaryTextColor: "#111827", + lineColor: "#6b7280", + secondaryColor: "#f3f4f6", + tertiaryColor: "#ffffff" + } + }); + + for (let i = 0; i < mermaidBlocks.length; i++) { + const codeEl = mermaidBlocks[i]; + const diagramText = codeEl.textContent || ""; + const id = `m-${sha256HexLocal(payload.repoMeta.commitSHA + "|" + payload.repoMeta.path + "|" + i + "|" + diagramText)}`; + + const { svg } = await mermaid.render(id, diagramText); + const clean = sanitizeSVG(svg); + + const container = document.createElement("figure"); + container.className = "mermaid-figure"; + container.innerHTML = clean; + + const svgEl = container.querySelector("svg"); + if (svgEl && shouldLandscape(svgEl)) { + container.classList.add("mermaid-landscape"); + } + + const pre = codeEl.parentElement; + pre.replaceWith(container); + } + + if (window.PagedPolyfill && window.PagedPolyfill.preview) { + await window.PagedPolyfill.preview(); + } + + return { ok: true, mermaid_count: mermaidBlocks.length }; + } catch (err) { + if (err && typeof err === "object" && err.error_id && err.message) { + return { ok: false, error_id: err.error_id, message: err.message }; + } + return { ok: false, error_id: "ERR_INTERNAL", message: "Render failed." }; + } + }, { pdf: input.config.pdf, repoMeta: input.repoMeta }); + + if (!renderResult || renderResult.ok !== true || typeof renderResult.mermaid_count !== "number") { + if (renderResult && renderResult.ok === false && renderResult.error_id && renderResult.message) { + const err = new Error(renderResult.error_id); + err.safeError = { error_id: renderResult.error_id, message: renderResult.message }; + throw err; + } + safeThrow(ERROR.INTERNAL, "render_result_invalid"); + } + const mermaidCount = renderResult.mermaid_count; + + const pdfBuf = await page.pdf({ + format: "A4", + printBackground: true, + preferCSSPageSize: true + }); + + return { pdf: Buffer.from(pdfBuf), blockedRequests, mermaidCount }; + } catch (e) { + if (e && typeof e === "object" && e.error_id && e.message) { + const err = new Error(e.error_id); + err.safeError = { error_id: e.error_id, message: e.message }; + throw err; + } + if (e && typeof e === "object" && e.safeError) throw e; + const msg = e && typeof e === "object" && typeof e.message === "string" ? e.message : ""; + const hint = msg || (typeof lastPageError === "string" ? lastPageError : ""); + const details = hint + ? `render_failed:${hint.slice(0, 160).replace(/[^A-Za-z0-9_.:/\\-]+/g, "_")}` + : "render_failed"; + safeThrow(ERROR.INTERNAL, details); + } finally { + try { + await browser.close(); + } catch {} + try { + fs.rmSync(userDataDir, { recursive: true, force: true }); + fs.rmSync(pageDir, { recursive: true, force: true }); + } catch {} + } +} + +module.exports = { renderToPDF }; diff --git a/worker/pdf/src/validate.js b/worker/pdf/src/validate.js new file mode 100644 index 0000000000..b8355701d3 --- /dev/null +++ b/worker/pdf/src/validate.js @@ -0,0 +1,37 @@ +const { ERROR } = require("./errors"); + +function isObject(value) { + return value && typeof value === "object" && !Array.isArray(value); +} + +function validateInput(input) { + if (!isObject(input)) return ERROR.BAD_INPUT; + if (typeof input.markdown !== "string") return ERROR.BAD_INPUT; + if (!isObject(input.repoMeta)) return ERROR.BAD_INPUT; + if (!isObject(input.config) || !isObject(input.config.pdf)) return ERROR.BAD_INPUT; + if (typeof input.manifestSHA !== "string") return ERROR.BAD_INPUT; + + const pdf = input.config.pdf; + if (!["strict", "relaxed"].includes(pdf.determinism)) return ERROR.BAD_CONFIG; + if (!["commit_time", "render_time"].includes(pdf.timestamp)) return ERROR.BAD_CONFIG; + if (!["basic", "professional"].includes(pdf.typography)) return ERROR.BAD_CONFIG; + if (!isObject(pdf.mermaid)) return ERROR.BAD_CONFIG; + if (!["fast", "balanced", "prestige"].includes(pdf.mermaid.strategy)) return ERROR.BAD_CONFIG; + if (typeof pdf.mermaid.caption !== "boolean") return ERROR.BAD_CONFIG; + if (!isObject(pdf.orphansWidows) || typeof pdf.orphansWidows.enforce !== "boolean") return ERROR.BAD_CONFIG; + if (!isObject(pdf.footer) || typeof pdf.footer.enabled !== "boolean") return ERROR.BAD_CONFIG; + + if (pdf.determinism === "strict" && pdf.timestamp !== "commit_time") return ERROR.BAD_CONFIG; + + const rm = input.repoMeta; + if (typeof rm.owner !== "string") return ERROR.BAD_INPUT; + if (typeof rm.repo !== "string") return ERROR.BAD_INPUT; + if (typeof rm.path !== "string") return ERROR.BAD_INPUT; + if (typeof rm.commitSHA !== "string") return ERROR.BAD_INPUT; + if (typeof rm.commitTimeRFC3339 !== "string") return ERROR.BAD_INPUT; + + return null; +} + +module.exports = { validateInput }; +