Phase 2 generate resource docs

This commit is contained in:
2026-07-11 21:55:03 -05:00
parent 29bbc41b55
commit afeb2c4d3b
7 changed files with 387 additions and 6 deletions
+8 -2
View File
@@ -2,12 +2,18 @@
## Current
- Added generated human-facing resource docs for Gitea and future ChristIT.com catalog work.
## 0.1.2 - 2026-07-12
- Phase 2 generated public docs: added a docs generator that writes `RESOURCE_INDEX.md` and per-resource detail pages from source manifests and generated package catalogs.
## 0.1.1 - 2026-07-12
- Enriched the KJV source manifest with translation, edition, contributor, feature, attachment, license, and catalog-display metadata.
- Recorded the reusable manifest convention for future Bible translations and study resources.
- Adopted the convention that each named implementation phase bumps the repo version and is committed locally and remotely.
## 0.1.1 - 2026-07-12
- Phase 1 metadata foundation: expanded the KJV manifest and manifest documentation so future translations can describe editions, source text basis, contributors, concordance links, study notes, commentaries, maps, and other attachments.
## 0.1.0 - 2026-07-12
+7
View File
@@ -20,6 +20,13 @@ npm run import:kjv
Generated packages land under `packages/json/<resource-id>/`.
## Resource Index
Generated human-facing resource docs:
- `RESOURCE_INDEX.md`
- `docs/resources/kjv-eng-kjv2006.md`
## Repo Rules
- Every hosted text needs a source manifest.
+9
View File
@@ -0,0 +1,9 @@
# LibreBible Resource Index
This file is generated from source manifests and package catalogs. Edit `sources/*.json`, package generators, or catalog scripts instead of hand-editing this index.
## Available Resources
| Resource | Type | Language | Date / Edition | Source | License | Features | Attachments | Counts | Detail |
| --- | --- | --- | --- | --- | --- | --- | --- | --- | --- |
| [King James (Authorized) Version](docs/resources/kjv-eng-kjv2006.md) | translation | English (eng) - archaic British | 1611; 1769 edition | eBible.org | Public Domain (redistributable) | Bible text, Strong's-linked words, USFM source | Strong's links, future: study_notes, future: commentary, future: cross_references, future: morphology, future: lemma_links, future: lexicon, future: dictionary, future: maps, future: timeline, future: media | 66 books, 31102 verses, 348884 Strong's links | [Details](docs/resources/kjv-eng-kjv2006.md) |
+89
View File
@@ -0,0 +1,89 @@
# King James (Authorized) Version
Public-domain KJV package based on the 1769 standardized text, with Strong's numbers embedded in the source USFM.
## Identity
- Resource ID: `kjv-eng-kjv2006`
- Type: translation
- Abbreviation: KJV
- Short title: King James Version
- Alternate IDs: ENGKJV, eng-kjv2006
- Language: English (eng) - archaic British
- Canon: protocanon
## Translation
- Base title: King James Version
- Translation year: 1611
- Edition: 1769 standardized text with Strong's numbers added
- Edition year: 1769
- Source text basis: Authorized Version / King James Version standardized text of 1769
- Tradition: Protestant
## Source
- Provider: eBible.org
- Upstream ID: eng-kjv2006
- Upstream page: https://ebible.org/find/show.php?id=eng-kjv2006
- Download URL: https://ebible.org/Scriptures/eng-kjv2006_usfm.zip
- Source format: usfm-zip
- Upstream last updated: 2026-05-16
- Available upstream formats: browser, mobile-html, epub, pdf, sword, plain-text, vpl-sql, browser-bible, usfx, usfm, xetex
## License
- Name: Public Domain
- Redistribution allowed: Yes
- License/source URL: https://ebible.org/find/show.php?id=eng-kjv2006
- Attribution: This free text is provided courtesy of CrossWire Bible Society and eBible.org.
- Jurisdiction notes: eBible states this work is firmly public domain outside the United Kingdom; UK printing/import may be restricted by Crown letters patent.
- Restrictions: Printing this translation in the United Kingdom or importing printed copies into the UK may require permission from the Crown patent holders.
## Contributors
- King James Version translators: translation
- CrossWire Bible Society: source text provider
- eBible.org: publisher and package provider
## Features
- Bible text: translation_text; languages: eng; embedded
- Strong's-linked words: concordance_links; languages: hebrew, aramaic, greek; systems: strongs; embedded; package: packages/json/kjv-eng-kjv2006/strongs-links.jsonl
- USFM source: source_format; format: usfm
## Attachments
Included:
- Strong's links: concordance_links; word-to-strongs; anchors: verse, word, strongs_number; languages: hebrew, aramaic, greek; systems: strongs; package: packages/json/kjv-eng-kjv2006/strongs-links.jsonl
Supported future attachment types:
- study_notes
- commentary
- cross_references
- morphology
- lemma_links
- lexicon
- dictionary
- maps
- timeline
- media
## Generated Package
- Package catalog: packages/json/kjv-eng-kjv2006/catalog.json
- Verses JSONL: packages/json/kjv-eng-kjv2006/verses.jsonl
- Strong's links JSONL: packages/json/kjv-eng-kjv2006/strongs-links.jsonl
- Source SHA-256: 4ea6952590d070bfa22985aded48a49581e31b568a60aa09e25f73462e700e7d
- Last checked: 2026-07-12T02:18:30Z
## Counts
- Books: 66
- Verses: 31102
- Strong's links: 348884
## Package Checksums
- verses_jsonl: `verses.jsonl` (e3102f854a99c4788ed259ec61cb93fea830845c0788c8c0929bc77e9db6ca10)
- strongs_jsonl: `strongs-links.jsonl` (fa9702224776509275ea17fb245cb72150f1c262c4fbe44c7161e8e83866c779)
+2 -2
View File
@@ -1,12 +1,12 @@
{
"name": "libre-bible-data",
"version": "0.1.1",
"version": "0.1.2",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "libre-bible-data",
"version": "0.1.1",
"version": "0.1.2",
"dependencies": {
"adm-zip": "^0.5.16"
}
+3 -2
View File
@@ -1,12 +1,13 @@
{
"name": "libre-bible-data",
"version": "0.1.1",
"version": "0.1.2",
"private": true,
"type": "module",
"scripts": {
"check": "node scripts/check-sources.js",
"docs": "node scripts/generate-docs.js",
"import:kjv": "node scripts/import-usfm.js sources/kjv-eng-kjv2006.json",
"build": "npm run check && npm run import:kjv"
"build": "npm run check && npm run import:kjv && npm run docs"
},
"dependencies": {
"adm-zip": "^0.5.16"
+269
View File
@@ -0,0 +1,269 @@
import { mkdir, readdir, writeFile } from 'node:fs/promises'
import path from 'node:path'
import { readJson } from './lib.js'
const root = process.cwd()
const sourcesDir = path.join(root, 'sources')
const docsResourcesDir = path.join(root, 'docs', 'resources')
const sourceFiles = (await readdir(sourcesDir))
.filter((file) => file.endsWith('.json'))
.sort((a, b) => a.localeCompare(b))
const resources = []
for (const file of sourceFiles) {
const manifest = await readJson(path.join(sourcesDir, file))
const packageCatalogPath = path.join(root, 'packages', 'json', manifest.id, 'catalog.json')
let packageCatalog = null
try {
packageCatalog = await readJson(packageCatalogPath)
} catch {
packageCatalog = null
}
resources.push({ manifest, packageCatalog })
}
await mkdir(docsResourcesDir, { recursive: true })
const index = [
'# LibreBible Resource Index',
'',
'This file is generated from source manifests and package catalogs. Edit `sources/*.json`, package generators, or catalog scripts instead of hand-editing this index.',
'',
'## Available Resources',
'',
'| Resource | Type | Language | Date / Edition | Source | License | Features | Attachments | Counts | Detail |',
'| --- | --- | --- | --- | --- | --- | --- | --- | --- | --- |',
]
for (const resource of resources) {
const { manifest, packageCatalog } = resource
const detailPath = `docs/resources/${manifest.id}.md`
index.push(
[
link(manifest.title, detailPath),
manifest.resource_type ?? 'resource',
languageLabel(manifest),
editionLabel(manifest),
sourceLabel(manifest),
licenseLabel(manifest),
featureLabels(manifest).join(', ') || 'None listed',
attachmentLabels(manifest).join(', ') || 'None listed',
countsLabel(packageCatalog),
link('Details', detailPath),
].join(' | ').replace(/^/, '| ').replace(/$/, ' |'),
)
await writeFile(
path.join(docsResourcesDir, `${manifest.id}.md`),
`${resourceDetailMarkdown(manifest, packageCatalog)}\n`,
'utf8',
)
}
await writeFile(path.join(root, 'RESOURCE_INDEX.md'), `${index.join('\n')}\n`, 'utf8')
console.log(`Generated RESOURCE_INDEX.md and ${resources.length} resource detail page(s).`)
function resourceDetailMarkdown(manifest, packageCatalog) {
const lines = [
`# ${manifest.title}`,
'',
manifest.catalog_display?.summary ?? manifest.translation?.public_description ?? '',
'',
'## Identity',
'',
`- Resource ID: \`${manifest.id}\``,
`- Type: ${manifest.resource_type ?? 'resource'}`,
`- Abbreviation: ${manifest.abbreviation ?? 'n/a'}`,
`- Short title: ${manifest.short_title ?? manifest.title}`,
`- Alternate IDs: ${formatList(manifest.alternate_ids)}`,
`- Language: ${languageLabel(manifest)}`,
`- Canon: ${manifest.canon?.scope ?? 'n/a'}`,
'',
'## Translation',
'',
`- Base title: ${manifest.translation?.base_title ?? 'n/a'}`,
`- Translation year: ${manifest.translation?.translation_year ?? 'n/a'}`,
`- Edition: ${manifest.translation?.edition ?? 'n/a'}`,
`- Edition year: ${manifest.translation?.edition_year ?? 'n/a'}`,
`- Source text basis: ${manifest.translation?.source_text_basis ?? 'n/a'}`,
`- Tradition: ${manifest.translation?.tradition ?? 'n/a'}`,
'',
'## Source',
'',
`- Provider: ${manifest.source?.provider ?? 'n/a'}`,
`- Upstream ID: ${manifest.source?.upstream_id ?? 'n/a'}`,
`- Upstream page: ${manifest.source?.url ?? 'n/a'}`,
`- Download URL: ${manifest.source?.download_url ?? 'n/a'}`,
`- Source format: ${manifest.source?.format ?? 'n/a'}`,
`- Upstream last updated: ${manifest.source?.upstream_last_updated ?? 'n/a'}`,
`- Available upstream formats: ${formatList(manifest.source?.available_formats)}`,
'',
'## License',
'',
`- Name: ${manifest.license?.name ?? 'n/a'}`,
`- Redistribution allowed: ${manifest.license?.redistribution === true ? 'Yes' : 'No or unknown'}`,
`- License/source URL: ${manifest.license?.source_license_url ?? 'n/a'}`,
`- Attribution: ${manifest.license?.attribution ?? 'n/a'}`,
`- Jurisdiction notes: ${manifest.license?.jurisdiction_notes ?? 'n/a'}`,
`- Restrictions: ${manifest.license?.restricted_notes ?? 'n/a'}`,
'',
'## Contributors',
'',
...contributorsList(manifest),
'',
'## Features',
'',
...featuresList(manifest),
'',
'## Attachments',
'',
...attachmentsList(manifest),
'',
'## Generated Package',
'',
`- Package catalog: ${manifest.packages?.json_catalog ?? 'n/a'}`,
`- Verses JSONL: ${manifest.packages?.verses_jsonl ?? 'n/a'}`,
`- Strong\'s links JSONL: ${manifest.packages?.strongs_jsonl ?? 'n/a'}`,
`- Source SHA-256: ${packageCatalog?.source_sha256 ?? manifest.checks?.expected_sha256 ?? 'n/a'}`,
`- Last checked: ${manifest.checks?.last_checked_at ?? 'n/a'}`,
'',
'## Counts',
'',
`- Books: ${packageCatalog?.counts?.books ?? 'n/a'}`,
`- Verses: ${packageCatalog?.counts?.verses ?? 'n/a'}`,
`- Strong\'s links: ${packageCatalog?.counts?.strongs_links ?? 'n/a'}`,
'',
'## Package Checksums',
'',
...packageFilesList(packageCatalog),
]
return lines.filter((line, index, all) => !(line === '' && all[index - 1] === '')).join('\n')
}
function languageLabel(manifest) {
if (typeof manifest.language === 'string') return manifest.language
const parts = [
manifest.language?.name,
manifest.language?.code ? `(${manifest.language.code})` : null,
manifest.language?.dialect ? `- ${manifest.language.dialect}` : null,
]
return parts.filter(Boolean).join(' ') || 'n/a'
}
function editionLabel(manifest) {
const translationYear = manifest.translation?.translation_year
const editionYear = manifest.translation?.edition_year
const edition = manifest.translation?.edition
if (translationYear && editionYear) return `${translationYear}; ${editionYear} edition`
if (editionYear) return `${editionYear} edition`
return edition ?? 'n/a'
}
function sourceLabel(manifest) {
return manifest.source?.provider ?? 'n/a'
}
function licenseLabel(manifest) {
const name = manifest.license?.name ?? 'n/a'
const redistribution = manifest.license?.redistribution === true ? 'redistributable' : 'restricted/unknown'
return `${name} (${redistribution})`
}
function featureLabels(manifest) {
return (manifest.features ?? []).map((feature) => feature.label ?? feature.id ?? feature.type).filter(Boolean)
}
function attachmentLabels(manifest) {
return [
...(manifest.attachments?.included ?? []).map((attachment) => attachment.label ?? attachment.id),
...(manifest.attachments?.supported_future_types ?? []).map((type) => `future: ${type}`),
].filter(Boolean)
}
function countsLabel(packageCatalog) {
if (!packageCatalog?.counts) return 'n/a'
const counts = packageCatalog.counts
const parts = [
counts.books != null ? `${counts.books} books` : null,
counts.verses != null ? `${counts.verses} verses` : null,
counts.strongs_links != null ? `${counts.strongs_links} Strong's links` : null,
]
return parts.filter(Boolean).join(', ')
}
function contributorsList(manifest) {
const contributors = manifest.contributors ?? []
if (contributors.length === 0) return ['- None listed.']
return contributors.map((contributor) => `- ${contributor.name}: ${contributor.role}`)
}
function featuresList(manifest) {
const features = manifest.features ?? []
if (features.length === 0) return ['- None listed.']
return features.map((feature) => {
const details = [
feature.type,
feature.languages?.length ? `languages: ${feature.languages.join(', ')}` : null,
feature.systems?.length ? `systems: ${feature.systems.join(', ')}` : null,
feature.format ? `format: ${feature.format}` : null,
feature.embedded === true ? 'embedded' : null,
feature.package ? `package: ${feature.package}` : null,
].filter(Boolean)
return `- ${feature.label ?? feature.id}: ${details.join('; ') || 'listed'}`
})
}
function attachmentsList(manifest) {
const included = manifest.attachments?.included ?? []
const future = manifest.attachments?.supported_future_types ?? []
const lines = []
if (included.length > 0) {
lines.push('Included:')
for (const attachment of included) {
lines.push(
`- ${attachment.label ?? attachment.id}: ${[
attachment.resource_type,
attachment.relationship,
attachment.anchor_types?.length ? `anchors: ${attachment.anchor_types.join(', ')}` : null,
attachment.languages?.length ? `languages: ${attachment.languages.join(', ')}` : null,
attachment.systems?.length ? `systems: ${attachment.systems.join(', ')}` : null,
attachment.package ? `package: ${attachment.package}` : null,
].filter(Boolean).join('; ')}`,
)
}
} else {
lines.push('- No included attachments listed.')
}
if (future.length > 0) {
lines.push('')
lines.push('Supported future attachment types:')
for (const type of future) {
lines.push(`- ${type}`)
}
}
return lines
}
function packageFilesList(packageCatalog) {
const files = packageCatalog?.files
if (!files) return ['- No package file checksums listed.']
return Object.entries(files).map(([key, value]) => `- ${key}: \`${value.path}\` (${value.sha256})`)
}
function formatList(values) {
return values?.length ? values.join(', ') : 'n/a'
}
function link(label, href) {
return `[${label}](${href})`
}