#lang racket/base ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; Pinned frontend dependency download and verification. ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; (require net/url net/url-connect racket/file racket/path racket/port "config.rkt") (provide vendor-files vendor-files-ready? download-vendor-files!) (define vendor-files (list (cons "lucide.min.js" "https://cdn.jsdelivr.net/npm/lucide@1.31.0/dist/umd/lucide.min.js") (cons "easymde.min.js" "https://cdn.jsdelivr.net/npm/easymde@2.21.0/dist/easymde.min.js") (cons "easymde.min.css" "https://cdn.jsdelivr.net/npm/easymde@2.21.0/dist/easymde.min.css") (cons "purify.min.js" "https://cdn.jsdelivr.net/npm/dompurify@3.4.13/dist/purify.min.js") (cons "mermaid.min.js" "https://cdn.jsdelivr.net/npm/mermaid@11.17.1/dist/mermaid.min.js") (cons "highlight.min.js" "https://cdn.jsdelivr.net/gh/highlightjs/cdn-release@11.12.0/build/highlight.min.js") (cons "highlight-scheme.min.js" "https://cdn.jsdelivr.net/gh/highlightjs/cdn-release@11.12.0/build/languages/scheme.min.js") (cons "highlight-github.min.css" "https://cdn.jsdelivr.net/gh/highlightjs/cdn-release@11.12.0/build/styles/github.min.css") (cons "diff2html-ui-base.min.js" "https://cdn.jsdelivr.net/npm/diff2html@3.4.56/bundles/js/diff2html-ui-base.min.js") (cons "diff2html.min.css" "https://cdn.jsdelivr.net/npm/diff2html@3.4.56/bundles/css/diff2html.min.css"))) (define (vendor-file-ready? config name) (let ((path (build-path (vendor-directory config) name))) (and (file-exists? path) (> (file-size path) 0)))) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ; goal : Check whether all required browser libraries are installed. ; pre : config is a wiki-config value. ; post : Vendor files have only been inspected. ; result : #t when every required vendor file exists and is non-empty, ; otherwise #f. ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; (define (vendor-files-ready? config) (for/and ([entry (in-list vendor-files)]) (vendor-file-ready? config (car entry)))) (define (download-content source) (parameterize ((current-https-protocol 'secure)) (let-values (((in headers) (get-pure-port/headers (string->url source) '() #:redirections 5 #:status? #t))) (dynamic-wind void (λ () (let ((status-match (regexp-match #px"^HTTP/[^ ]+ ([0-9][0-9][0-9])" headers))) (unless (and status-match (= (string->number (list-ref status-match 1)) 200)) (error 'download-vendor-files! "download failed for ~a: ~a" source (or (and status-match (list-ref status-match 1)) "invalid HTTP status"))) (port->bytes in))) (λ () (close-input-port in)))))) (define (download-file! config name source) (let* ((directory (vendor-directory config)) (target (build-path directory name)) (temporary-target (build-path directory (string-append name ".download"))) (content (download-content source))) (make-directory* (path-only target)) (call-with-output-file temporary-target (λ (out) (write-bytes content out)) #:exists 'truncate/replace) (rename-file-or-directory temporary-target target #t) (void))) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ; goal : Download all browser libraries required by the wiki frontend. ; pre : config identifies a writable data directory and outbound HTTPS is ; available for the configured vendor URLs. ; post : Every required vendor file is stored below data/static/vendor. ; result : void. ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; (define (download-vendor-files! config) (make-directory* (vendor-directory config)) (for ([entry (in-list vendor-files)]) (let ((name (car entry)) (source (cdr entry))) (unless (vendor-file-ready? config name) (download-file! config name source)))) (unless (vendor-files-ready? config) (error 'download-vendor-files! "frontend vendor setup is incomplete")) (void))