livemark 0.28.1 → 0.28.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -106,6 +106,7 @@ export type SystemConfig = z.infer<typeof SystemConfig>
106
106
  export const SystemConfig = z.object({
107
107
  root: z.string(),
108
108
  configPath: z.string(),
109
+ configDependencies: z.array(z.string()).default([]),
109
110
  })
110
111
 
111
112
  /**
@@ -1,9 +1,10 @@
1
- import { cpSync, existsSync, rmSync, statSync } from "node:fs"
2
- import { join, sep } from "node:path"
3
- import type { ConfigEnv, Plugin, UserConfig as ViteUserConfig } from "vite"
1
+ import { cpSync, existsSync, readFileSync, rmSync, statSync } from "node:fs"
2
+ import { dirname, join, sep } from "node:path"
3
+ import type { ConfigEnv, Plugin, UserConfig as ViteUserConfig, ViteDevServer } from "vite"
4
4
  import { mergeConfig } from "vite"
5
5
  import packageJson from "../../package.json" with { type: "json" }
6
6
  import { buildTarget } from "../actions/target/build.ts"
7
+ import { collectLocalImports } from "../helpers/local-imports.ts"
7
8
  import { renderInlineMarkdown } from "../helpers/render-inline-markdown.ts"
8
9
  import { excludeTailwindSources } from "../helpers/tailwind-sources.ts"
9
10
  import { resolveTargetExport } from "../helpers/target-export.ts"
@@ -17,6 +18,13 @@ export interface LivemarkOptions {
17
18
 
18
19
  const SOURCE_DIR = join(import.meta.dirname, "..")
19
20
  const IS_INSTALLED = SOURCE_DIR.includes(`${sep}node_modules${sep}`)
21
+ const LOCKFILES = [
22
+ "pnpm-lock.yaml",
23
+ "package-lock.json",
24
+ "yarn.lock",
25
+ "bun.lock",
26
+ "bun.lockb",
27
+ ]
20
28
 
21
29
  /** Single virtual module exposing livemark's runtime values to consumer
22
30
  * override files via named exports:
@@ -102,18 +110,29 @@ export function livemark(opts: LivemarkOptions): Plugin {
102
110
  ].join("\n")
103
111
  },
104
112
  configureServer(server) {
113
+ // Vite restarts only for its own config's imports; the project config's
114
+ // imports and, in livemark's own dev, the content-collections config's
115
+ // imports need it too. A restart rebuilds the target, so no sync is lost.
116
+ const restartFiles = new Set([
117
+ opts.config.configPath,
118
+ ...opts.config.configDependencies,
119
+ ...(IS_INSTALLED
120
+ ? []
121
+ : collectLocalImports(join(SOURCE_DIR, "content-collections.ts"))),
122
+ ])
105
123
  // Vite watches only its root (the target); the files the target is
106
124
  // copied from join its watcher here. `.livemark/` always exists
107
125
  // because the target lives inside it.
108
- server.watcher.add([opts.config.configPath, overridesRoot])
109
- if (!IS_INSTALLED) server.watcher.add(SOURCE_DIR)
126
+ server.watcher.add([...restartFiles, overridesRoot])
127
+ if (IS_INSTALLED) watchInstalledVersion(server, opts.config.root)
128
+ else server.watcher.add(SOURCE_DIR)
110
129
  server.watcher.on("change", file => onChange(file, "change"))
111
130
  server.watcher.on("add", file => onChange(file, "add"))
112
131
  server.watcher.on("unlink", file => onChange(file, "unlink"))
113
132
  server.watcher.on("unlinkDir", file => onChange(file, "unlink"))
114
133
 
115
134
  function onChange(file: string, kind: "change" | "add" | "unlink") {
116
- if (file === opts.config.configPath) {
135
+ if (restartFiles.has(file)) {
117
136
  server.restart()
118
137
  return
119
138
  }
@@ -196,6 +215,53 @@ function syncSource(
196
215
  if (existsSync(source)) cpSync(source, dest, { recursive: true, force: true })
197
216
  }
198
217
 
218
+ // A running server keeps the livemark it started with: an in-process restart cannot
219
+ // reload the package's own modules, so an upgrade is reported instead.
220
+ function watchInstalledVersion(server: ViteDevServer, root: string) {
221
+ const packageDir = findInstalledPackageDir(root)
222
+ if (!packageDir) return
223
+ // Vite's watcher ignores node_modules, so the lockfile is what signals an install.
224
+ const projectDir = dirname(dirname(packageDir))
225
+ const manifest = join(packageDir, "package.json")
226
+ const watched = LOCKFILES.map(name => join(projectDir, name))
227
+ server.watcher.add(watched)
228
+ let timer: NodeJS.Timeout | undefined
229
+ server.watcher.on("all", (_, file) => {
230
+ if (!watched.includes(file)) return
231
+ clearTimeout(timer)
232
+ // Let the package manager finish relinking after it writes the lockfile.
233
+ timer = setTimeout(reportUpgrade, 1000)
234
+ })
235
+
236
+ function reportUpgrade() {
237
+ const version = readPackageVersion(manifest)
238
+ if (!version || version === packageJson.version) return
239
+ server.config.logger.warn(
240
+ `livemark ${packageJson.version} → ${version} installed; restart the dev server to use it`,
241
+ { timestamp: true },
242
+ )
243
+ }
244
+ }
245
+
246
+ function findInstalledPackageDir(root: string) {
247
+ for (let dir = root; ; dir = dirname(dir)) {
248
+ const packageDir = join(dir, "node_modules", packageJson.name)
249
+ if (existsSync(packageDir)) return packageDir
250
+ if (dirname(dir) === dir) return undefined
251
+ }
252
+ }
253
+
254
+ function readPackageVersion(manifest: string) {
255
+ try {
256
+ const parsed: unknown = JSON.parse(readFileSync(manifest, "utf8"))
257
+ return isObject(parsed) && typeof parsed.version === "string"
258
+ ? parsed.version
259
+ : undefined
260
+ } catch {
261
+ return undefined
262
+ }
263
+ }
264
+
199
265
  /** Resolve livemark.config.ts's `vite` field — either a config object
200
266
  * or a function `(env) => config` (matching Vite's own `defineConfig`).
201
267
  * Schema stores it as `unknown`; typing for the consumer's editor
@@ -44,6 +44,11 @@
44
44
  @apply px-4;
45
45
  }
46
46
 
47
+ /* `code` is a flex column, where an empty line span collapses to no height */
48
+ .prose pre code .line:empty::before {
49
+ content: " ";
50
+ }
51
+
47
52
  .prose pre code .line[data-highlighted] {
48
53
  @apply bg-primary/10;
49
54
  }