@doxbrix/doxloop 0.1.4 → 0.2.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/CHANGELOG.md +1153 -0
- package/README.md +422 -226
- package/assets/doxbrix-preview.css +77 -15
- package/contracts/agent-events-v1.schema.json +1 -0
- package/contracts/coverage-v1.schema.json +59 -0
- package/contracts/drift-v1.schema.json +1 -0
- package/contracts/evaluation-v1.schema.json +62 -0
- package/contracts/quality-codes-v1.json +1 -0
- package/contracts/quality-report-v1.schema.json +1 -0
- package/contracts/validation-v1.schema.json +1 -0
- package/dist/agent-failure.d.ts +36 -0
- package/dist/agent-failure.js +98 -0
- package/dist/agent-isolation.d.ts +17 -0
- package/dist/agent-isolation.js +94 -0
- package/dist/agent-log.d.ts +197 -0
- package/dist/agent-log.js +1094 -0
- package/dist/agent-process.d.ts +63 -0
- package/dist/agent-process.js +128 -0
- package/dist/agent-reply.d.ts +47 -0
- package/dist/agent-reply.js +315 -0
- package/dist/agents.d.ts +40 -4
- package/dist/agents.js +115 -11
- package/dist/api-coverage.d.ts +11 -0
- package/dist/api-coverage.js +72 -0
- package/dist/application-probe.d.ts +28 -0
- package/dist/application-probe.js +93 -0
- package/dist/application-url.d.ts +14 -0
- package/dist/application-url.js +26 -0
- package/dist/args.js +15 -0
- package/dist/artifact-deploy.d.ts +9 -0
- package/dist/artifact-deploy.js +28 -8
- package/dist/assets.d.ts +72 -0
- package/dist/assets.js +382 -0
- package/dist/auth.d.ts +10 -0
- package/dist/auth.js +38 -9
- package/dist/author.d.ts +206 -4
- package/dist/author.js +1650 -52
- package/dist/authoring-batches.d.ts +190 -0
- package/dist/authoring-batches.js +489 -0
- package/dist/authoring-postpass.d.ts +79 -0
- package/dist/authoring-postpass.js +1289 -0
- package/dist/authoring-progress.d.ts +95 -0
- package/dist/authoring-progress.js +269 -0
- package/dist/autosync.d.ts +56 -0
- package/dist/autosync.js +442 -0
- package/dist/batch-artifacts.d.ts +81 -0
- package/dist/batch-artifacts.js +412 -0
- package/dist/batch-limits.d.ts +37 -0
- package/dist/batch-limits.js +80 -0
- package/dist/branding.d.ts +41 -0
- package/dist/branding.js +229 -0
- package/dist/bundle-upload.d.ts +26 -0
- package/dist/bundle-upload.js +99 -0
- package/dist/capture-auth.d.ts +95 -0
- package/dist/capture-auth.js +204 -0
- package/dist/capture.d.ts +48 -0
- package/dist/capture.js +20 -1
- package/dist/cli.js +839 -32
- package/dist/content-links.d.ts +10 -0
- package/dist/content-links.js +49 -0
- package/dist/contract-validation.d.ts +4 -0
- package/dist/contract-validation.js +25 -0
- package/dist/coverage-actions.d.ts +8 -0
- package/dist/coverage-actions.js +71 -0
- package/dist/coverage-resolutions.d.ts +16 -0
- package/dist/coverage-resolutions.js +58 -0
- package/dist/db.d.ts +37 -0
- package/dist/db.js +288 -0
- package/dist/demo.d.ts +12 -0
- package/dist/demo.js +122 -0
- package/dist/deploy-credentials.d.ts +7 -0
- package/dist/deploy-credentials.js +76 -0
- package/dist/deploy-targets/github-pages.d.ts +3 -0
- package/dist/deploy-targets/github-pages.js +100 -0
- package/dist/deploy-targets/index.d.ts +8 -0
- package/dist/deploy-targets/index.js +78 -0
- package/dist/deploy-targets/netlify.d.ts +3 -0
- package/dist/deploy-targets/netlify.js +49 -0
- package/dist/deploy-targets/types.d.ts +32 -0
- package/dist/deploy-targets/types.js +2 -0
- package/dist/deploy-targets/vercel.d.ts +3 -0
- package/dist/deploy-targets/vercel.js +95 -0
- package/dist/deploy.d.ts +9 -0
- package/dist/deploy.js +87 -13
- package/dist/deterministic-capture.d.ts +90 -0
- package/dist/deterministic-capture.js +435 -0
- package/dist/direct-edit.d.ts +40 -0
- package/dist/direct-edit.js +159 -0
- package/dist/docs-crawl.d.ts +86 -0
- package/dist/docs-crawl.js +536 -0
- package/dist/docs-site.d.ts +49 -0
- package/dist/docs-site.js +233 -0
- package/dist/doctor.js +8 -0
- package/dist/documentation-collections.d.ts +23 -0
- package/dist/documentation-collections.js +191 -0
- package/dist/documentation-plan.d.ts +253 -0
- package/dist/documentation-plan.js +2563 -0
- package/dist/doxbrix-build.d.ts +19 -0
- package/dist/doxbrix-build.js +167 -0
- package/dist/doxbrix-markdown.d.ts +9 -0
- package/dist/doxbrix-markdown.js +115 -14
- package/dist/drift.d.ts +10 -0
- package/dist/drift.js +164 -0
- package/dist/evaluation.d.ts +46 -0
- package/dist/evaluation.js +113 -0
- package/dist/evidence-pack.d.ts +47 -0
- package/dist/evidence-pack.js +358 -0
- package/dist/evidence.d.ts +25 -0
- package/dist/evidence.js +175 -0
- package/dist/fs.d.ts +8 -2
- package/dist/fs.js +41 -11
- package/dist/generator-api.d.ts +89 -0
- package/dist/generator-preflight.d.ts +27 -0
- package/dist/generator-preflight.js +105 -0
- package/dist/generator-runtime.d.ts +7 -0
- package/dist/generator-runtime.js +17 -1
- package/dist/generators.d.ts +24 -2
- package/dist/generators.js +54 -1
- package/dist/git-delivery.d.ts +17 -0
- package/dist/git-delivery.js +123 -0
- package/dist/globs.d.ts +16 -0
- package/dist/globs.js +65 -0
- package/dist/glossary.d.ts +26 -0
- package/dist/glossary.js +179 -0
- package/dist/history.d.ts +106 -0
- package/dist/history.js +600 -0
- package/dist/html-markdown.d.ts +46 -0
- package/dist/html-markdown.js +423 -0
- package/dist/interactive.js +16 -15
- package/dist/job-events.d.ts +74 -0
- package/dist/job-events.js +377 -0
- package/dist/keep-awake.d.ts +50 -0
- package/dist/keep-awake.js +123 -0
- package/dist/local-source-snapshot.d.ts +20 -0
- package/dist/local-source-snapshot.js +61 -0
- package/dist/mintlify-detect.d.ts +3 -0
- package/dist/mintlify-detect.js +18 -0
- package/dist/mintlify-import.d.ts +75 -0
- package/dist/mintlify-import.js +190 -0
- package/dist/navigation.d.ts +98 -0
- package/dist/navigation.js +310 -0
- package/dist/openapi.d.ts +60 -0
- package/dist/openapi.js +439 -0
- package/dist/page-editor-bridge.d.ts +3 -0
- package/dist/page-editor-bridge.js +109 -0
- package/dist/page-editor-preview.d.ts +10 -0
- package/dist/page-editor-preview.js +55 -0
- package/dist/page-extension.d.ts +9 -0
- package/dist/page-extension.js +15 -0
- package/dist/page-metadata.d.ts +28 -0
- package/dist/page-metadata.js +166 -0
- package/dist/page-operations.d.ts +34 -0
- package/dist/page-operations.js +215 -0
- package/dist/page-routes.d.ts +4 -0
- package/dist/page-routes.js +61 -0
- package/dist/pages.d.ts +20 -0
- package/dist/pages.js +184 -0
- package/dist/plan-generator.d.ts +3 -0
- package/dist/plan-generator.js +21 -0
- package/dist/plan-navigation.d.ts +11 -0
- package/dist/plan-navigation.js +30 -0
- package/dist/planning-captures.d.ts +20 -0
- package/dist/planning-captures.js +143 -0
- package/dist/planning-research.d.ts +135 -0
- package/dist/planning-research.js +472 -0
- package/dist/planning-triage.d.ts +23 -0
- package/dist/planning-triage.js +131 -0
- package/dist/preview.d.ts +24 -0
- package/dist/preview.js +280 -29
- package/dist/project-detect.d.ts +36 -0
- package/dist/project-detect.js +251 -0
- package/dist/project-import.d.ts +54 -0
- package/dist/project-import.js +157 -0
- package/dist/project-lock.d.ts +6 -0
- package/dist/project-lock.js +96 -0
- package/dist/project-registry.d.ts +25 -0
- package/dist/project-registry.js +79 -0
- package/dist/project.d.ts +35 -3
- package/dist/project.js +388 -38
- package/dist/prompts.d.ts +9 -0
- package/dist/prompts.js +33 -4
- package/dist/proposal-replay.d.ts +32 -0
- package/dist/proposal-replay.js +99 -0
- package/dist/quality-claims.d.ts +8 -0
- package/dist/quality-claims.js +168 -0
- package/dist/quality-config.d.ts +5 -0
- package/dist/quality-config.js +84 -0
- package/dist/quality-contract.d.ts +37 -0
- package/dist/quality-contract.js +46 -0
- package/dist/quality-examples.d.ts +4 -0
- package/dist/quality-examples.js +233 -0
- package/dist/quality-gates.d.ts +16 -0
- package/dist/quality-gates.js +192 -0
- package/dist/quality-links.d.ts +7 -0
- package/dist/quality-links.js +149 -0
- package/dist/quality-lint.d.ts +6 -0
- package/dist/quality-lint.js +124 -0
- package/dist/quality-rendered.d.ts +11 -0
- package/dist/quality-rendered.js +222 -0
- package/dist/quality-schema.d.ts +3 -0
- package/dist/quality-schema.js +71 -0
- package/dist/release-notes.d.ts +44 -0
- package/dist/release-notes.js +183 -0
- package/dist/remote-monitor.d.ts +16 -0
- package/dist/remote-monitor.js +74 -0
- package/dist/remote-source.d.ts +34 -0
- package/dist/remote-source.js +426 -0
- package/dist/review-diff.d.ts +82 -0
- package/dist/review-diff.js +400 -0
- package/dist/review-learning.d.ts +11 -0
- package/dist/review-learning.js +60 -0
- package/dist/review-render.d.ts +38 -0
- package/dist/review-render.js +224 -0
- package/dist/review-report.d.ts +9 -0
- package/dist/review-report.js +89 -0
- package/dist/review-ui.d.ts +14 -0
- package/dist/review-ui.js +1248 -0
- package/dist/schedule.d.ts +78 -0
- package/dist/schedule.js +480 -0
- package/dist/screen-capture-provider.d.ts +52 -0
- package/dist/screen-capture-provider.js +218 -0
- package/dist/screenshot-workflow.d.ts +167 -0
- package/dist/screenshot-workflow.js +1237 -0
- package/dist/settings.d.ts +1 -1
- package/dist/settings.js +95 -7
- package/dist/site-export.d.ts +18 -0
- package/dist/site-export.js +87 -0
- package/dist/source-connectors.d.ts +33 -0
- package/dist/source-connectors.js +268 -0
- package/dist/source-discovery.d.ts +132 -0
- package/dist/source-discovery.js +823 -0
- package/dist/source-intelligence.d.ts +9 -0
- package/dist/source-intelligence.js +306 -0
- package/dist/sync-review.d.ts +28 -0
- package/dist/sync-review.js +264 -0
- package/dist/sync-runs.d.ts +192 -0
- package/dist/sync-runs.js +2244 -0
- package/dist/sync.d.ts +35 -0
- package/dist/sync.js +298 -32
- package/dist/text-diff.d.ts +9 -0
- package/dist/text-diff.js +59 -0
- package/dist/types.d.ts +946 -1
- package/dist/ui/assets/doxloop-logo-light-De7Nx7j7.png +0 -0
- package/dist/ui/assets/index-BHBYU2aG.css +1 -0
- package/dist/ui/assets/index-Cq3RPQiC.js +33 -0
- package/dist/ui/index.html +18 -0
- package/dist/ui-server.d.ts +83 -0
- package/dist/ui-server.js +3532 -0
- package/dist/usage-budget.d.ts +28 -0
- package/dist/usage-budget.js +90 -0
- package/dist/validation.d.ts +25 -1
- package/dist/validation.js +312 -26
- package/dist/workspace-tools.d.ts +54 -0
- package/dist/workspace-tools.js +123 -0
- package/docs/agent-compatibility.md +50 -28
- package/docs/ci-and-automation.md +105 -66
- package/docs/doxbrix-http-api.md +8 -1
- package/docs/existing-documentation.md +80 -0
- package/docs/generation-performance.md +108 -0
- package/docs/generator-authoring.md +68 -5
- package/docs/generator-selection.md +50 -13
- package/docs/mintlify-import.md +71 -0
- package/docs/openapi-security.md +25 -0
- package/docs/project-format.md +331 -36
- package/docs/release-quality.md +158 -0
- package/docs/releasing.md +72 -0
- package/docs/review-workflows.md +51 -0
- package/docs/security-model.md +102 -39
- package/docs/troubleshooting.md +226 -93
- package/package.json +46 -15
- package/scripts/test-auto-screenshot.mjs +172 -0
- package/skills/doxloop-authoring/SKILL.md +242 -361
- package/skills/doxloop-authoring/references/existing-documentation.md +94 -0
- package/skills/doxloop-authoring/references/navigation-architecture.md +18 -7
- package/skills/doxloop-authoring/references/page-depth.md +169 -0
- package/skills/doxloop-authoring/references/project-format.md +106 -7
- package/skills/doxloop-authoring/references/quality.md +10 -0
- package/skills/doxloop-authoring/references/screenshot-manifest.md +113 -0
- package/skills/doxloop-authoring/references/screenshots.md +149 -235
- package/skills/doxloop-authoring/references/workflows.md +76 -0
- package/skills/doxloop-doxbrix/SKILL.md +38 -22
- package/skills/doxloop-doxbrix/references/api-endpoints.md +15 -14
- package/skills/doxloop-doxbrix/references/components.md +30 -3
- package/skills/doxloop-doxbrix/references/manifest.md +3 -2
- package/vendor/doxbrix-import/LICENSE +202 -0
- package/vendor/doxbrix-import/README.md +17 -0
- package/vendor/doxbrix-import/UPSTREAM.json +47 -0
- package/vendor/doxbrix-import/dist/docs/frontmatter.d.ts +13 -0
- package/vendor/doxbrix-import/dist/docs/frontmatter.js +83 -0
- package/vendor/doxbrix-import/dist/docs/import.d.ts +21 -0
- package/vendor/doxbrix-import/dist/docs/import.js +147 -0
- package/vendor/doxbrix-import/dist/docs/manifest.d.ts +163 -0
- package/vendor/doxbrix-import/dist/docs/manifest.js +64 -0
- package/vendor/doxbrix-import/dist/docs/project.d.ts +25 -0
- package/vendor/doxbrix-import/dist/docs/project.js +77 -0
- package/vendor/doxbrix-import/dist/docs/starter.d.ts +4 -0
- package/vendor/doxbrix-import/dist/docs/starter.js +11 -0
- package/vendor/doxbrix-import/dist/importer.d.ts +227 -0
- package/vendor/doxbrix-import/dist/importer.js +1567 -0
- package/vendor/doxbrix-import/dist/mintlify-openapi.d.ts +37 -0
- package/vendor/doxbrix-import/dist/mintlify-openapi.js +305 -0
- package/vendor/doxbrix-import/dist/safe-path.d.ts +9 -0
- package/vendor/doxbrix-import/dist/safe-path.js +47 -0
- package/dist/agents.d.ts.map +0 -1
- package/dist/agents.js.map +0 -1
- package/dist/args.d.ts.map +0 -1
- package/dist/args.js.map +0 -1
- package/dist/artifact-deploy.d.ts.map +0 -1
- package/dist/artifact-deploy.js.map +0 -1
- package/dist/auth.d.ts.map +0 -1
- package/dist/auth.js.map +0 -1
- package/dist/author.d.ts.map +0 -1
- package/dist/author.js.map +0 -1
- package/dist/capture.d.ts.map +0 -1
- package/dist/capture.js.map +0 -1
- package/dist/cli.d.ts.map +0 -1
- package/dist/cli.js.map +0 -1
- package/dist/deploy.d.ts.map +0 -1
- package/dist/deploy.js.map +0 -1
- package/dist/deployment-visibility.d.ts.map +0 -1
- package/dist/deployment-visibility.js.map +0 -1
- package/dist/doctor.d.ts.map +0 -1
- package/dist/doctor.js.map +0 -1
- package/dist/doxbrix-markdown.d.ts.map +0 -1
- package/dist/doxbrix-markdown.js.map +0 -1
- package/dist/errors.d.ts.map +0 -1
- package/dist/errors.js.map +0 -1
- package/dist/fs.d.ts.map +0 -1
- package/dist/fs.js.map +0 -1
- package/dist/generator-api.d.ts.map +0 -1
- package/dist/generator-api.js.map +0 -1
- package/dist/generator-manager.d.ts.map +0 -1
- package/dist/generator-manager.js.map +0 -1
- package/dist/generator-runtime.d.ts.map +0 -1
- package/dist/generator-runtime.js.map +0 -1
- package/dist/generators.d.ts.map +0 -1
- package/dist/generators.js.map +0 -1
- package/dist/interactive.d.ts.map +0 -1
- package/dist/interactive.js.map +0 -1
- package/dist/preview.d.ts.map +0 -1
- package/dist/preview.js.map +0 -1
- package/dist/progress.d.ts.map +0 -1
- package/dist/progress.js.map +0 -1
- package/dist/project.d.ts.map +0 -1
- package/dist/project.js.map +0 -1
- package/dist/prompts.d.ts.map +0 -1
- package/dist/prompts.js.map +0 -1
- package/dist/settings.d.ts.map +0 -1
- package/dist/settings.js.map +0 -1
- package/dist/sync.d.ts.map +0 -1
- package/dist/sync.js.map +0 -1
- package/dist/types.d.ts.map +0 -1
- package/dist/types.js.map +0 -1
- package/dist/validation.d.ts.map +0 -1
- package/dist/validation.js.map +0 -1
- package/dist/version.d.ts.map +0 -1
- package/dist/version.js.map +0 -1
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `docs-site` sources: an existing documentation website crawled into a
|
|
3
|
+
* read-only Markdown snapshot that the authoring agent reads as evidence of the
|
|
4
|
+
* documentation being rewritten.
|
|
5
|
+
*
|
|
6
|
+
* Snapshots live beside other materialized sources under
|
|
7
|
+
* `<parent>/.doxloop-sources/<projectId>/<name>/docs-<hash>/`, outside the
|
|
8
|
+
* documentation deployment boundary, so the crawled third-party content can
|
|
9
|
+
* never be published by accident. The layout is plain files: `snapshot.json`
|
|
10
|
+
* (page metadata, skipped URLs, broken links, warnings), `index.md` (a table of
|
|
11
|
+
* every page the agent can skim before opening files), and `pages/**.md` (one
|
|
12
|
+
* Markdown page per crawled URL with its title and original URL in frontmatter).
|
|
13
|
+
*/
|
|
14
|
+
import { createHash } from 'node:crypto';
|
|
15
|
+
import { mkdir, mkdtemp, readFile, rename, rm, writeFile } from 'node:fs/promises';
|
|
16
|
+
import { dirname, join, resolve } from 'node:path';
|
|
17
|
+
import { DoxloopError } from './errors.js';
|
|
18
|
+
import { pathExists } from './fs.js';
|
|
19
|
+
import { REDIRECTS_FILE, readRedirects } from './page-operations.js';
|
|
20
|
+
import { portableSourcePath } from './remote-source.js';
|
|
21
|
+
export const DOCS_SITE_SNAPSHOT_FILE = 'snapshot.json';
|
|
22
|
+
export const DOCS_SITE_INDEX_FILE = 'index.md';
|
|
23
|
+
export const DOCS_SITE_PAGES_DIRECTORY = 'pages';
|
|
24
|
+
/** Write a crawl result as a snapshot directory outside the documentation project. */
|
|
25
|
+
export async function materializeDocsSiteSnapshot(root, name, snapshot) {
|
|
26
|
+
const projectId = createHash('sha256').update(resolve(root)).digest('hex').slice(0, 12);
|
|
27
|
+
const destination = join(dirname(resolve(root)), '.doxloop-sources', projectId, safeSourceName(name), `docs-${snapshot.hash.slice(0, 12)}`);
|
|
28
|
+
const manifest = docsSiteManifest(snapshot);
|
|
29
|
+
if (!(await pathExists(destination))) {
|
|
30
|
+
const parent = dirname(destination);
|
|
31
|
+
await mkdir(parent, { recursive: true });
|
|
32
|
+
const temporary = await mkdtemp(join(parent, '.docs-'));
|
|
33
|
+
try {
|
|
34
|
+
await writeFile(join(temporary, DOCS_SITE_SNAPSHOT_FILE), `${JSON.stringify(manifest, null, 2)}\n`, 'utf8');
|
|
35
|
+
await writeFile(join(temporary, DOCS_SITE_INDEX_FILE), docsSiteIndexMarkdown(manifest), 'utf8');
|
|
36
|
+
for (const page of snapshot.pages) {
|
|
37
|
+
const file = join(temporary, docsSitePageFile(page.path));
|
|
38
|
+
await mkdir(dirname(file), { recursive: true });
|
|
39
|
+
await writeFile(file, docsSitePageMarkdown(page), 'utf8');
|
|
40
|
+
}
|
|
41
|
+
await rename(temporary, destination);
|
|
42
|
+
}
|
|
43
|
+
catch (error) {
|
|
44
|
+
await rm(temporary, { recursive: true, force: true });
|
|
45
|
+
throw error;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
return { path: destination, manifest, site: docsSiteSource(manifest) };
|
|
49
|
+
}
|
|
50
|
+
/** The binding stored in project.json for a materialized snapshot. */
|
|
51
|
+
export function docsSiteBinding(root, name, materialized, scope) {
|
|
52
|
+
return {
|
|
53
|
+
name,
|
|
54
|
+
path: portableSourcePath(root, materialized.path),
|
|
55
|
+
kind: 'docs-site',
|
|
56
|
+
site: materialized.site,
|
|
57
|
+
...(scope ? { scope } : {}),
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
export function docsSiteSource(manifest) {
|
|
61
|
+
return {
|
|
62
|
+
url: manifest.url,
|
|
63
|
+
crawledAt: manifest.crawledAt,
|
|
64
|
+
pages: manifest.totals.pages,
|
|
65
|
+
words: manifest.totals.words,
|
|
66
|
+
hash: manifest.hash,
|
|
67
|
+
...(manifest.generator ? { generator: manifest.generator } : {}),
|
|
68
|
+
...(manifest.truncated ? { truncated: true } : {}),
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
export function docsSiteManifest(snapshot) {
|
|
72
|
+
return {
|
|
73
|
+
...snapshot,
|
|
74
|
+
pages: snapshot.pages.map(({ markdown: _markdown, ...page }) => ({ ...page, file: docsSitePageFile(page.path) })),
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
/** Read the manifest of a materialized `docs-site` source. */
|
|
78
|
+
export async function readDocsSiteManifest(root, source) {
|
|
79
|
+
const path = join(resolve(root, source.path), DOCS_SITE_SNAPSHOT_FILE);
|
|
80
|
+
let raw;
|
|
81
|
+
try {
|
|
82
|
+
raw = await readFile(path, 'utf8');
|
|
83
|
+
}
|
|
84
|
+
catch {
|
|
85
|
+
throw new DoxloopError(`The documentation site snapshot for source "${source.name}" is missing: ${path}. Re-crawl the site from Sources.`);
|
|
86
|
+
}
|
|
87
|
+
let parsed;
|
|
88
|
+
try {
|
|
89
|
+
parsed = JSON.parse(raw);
|
|
90
|
+
}
|
|
91
|
+
catch {
|
|
92
|
+
throw new DoxloopError(`The documentation site snapshot for source "${source.name}" is not valid JSON: ${path}`);
|
|
93
|
+
}
|
|
94
|
+
if (!isManifest(parsed))
|
|
95
|
+
throw new DoxloopError(`The documentation site snapshot for source "${source.name}" is invalid: ${path}`);
|
|
96
|
+
return parsed;
|
|
97
|
+
}
|
|
98
|
+
export async function assertDocsSiteSnapshot(root, source) {
|
|
99
|
+
await readDocsSiteManifest(root, source);
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Snapshot-relative Markdown file for a crawled page path. The entry page is
|
|
103
|
+
* `pages/index.md`; nested URLs keep their folders so the agent can relate a
|
|
104
|
+
* file back to the original site structure.
|
|
105
|
+
*/
|
|
106
|
+
export function docsSitePageFile(pagePath) {
|
|
107
|
+
const segments = pagePath.split('/').map((segment) => segment.trim()).filter((segment) => segment && segment !== '.' && segment !== '..')
|
|
108
|
+
.map((segment) => segment.replace(/[^A-Za-z0-9._-]+/g, '-').replace(/^\.+/, '').replace(/\.(md|mdx|html?)$/i, '') || 'page');
|
|
109
|
+
if (segments.length === 0)
|
|
110
|
+
return `${DOCS_SITE_PAGES_DIRECTORY}/index.md`;
|
|
111
|
+
return `${DOCS_SITE_PAGES_DIRECTORY}/${segments.join('/')}.md`;
|
|
112
|
+
}
|
|
113
|
+
export function docsSitePageMarkdown(page) {
|
|
114
|
+
const frontmatter = [
|
|
115
|
+
`title: ${JSON.stringify(page.title)}`,
|
|
116
|
+
`url: ${JSON.stringify(page.url)}`,
|
|
117
|
+
...(page.description ? [`description: ${JSON.stringify(page.description)}`] : []),
|
|
118
|
+
`crawledAt: ${JSON.stringify(page.fetchedAt)}`,
|
|
119
|
+
`words: ${page.words}`,
|
|
120
|
+
];
|
|
121
|
+
return `---\n${frontmatter.join('\n')}\n---\n\n${page.markdown}\n`;
|
|
122
|
+
}
|
|
123
|
+
export function docsSiteIndexMarkdown(manifest) {
|
|
124
|
+
const lines = [
|
|
125
|
+
`# Existing documentation snapshot`,
|
|
126
|
+
'',
|
|
127
|
+
`- Site: ${manifest.url}`,
|
|
128
|
+
`- Crawled: ${manifest.crawledAt}`,
|
|
129
|
+
`- Pages: ${manifest.totals.pages} (${manifest.totals.words} words, ${manifest.totals.images} images)`,
|
|
130
|
+
...(manifest.generator ? [`- Generator: ${manifest.generator}`] : []),
|
|
131
|
+
`- Discovered through: ${manifest.discovery.length > 0 ? manifest.discovery.join(', ') : 'entry page only'}`,
|
|
132
|
+
...(manifest.truncated ? [`- Truncated: the crawl stopped at ${manifest.pageLimit} pages; ${manifest.totals.discovered - manifest.totals.pages} discovered pages were not fetched.`] : []),
|
|
133
|
+
'',
|
|
134
|
+
'This snapshot is read-only evidence of the documentation being rewritten. Each page file records its original URL in frontmatter. Verify factual claims against the configured product sources before reusing them.',
|
|
135
|
+
'',
|
|
136
|
+
'## Pages',
|
|
137
|
+
'',
|
|
138
|
+
'| File | Title | Words | Original URL |',
|
|
139
|
+
'| --- | --- | --- | --- |',
|
|
140
|
+
...manifest.pages.map((page) => `| ${page.file} | ${cell(page.title)} | ${page.words} | ${page.url} |`),
|
|
141
|
+
];
|
|
142
|
+
if (manifest.brokenLinks.length > 0) {
|
|
143
|
+
lines.push('', `## Broken internal links (${manifest.brokenLinks.length})`, '');
|
|
144
|
+
for (const link of manifest.brokenLinks.slice(0, 100))
|
|
145
|
+
lines.push(`- ${link.url} (linked from ${link.from})${link.status ? ` — HTTP ${link.status}` : ''}`);
|
|
146
|
+
}
|
|
147
|
+
if (manifest.skipped.length > 0) {
|
|
148
|
+
lines.push('', `## Skipped URLs (${manifest.skipped.length})`, '');
|
|
149
|
+
for (const item of manifest.skipped.slice(0, 100))
|
|
150
|
+
lines.push(`- ${item.url} — ${item.reason}`);
|
|
151
|
+
}
|
|
152
|
+
if (manifest.warnings.length > 0) {
|
|
153
|
+
lines.push('', '## Crawl warnings', '');
|
|
154
|
+
for (const warning of manifest.warnings)
|
|
155
|
+
lines.push(`- ${warning}`);
|
|
156
|
+
}
|
|
157
|
+
return `${lines.join('\n')}\n`;
|
|
158
|
+
}
|
|
159
|
+
/** Human-readable one-line description used by health, tables, and CLI output. */
|
|
160
|
+
export function describeDocsSite(site) {
|
|
161
|
+
const date = new Date(site.crawledAt);
|
|
162
|
+
const when = Number.isNaN(date.getTime()) ? site.crawledAt : date.toISOString().slice(0, 10);
|
|
163
|
+
return `${site.pages} page${site.pages === 1 ? '' : 's'} · crawled ${when}${site.generator ? ` · ${site.generator}` : ''}`;
|
|
164
|
+
}
|
|
165
|
+
/**
|
|
166
|
+
* Redirects implied by an approved plan: every existing page that a new page
|
|
167
|
+
* absorbs, when the old route differs from the new one. Written to the same
|
|
168
|
+
* redirect file page moves use, so the preview and the Doxbrix build honor
|
|
169
|
+
* them, and exported for the old host when the new site is deployed elsewhere.
|
|
170
|
+
* The site root is never redirected: the new site always serves its own
|
|
171
|
+
* navigation homepage there, and a root redirect would only send readers to a
|
|
172
|
+
* planned path the authored pages may not use.
|
|
173
|
+
*/
|
|
174
|
+
export function existingDocumentationRedirects(plan) {
|
|
175
|
+
const redirects = {};
|
|
176
|
+
const pagesById = new Map(plan.pages.map((page) => [page.id, page]));
|
|
177
|
+
const routeOf = (path) => `/${path.replace(/^\/+/, '').replace(/\.(mdx?|rst|html?)$/i, '').replace(/(^|\/)index$/, '')}`.replace(/\/+$/, '') || '/';
|
|
178
|
+
const newRoutes = new Set(plan.pages.filter((page) => page.action !== 'remove').map((page) => routeOf(page.path)));
|
|
179
|
+
for (const assessment of plan.existingDocumentation ?? []) {
|
|
180
|
+
for (const disposition of assessment.pages) {
|
|
181
|
+
if (disposition.disposition === 'drop')
|
|
182
|
+
continue;
|
|
183
|
+
const target = disposition.into.map((id) => pagesById.get(id)).find((page) => page !== undefined && page.action !== 'remove');
|
|
184
|
+
if (!target)
|
|
185
|
+
continue;
|
|
186
|
+
const from = existingPageRoute(disposition);
|
|
187
|
+
const to = routeOf(target.path);
|
|
188
|
+
if (!from || from === '/' || from === to || newRoutes.has(from) || !/^\/[a-zA-Z0-9/_-]*$/.test(from) || !/^\/[a-zA-Z0-9/_-]*$/.test(to))
|
|
189
|
+
continue;
|
|
190
|
+
redirects[from] = to;
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
return redirects;
|
|
194
|
+
}
|
|
195
|
+
export async function writeExistingDocumentationRedirects(root, plan) {
|
|
196
|
+
const additions = existingDocumentationRedirects(plan);
|
|
197
|
+
if (Object.keys(additions).length === 0)
|
|
198
|
+
return {};
|
|
199
|
+
const current = await readRedirects(root);
|
|
200
|
+
const merged = { ...current };
|
|
201
|
+
for (const [from, to] of Object.entries(additions))
|
|
202
|
+
if (!(from in merged))
|
|
203
|
+
merged[from] = to;
|
|
204
|
+
await mkdir(join(root, '.doxloop'), { recursive: true });
|
|
205
|
+
await writeFile(join(root, REDIRECTS_FILE), `${JSON.stringify(merged, null, 2)}\n`, 'utf8');
|
|
206
|
+
return additions;
|
|
207
|
+
}
|
|
208
|
+
function existingPageRoute(disposition) {
|
|
209
|
+
if (disposition.url) {
|
|
210
|
+
try {
|
|
211
|
+
const pathname = new URL(disposition.url).pathname.replace(/\/index\.html?$/i, '/').replace(/\.html?$/i, '').replace(/\/+$/, '');
|
|
212
|
+
return pathname === '' ? '/' : pathname;
|
|
213
|
+
}
|
|
214
|
+
catch { /* fall back to the snapshot path */ }
|
|
215
|
+
}
|
|
216
|
+
const relative = disposition.path.replace(/^pages\//, '').replace(/\.md$/, '').replace(/(^|\/)index$/, '');
|
|
217
|
+
return `/${relative}`.replace(/\/+$/, '') || '/';
|
|
218
|
+
}
|
|
219
|
+
function safeSourceName(name) {
|
|
220
|
+
return name.replace(/[^a-zA-Z0-9._-]+/g, '-').replace(/^\.+/, '') || 'docs';
|
|
221
|
+
}
|
|
222
|
+
function cell(value) {
|
|
223
|
+
return value.replace(/\|/g, '\\|').replace(/\s+/g, ' ').trim();
|
|
224
|
+
}
|
|
225
|
+
function isManifest(value) {
|
|
226
|
+
if (!value || typeof value !== 'object')
|
|
227
|
+
return false;
|
|
228
|
+
const manifest = value;
|
|
229
|
+
return manifest.schemaVersion === 1 && typeof manifest.url === 'string' && typeof manifest.crawledAt === 'string' && typeof manifest.hash === 'string' &&
|
|
230
|
+
Array.isArray(manifest.pages) && manifest.pages.every((page) => page && typeof page === 'object' && typeof page.file === 'string' && typeof page.url === 'string') &&
|
|
231
|
+
Boolean(manifest.totals) && typeof manifest.totals.pages === 'number' && Array.isArray(manifest.skipped) && Array.isArray(manifest.brokenLinks) && Array.isArray(manifest.warnings) && Array.isArray(manifest.discovery);
|
|
232
|
+
}
|
|
233
|
+
//# sourceMappingURL=docs-site.js.map
|
package/dist/doctor.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { createServer } from 'node:net';
|
|
2
2
|
import { agentAuthenticationStatus, chooseAgent, skillStatus, } from './agents.js';
|
|
3
3
|
import { DoxloopError } from './errors.js';
|
|
4
|
+
import { historyAvailable, historyRuntimeStatus } from './db.js';
|
|
4
5
|
import { diagnoseGenerator } from './generator-manager.js';
|
|
5
6
|
import { findProjectRoot, loadProject, resolveSeparateProjectLayout, validateProjectSourceBoundaries, } from './project.js';
|
|
6
7
|
import { validateProject } from './validation.js';
|
|
@@ -46,6 +47,13 @@ export async function runDoctor(options) {
|
|
|
46
47
|
await inspectExistingProject(options.cwd, checks);
|
|
47
48
|
}
|
|
48
49
|
await inspectAgent(options.agent, checks, preflight ? undefined : options.cwd);
|
|
50
|
+
const runtime = historyRuntimeStatus(process.versions.node);
|
|
51
|
+
const historyDisabled = process.env.DOXLOOP_NO_HISTORY === '1';
|
|
52
|
+
checks.push({
|
|
53
|
+
status: historyDisabled ? 'warning' : runtime.available && await historyAvailable() ? 'pass' : 'fail',
|
|
54
|
+
label: runtime.available ? 'Request history runtime is available' : 'Request history runtime',
|
|
55
|
+
detail: historyDisabled ? 'History is explicitly disabled by DOXLOOP_NO_HISTORY=1; requests and deployments will not appear in the UI history.' : runtime.detail,
|
|
56
|
+
});
|
|
49
57
|
checks.push(await previewPortCheck());
|
|
50
58
|
return {
|
|
51
59
|
ready: checks.every((check) => check.status !== 'fail'),
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { DoxloopProject } from './types.js';
|
|
2
|
+
export interface DocumentationCollection {
|
|
3
|
+
directory: string;
|
|
4
|
+
version: string;
|
|
5
|
+
locale: string;
|
|
6
|
+
native: boolean;
|
|
7
|
+
}
|
|
8
|
+
export declare function documentationCollections(root: string, project: Pick<DoxloopProject, 'generator' | 'contentDir'>): Promise<DocumentationCollection[]>;
|
|
9
|
+
export declare function collectionForPath(collections: DocumentationCollection[], path: string): DocumentationCollection | undefined;
|
|
10
|
+
/** Snapshot an existing version/locale; translation remains a reviewed editing task. */
|
|
11
|
+
export declare function createDocumentationCollection(root: string, input: {
|
|
12
|
+
sourceDirectory: string;
|
|
13
|
+
version: string;
|
|
14
|
+
locale: string;
|
|
15
|
+
}): Promise<{
|
|
16
|
+
directory: string;
|
|
17
|
+
pages: string[];
|
|
18
|
+
message: string;
|
|
19
|
+
validation: import("./types.js").ValidationResult;
|
|
20
|
+
requestId?: string;
|
|
21
|
+
editId: string;
|
|
22
|
+
}>;
|
|
23
|
+
//# sourceMappingURL=documentation-collections.d.ts.map
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
import { rewriteLinks } from './page-operations.js';
|
|
2
|
+
import { readFile, readdir, mkdir, writeFile } from 'node:fs/promises';
|
|
3
|
+
import { dirname, join, relative, resolve } from 'node:path';
|
|
4
|
+
import matter from 'gray-matter';
|
|
5
|
+
import { applyDirectEdit, safePath } from './direct-edit.js';
|
|
6
|
+
import { DoxloopError } from './errors.js';
|
|
7
|
+
import { listFiles, pathExists } from './fs.js';
|
|
8
|
+
import { readEvidenceMap, EVIDENCE_MAP_FILE } from './evidence.js';
|
|
9
|
+
import { loadProject, loadSiteConfig, siteConfigPath, pageId } from './project.js';
|
|
10
|
+
import { withProjectLock } from './project-lock.js';
|
|
11
|
+
import { parseDocument } from 'yaml';
|
|
12
|
+
const REGISTRY = '.doxloop/collections.json';
|
|
13
|
+
export async function documentationCollections(root, project) {
|
|
14
|
+
const collections = [{ directory: project.contentDir, version: 'current', locale: 'default', native: true }];
|
|
15
|
+
if (project.generator === 'docusaurus') {
|
|
16
|
+
for (const name of await directories(root, 'versioned_docs'))
|
|
17
|
+
if (/^version-[\w.-]+$/.test(name))
|
|
18
|
+
collections.push({ directory: `versioned_docs/${name}`, version: name.slice(8), locale: 'default', native: true });
|
|
19
|
+
for (const locale of await directories(root, 'i18n'))
|
|
20
|
+
if (/^[\w-]+$/.test(locale)) {
|
|
21
|
+
const base = `i18n/${locale}/docusaurus-plugin-content-docs`;
|
|
22
|
+
for (const version of await directories(root, base))
|
|
23
|
+
if (version === 'current' || /^version-[\w.-]+$/.test(version))
|
|
24
|
+
collections.push({ directory: `${base}/${version}`, version: version === 'current' ? version : version.slice(8), locale, native: true });
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
try {
|
|
28
|
+
const recorded = JSON.parse(await readFile(await safePath(root, REGISTRY), 'utf8'));
|
|
29
|
+
for (const item of recorded) {
|
|
30
|
+
await safePath(root, item.directory);
|
|
31
|
+
if (!collections.some((collection) => collection.directory === item.directory))
|
|
32
|
+
collections.push(item);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
catch (error) {
|
|
36
|
+
if (error.code !== 'ENOENT')
|
|
37
|
+
throw error;
|
|
38
|
+
}
|
|
39
|
+
return collections;
|
|
40
|
+
}
|
|
41
|
+
async function directories(root, path) {
|
|
42
|
+
try {
|
|
43
|
+
return (await readdir(await safePath(root, path), { withFileTypes: true })).filter((item) => item.isDirectory() && !item.isSymbolicLink()).map((item) => item.name);
|
|
44
|
+
}
|
|
45
|
+
catch (error) {
|
|
46
|
+
if (error.code !== 'ENOENT')
|
|
47
|
+
throw error;
|
|
48
|
+
return [];
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
export function collectionForPath(collections, path) {
|
|
52
|
+
return [...collections].sort((a, b) => b.directory.length - a.directory.length).find((item) => !item.directory || path.startsWith(`${item.directory}/`));
|
|
53
|
+
}
|
|
54
|
+
/** Snapshot an existing version/locale; translation remains a reviewed editing task. */
|
|
55
|
+
export async function createDocumentationCollection(root, input) {
|
|
56
|
+
return withProjectLock(root, 'write', async () => {
|
|
57
|
+
const project = await loadProject(root);
|
|
58
|
+
if (!['doxbrix', 'mkdocs', 'docusaurus'].includes(project.generator))
|
|
59
|
+
throw new DoxloopError('Version and locale collections currently support Doxbrix, MkDocs, and Docusaurus.');
|
|
60
|
+
for (const [label, value] of Object.entries({ version: input.version, locale: input.locale }))
|
|
61
|
+
if (!/^[a-zA-Z0-9][a-zA-Z0-9._-]{0,63}$/.test(value) || ['.', '..'].includes(value))
|
|
62
|
+
throw new DoxloopError(`Use a simple ${label} name with letters, numbers, dots, or hyphens.`);
|
|
63
|
+
const collections = await documentationCollections(root, project);
|
|
64
|
+
const source = collections.find((item) => item.directory === input.sourceDirectory);
|
|
65
|
+
if (!source)
|
|
66
|
+
throw new DoxloopError('Choose an existing source collection.');
|
|
67
|
+
const native = project.generator === 'docusaurus';
|
|
68
|
+
const destination = native ? input.locale === 'default' ? `versioned_docs/version-${input.version}` : `i18n/${input.locale}/docusaurus-plugin-content-docs/${input.version === 'current' ? 'current' : `version-${input.version}`}` : [project.contentDir, 'editions', input.version, input.locale].filter(Boolean).join('/');
|
|
69
|
+
if (collections.some((item) => item.version === input.version && item.locale === input.locale) || (await pathExists(await safePath(root, destination)) && await hasFiles(await safePath(root, destination))))
|
|
70
|
+
throw new DoxloopError('That version and locale already exists.');
|
|
71
|
+
const changes = new Map();
|
|
72
|
+
const sourceRoot = resolve(root, source.directory);
|
|
73
|
+
const files = await listFiles(sourceRoot, new Set(['.md', '.mdx', '.png', '.jpg', '.jpeg', '.svg', '.gif', '.webp', '.avif', '.pdf', '.json']), { ignoredDirectories: new Set(['.doxloop', 'node_modules', '.git', 'build', 'site', 'dist', 'editions']) });
|
|
74
|
+
const pages = [];
|
|
75
|
+
for (const absolute of files) {
|
|
76
|
+
const from = relative(root, absolute).replace(/\\/g, '/');
|
|
77
|
+
if (collectionForPath(collections, from)?.directory !== source.directory)
|
|
78
|
+
continue;
|
|
79
|
+
const local = relative(sourceRoot, absolute).replace(/\\/g, '/');
|
|
80
|
+
if (local.split('/').some((part) => part.startsWith('.') || ['node_modules', 'build', 'site', 'dist', 'editions'].includes(part)))
|
|
81
|
+
continue;
|
|
82
|
+
if (!/\.(mdx?|png|jpe?g|svg|gif|webp|avif|pdf|json)$/i.test(local))
|
|
83
|
+
continue;
|
|
84
|
+
const to = `${destination}/${local}`;
|
|
85
|
+
const data = await readFile(await safePath(root, from));
|
|
86
|
+
changes.set(to, data);
|
|
87
|
+
if (/\.mdx?$/.test(local)) {
|
|
88
|
+
const rewritten = rewriteLinks(data.toString(), (href) => {
|
|
89
|
+
if (/^(?:[a-z][a-z0-9+.-]*:|#|\/)/i.test(href))
|
|
90
|
+
return href;
|
|
91
|
+
const [target, suffix = ''] = href.split(/(?=[?#])/s, 2);
|
|
92
|
+
const resolved = resolve(dirname(join(root, from)), target);
|
|
93
|
+
if (resolved.startsWith(`${sourceRoot}/`))
|
|
94
|
+
return href;
|
|
95
|
+
return relative(dirname(join(root, to)), resolved).replace(/\\/g, '/') + suffix;
|
|
96
|
+
});
|
|
97
|
+
changes.set(to, rewritten);
|
|
98
|
+
const parsed = matter(rewritten);
|
|
99
|
+
if (!native) {
|
|
100
|
+
// The collection's directory defines the route; copied custom slugs must not collide.
|
|
101
|
+
delete parsed.data.slug;
|
|
102
|
+
parsed.data.locale = input.locale;
|
|
103
|
+
parsed.data.version = input.version;
|
|
104
|
+
changes.set(to, matter.stringify(parsed.content, parsed.data));
|
|
105
|
+
}
|
|
106
|
+
pages.push({ from, to, title: String(parsed.data.title ?? local) });
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
if (!pages.length)
|
|
110
|
+
throw new DoxloopError('The selected collection contains no Markdown pages.');
|
|
111
|
+
if (native) {
|
|
112
|
+
if (input.locale === 'default') {
|
|
113
|
+
if (input.version === 'current')
|
|
114
|
+
throw new DoxloopError('The current version already exists.');
|
|
115
|
+
const versions = await jsonOr(root, 'versions.json', []);
|
|
116
|
+
let sidebarName = 'docs';
|
|
117
|
+
for (const sidebarPath of ['sidebars.js', 'sidebars.ts', 'sidebars.json']) {
|
|
118
|
+
if (!(await pathExists(join(root, sidebarPath))))
|
|
119
|
+
continue;
|
|
120
|
+
const sidebar = await readFile(await safePath(root, sidebarPath), 'utf8');
|
|
121
|
+
if (!/autogenerated/.test(sidebar))
|
|
122
|
+
throw new DoxloopError('Custom Docusaurus sidebars require a reviewed native versioning proposal. No files were written.');
|
|
123
|
+
sidebarName = /["']?([a-zA-Z][\w-]*)["']?\s*:\s*\[/.exec(sidebar)?.[1] ?? 'docs';
|
|
124
|
+
}
|
|
125
|
+
changes.set('versions.json', JSON.stringify([input.version, ...versions], null, 2) + '\n');
|
|
126
|
+
// Autogenerated sidebars work without executing arbitrary project configuration.
|
|
127
|
+
changes.set(`versioned_sidebars/version-${input.version}-sidebars.json`, JSON.stringify({ [sidebarName]: [{ type: 'autogenerated', dirName: '.' }] }, null, 2) + '\n');
|
|
128
|
+
}
|
|
129
|
+
else {
|
|
130
|
+
const configPath = (await Promise.all(['docusaurus.config.js', 'docusaurus.config.ts', 'docusaurus.config.mjs', 'docusaurus.config.cjs'].map(async (path) => await pathExists(join(root, path)) ? path : undefined))).find(Boolean);
|
|
131
|
+
if (!configPath)
|
|
132
|
+
throw new DoxloopError('Docusaurus configuration was not found.');
|
|
133
|
+
const text = await readFile(await safePath(root, configPath), 'utf8');
|
|
134
|
+
const locales = /\blocales\s*:\s*\[([^\]]*)\]/.exec(text);
|
|
135
|
+
if (!locales || !/^[\s,'"\w-]*$/.test(locales[1]))
|
|
136
|
+
throw new DoxloopError('Declare a literal i18n.locales array in Docusaurus configuration before creating a locale.');
|
|
137
|
+
if (!new RegExp(`['"]${input.locale}['"]`).test(locales[1]))
|
|
138
|
+
changes.set(configPath, text.replace(locales[0], `locales: [${locales[1].trim().replace(/,$/, '')}, '${input.locale}']`));
|
|
139
|
+
if (input.version !== 'current' && !(await jsonOr(root, 'versions.json', [])).includes(input.version))
|
|
140
|
+
throw new DoxloopError('Create the source version before translating it.');
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
else if (project.generator === 'doxbrix') {
|
|
144
|
+
const site = await loadSiteConfig(root, project);
|
|
145
|
+
const nav = pages.map((page) => ({ type: 'page', file: pageId(join(root, project.contentDir), join(root, page.to)), label: page.title }));
|
|
146
|
+
site.spaces.push({ name: `${input.version} · ${input.locale}`, nav });
|
|
147
|
+
changes.set(relative(root, await siteConfigPath(root, project)), JSON.stringify(site, null, 2) + '\n');
|
|
148
|
+
}
|
|
149
|
+
else {
|
|
150
|
+
const configPath = await pathExists(join(root, 'mkdocs.yml')) ? 'mkdocs.yml' : 'mkdocs.yaml';
|
|
151
|
+
const document = parseDocument(await readFile(await safePath(root, configPath), 'utf8'));
|
|
152
|
+
const nav = document.toJS().nav;
|
|
153
|
+
if (!Array.isArray(nav))
|
|
154
|
+
throw new DoxloopError('Define an explicit MkDocs nav list before creating a collection.');
|
|
155
|
+
document.set('nav', [...nav, { [`${input.version} · ${input.locale}`]: pages.map((page) => ({ [page.title]: relative(join(root, project.contentDir), join(root, page.to)).replace(/\\/g, '/') })) }]);
|
|
156
|
+
changes.set(configPath, document.toString());
|
|
157
|
+
}
|
|
158
|
+
const map = await readEvidenceMap(root) ?? { schemaVersion: 1, pages: {} };
|
|
159
|
+
for (const page of pages)
|
|
160
|
+
map.pages[page.to] = { sources: map.pages[page.from]?.sources ?? [], confidence: 'needs-human' };
|
|
161
|
+
changes.set(EVIDENCE_MAP_FILE, JSON.stringify(map, null, 2) + '\n');
|
|
162
|
+
const recorded = await jsonOr(root, REGISTRY, []);
|
|
163
|
+
changes.set(REGISTRY, JSON.stringify([...recorded, { directory: destination, version: input.version, locale: input.locale, native }], null, 2) + '\n');
|
|
164
|
+
const result = await applyDirectEdit(root, { kind: 'metadata', requestText: `Created ${input.version} / ${input.locale} from ${source.version} / ${source.locale}; copied text requires review`, files: [...changes.keys()], pagesChanged: pages.length, apply: async () => {
|
|
165
|
+
for (const [file, text] of changes) {
|
|
166
|
+
const absolute = await safePath(root, file);
|
|
167
|
+
await mkdir(dirname(absolute), { recursive: true });
|
|
168
|
+
await writeFile(absolute, text);
|
|
169
|
+
}
|
|
170
|
+
} });
|
|
171
|
+
return { ...result, directory: destination, pages: pages.map((page) => page.to), message: 'Collection created. Review or translate its copied text before publication; verification was not inherited.' };
|
|
172
|
+
});
|
|
173
|
+
}
|
|
174
|
+
async function jsonOr(root, path, fallback) {
|
|
175
|
+
try {
|
|
176
|
+
return JSON.parse(await readFile(await safePath(root, path), 'utf8'));
|
|
177
|
+
}
|
|
178
|
+
catch (error) {
|
|
179
|
+
if (error.code !== 'ENOENT')
|
|
180
|
+
throw error;
|
|
181
|
+
return fallback;
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
async function hasFiles(directory) {
|
|
185
|
+
for (const entry of await readdir(directory, { withFileTypes: true })) {
|
|
186
|
+
if (!entry.isDirectory() || entry.isSymbolicLink() || await hasFiles(join(directory, entry.name)))
|
|
187
|
+
return true;
|
|
188
|
+
}
|
|
189
|
+
return false;
|
|
190
|
+
}
|
|
191
|
+
//# sourceMappingURL=documentation-collections.js.map
|