@inovus-medical/docs 0.9.0 → 0.10.0

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.
package/bin/cli.js CHANGED
@@ -29,7 +29,7 @@ const flags = parseFlags(cmd === argv[0] ? argv.slice(1) : argv)
29
29
 
30
30
  const root = path.resolve(flags.root || process.cwd())
31
31
  const inDir = flags.in // undefined -> build() auto-detects (docs/ or repo root)
32
- const outDir = flags.out || 'dist'
32
+ const outDirFlag = typeof flags.out === 'string' ? flags.out : undefined
33
33
  const port = Number(flags.port || 8788)
34
34
  const theme = typeof flags.theme === 'string' ? flags.theme : undefined
35
35
 
@@ -48,6 +48,9 @@ async function run() {
48
48
  content,
49
49
  buildCommand,
50
50
  deployCommand,
51
+ localInstall,
52
+ localDev,
53
+ packageManager,
51
54
  coexisting
52
55
  } = result
53
56
  console.log(
@@ -55,6 +58,7 @@ async function run() {
55
58
  )
56
59
  if (content && content !== '.') console.log(` content: ${content}/`)
57
60
  if (coexisting) console.log(' mode: coexisting with existing package.json (docs scripts are docs:*)')
61
+ if (packageManager) console.log(` package manager: ${packageManager}`)
58
62
  if (created.length) console.log(' created: ' + created.join(', '))
59
63
  if (skipped.length) console.log(' kept existing (use --force to overwrite): ' + skipped.join(', '))
60
64
  for (const n of notes || []) console.log(' ⚠ ' + n)
@@ -64,21 +68,36 @@ async function run() {
64
68
  )
65
69
  }
66
70
  console.log('\nNext:')
67
- console.log(' npm install')
68
- console.log(' npm run docs:dev # preview at http://localhost:8788')
71
+ console.log(` ${localInstall}`)
72
+ console.log(` ${localDev} # preview at http://localhost:8788`)
69
73
  console.log(' git add -A && git commit -m "Add docs site config" && git push')
70
- console.log(` Cloudflare: build \`${buildCommand}\`, deploy \`${deployCommand}\``)
74
+ console.log(' (Commit the lockfile too — Cloudflare installs with --frozen-lockfile.)')
75
+ console.log('\nCloudflare Worker (same for every repo — copy exactly):')
76
+ console.log(` Build command: ${buildCommand}`)
77
+ console.log(` Deploy command: ${deployCommand}`)
71
78
  return
72
79
  }
73
80
 
74
81
  if (cmd === 'build') {
75
- const { pageCount, outPath, contentDir } = await build({ root, inDir, outDir, theme })
76
- console.log(`✓ Built ${pageCount} page(s) from ${contentDir === '.' ? 'repo root' : contentDir + '/'} -> ${path.relative(process.cwd(), outPath) || outPath}`)
82
+ const { pageCount, outPath, contentDir, outDir } = await build({
83
+ root,
84
+ inDir,
85
+ outDir: outDirFlag,
86
+ theme
87
+ })
88
+ console.log(
89
+ `✓ Built ${pageCount} page(s) from ${contentDir === '.' ? 'repo root' : contentDir + '/'} -> ${path.relative(process.cwd(), outPath) || outDir}`
90
+ )
77
91
  return
78
92
  }
79
93
 
80
94
  if (cmd === 'dev') {
81
- const { pageCount, outPath, contentDir } = await build({ root, inDir, outDir, theme })
95
+ const { pageCount, outPath, contentDir, outDir } = await build({
96
+ root,
97
+ inDir,
98
+ outDir: outDirFlag,
99
+ theme
100
+ })
82
101
  console.log(`✓ Built ${pageCount} page(s) from ${contentDir === '.' ? 'repo root' : contentDir + '/'}`)
83
102
  const { port: actual } = await serve({ dir: outPath, port })
84
103
  const busy = actual !== port ? ` (port ${port} was busy)` : ''
@@ -88,6 +107,9 @@ async function run() {
88
107
  }
89
108
 
90
109
  if (cmd === 'serve') {
110
+ const { loadConfig } = await import('../src/config.js')
111
+ const config = await loadConfig(root)
112
+ const outDir = outDirFlag || config.outDir || 'dist'
91
113
  const outPath = path.join(root, outDir)
92
114
  const { port: actual } = await serve({ dir: outPath, port })
93
115
  const busy = actual !== port ? ` (port ${port} was busy)` : ''
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@inovus-medical/docs",
3
- "version": "0.9.0",
3
+ "version": "0.10.0",
4
4
  "description": "Turn a folder of markdown into a themed, Cloudflare-ready docs site.",
5
5
  "type": "module",
6
6
  "bin": {
package/src/build.js CHANGED
@@ -119,11 +119,12 @@ function routeToFile(outPath, route) {
119
119
  return path.join(outPath, rel, 'index.html')
120
120
  }
121
121
 
122
- export async function build({ root = process.cwd(), inDir, outDir = 'dist', theme: themeFlag } = {}) {
122
+ export async function build({ root = process.cwd(), inDir, outDir, theme: themeFlag } = {}) {
123
123
  const config = await loadConfig(root)
124
124
  const theme = await resolveTheme({ themeFlag, config })
125
125
  config.theme = theme
126
- const outPath = path.join(root, outDir)
126
+ const resolvedOut = outDir || config.outDir || 'dist'
127
+ const outPath = path.join(root, resolvedOut)
127
128
  const ignore = makeIgnore(config, path.basename(outPath))
128
129
 
129
130
  const contentDir = await resolveContentDir({ root, inDir, config, ignore })
@@ -235,5 +236,5 @@ export async function build({ root = process.cwd(), inDir, outDir = 'dist', them
235
236
  await fs.copyFile(file, dest)
236
237
  }
237
238
 
238
- return { pageCount: pages.length, outPath, contentDir }
239
+ return { pageCount: pages.length, outPath, contentDir, outDir: resolvedOut }
239
240
  }
package/src/config.js CHANGED
@@ -9,6 +9,8 @@ const DEFAULTS = {
9
9
  accent: null,
10
10
  // Named theme (each has light + dark). Overridden by DOCS_THEME / --theme.
11
11
  theme: 'totum',
12
+ // Build output directory. null → "dist". App repos usually use "dist-docs".
13
+ outDir: null,
12
14
  repo: null,
13
15
  editBase: null,
14
16
  visibility: 'private',
package/src/init.js CHANGED
@@ -71,6 +71,34 @@ function titleCase(s) {
71
71
  .replace(/\b\w/g, (c) => c.toUpperCase())
72
72
  }
73
73
 
74
+ /**
75
+ * Detect the repo's package manager from lockfiles / packageManager field.
76
+ * Cloudflare Workers Builds does the same (pnpm/yarn/npm from lockfile).
77
+ */
78
+ async function detectPackageManager(root, pkg) {
79
+ if (await fileExists(path.join(root, 'pnpm-lock.yaml'))) return 'pnpm'
80
+ if (await fileExists(path.join(root, 'yarn.lock'))) return 'yarn'
81
+ if (
82
+ (await fileExists(path.join(root, 'bun.lockb'))) ||
83
+ (await fileExists(path.join(root, 'bun.lock')))
84
+ ) {
85
+ return 'bun'
86
+ }
87
+ const field = pkg && typeof pkg.packageManager === 'string' ? pkg.packageManager : ''
88
+ if (field.startsWith('pnpm@')) return 'pnpm'
89
+ if (field.startsWith('yarn@')) return 'yarn'
90
+ if (field.startsWith('bun@')) return 'bun'
91
+ if (await fileExists(path.join(root, 'package-lock.json'))) return 'npm'
92
+ return 'npm'
93
+ }
94
+
95
+ function runScript(pm, script) {
96
+ if (pm === 'yarn') return `yarn ${script}`
97
+ if (pm === 'bun') return `bun run ${script}`
98
+ if (pm === 'pnpm') return `pnpm run ${script}`
99
+ return `npm run ${script}`
100
+ }
101
+
74
102
  const WRANGLER = (name, outDir) => `{
75
103
  "$schema": "node_modules/wrangler/config-schema.json",
76
104
  "name": "${name}",
@@ -145,10 +173,13 @@ export async function init({ root = process.cwd(), flags = {} } = {}) {
145
173
  let content = typeof flags.content === 'string' ? flags.content.trim() : null
146
174
  if (!content && (await fileExists(path.join(root, 'docs')))) content = 'docs'
147
175
 
148
- // App repos often already use dist/ and wrangler.jsonc — keep docs out of their way.
176
+ // App repos often already use dist/ — keep docs output out of the way via config.outDir.
149
177
  const outDir = coexisting ? 'dist-docs' : 'dist'
150
- const buildScript = outDir === 'dist' ? 'inovus-docs build' : `inovus-docs build --out ${outDir}`
151
- const devScript = outDir === 'dist' ? 'inovus-docs dev' : `inovus-docs dev --out ${outDir}`
178
+ // One Cloudflare build command for every repo (npm/pnpm/yarn). Reads docs.config.json.
179
+ const buildCommand = 'npx inovus-docs build'
180
+ const pm = await detectPackageManager(root, existingPkg)
181
+ const localInstall = pm === 'yarn' ? 'yarn' : pm === 'bun' ? 'bun install' : `${pm} install`
182
+ const localDev = runScript(pm, 'docs:dev')
152
183
 
153
184
  const hasAppWrangler =
154
185
  (await fileExists(path.join(root, 'wrangler.jsonc'))) ||
@@ -160,19 +191,39 @@ export async function init({ root = process.cwd(), flags = {} } = {}) {
160
191
  const skipped = []
161
192
  const notes = []
162
193
 
163
- // docs.config.json (don't clobber existing unless --force)
194
+ // docs.config.json — create or merge additive fields (never wipe an existing file)
164
195
  const cfgPath = path.join(root, 'docs.config.json')
165
196
  if ((await fileExists(cfgPath)) && !force) {
166
- skipped.push('docs.config.json')
197
+ const cfg = (await readJsonIfExists(cfgPath)) || {}
198
+ let changed = false
199
+ if (content && !cfg.content) {
200
+ cfg.content = content
201
+ changed = true
202
+ }
203
+ if (coexisting && !cfg.outDir) {
204
+ cfg.outDir = outDir
205
+ changed = true
206
+ }
207
+ if (!cfg.theme) {
208
+ cfg.theme = 'totum'
209
+ changed = true
210
+ }
211
+ if (changed) {
212
+ await fs.writeFile(cfgPath, JSON.stringify(cfg, null, 2) + '\n')
213
+ created.push('docs.config.json (updated)')
214
+ } else {
215
+ skipped.push('docs.config.json')
216
+ }
167
217
  } else {
168
218
  const cfg = { title, description: '', visibility, theme: 'totum' }
169
219
  if (repoUrl) cfg.repo = repoUrl
170
220
  if (content) cfg.content = content
221
+ if (outDir !== 'dist') cfg.outDir = outDir
171
222
  await fs.writeFile(cfgPath, JSON.stringify(cfg, null, 2) + '\n')
172
223
  created.push('docs.config.json')
173
224
  }
174
225
 
175
- // Wrangler config — never overwrite an app's wrangler unless --force on wrangler.jsonc path
226
+ // Wrangler config — never overwrite an app's wrangler unless --force on our docs file
176
227
  const wranglerPath = path.join(root, wranglerFile)
177
228
  if ((await fileExists(wranglerPath)) && !force) {
178
229
  skipped.push(wranglerFile)
@@ -190,18 +241,18 @@ export async function init({ root = process.cwd(), flags = {} } = {}) {
190
241
  const pkgPath = path.join(root, 'package.json')
191
242
  const pkg = existingPkg ? { ...existingPkg } : { name, private: true }
192
243
  pkg.scripts = { ...(pkg.scripts || {}) }
193
- pkg.scripts['docs:build'] = buildScript
194
- pkg.scripts['docs:dev'] = devScript
195
- // Convenience aliases only when the repo doesn't already define them (pure docs repos).
244
+ // Local convenience scripts (CF uses `npx inovus-docs build` not these names).
245
+ pkg.scripts['docs:build'] = 'inovus-docs build'
246
+ pkg.scripts['docs:dev'] = 'inovus-docs dev'
196
247
  if (!pkg.scripts.build) {
197
- pkg.scripts.build = 'npm run docs:build'
248
+ pkg.scripts.build = 'inovus-docs build'
198
249
  } else {
199
250
  notes.push(
200
- `Left existing "build" script alone. Cloudflare build command should be: npm install && npm run docs:build`
251
+ `Left existing "build" script alone (app build). Cloudflare must use: ${buildCommand}`
201
252
  )
202
253
  }
203
254
  if (!pkg.scripts.dev) {
204
- pkg.scripts.dev = 'npm run docs:dev'
255
+ pkg.scripts.dev = 'inovus-docs dev'
205
256
  }
206
257
  if (wranglerFile !== 'wrangler.jsonc' && !pkg.scripts['docs:deploy']) {
207
258
  pkg.scripts['docs:deploy'] = `wrangler deploy -c ${wranglerFile}`
@@ -214,6 +265,17 @@ export async function init({ root = process.cwd(), flags = {} } = {}) {
214
265
  await fs.writeFile(pkgPath, JSON.stringify(pkg, null, 2) + '\n')
215
266
  created.push(existingPkg ? 'package.json (updated)' : 'package.json')
216
267
 
268
+ // Update the lockfile so Cloudflare --frozen-lockfile can install the engine.
269
+ try {
270
+ console.log(`Updating lockfile (${localInstall})…`)
271
+ execSync(localInstall, { cwd: root, stdio: 'inherit', shell: true })
272
+ created.push('lockfile (updated)')
273
+ } catch {
274
+ notes.push(
275
+ `Could not run "${localInstall}" automatically. Run it yourself and commit the lockfile before deploying.`
276
+ )
277
+ }
278
+
217
279
  // .node-version — only for greenfield docs repos (apps often pin their own Node).
218
280
  const nodeVerPath = path.join(root, '.node-version')
219
281
  if (!coexisting && !(await fileExists(nodeVerPath))) {
@@ -235,7 +297,6 @@ export async function init({ root = process.cwd(), flags = {} } = {}) {
235
297
  const rel = path.join(contentRel === '.' ? '' : contentRel, 'README.md').replace(/^\//, '')
236
298
  const readmePath = path.join(root, rel || 'README.md')
237
299
  await fs.mkdir(path.dirname(readmePath), { recursive: true })
238
- // Don't overwrite an existing root README when content is "."
239
300
  if (!(await fileExists(readmePath))) {
240
301
  await fs.writeFile(readmePath, starterReadme(title))
241
302
  created.push((rel || 'README.md').split(path.sep).join('/'))
@@ -245,7 +306,6 @@ export async function init({ root = process.cwd(), flags = {} } = {}) {
245
306
  }
246
307
  }
247
308
 
248
- const buildCommand = 'npm install && npm run docs:build'
249
309
  const deployCommand =
250
310
  wranglerFile === 'wrangler.jsonc'
251
311
  ? 'npx wrangler deploy'
@@ -265,6 +325,9 @@ export async function init({ root = process.cwd(), flags = {} } = {}) {
265
325
  wranglerFile,
266
326
  buildCommand,
267
327
  deployCommand,
328
+ localInstall,
329
+ localDev,
330
+ packageManager: pm,
268
331
  coexisting
269
332
  }
270
333
  }
package/src/watch.js CHANGED
@@ -2,7 +2,7 @@ import { watch } from 'node:fs'
2
2
  import path from 'node:path'
3
3
  import { build } from './build.js'
4
4
 
5
- const IGNORED = /(^|[\\/])(node_modules|\.git|dist)([\\/]|$)/
5
+ const IGNORED = /(^|[\\/])(node_modules|\.git|dist-docs|dist)([\\/]|$)/
6
6
 
7
7
  /**
8
8
  * Rebuild on any change under the content directory. Debounced so a burst of
@@ -12,7 +12,7 @@ const IGNORED = /(^|[\\/])(node_modules|\.git|dist)([\\/]|$)/
12
12
  */
13
13
  export function watchAndRebuild({ root, inDir, outDir, theme }) {
14
14
  const watchPath = inDir === '.' ? root : path.join(root, inDir)
15
- const outBase = path.basename(outDir)
15
+ const outBase = path.basename(outDir || 'dist')
16
16
  let timer = null
17
17
  let building = false
18
18
 
@@ -23,7 +23,12 @@ export function watchAndRebuild({ root, inDir, outDir, theme }) {
23
23
  if (building) return
24
24
  building = true
25
25
  try {
26
- const { pageCount } = await build({ root, inDir: inDir === '.' ? '.' : inDir, outDir, theme })
26
+ const { pageCount } = await build({
27
+ root,
28
+ inDir: inDir === '.' ? '.' : inDir,
29
+ outDir,
30
+ theme
31
+ })
27
32
  console.log(`↻ Rebuilt ${pageCount} page(s)`)
28
33
  } catch (err) {
29
34
  console.error('Build failed:', err.message)