@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
package/dist/settings.d.ts
CHANGED
|
@@ -2,5 +2,5 @@ import { type PromptIo } from './prompts.js';
|
|
|
2
2
|
import type { DeploymentConfig, DoxloopProject } from './types.js';
|
|
3
3
|
export declare function runSettingsWizard(root: string, cwd: string, io?: PromptIo): Promise<void>;
|
|
4
4
|
export declare function formatProjectSettings(root: string, project: DoxloopProject): string;
|
|
5
|
-
export declare function effectiveDeployment(project: DoxloopProject, fallbackApiUrl?: string): Required<DeploymentConfig>;
|
|
5
|
+
export declare function effectiveDeployment(project: DoxloopProject, fallbackApiUrl?: string): Required<Pick<DeploymentConfig, 'target' | 'name' | 'slug' | 'visibility' | 'apiUrl'>> & Omit<DeploymentConfig, 'target' | 'name' | 'slug' | 'visibility' | 'apiUrl'>;
|
|
6
6
|
//# sourceMappingURL=settings.d.ts.map
|
package/dist/settings.js
CHANGED
|
@@ -2,8 +2,10 @@ import { relative, resolve } from 'node:path';
|
|
|
2
2
|
import { detectAgents } from './agents.js';
|
|
3
3
|
import { apiUrl } from './auth.js';
|
|
4
4
|
import { generatorCatalogEntry } from './generators.js';
|
|
5
|
-
import { isSpecUrl, loadProject, parseDesignReference, parseSpec, saveProjectSettings, validateProjectSourceBoundaries, } from './project.js';
|
|
6
|
-
import { heading, note, promptConfirm, promptSelect, promptText, } from './prompts.js';
|
|
5
|
+
import { isSpecUrl, loadProject, parseDesignReference, parseSpec, projectDefaultModel, saveProjectSettings, validateProjectSourceBoundaries, } from './project.js';
|
|
6
|
+
import { heading, note, promptConfirm, promptSecret, promptSelect, promptText, } from './prompts.js';
|
|
7
|
+
import { captureAuthStatus, removeCaptureCredentials, removeCaptureSession, saveCaptureCredentials, saveCaptureSession, } from './capture-auth.js';
|
|
8
|
+
import { startCaptureSignIn } from './screen-capture-provider.js';
|
|
7
9
|
export async function runSettingsWizard(root, cwd, io = { input: process.stdin, output: process.stdout }) {
|
|
8
10
|
heading(io, 'Doxloop — project settings');
|
|
9
11
|
while (true) {
|
|
@@ -60,17 +62,23 @@ export function formatProjectSettings(root, project) {
|
|
|
60
62
|
const evidence = project.sources.length === 0
|
|
61
63
|
? 'None configured'
|
|
62
64
|
: project.sources
|
|
63
|
-
.map((source) => `${source.name}: ${(source.kind ?? 'directory') === 'openapi' ? 'OpenAPI ' : ''}${source.path}`)
|
|
65
|
+
.map((source) => `${source.name}: ${(source.kind ?? 'directory') === 'openapi' ? 'OpenAPI ' : (source.kind ?? 'directory') === 'docs-site' ? 'Docs site ' : ''}${source.site?.url ?? source.path}`)
|
|
64
66
|
.join('\n ');
|
|
65
67
|
const deployment = effectiveDeployment(project);
|
|
66
|
-
return `Project\n Root: ${root}\n Title: ${project.title}\n Generator: ${generator}\n Agent: ${project.defaultAgent ?? 'Choose at authoring time'}\n\nEvidence\n ${evidence}\n\nDocumentation\n Audience: ${project.documentation.primaryAudience ?? 'Agent will determine'}\n Locale: ${project.documentation.locale}\n Tone: ${project.documentation.tone.join(', ')}\n\nApplication screenshots\n ${project.application ? `${project.application.screenshots?.policy ?? 'requested'} at ${project.application.baseUrl}` : 'Not configured'}\n\nDeployment\n Name: ${deployment.name}\n Slug: ${deployment.slug}\n Visibility: ${deployment.visibility}\n Destination: ${deployment.apiUrl}`;
|
|
68
|
+
return `Project\n Root: ${root}\n Title: ${project.title}\n Generator: ${generator}\n Agent: ${project.defaultAgent ?? 'Choose at authoring time'}\n Model: ${project.defaultModel ?? 'Agent default'}\n\nEvidence\n ${evidence}\n\nDocumentation\n Audience: ${project.documentation.primaryAudience ?? 'Agent will determine'}\n Locale: ${project.documentation.locale}\n Tone: ${project.documentation.tone.join(', ')}\n\nApplication screenshots\n ${project.application ? `${project.application.screenshots?.policy ?? 'requested'} at ${project.application.baseUrl}` : 'Not configured'}\n\nDeployment\n Target: ${deployment.target}\n Name: ${deployment.name}\n Slug: ${deployment.slug}\n Visibility: ${deployment.visibility}\n Destination: ${deployment.apiUrl}`;
|
|
67
69
|
}
|
|
68
70
|
export function effectiveDeployment(project, fallbackApiUrl) {
|
|
69
71
|
return {
|
|
72
|
+
target: project.deployment?.target ?? 'doxbrix',
|
|
70
73
|
name: project.deployment?.name?.trim() || project.title,
|
|
71
74
|
slug: project.deployment?.slug?.trim() || slugify(project.title),
|
|
72
75
|
visibility: project.deployment?.visibility ?? 'private',
|
|
73
76
|
apiUrl: apiUrl(project.deployment?.apiUrl ?? fallbackApiUrl),
|
|
77
|
+
...(project.deployment?.siteId ? { siteId: project.deployment.siteId } : {}),
|
|
78
|
+
...(project.deployment?.projectId ? { projectId: project.deployment.projectId } : {}),
|
|
79
|
+
...(project.deployment?.teamId ? { teamId: project.deployment.teamId } : {}),
|
|
80
|
+
...(project.deployment?.branch ? { branch: project.deployment.branch } : {}),
|
|
81
|
+
...(project.deployment?.basePath ? { basePath: project.deployment.basePath } : {}),
|
|
74
82
|
};
|
|
75
83
|
}
|
|
76
84
|
async function editTitle(root, project, io) {
|
|
@@ -104,7 +112,7 @@ async function editEvidence(root, cwd, project, io) {
|
|
|
104
112
|
note(io, project.sources.length === 0
|
|
105
113
|
? 'No product evidence is configured.'
|
|
106
114
|
: project.sources
|
|
107
|
-
.map((source) => `${source.name}: ${(source.kind ?? 'directory') === 'openapi' ? 'OpenAPI — ' : ''}${source.path}`)
|
|
115
|
+
.map((source) => `${source.name}: ${(source.kind ?? 'directory') === 'openapi' ? 'OpenAPI — ' : (source.kind ?? 'directory') === 'docs-site' ? 'Docs site — ' : ''}${source.site?.url ?? source.path}`)
|
|
108
116
|
.join('\n'));
|
|
109
117
|
return;
|
|
110
118
|
}
|
|
@@ -210,8 +218,15 @@ async function editAgent(root, io) {
|
|
|
210
218
|
})),
|
|
211
219
|
io,
|
|
212
220
|
});
|
|
213
|
-
await
|
|
214
|
-
|
|
221
|
+
const project = await loadProject(root);
|
|
222
|
+
const model = (await promptText({
|
|
223
|
+
message: `Default ${labels[agent]} model (leave blank for the agent default)`,
|
|
224
|
+
initial: projectDefaultModel(project, agent) ?? '',
|
|
225
|
+
allowEmpty: true,
|
|
226
|
+
io,
|
|
227
|
+
})).trim();
|
|
228
|
+
await saveProjectSettings(root, { defaultAgent: agent, defaultModel: model || undefined });
|
|
229
|
+
note(io, `✓ Default agent changed to ${labels[agent]}${model ? ` using ${model}` : ''}.`);
|
|
215
230
|
}
|
|
216
231
|
async function editDocumentation(root, documentation, io) {
|
|
217
232
|
const field = await promptSelect({
|
|
@@ -328,6 +343,12 @@ async function editScreenshots(root, project, io) {
|
|
|
328
343
|
label: 'Screenshot policy',
|
|
329
344
|
...(!project.application ? { disabled: 'configure an application first' } : {}),
|
|
330
345
|
},
|
|
346
|
+
{
|
|
347
|
+
value: 'signin',
|
|
348
|
+
label: 'Application sign-in',
|
|
349
|
+
hint: 'browser session or test-account credentials for login pages',
|
|
350
|
+
...(!project.application ? { disabled: 'configure an application first' } : {}),
|
|
351
|
+
},
|
|
331
352
|
{
|
|
332
353
|
value: 'remove',
|
|
333
354
|
label: 'Remove application configuration',
|
|
@@ -339,6 +360,10 @@ async function editScreenshots(root, project, io) {
|
|
|
339
360
|
});
|
|
340
361
|
if (action === 'back')
|
|
341
362
|
return;
|
|
363
|
+
if (action === 'signin') {
|
|
364
|
+
await editCaptureSignIn(root, project, io);
|
|
365
|
+
return;
|
|
366
|
+
}
|
|
342
367
|
if (action === 'remove') {
|
|
343
368
|
await saveProjectSettings(root, { application: undefined });
|
|
344
369
|
note(io, '✓ Application screenshot configuration removed.');
|
|
@@ -382,6 +407,69 @@ async function editScreenshots(root, project, io) {
|
|
|
382
407
|
});
|
|
383
408
|
note(io, `✓ Screenshot policy changed to ${policy}.`);
|
|
384
409
|
}
|
|
410
|
+
/**
|
|
411
|
+
* Sign-in for the capture browser. Both options are stored in the user's
|
|
412
|
+
* Doxloop config home against this project, never in `.doxloop/project.json`.
|
|
413
|
+
*/
|
|
414
|
+
async function editCaptureSignIn(root, project, io) {
|
|
415
|
+
const application = project.application;
|
|
416
|
+
const status = await captureAuthStatus(root);
|
|
417
|
+
note(io, `Browser session: ${status.session ? `saved ${status.session.savedAt} for ${status.session.origin} (${status.session.cookies} cookies)` : 'none'}`);
|
|
418
|
+
note(io, `Credentials: ${status.credentials ? `saved for ${status.credentials.username}` : 'none'}`);
|
|
419
|
+
const action = await promptSelect({
|
|
420
|
+
message: 'Application sign-in',
|
|
421
|
+
choices: [
|
|
422
|
+
{ value: 'browser', label: 'Sign in with browser', hint: 'opens Chrome; sign in by hand, including MFA or SSO' },
|
|
423
|
+
{ value: 'credentials', label: 'Save test-account credentials', hint: 'typed by the capture server, never shown to the agent' },
|
|
424
|
+
{ value: 'login-path', label: 'Set the sign-in route', hint: application.authentication?.loginPath ?? 'not set' },
|
|
425
|
+
{ value: 'forget-session', label: 'Forget the browser session', ...(!status.session ? { disabled: 'none saved' } : {}) },
|
|
426
|
+
{ value: 'forget-credentials', label: 'Remove the credentials', ...(!status.credentials ? { disabled: 'none saved' } : {}) },
|
|
427
|
+
{ value: 'back', label: 'Back' },
|
|
428
|
+
],
|
|
429
|
+
io,
|
|
430
|
+
});
|
|
431
|
+
if (action === 'back')
|
|
432
|
+
return;
|
|
433
|
+
if (action === 'browser') {
|
|
434
|
+
const session = await startCaptureSignIn(application);
|
|
435
|
+
note(io, `Chrome opened at ${session.url}. Complete the sign-in there and wait for the signed-in screen.`);
|
|
436
|
+
const save = await promptConfirm({ message: 'Save the signed-in session now?', initial: true, io });
|
|
437
|
+
if (!save) {
|
|
438
|
+
await session.cancel();
|
|
439
|
+
note(io, 'Sign-in discarded.');
|
|
440
|
+
return;
|
|
441
|
+
}
|
|
442
|
+
const state = await session.finish();
|
|
443
|
+
const stored = await saveCaptureSession(root, new URL(application.baseUrl).origin, state);
|
|
444
|
+
note(io, `✓ Browser session saved with ${stored.state.cookies.length} cookies. Capture runs start signed in.`);
|
|
445
|
+
return;
|
|
446
|
+
}
|
|
447
|
+
if (action === 'credentials') {
|
|
448
|
+
const username = await promptText({ message: 'Test account username or email', ...(status.credentials ? { initial: status.credentials.username } : {}), io });
|
|
449
|
+
const password = await promptSecret({ message: 'Test account password', io });
|
|
450
|
+
await saveCaptureCredentials(root, { username, password });
|
|
451
|
+
note(io, `✓ Credentials saved for ${username}. The agent fills the sign-in form by secret name during capture.`);
|
|
452
|
+
return;
|
|
453
|
+
}
|
|
454
|
+
if (action === 'login-path') {
|
|
455
|
+
const loginPath = await promptText({
|
|
456
|
+
message: 'Sign-in route (relative to the application URL)',
|
|
457
|
+
initial: application.authentication?.loginPath ?? '/login',
|
|
458
|
+
validate: (value) => value.startsWith('/') && !value.startsWith('//') ? undefined : 'Start with one slash.',
|
|
459
|
+
io,
|
|
460
|
+
});
|
|
461
|
+
await saveProjectSettings(root, { application: { ...application, authentication: { ...application.authentication, loginPath } } });
|
|
462
|
+
note(io, `✓ Sign-in route set to ${loginPath}.`);
|
|
463
|
+
return;
|
|
464
|
+
}
|
|
465
|
+
if (action === 'forget-session') {
|
|
466
|
+
await removeCaptureSession(root);
|
|
467
|
+
note(io, '✓ Browser session removed.');
|
|
468
|
+
return;
|
|
469
|
+
}
|
|
470
|
+
await removeCaptureCredentials(root);
|
|
471
|
+
note(io, '✓ Credentials removed.');
|
|
472
|
+
}
|
|
385
473
|
async function editDeployment(root, project, io) {
|
|
386
474
|
const effective = effectiveDeployment(project);
|
|
387
475
|
const field = await promptSelect({
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export interface ExportSiteOptions {
|
|
2
|
+
root: string;
|
|
3
|
+
out: string;
|
|
4
|
+
zip?: boolean;
|
|
5
|
+
basePath?: string;
|
|
6
|
+
siteUrl?: string;
|
|
7
|
+
}
|
|
8
|
+
export interface ExportSiteResult {
|
|
9
|
+
outputDir: string;
|
|
10
|
+
zipPath?: string;
|
|
11
|
+
files: number;
|
|
12
|
+
bytes: number;
|
|
13
|
+
sha256: string;
|
|
14
|
+
generator: string;
|
|
15
|
+
}
|
|
16
|
+
/** Build and copy a generator's deployable static output to a user-selected folder. */
|
|
17
|
+
export declare function exportStaticSite(options: ExportSiteOptions): Promise<ExportSiteResult>;
|
|
18
|
+
//# sourceMappingURL=site-export.d.ts.map
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { cp, mkdir, mkdtemp, readdir, rm, writeFile } from 'node:fs/promises';
|
|
2
|
+
import { tmpdir } from 'node:os';
|
|
3
|
+
import { dirname, relative, resolve, sep } from 'node:path';
|
|
4
|
+
import { containedOutput, packageStaticOutput, runLocalBuild, } from './artifact-deploy.js';
|
|
5
|
+
import { buildDoxbrixStaticSite } from './doxbrix-build.js';
|
|
6
|
+
import { DoxloopError } from './errors.js';
|
|
7
|
+
import { pathExists } from './fs.js';
|
|
8
|
+
import { loadGeneratorAdapter } from './generators.js';
|
|
9
|
+
import { loadProject } from './project.js';
|
|
10
|
+
import { validateProject } from './validation.js';
|
|
11
|
+
/** Build and copy a generator's deployable static output to a user-selected folder. */
|
|
12
|
+
export async function exportStaticSite(options) {
|
|
13
|
+
const root = resolve(options.root);
|
|
14
|
+
const outputDir = resolve(options.out);
|
|
15
|
+
if (outputDir === root)
|
|
16
|
+
throw new DoxloopError('Export output cannot replace the project directory.');
|
|
17
|
+
await assertEmptyDestination(outputDir);
|
|
18
|
+
const validation = await validateProject(root);
|
|
19
|
+
if (validation.errors > 0) {
|
|
20
|
+
throw new DoxloopError(`Export stopped because documentation has ${validation.errors} validation error${validation.errors === 1 ? '' : 's'}. Run \`doxloop test\`.`);
|
|
21
|
+
}
|
|
22
|
+
const project = await loadProject(root);
|
|
23
|
+
const temporary = await mkdtemp(`${tmpdir()}/doxloop-export-`);
|
|
24
|
+
try {
|
|
25
|
+
let builtOutput;
|
|
26
|
+
if (project.generator === 'doxbrix') {
|
|
27
|
+
builtOutput = resolve(temporary, 'site');
|
|
28
|
+
await buildDoxbrixStaticSite({
|
|
29
|
+
root,
|
|
30
|
+
outDir: builtOutput,
|
|
31
|
+
...(options.basePath ? { basePath: options.basePath } : {}),
|
|
32
|
+
...(options.siteUrl ? { siteUrl: options.siteUrl } : {}),
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
else {
|
|
36
|
+
const adapter = await loadGeneratorAdapter(root, project);
|
|
37
|
+
const siteUrl = options.siteUrl ?? process.env.DOXLOOP_SITE_URL ?? 'https://example.com';
|
|
38
|
+
await runLocalBuild(adapter.id, adapter.build.command, root, siteUrl);
|
|
39
|
+
builtOutput = containedOutput(root, adapter.build.outputDir);
|
|
40
|
+
}
|
|
41
|
+
if (overlaps(outputDir, builtOutput)) {
|
|
42
|
+
throw new DoxloopError('Export output must be different from the generator build directory.');
|
|
43
|
+
}
|
|
44
|
+
const packaged = await packageStaticOutput(builtOutput);
|
|
45
|
+
await mkdir(dirname(outputDir), { recursive: true });
|
|
46
|
+
await cp(builtOutput, outputDir, { recursive: true, errorOnExist: true, force: false });
|
|
47
|
+
const zipPath = options.zip ? `${outputDir}.zip` : undefined;
|
|
48
|
+
if (zipPath) {
|
|
49
|
+
if (await pathExists(zipPath))
|
|
50
|
+
throw new DoxloopError(`Export archive already exists: ${zipPath}`);
|
|
51
|
+
await writeFile(zipPath, packaged.archive, { mode: 0o600 });
|
|
52
|
+
}
|
|
53
|
+
return {
|
|
54
|
+
outputDir,
|
|
55
|
+
...(zipPath ? { zipPath } : {}),
|
|
56
|
+
files: packaged.files,
|
|
57
|
+
bytes: packaged.archive.byteLength,
|
|
58
|
+
sha256: packaged.sha256,
|
|
59
|
+
generator: project.generator,
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
finally {
|
|
63
|
+
await rm(temporary, { recursive: true, force: true });
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
function overlaps(left, right) {
|
|
67
|
+
const relation = relative(left, right);
|
|
68
|
+
const reverse = relative(right, left);
|
|
69
|
+
const within = (value) => value === '' || (value !== '..' && !value.startsWith(`..${sep}`));
|
|
70
|
+
return within(relation) || within(reverse);
|
|
71
|
+
}
|
|
72
|
+
async function assertEmptyDestination(path) {
|
|
73
|
+
if (!(await pathExists(path)))
|
|
74
|
+
return;
|
|
75
|
+
let entries;
|
|
76
|
+
try {
|
|
77
|
+
entries = await readdir(path);
|
|
78
|
+
}
|
|
79
|
+
catch {
|
|
80
|
+
throw new DoxloopError(`Export output must be a directory: ${path}`);
|
|
81
|
+
}
|
|
82
|
+
if (entries.length > 0)
|
|
83
|
+
throw new DoxloopError(`Export output directory is not empty: ${path}`);
|
|
84
|
+
// cp with errorOnExist requires the destination not to exist.
|
|
85
|
+
await rm(path, { recursive: true });
|
|
86
|
+
}
|
|
87
|
+
//# sourceMappingURL=site-export.js.map
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import type { OpenApiSnapshot, SourceBinding, SourceHealth } from './types.js';
|
|
2
|
+
export declare const SOURCE_CONNECTOR_API_VERSION: 1;
|
|
3
|
+
export interface SourceConnectorSnapshot {
|
|
4
|
+
revision: string;
|
|
5
|
+
capturedAt: string;
|
|
6
|
+
validators?: {
|
|
7
|
+
etag?: string;
|
|
8
|
+
lastModified?: string;
|
|
9
|
+
};
|
|
10
|
+
openapi?: OpenApiSnapshot;
|
|
11
|
+
}
|
|
12
|
+
export interface SourceConnectorInventory {
|
|
13
|
+
identifiers: string[];
|
|
14
|
+
metadata: Record<string, string | number | string[]>;
|
|
15
|
+
}
|
|
16
|
+
/** Versioned provider boundary used by source validation, inventory, drift, and health. */
|
|
17
|
+
export interface SourceConnector {
|
|
18
|
+
readonly id: string;
|
|
19
|
+
readonly version: typeof SOURCE_CONNECTOR_API_VERSION;
|
|
20
|
+
validate(root: string, source: SourceBinding): Promise<void>;
|
|
21
|
+
inventory(root: string, source: SourceBinding): Promise<SourceConnectorInventory>;
|
|
22
|
+
snapshot(root: string, source: SourceBinding): Promise<SourceConnectorSnapshot>;
|
|
23
|
+
detectChanges(previous: SourceConnectorSnapshot | undefined, current: SourceConnectorSnapshot): Promise<{
|
|
24
|
+
changed: boolean;
|
|
25
|
+
identifiers: string[];
|
|
26
|
+
}>;
|
|
27
|
+
evidenceIdentifiers(snapshot: SourceConnectorSnapshot): string[];
|
|
28
|
+
redact(source: SourceBinding): SourceBinding;
|
|
29
|
+
health(root: string, source: SourceBinding): Promise<SourceHealth>;
|
|
30
|
+
}
|
|
31
|
+
export declare function connectorForSource(source: SourceBinding): SourceConnector;
|
|
32
|
+
export declare function sourceHealth(root: string, sources: SourceBinding[]): Promise<SourceHealth[]>;
|
|
33
|
+
//# sourceMappingURL=source-connectors.d.ts.map
|
|
@@ -0,0 +1,268 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
2
|
+
import { lstat, readdir } from 'node:fs/promises';
|
|
3
|
+
import { basename, resolve } from 'node:path';
|
|
4
|
+
import { describeDocsSite, readDocsSiteManifest } from './docs-site.js';
|
|
5
|
+
import { DoxloopError } from './errors.js';
|
|
6
|
+
import { loadOpenApiSource } from './openapi.js';
|
|
7
|
+
import { isSpecUrl, sourceKind } from './project.js';
|
|
8
|
+
import { remoteHead } from './remote-source.js';
|
|
9
|
+
import { readSyncState } from './sync.js';
|
|
10
|
+
export const SOURCE_CONNECTOR_API_VERSION = 1;
|
|
11
|
+
export function connectorForSource(source) {
|
|
12
|
+
const kind = sourceKind(source);
|
|
13
|
+
return kind === 'openapi' ? openApiConnector : kind === 'docs-site' ? docsSiteConnector : directoryConnector;
|
|
14
|
+
}
|
|
15
|
+
export async function sourceHealth(root, sources) {
|
|
16
|
+
return Promise.all(sources.map(async (source) => {
|
|
17
|
+
try {
|
|
18
|
+
return await connectorForSource(source).health(root, source);
|
|
19
|
+
}
|
|
20
|
+
catch (error) {
|
|
21
|
+
const connector = connectorForSource(source);
|
|
22
|
+
const redacted = connector.redact(source);
|
|
23
|
+
return {
|
|
24
|
+
name: source.name,
|
|
25
|
+
connector: connector.id,
|
|
26
|
+
status: 'error',
|
|
27
|
+
checkedAt: new Date().toISOString(),
|
|
28
|
+
location: redacted.remote?.repository ?? redacted.path,
|
|
29
|
+
provider: source.remote?.provider ?? connector.id,
|
|
30
|
+
...(source.remote?.branch ? { branch: source.remote.branch } : {}),
|
|
31
|
+
...(source.remote?.subdirectory ? { subdirectory: source.remote.subdirectory } : {}),
|
|
32
|
+
monitored: Boolean(source.remote),
|
|
33
|
+
summary: safeHealthError(error, source, root),
|
|
34
|
+
details: ['Check that the location is reachable and that read-only credentials are available in the configured environment.'],
|
|
35
|
+
...(source.scope ? { scope: source.scope } : {}),
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
}));
|
|
39
|
+
}
|
|
40
|
+
const openApiConnector = {
|
|
41
|
+
id: 'openapi',
|
|
42
|
+
version: SOURCE_CONNECTOR_API_VERSION,
|
|
43
|
+
async validate(root, source) { await loadOpenApiSource(root, source); },
|
|
44
|
+
async inventory(root, source) {
|
|
45
|
+
const loaded = await loadOpenApiSource(root, source);
|
|
46
|
+
return {
|
|
47
|
+
identifiers: [
|
|
48
|
+
...Object.keys(loaded.snapshot.operations),
|
|
49
|
+
...Object.keys(loaded.snapshot.schemas).map((item) => `schema:${item}`),
|
|
50
|
+
],
|
|
51
|
+
metadata: {
|
|
52
|
+
title: loaded.summary.title,
|
|
53
|
+
version: loaded.summary.version,
|
|
54
|
+
operationCount: loaded.summary.operationCount,
|
|
55
|
+
servers: loaded.summary.servers,
|
|
56
|
+
securitySchemes: loaded.summary.securitySchemes,
|
|
57
|
+
schemas: loaded.summary.schemas,
|
|
58
|
+
},
|
|
59
|
+
};
|
|
60
|
+
},
|
|
61
|
+
async snapshot(root, source) {
|
|
62
|
+
const loaded = await loadOpenApiSource(root, source);
|
|
63
|
+
return {
|
|
64
|
+
revision: loaded.hash,
|
|
65
|
+
capturedAt: new Date().toISOString(),
|
|
66
|
+
...(loaded.etag || loaded.lastModified ? { validators: { ...(loaded.etag ? { etag: loaded.etag } : {}), ...(loaded.lastModified ? { lastModified: loaded.lastModified } : {}) } } : {}),
|
|
67
|
+
openapi: loaded.snapshot,
|
|
68
|
+
};
|
|
69
|
+
},
|
|
70
|
+
async detectChanges(previous, current) {
|
|
71
|
+
return { changed: previous?.revision !== current.revision, identifiers: this.evidenceIdentifiers(current) };
|
|
72
|
+
},
|
|
73
|
+
evidenceIdentifiers(snapshot) {
|
|
74
|
+
return snapshot.openapi ? [
|
|
75
|
+
...Object.keys(snapshot.openapi.operations),
|
|
76
|
+
...Object.keys(snapshot.openapi.schemas).map((item) => `schema:${item}`),
|
|
77
|
+
] : [];
|
|
78
|
+
},
|
|
79
|
+
redact(source) {
|
|
80
|
+
if (!isSpecUrl(source.path))
|
|
81
|
+
return source;
|
|
82
|
+
const url = new URL(source.path);
|
|
83
|
+
url.search = url.search ? '?…' : '';
|
|
84
|
+
return { ...source, path: url.toString() };
|
|
85
|
+
},
|
|
86
|
+
async health(root, source) {
|
|
87
|
+
const loaded = await loadOpenApiSource(root, source);
|
|
88
|
+
const baseline = (await readSyncState(root)).sources[source.name];
|
|
89
|
+
const redacted = this.redact(source);
|
|
90
|
+
const checkedAt = new Date().toISOString();
|
|
91
|
+
return {
|
|
92
|
+
name: source.name,
|
|
93
|
+
connector: this.id,
|
|
94
|
+
status: 'healthy',
|
|
95
|
+
checkedAt,
|
|
96
|
+
lastSuccessfulAt: checkedAt,
|
|
97
|
+
...(baseline?.recordedAt ? { lastMonitoringAt: baseline.recordedAt } : {}),
|
|
98
|
+
location: redacted.path,
|
|
99
|
+
provider: isSpecUrl(source.path) ? 'https' : 'file',
|
|
100
|
+
monitored: isSpecUrl(source.path),
|
|
101
|
+
revision: loaded.hash,
|
|
102
|
+
summary: `${loaded.summary.title} ${loaded.summary.version} · ${loaded.summary.operationCount} operations`,
|
|
103
|
+
details: [
|
|
104
|
+
`OpenAPI ${loaded.summary.specificationVersion}`,
|
|
105
|
+
`${loaded.summary.schemas.length} schemas`,
|
|
106
|
+
`${loaded.summary.securitySchemes.length} security schemes`,
|
|
107
|
+
`${loaded.summary.servers.length} servers`,
|
|
108
|
+
],
|
|
109
|
+
openapi: loaded.summary,
|
|
110
|
+
...(source.scope ? { scope: source.scope } : {}),
|
|
111
|
+
};
|
|
112
|
+
},
|
|
113
|
+
};
|
|
114
|
+
const directoryConnector = {
|
|
115
|
+
id: 'directory',
|
|
116
|
+
version: SOURCE_CONNECTOR_API_VERSION,
|
|
117
|
+
async validate(root, source) {
|
|
118
|
+
const path = resolve(root, source.path);
|
|
119
|
+
let stats;
|
|
120
|
+
try {
|
|
121
|
+
stats = await lstat(path);
|
|
122
|
+
}
|
|
123
|
+
catch {
|
|
124
|
+
throw new DoxloopError(`Source directory does not exist: ${path}`);
|
|
125
|
+
}
|
|
126
|
+
if (!stats.isDirectory() || stats.isSymbolicLink())
|
|
127
|
+
throw new DoxloopError(`Source must be a non-symlinked directory: ${path}`);
|
|
128
|
+
},
|
|
129
|
+
async inventory(root, source) {
|
|
130
|
+
await this.validate(root, source);
|
|
131
|
+
const entries = await readdir(resolve(root, source.path), { withFileTypes: true });
|
|
132
|
+
return { identifiers: entries.map((entry) => entry.name).sort(), metadata: { entries: entries.length } };
|
|
133
|
+
},
|
|
134
|
+
async snapshot(root, source) {
|
|
135
|
+
const inventory = await this.inventory(root, source);
|
|
136
|
+
return { revision: createHash('sha256').update(JSON.stringify(inventory)).digest('hex'), capturedAt: new Date().toISOString() };
|
|
137
|
+
},
|
|
138
|
+
async detectChanges(previous, current) { return { changed: previous?.revision !== current.revision, identifiers: [] }; },
|
|
139
|
+
evidenceIdentifiers() { return []; },
|
|
140
|
+
redact(source) {
|
|
141
|
+
return {
|
|
142
|
+
...source,
|
|
143
|
+
path: basename(source.path) || source.path,
|
|
144
|
+
...(source.remote ? { remote: { ...source.remote, repository: redactedRepository(source.remote.repository) } } : {}),
|
|
145
|
+
};
|
|
146
|
+
},
|
|
147
|
+
async health(root, source) {
|
|
148
|
+
const [snapshot, baseline, head] = await Promise.all([
|
|
149
|
+
this.snapshot(root, source),
|
|
150
|
+
readSyncState(root).then((state) => state.sources[source.name]),
|
|
151
|
+
source.remote ? remoteHead(source.remote) : Promise.resolve(undefined),
|
|
152
|
+
]);
|
|
153
|
+
const inventory = await this.inventory(root, source);
|
|
154
|
+
const redacted = this.redact(source);
|
|
155
|
+
const checkedAt = new Date().toISOString();
|
|
156
|
+
return {
|
|
157
|
+
name: source.name,
|
|
158
|
+
connector: this.id,
|
|
159
|
+
status: 'healthy',
|
|
160
|
+
checkedAt,
|
|
161
|
+
lastSuccessfulAt: checkedAt,
|
|
162
|
+
...(baseline?.recordedAt ? { lastMonitoringAt: baseline.recordedAt } : {}),
|
|
163
|
+
location: redacted.remote?.repository ?? redacted.path,
|
|
164
|
+
provider: source.remote?.provider ?? 'directory',
|
|
165
|
+
...(source.remote?.branch ? { branch: source.remote.branch } : {}),
|
|
166
|
+
...(source.remote?.subdirectory ? { subdirectory: source.remote.subdirectory } : {}),
|
|
167
|
+
monitored: Boolean(source.remote),
|
|
168
|
+
revision: head ?? snapshot.revision,
|
|
169
|
+
summary: `${inventory.metadata.entries ?? 0} top-level entries available`,
|
|
170
|
+
details: source.remote ? [`Monitored from ${source.remote.provider}:${redacted.remote?.repository}@${source.remote.branch}`] : ['Local read-only directory'],
|
|
171
|
+
...(source.scope ? { scope: source.scope } : {}),
|
|
172
|
+
};
|
|
173
|
+
},
|
|
174
|
+
};
|
|
175
|
+
/**
|
|
176
|
+
* An existing documentation website, crawled into a Markdown snapshot. The
|
|
177
|
+
* snapshot is frozen at crawl time, so health reports what was crawled and
|
|
178
|
+
* change detection compares snapshot contents; a re-crawl replaces the folder.
|
|
179
|
+
*/
|
|
180
|
+
const docsSiteConnector = {
|
|
181
|
+
id: 'docs-site',
|
|
182
|
+
version: SOURCE_CONNECTOR_API_VERSION,
|
|
183
|
+
async validate(root, source) { await readDocsSiteManifest(root, source); },
|
|
184
|
+
async inventory(root, source) {
|
|
185
|
+
const manifest = await readDocsSiteManifest(root, source);
|
|
186
|
+
return {
|
|
187
|
+
identifiers: manifest.pages.map((page) => page.file).sort(),
|
|
188
|
+
metadata: {
|
|
189
|
+
url: manifest.url,
|
|
190
|
+
pages: manifest.totals.pages,
|
|
191
|
+
words: manifest.totals.words,
|
|
192
|
+
crawledAt: manifest.crawledAt,
|
|
193
|
+
...(manifest.generator ? { generator: manifest.generator } : {}),
|
|
194
|
+
},
|
|
195
|
+
};
|
|
196
|
+
},
|
|
197
|
+
async snapshot(root, source) {
|
|
198
|
+
const manifest = await readDocsSiteManifest(root, source);
|
|
199
|
+
return { revision: manifest.hash, capturedAt: manifest.crawledAt };
|
|
200
|
+
},
|
|
201
|
+
async detectChanges(previous, current) { return { changed: previous?.revision !== current.revision, identifiers: [] }; },
|
|
202
|
+
evidenceIdentifiers() { return []; },
|
|
203
|
+
redact(source) {
|
|
204
|
+
if (!source.site)
|
|
205
|
+
return source;
|
|
206
|
+
try {
|
|
207
|
+
const url = new URL(source.site.url);
|
|
208
|
+
url.search = url.search ? '?…' : '';
|
|
209
|
+
return { ...source, site: { ...source.site, url: url.toString() } };
|
|
210
|
+
}
|
|
211
|
+
catch {
|
|
212
|
+
return source;
|
|
213
|
+
}
|
|
214
|
+
},
|
|
215
|
+
async health(root, source) {
|
|
216
|
+
const manifest = await readDocsSiteManifest(root, source);
|
|
217
|
+
const baseline = (await readSyncState(root)).sources[source.name];
|
|
218
|
+
const checkedAt = new Date().toISOString();
|
|
219
|
+
const site = source.site ?? { url: manifest.url, crawledAt: manifest.crawledAt, pages: manifest.totals.pages, words: manifest.totals.words, hash: manifest.hash };
|
|
220
|
+
const stale = source.site !== undefined && source.site.hash !== manifest.hash;
|
|
221
|
+
return {
|
|
222
|
+
name: source.name,
|
|
223
|
+
connector: this.id,
|
|
224
|
+
status: stale ? 'warning' : 'healthy',
|
|
225
|
+
checkedAt,
|
|
226
|
+
lastSuccessfulAt: checkedAt,
|
|
227
|
+
...(baseline?.recordedAt ? { lastMonitoringAt: baseline.recordedAt } : {}),
|
|
228
|
+
location: this.redact(source).site?.url ?? manifest.url,
|
|
229
|
+
provider: 'https',
|
|
230
|
+
monitored: false,
|
|
231
|
+
revision: manifest.hash,
|
|
232
|
+
summary: stale ? `Snapshot on disk differs from the recorded crawl; re-crawl the site.` : describeDocsSite(site),
|
|
233
|
+
details: [
|
|
234
|
+
`Existing documentation site: ${manifest.url}`,
|
|
235
|
+
`${manifest.totals.pages} pages, ${manifest.totals.words} words${manifest.generator ? `, ${manifest.generator}` : ''}`,
|
|
236
|
+
`Discovered through ${manifest.discovery.length > 0 ? manifest.discovery.join(', ') : 'the entry page'}`,
|
|
237
|
+
...(manifest.truncated ? [`Crawl stopped at the ${manifest.pageLimit}-page limit.`] : []),
|
|
238
|
+
...(manifest.brokenLinks.length > 0 ? [`${manifest.brokenLinks.length} broken internal links`] : []),
|
|
239
|
+
'Frozen at crawl time; use Re-crawl to refresh.',
|
|
240
|
+
],
|
|
241
|
+
docsSite: { ...site, brokenLinks: manifest.brokenLinks.length },
|
|
242
|
+
...(source.scope ? { scope: source.scope } : {}),
|
|
243
|
+
};
|
|
244
|
+
},
|
|
245
|
+
};
|
|
246
|
+
function redactedRepository(value) {
|
|
247
|
+
try {
|
|
248
|
+
const url = new URL(value);
|
|
249
|
+
if (url.username)
|
|
250
|
+
url.username = '…';
|
|
251
|
+
url.password = '';
|
|
252
|
+
url.search = url.search ? '?…' : '';
|
|
253
|
+
url.hash = '';
|
|
254
|
+
return url.toString();
|
|
255
|
+
}
|
|
256
|
+
catch {
|
|
257
|
+
return value;
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
function safeHealthError(error, source, root) {
|
|
261
|
+
let value = error instanceof Error ? error.message : String(error);
|
|
262
|
+
for (const secret of [resolve(root, source.path), source.path, source.remote?.repository]) {
|
|
263
|
+
if (secret)
|
|
264
|
+
value = value.split(secret).join(connectorForSource(source).redact(source).remote?.repository ?? connectorForSource(source).redact(source).path);
|
|
265
|
+
}
|
|
266
|
+
return value.replace(/([?&](?:token|key|secret|password|signature)=)[^&\s]+/gi, '$1…').slice(0, 500);
|
|
267
|
+
}
|
|
268
|
+
//# sourceMappingURL=source-connectors.js.map
|