more robust error handling, config hotreload
This commit is contained in:
		@@ -4,8 +4,6 @@ draft: true
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
## high priority
 | 
					## high priority
 | 
				
			||||||
 | 
					
 | 
				
			||||||
- images in same folder are broken on shortest path mode
 | 
					 | 
				
			||||||
- watch mode for config/source code
 | 
					 | 
				
			||||||
- block links: https://help.obsidian.md/Linking+notes+and+files/Internal+links#Link+to+a+block+in+a+note
 | 
					- block links: https://help.obsidian.md/Linking+notes+and+files/Internal+links#Link+to+a+block+in+a+note
 | 
				
			||||||
- note/header/block transcludes: https://help.obsidian.md/Linking+notes+and+files/Embedding+files
 | 
					- note/header/block transcludes: https://help.obsidian.md/Linking+notes+and+files/Embedding+files
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -22,7 +20,5 @@ draft: true
 | 
				
			|||||||
- https://help.obsidian.md/Advanced+topics/Using+Obsidian+URI
 | 
					- https://help.obsidian.md/Advanced+topics/Using+Obsidian+URI
 | 
				
			||||||
- audio/video embed styling
 | 
					- audio/video embed styling
 | 
				
			||||||
- Canvas
 | 
					- Canvas
 | 
				
			||||||
- mermaid styling: https://mermaid.js.org/config/theming.html#theme-variables-reference-table
 | 
					 | 
				
			||||||
  - https://github.com/jackyzha0/quartz/issues/331
 | 
					 | 
				
			||||||
- parse all images in page: use this for page lists if applicable?
 | 
					- parse all images in page: use this for page lists if applicable?
 | 
				
			||||||
- CV mode? with print stylesheet
 | 
					- CV mode? with print stylesheet
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										4
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										4
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							@@ -1,12 +1,12 @@
 | 
				
			|||||||
{
 | 
					{
 | 
				
			||||||
  "name": "@jackyzha0/quartz",
 | 
					  "name": "@jackyzha0/quartz",
 | 
				
			||||||
  "version": "4.0.6",
 | 
					  "version": "4.0.7",
 | 
				
			||||||
  "lockfileVersion": 3,
 | 
					  "lockfileVersion": 3,
 | 
				
			||||||
  "requires": true,
 | 
					  "requires": true,
 | 
				
			||||||
  "packages": {
 | 
					  "packages": {
 | 
				
			||||||
    "": {
 | 
					    "": {
 | 
				
			||||||
      "name": "@jackyzha0/quartz",
 | 
					      "name": "@jackyzha0/quartz",
 | 
				
			||||||
      "version": "4.0.6",
 | 
					      "version": "4.0.7",
 | 
				
			||||||
      "license": "MIT",
 | 
					      "license": "MIT",
 | 
				
			||||||
      "dependencies": {
 | 
					      "dependencies": {
 | 
				
			||||||
        "@clack/prompts": "^0.6.3",
 | 
					        "@clack/prompts": "^0.6.3",
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -2,7 +2,7 @@
 | 
				
			|||||||
  "name": "@jackyzha0/quartz",
 | 
					  "name": "@jackyzha0/quartz",
 | 
				
			||||||
  "description": "🌱 publish your digital garden and notes as a website",
 | 
					  "description": "🌱 publish your digital garden and notes as a website",
 | 
				
			||||||
  "private": true,
 | 
					  "private": true,
 | 
				
			||||||
  "version": "4.0.6",
 | 
					  "version": "4.0.7",
 | 
				
			||||||
  "type": "module",
 | 
					  "type": "module",
 | 
				
			||||||
  "author": "jackyzha0 <j.zhao2k19@gmail.com>",
 | 
					  "author": "jackyzha0 <j.zhao2k19@gmail.com>",
 | 
				
			||||||
  "license": "MIT",
 | 
					  "license": "MIT",
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -9,9 +9,13 @@ import { sassPlugin } from "esbuild-sass-plugin"
 | 
				
			|||||||
import fs from "fs"
 | 
					import fs from "fs"
 | 
				
			||||||
import { intro, isCancel, outro, select, text } from "@clack/prompts"
 | 
					import { intro, isCancel, outro, select, text } from "@clack/prompts"
 | 
				
			||||||
import { rimraf } from "rimraf"
 | 
					import { rimraf } from "rimraf"
 | 
				
			||||||
 | 
					import chokidar from "chokidar"
 | 
				
			||||||
import prettyBytes from "pretty-bytes"
 | 
					import prettyBytes from "pretty-bytes"
 | 
				
			||||||
import { execSync, spawnSync } from "child_process"
 | 
					import { execSync, spawnSync } from "child_process"
 | 
				
			||||||
import { transform as cssTransform } from "lightningcss"
 | 
					import { transform as cssTransform } from "lightningcss"
 | 
				
			||||||
 | 
					import http from "http"
 | 
				
			||||||
 | 
					import serveHandler from "serve-handler"
 | 
				
			||||||
 | 
					import { WebSocketServer } from "ws"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const ORIGIN_NAME = "origin"
 | 
					const ORIGIN_NAME = "origin"
 | 
				
			||||||
const UPSTREAM_NAME = "upstream"
 | 
					const UPSTREAM_NAME = "upstream"
 | 
				
			||||||
@@ -287,86 +291,132 @@ See the [documentation](https://quartz.jzhao.xyz) for how to get started.
 | 
				
			|||||||
    console.log(chalk.green("Done!"))
 | 
					    console.log(chalk.green("Done!"))
 | 
				
			||||||
  })
 | 
					  })
 | 
				
			||||||
  .command("build", "Build Quartz into a bundle of static HTML files", BuildArgv, async (argv) => {
 | 
					  .command("build", "Build Quartz into a bundle of static HTML files", BuildArgv, async (argv) => {
 | 
				
			||||||
    const result = await esbuild
 | 
					    console.log(chalk.bgGreen.black(`\n Quartz v${version} \n`))
 | 
				
			||||||
      .build({
 | 
					    const ctx = await esbuild.context({
 | 
				
			||||||
        entryPoints: [fp],
 | 
					      entryPoints: [fp],
 | 
				
			||||||
        outfile: path.join("quartz", cacheFile),
 | 
					      outfile: path.join("quartz", cacheFile),
 | 
				
			||||||
        bundle: true,
 | 
					      bundle: true,
 | 
				
			||||||
        keepNames: true,
 | 
					      keepNames: true,
 | 
				
			||||||
        minify: true,
 | 
					      minify: true,
 | 
				
			||||||
        platform: "node",
 | 
					      platform: "node",
 | 
				
			||||||
        format: "esm",
 | 
					      format: "esm",
 | 
				
			||||||
        jsx: "automatic",
 | 
					      jsx: "automatic",
 | 
				
			||||||
        jsxImportSource: "preact",
 | 
					      jsxImportSource: "preact",
 | 
				
			||||||
        packages: "external",
 | 
					      packages: "external",
 | 
				
			||||||
        metafile: true,
 | 
					      metafile: true,
 | 
				
			||||||
        sourcemap: true,
 | 
					      sourcemap: true,
 | 
				
			||||||
        plugins: [
 | 
					      plugins: [
 | 
				
			||||||
          sassPlugin({
 | 
					        sassPlugin({
 | 
				
			||||||
            type: "css-text",
 | 
					          type: "css-text",
 | 
				
			||||||
            cssImports: true,
 | 
					          cssImports: true,
 | 
				
			||||||
            async transform(css) {
 | 
					          async transform(css) {
 | 
				
			||||||
              const { code } = cssTransform({
 | 
					            const { code } = cssTransform({
 | 
				
			||||||
                filename: "style.css",
 | 
					              filename: "style.css",
 | 
				
			||||||
                code: Buffer.from(css),
 | 
					              code: Buffer.from(css),
 | 
				
			||||||
                minify: true,
 | 
					              minify: true,
 | 
				
			||||||
              })
 | 
					            })
 | 
				
			||||||
              return code.toString()
 | 
					            return code.toString()
 | 
				
			||||||
            },
 | 
					 | 
				
			||||||
          }),
 | 
					 | 
				
			||||||
          {
 | 
					 | 
				
			||||||
            name: "inline-script-loader",
 | 
					 | 
				
			||||||
            setup(build) {
 | 
					 | 
				
			||||||
              build.onLoad({ filter: /\.inline\.(ts|js)$/ }, async (args) => {
 | 
					 | 
				
			||||||
                let text = await promises.readFile(args.path, "utf8")
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                // remove default exports that we manually inserted
 | 
					 | 
				
			||||||
                text = text.replace("export default", "")
 | 
					 | 
				
			||||||
                text = text.replace("export", "")
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                const sourcefile = path.relative(path.resolve("."), args.path)
 | 
					 | 
				
			||||||
                const resolveDir = path.dirname(sourcefile)
 | 
					 | 
				
			||||||
                const transpiled = await esbuild.build({
 | 
					 | 
				
			||||||
                  stdin: {
 | 
					 | 
				
			||||||
                    contents: text,
 | 
					 | 
				
			||||||
                    loader: "ts",
 | 
					 | 
				
			||||||
                    resolveDir,
 | 
					 | 
				
			||||||
                    sourcefile,
 | 
					 | 
				
			||||||
                  },
 | 
					 | 
				
			||||||
                  write: false,
 | 
					 | 
				
			||||||
                  bundle: true,
 | 
					 | 
				
			||||||
                  platform: "browser",
 | 
					 | 
				
			||||||
                  format: "esm",
 | 
					 | 
				
			||||||
                })
 | 
					 | 
				
			||||||
                const rawMod = transpiled.outputFiles[0].text
 | 
					 | 
				
			||||||
                return {
 | 
					 | 
				
			||||||
                  contents: rawMod,
 | 
					 | 
				
			||||||
                  loader: "text",
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
              })
 | 
					 | 
				
			||||||
            },
 | 
					 | 
				
			||||||
          },
 | 
					          },
 | 
				
			||||||
        ],
 | 
					        }),
 | 
				
			||||||
      })
 | 
					        {
 | 
				
			||||||
      .catch((err) => {
 | 
					          name: "inline-script-loader",
 | 
				
			||||||
 | 
					          setup(build) {
 | 
				
			||||||
 | 
					            build.onLoad({ filter: /\.inline\.(ts|js)$/ }, async (args) => {
 | 
				
			||||||
 | 
					              let text = await promises.readFile(args.path, "utf8")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					              // remove default exports that we manually inserted
 | 
				
			||||||
 | 
					              text = text.replace("export default", "")
 | 
				
			||||||
 | 
					              text = text.replace("export", "")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					              const sourcefile = path.relative(path.resolve("."), args.path)
 | 
				
			||||||
 | 
					              const resolveDir = path.dirname(sourcefile)
 | 
				
			||||||
 | 
					              const transpiled = await esbuild.build({
 | 
				
			||||||
 | 
					                stdin: {
 | 
				
			||||||
 | 
					                  contents: text,
 | 
				
			||||||
 | 
					                  loader: "ts",
 | 
				
			||||||
 | 
					                  resolveDir,
 | 
				
			||||||
 | 
					                  sourcefile,
 | 
				
			||||||
 | 
					                },
 | 
				
			||||||
 | 
					                write: false,
 | 
				
			||||||
 | 
					                bundle: true,
 | 
				
			||||||
 | 
					                platform: "browser",
 | 
				
			||||||
 | 
					                format: "esm",
 | 
				
			||||||
 | 
					              })
 | 
				
			||||||
 | 
					              const rawMod = transpiled.outputFiles[0].text
 | 
				
			||||||
 | 
					              return {
 | 
				
			||||||
 | 
					                contents: rawMod,
 | 
				
			||||||
 | 
					                loader: "text",
 | 
				
			||||||
 | 
					              }
 | 
				
			||||||
 | 
					            })
 | 
				
			||||||
 | 
					          },
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					      ],
 | 
				
			||||||
 | 
					    })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    let clientRefresh = () => {}
 | 
				
			||||||
 | 
					    let closeHandler = null
 | 
				
			||||||
 | 
					    const build = async () => {
 | 
				
			||||||
 | 
					      const result = await ctx.rebuild().catch((err) => {
 | 
				
			||||||
        console.error(`${chalk.red("Couldn't parse Quartz configuration:")} ${fp}`)
 | 
					        console.error(`${chalk.red("Couldn't parse Quartz configuration:")} ${fp}`)
 | 
				
			||||||
        console.log(`Reason: ${chalk.grey(err)}`)
 | 
					        console.log(`Reason: ${chalk.grey(err)}`)
 | 
				
			||||||
        process.exit(1)
 | 
					        process.exit(1)
 | 
				
			||||||
      })
 | 
					      })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if (argv.bundleInfo) {
 | 
					      if (argv.bundleInfo) {
 | 
				
			||||||
      const outputFileName = "quartz/.quartz-cache/transpiled-build.mjs"
 | 
					        const outputFileName = "quartz/.quartz-cache/transpiled-build.mjs"
 | 
				
			||||||
      const meta = result.metafile.outputs[outputFileName]
 | 
					        const meta = result.metafile.outputs[outputFileName]
 | 
				
			||||||
      console.log(
 | 
					        console.log(
 | 
				
			||||||
        `Successfully transpiled ${Object.keys(meta.inputs).length} files (${prettyBytes(
 | 
					          `Successfully transpiled ${Object.keys(meta.inputs).length} files (${prettyBytes(
 | 
				
			||||||
          meta.bytes,
 | 
					            meta.bytes,
 | 
				
			||||||
        )})`,
 | 
					          )})`,
 | 
				
			||||||
      )
 | 
					        )
 | 
				
			||||||
      console.log(await esbuild.analyzeMetafile(result.metafile, { color: true }))
 | 
					        console.log(await esbuild.analyzeMetafile(result.metafile, { color: true }))
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      // bypass module cache
 | 
				
			||||||
 | 
					      const { default: buildQuartz } = await import(cacheFile + `?update=${new Date()}`)
 | 
				
			||||||
 | 
					      if (closeHandler) {
 | 
				
			||||||
 | 
					        await closeHandler()
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      closeHandler = await buildQuartz(argv, clientRefresh)
 | 
				
			||||||
 | 
					      clientRefresh()
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const { default: buildQuartz } = await import(cacheFile)
 | 
					    await build()
 | 
				
			||||||
    buildQuartz(argv, version)
 | 
					    if (argv.serve) {
 | 
				
			||||||
 | 
					      const wss = new WebSocketServer({ port: 3001 })
 | 
				
			||||||
 | 
					      const connections = []
 | 
				
			||||||
 | 
					      wss.on("connection", (ws) => connections.push(ws))
 | 
				
			||||||
 | 
					      clientRefresh = () => connections.forEach((conn) => conn.send("rebuild"))
 | 
				
			||||||
 | 
					      const server = http.createServer(async (req, res) => {
 | 
				
			||||||
 | 
					        await serveHandler(req, res, {
 | 
				
			||||||
 | 
					          public: argv.output,
 | 
				
			||||||
 | 
					          directoryListing: false,
 | 
				
			||||||
 | 
					        })
 | 
				
			||||||
 | 
					        const status = res.statusCode
 | 
				
			||||||
 | 
					        const statusString =
 | 
				
			||||||
 | 
					          status >= 200 && status < 300
 | 
				
			||||||
 | 
					            ? chalk.green(`[${status}]`)
 | 
				
			||||||
 | 
					            : status >= 300 && status < 400
 | 
				
			||||||
 | 
					            ? chalk.yellow(`[${status}]`)
 | 
				
			||||||
 | 
					            : chalk.red(`[${status}]`)
 | 
				
			||||||
 | 
					        console.log(statusString + chalk.grey(` ${req.url}`))
 | 
				
			||||||
 | 
					      })
 | 
				
			||||||
 | 
					      server.listen(argv.port)
 | 
				
			||||||
 | 
					      console.log(chalk.cyan(`Started a Quartz server listening at http://localhost:${argv.port}`))
 | 
				
			||||||
 | 
					      console.log("hint: exit with ctrl+c")
 | 
				
			||||||
 | 
					      chokidar
 | 
				
			||||||
 | 
					        .watch(["**/*.ts", "**/*.tsx", "**/*.scss", "package.json"], {
 | 
				
			||||||
 | 
					          ignoreInitial: true,
 | 
				
			||||||
 | 
					        })
 | 
				
			||||||
 | 
					        .on("all", async () => {
 | 
				
			||||||
 | 
					          console.log(chalk.yellow("Detected a source code change, doing a hard rebuild..."))
 | 
				
			||||||
 | 
					          await build()
 | 
				
			||||||
 | 
					        })
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					      ctx.dispose()
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
  })
 | 
					  })
 | 
				
			||||||
  .showHelpOnFail(false)
 | 
					  .showHelpOnFail(false)
 | 
				
			||||||
  .help()
 | 
					  .help()
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -4,8 +4,6 @@ import { PerfTimer } from "./perf"
 | 
				
			|||||||
import { rimraf } from "rimraf"
 | 
					import { rimraf } from "rimraf"
 | 
				
			||||||
import { isGitIgnored } from "globby"
 | 
					import { isGitIgnored } from "globby"
 | 
				
			||||||
import chalk from "chalk"
 | 
					import chalk from "chalk"
 | 
				
			||||||
import http from "http"
 | 
					 | 
				
			||||||
import serveHandler from "serve-handler"
 | 
					 | 
				
			||||||
import { parseMarkdown } from "./processors/parse"
 | 
					import { parseMarkdown } from "./processors/parse"
 | 
				
			||||||
import { filterContent } from "./processors/filter"
 | 
					import { filterContent } from "./processors/filter"
 | 
				
			||||||
import { emitContent } from "./processors/emit"
 | 
					import { emitContent } from "./processors/emit"
 | 
				
			||||||
@@ -13,18 +11,17 @@ import cfg from "../quartz.config"
 | 
				
			|||||||
import { FilePath, joinSegments, slugifyFilePath } from "./path"
 | 
					import { FilePath, joinSegments, slugifyFilePath } from "./path"
 | 
				
			||||||
import chokidar from "chokidar"
 | 
					import chokidar from "chokidar"
 | 
				
			||||||
import { ProcessedContent } from "./plugins/vfile"
 | 
					import { ProcessedContent } from "./plugins/vfile"
 | 
				
			||||||
import WebSocket, { WebSocketServer } from "ws"
 | 
					 | 
				
			||||||
import { Argv, BuildCtx } from "./ctx"
 | 
					import { Argv, BuildCtx } from "./ctx"
 | 
				
			||||||
import { glob, toPosixPath } from "./glob"
 | 
					import { glob, toPosixPath } from "./glob"
 | 
				
			||||||
 | 
					import { trace } from "./trace"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
async function buildQuartz(argv: Argv, version: string) {
 | 
					async function buildQuartz(argv: Argv, clientRefresh: () => void) {
 | 
				
			||||||
  const ctx: BuildCtx = {
 | 
					  const ctx: BuildCtx = {
 | 
				
			||||||
    argv,
 | 
					    argv,
 | 
				
			||||||
    cfg,
 | 
					    cfg,
 | 
				
			||||||
    allSlugs: [],
 | 
					    allSlugs: [],
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  console.log(chalk.bgGreen.black(`\n Quartz v${version} \n`))
 | 
					 | 
				
			||||||
  const perf = new PerfTimer()
 | 
					  const perf = new PerfTimer()
 | 
				
			||||||
  const output = argv.output
 | 
					  const output = argv.output
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -57,15 +54,17 @@ async function buildQuartz(argv: Argv, version: string) {
 | 
				
			|||||||
  console.log(chalk.green(`Done processing ${fps.length} files in ${perf.timeSince()}`))
 | 
					  console.log(chalk.green(`Done processing ${fps.length} files in ${perf.timeSince()}`))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  if (argv.serve) {
 | 
					  if (argv.serve) {
 | 
				
			||||||
    await startServing(ctx, parsedFiles)
 | 
					    return startServing(ctx, parsedFiles, clientRefresh)
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
async function startServing(ctx: BuildCtx, initialContent: ProcessedContent[]) {
 | 
					// setup watcher for rebuilds
 | 
				
			||||||
 | 
					async function startServing(
 | 
				
			||||||
 | 
					  ctx: BuildCtx,
 | 
				
			||||||
 | 
					  initialContent: ProcessedContent[],
 | 
				
			||||||
 | 
					  clientRefresh: () => void,
 | 
				
			||||||
 | 
					) {
 | 
				
			||||||
  const { argv } = ctx
 | 
					  const { argv } = ctx
 | 
				
			||||||
  const wss = new WebSocketServer({ port: 3001 })
 | 
					 | 
				
			||||||
  const connections: WebSocket[] = []
 | 
					 | 
				
			||||||
  wss.on("connection", (ws) => connections.push(ws))
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const ignored = await isGitIgnored()
 | 
					  const ignored = await isGitIgnored()
 | 
				
			||||||
  const contentMap = new Map<FilePath, ProcessedContent>()
 | 
					  const contentMap = new Map<FilePath, ProcessedContent>()
 | 
				
			||||||
@@ -78,6 +77,12 @@ async function startServing(ctx: BuildCtx, initialContent: ProcessedContent[]) {
 | 
				
			|||||||
  let toRebuild: Set<FilePath> = new Set()
 | 
					  let toRebuild: Set<FilePath> = new Set()
 | 
				
			||||||
  let toRemove: Set<FilePath> = new Set()
 | 
					  let toRemove: Set<FilePath> = new Set()
 | 
				
			||||||
  async function rebuild(fp: string, action: "add" | "change" | "delete") {
 | 
					  async function rebuild(fp: string, action: "add" | "change" | "delete") {
 | 
				
			||||||
 | 
					    if (path.extname(fp) !== ".md") {
 | 
				
			||||||
 | 
					      // dont bother rebuilding for non-content files, just refresh
 | 
				
			||||||
 | 
					      clientRefresh()
 | 
				
			||||||
 | 
					      return
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    fp = toPosixPath(fp)
 | 
					    fp = toPosixPath(fp)
 | 
				
			||||||
    if (!ignored(fp)) {
 | 
					    if (!ignored(fp)) {
 | 
				
			||||||
      const filePath = joinSegments(argv.directory, fp) as FilePath
 | 
					      const filePath = joinSegments(argv.directory, fp) as FilePath
 | 
				
			||||||
@@ -120,7 +125,8 @@ async function startServing(ctx: BuildCtx, initialContent: ProcessedContent[]) {
 | 
				
			|||||||
        } catch {
 | 
					        } catch {
 | 
				
			||||||
          console.log(chalk.yellow(`Rebuild failed. Waiting on a change to fix the error...`))
 | 
					          console.log(chalk.yellow(`Rebuild failed. Waiting on a change to fix the error...`))
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        connections.forEach((conn) => conn.send("rebuild"))
 | 
					
 | 
				
			||||||
 | 
					        clientRefresh()
 | 
				
			||||||
        toRebuild.clear()
 | 
					        toRebuild.clear()
 | 
				
			||||||
        toRemove.clear()
 | 
					        toRemove.clear()
 | 
				
			||||||
      }, 250)
 | 
					      }, 250)
 | 
				
			||||||
@@ -137,31 +143,12 @@ async function startServing(ctx: BuildCtx, initialContent: ProcessedContent[]) {
 | 
				
			|||||||
    .on("add", (fp) => rebuild(fp, "add"))
 | 
					    .on("add", (fp) => rebuild(fp, "add"))
 | 
				
			||||||
    .on("change", (fp) => rebuild(fp, "change"))
 | 
					    .on("change", (fp) => rebuild(fp, "change"))
 | 
				
			||||||
    .on("unlink", (fp) => rebuild(fp, "delete"))
 | 
					    .on("unlink", (fp) => rebuild(fp, "delete"))
 | 
				
			||||||
 | 
					 | 
				
			||||||
  const server = http.createServer(async (req, res) => {
 | 
					 | 
				
			||||||
    await serveHandler(req, res, {
 | 
					 | 
				
			||||||
      public: argv.output,
 | 
					 | 
				
			||||||
      directoryListing: false,
 | 
					 | 
				
			||||||
    })
 | 
					 | 
				
			||||||
    const status = res.statusCode
 | 
					 | 
				
			||||||
    const statusString =
 | 
					 | 
				
			||||||
      status >= 200 && status < 300
 | 
					 | 
				
			||||||
        ? chalk.green(`[${status}]`)
 | 
					 | 
				
			||||||
        : status >= 300 && status < 400
 | 
					 | 
				
			||||||
        ? chalk.yellow(`[${status}]`)
 | 
					 | 
				
			||||||
        : chalk.red(`[${status}]`)
 | 
					 | 
				
			||||||
    console.log(statusString + chalk.grey(` ${req.url}`))
 | 
					 | 
				
			||||||
  })
 | 
					 | 
				
			||||||
  server.listen(argv.port)
 | 
					 | 
				
			||||||
  console.log(chalk.cyan(`Started a Quartz server listening at http://localhost:${argv.port}`))
 | 
					 | 
				
			||||||
  console.log("hint: exit with ctrl+c")
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export default async (argv: Argv, version: string) => {
 | 
					export default async (argv: Argv, clientRefresh: () => void) => {
 | 
				
			||||||
  try {
 | 
					  try {
 | 
				
			||||||
    await buildQuartz(argv, version)
 | 
					    return await buildQuartz(argv, clientRefresh)
 | 
				
			||||||
  } catch {
 | 
					  } catch (err) {
 | 
				
			||||||
    console.log(chalk.red("\nExiting Quartz due to a fatal error"))
 | 
					    trace("\nExiting Quartz due to a fatal error", err as Error)
 | 
				
			||||||
    process.exit(1)
 | 
					 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,5 +1,4 @@
 | 
				
			|||||||
import { slug } from "github-slugger"
 | 
					import { slug } from "github-slugger"
 | 
				
			||||||
import { trace } from "./trace"
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
// Quartz Paths
 | 
					// Quartz Paths
 | 
				
			||||||
// Things in boxes are not actual types but rather sources which these types can be acquired from
 | 
					// Things in boxes are not actual types but rather sources which these types can be acquired from
 | 
				
			||||||
@@ -43,18 +42,6 @@ import { trace } from "./trace"
 | 
				
			|||||||
//                                             └────────────┤ MD File ├─────┴─────────────────┘
 | 
					//                                             └────────────┤ MD File ├─────┴─────────────────┘
 | 
				
			||||||
//                                                          └─────────┘
 | 
					//                                                          └─────────┘
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const STRICT_TYPE_CHECKS = false
 | 
					 | 
				
			||||||
const HARD_EXIT_ON_FAIL = false
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
function conditionCheck<T>(name: string, label: "pre" | "post", s: T, chk: (x: any) => x is T) {
 | 
					 | 
				
			||||||
  if (STRICT_TYPE_CHECKS && !chk(s)) {
 | 
					 | 
				
			||||||
    trace(`${name} failed ${label}-condition check: ${s} does not pass ${chk.name}`, new Error())
 | 
					 | 
				
			||||||
    if (HARD_EXIT_ON_FAIL) {
 | 
					 | 
				
			||||||
      process.exit(1)
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
/// Utility type to simulate nominal types in TypeScript
 | 
					/// Utility type to simulate nominal types in TypeScript
 | 
				
			||||||
type SlugLike<T> = string & { __brand: T }
 | 
					type SlugLike<T> = string & { __brand: T }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -102,36 +89,29 @@ export function isFilePath(s: string): s is FilePath {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
export function getClientSlug(window: Window): ClientSlug {
 | 
					export function getClientSlug(window: Window): ClientSlug {
 | 
				
			||||||
  const res = window.location.href as ClientSlug
 | 
					  const res = window.location.href as ClientSlug
 | 
				
			||||||
  conditionCheck(getClientSlug.name, "post", res, isClientSlug)
 | 
					 | 
				
			||||||
  return res
 | 
					  return res
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export function getCanonicalSlug(window: Window): CanonicalSlug {
 | 
					export function getCanonicalSlug(window: Window): CanonicalSlug {
 | 
				
			||||||
  const res = window.document.body.dataset.slug! as CanonicalSlug
 | 
					  const res = window.document.body.dataset.slug! as CanonicalSlug
 | 
				
			||||||
  conditionCheck(getCanonicalSlug.name, "post", res, isCanonicalSlug)
 | 
					 | 
				
			||||||
  return res
 | 
					  return res
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export function canonicalizeClient(slug: ClientSlug): CanonicalSlug {
 | 
					export function canonicalizeClient(slug: ClientSlug): CanonicalSlug {
 | 
				
			||||||
  conditionCheck(canonicalizeClient.name, "pre", slug, isClientSlug)
 | 
					 | 
				
			||||||
  const { pathname } = new URL(slug)
 | 
					  const { pathname } = new URL(slug)
 | 
				
			||||||
  let fp = pathname.slice(1)
 | 
					  let fp = pathname.slice(1)
 | 
				
			||||||
  fp = fp.replace(new RegExp(_getFileExtension(fp) + "$"), "")
 | 
					  fp = fp.replace(new RegExp(_getFileExtension(fp) + "$"), "")
 | 
				
			||||||
  const res = _canonicalize(fp) as CanonicalSlug
 | 
					  const res = _canonicalize(fp) as CanonicalSlug
 | 
				
			||||||
  conditionCheck(canonicalizeClient.name, "post", res, isCanonicalSlug)
 | 
					 | 
				
			||||||
  return res
 | 
					  return res
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export function canonicalizeServer(slug: ServerSlug): CanonicalSlug {
 | 
					export function canonicalizeServer(slug: ServerSlug): CanonicalSlug {
 | 
				
			||||||
  conditionCheck(canonicalizeServer.name, "pre", slug, isServerSlug)
 | 
					 | 
				
			||||||
  let fp = slug as string
 | 
					  let fp = slug as string
 | 
				
			||||||
  const res = _canonicalize(fp) as CanonicalSlug
 | 
					  const res = _canonicalize(fp) as CanonicalSlug
 | 
				
			||||||
  conditionCheck(canonicalizeServer.name, "post", res, isCanonicalSlug)
 | 
					 | 
				
			||||||
  return res
 | 
					  return res
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export function slugifyFilePath(fp: FilePath): ServerSlug {
 | 
					export function slugifyFilePath(fp: FilePath): ServerSlug {
 | 
				
			||||||
  conditionCheck(slugifyFilePath.name, "pre", fp, isFilePath)
 | 
					 | 
				
			||||||
  fp = _stripSlashes(fp) as FilePath
 | 
					  fp = _stripSlashes(fp) as FilePath
 | 
				
			||||||
  const withoutFileExt = fp.replace(new RegExp(_getFileExtension(fp) + "$"), "")
 | 
					  const withoutFileExt = fp.replace(new RegExp(_getFileExtension(fp) + "$"), "")
 | 
				
			||||||
  let slug = withoutFileExt
 | 
					  let slug = withoutFileExt
 | 
				
			||||||
@@ -145,7 +125,6 @@ export function slugifyFilePath(fp: FilePath): ServerSlug {
 | 
				
			|||||||
    slug = slug.replace(/_index$/, "index")
 | 
					    slug = slug.replace(/_index$/, "index")
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  conditionCheck(slugifyFilePath.name, "post", slug, isServerSlug)
 | 
					 | 
				
			||||||
  return slug as ServerSlug
 | 
					  return slug as ServerSlug
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -165,13 +144,11 @@ export function transformInternalLink(link: string): RelativeURL {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
  let joined = joinSegments(_stripSlashes(prefix), _stripSlashes(fp))
 | 
					  let joined = joinSegments(_stripSlashes(prefix), _stripSlashes(fp))
 | 
				
			||||||
  const res = (_addRelativeToStart(joined) + anchor) as RelativeURL
 | 
					  const res = (_addRelativeToStart(joined) + anchor) as RelativeURL
 | 
				
			||||||
  conditionCheck(transformInternalLink.name, "post", res, isRelativeURL)
 | 
					 | 
				
			||||||
  return res
 | 
					  return res
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// resolve /a/b/c to ../../
 | 
					// resolve /a/b/c to ../../
 | 
				
			||||||
export function pathToRoot(slug: CanonicalSlug): RelativeURL {
 | 
					export function pathToRoot(slug: CanonicalSlug): RelativeURL {
 | 
				
			||||||
  conditionCheck(pathToRoot.name, "pre", slug, isCanonicalSlug)
 | 
					 | 
				
			||||||
  let rootPath = slug
 | 
					  let rootPath = slug
 | 
				
			||||||
    .split("/")
 | 
					    .split("/")
 | 
				
			||||||
    .filter((x) => x !== "")
 | 
					    .filter((x) => x !== "")
 | 
				
			||||||
@@ -179,15 +156,11 @@ export function pathToRoot(slug: CanonicalSlug): RelativeURL {
 | 
				
			|||||||
    .join("/")
 | 
					    .join("/")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const res = _addRelativeToStart(rootPath) as RelativeURL
 | 
					  const res = _addRelativeToStart(rootPath) as RelativeURL
 | 
				
			||||||
  conditionCheck(pathToRoot.name, "post", res, isRelativeURL)
 | 
					 | 
				
			||||||
  return res
 | 
					  return res
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export function resolveRelative(current: CanonicalSlug, target: CanonicalSlug): RelativeURL {
 | 
					export function resolveRelative(current: CanonicalSlug, target: CanonicalSlug): RelativeURL {
 | 
				
			||||||
  conditionCheck(resolveRelative.name, "pre", current, isCanonicalSlug)
 | 
					 | 
				
			||||||
  conditionCheck(resolveRelative.name, "pre", target, isCanonicalSlug)
 | 
					 | 
				
			||||||
  const res = joinSegments(pathToRoot(current), target) as RelativeURL
 | 
					  const res = joinSegments(pathToRoot(current), target) as RelativeURL
 | 
				
			||||||
  conditionCheck(resolveRelative.name, "post", res, isRelativeURL)
 | 
					 | 
				
			||||||
  return res
 | 
					  return res
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -184,7 +184,7 @@ export const ObsidianFlavoredMarkdown: QuartzTransformerPlugin<Partial<Options>
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
              // embed cases
 | 
					              // embed cases
 | 
				
			||||||
              if (value.startsWith("!")) {
 | 
					              if (value.startsWith("!")) {
 | 
				
			||||||
                const ext: string | undefined = path.extname(fp).toLowerCase()
 | 
					                const ext: string = path.extname(fp).toLowerCase()
 | 
				
			||||||
                const url = slugifyFilePath(fp as FilePath) + ext
 | 
					                const url = slugifyFilePath(fp as FilePath) + ext
 | 
				
			||||||
                if ([".png", ".jpg", ".jpeg", ".gif", ".bmp", ".svg"].includes(ext)) {
 | 
					                if ([".png", ".jpg", ".jpeg", ".gif", ".bmp", ".svg"].includes(ext)) {
 | 
				
			||||||
                  const dims = alias ?? ""
 | 
					                  const dims = alias ?? ""
 | 
				
			||||||
@@ -218,8 +218,8 @@ export const ObsidianFlavoredMarkdown: QuartzTransformerPlugin<Partial<Options>
 | 
				
			|||||||
                    type: "html",
 | 
					                    type: "html",
 | 
				
			||||||
                    value: `<iframe src="${url}"></iframe>`,
 | 
					                    value: `<iframe src="${url}"></iframe>`,
 | 
				
			||||||
                  }
 | 
					                  }
 | 
				
			||||||
                } else {
 | 
					                } else if (ext === "") {
 | 
				
			||||||
                  // TODO: this is the node embed case
 | 
					                  // TODO: note embed
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
                // otherwise, fall through to regular link
 | 
					                // otherwise, fall through to regular link
 | 
				
			||||||
              }
 | 
					              }
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -37,7 +37,6 @@ export async function emitContent(ctx: BuildCtx, content: ProcessedContent[]) {
 | 
				
			|||||||
      }
 | 
					      }
 | 
				
			||||||
    } catch (err) {
 | 
					    } catch (err) {
 | 
				
			||||||
      trace(`Failed to emit from plugin \`${emitter.name}\``, err as Error)
 | 
					      trace(`Failed to emit from plugin \`${emitter.name}\``, err as Error)
 | 
				
			||||||
      throw err
 | 
					 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -103,7 +103,6 @@ export function createFileParser(ctx: BuildCtx, fps: FilePath[]) {
 | 
				
			|||||||
        }
 | 
					        }
 | 
				
			||||||
      } catch (err) {
 | 
					      } catch (err) {
 | 
				
			||||||
        trace(`\nFailed to process \`${fp}\``, err as Error)
 | 
					        trace(`\nFailed to process \`${fp}\``, err as Error)
 | 
				
			||||||
        throw err
 | 
					 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,4 +1,5 @@
 | 
				
			|||||||
import chalk from "chalk"
 | 
					import chalk from "chalk"
 | 
				
			||||||
 | 
					import process from "process"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const rootFile = /.*at file:/
 | 
					const rootFile = /.*at file:/
 | 
				
			||||||
export function trace(msg: string, err: Error) {
 | 
					export function trace(msg: string, err: Error) {
 | 
				
			||||||
@@ -28,4 +29,5 @@ export function trace(msg: string, err: Error) {
 | 
				
			|||||||
      }
 | 
					      }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					  process.exit(1)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user