@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/agents.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { createHash } from 'node:crypto';
|
|
2
2
|
import { constants } from 'node:fs';
|
|
3
3
|
import { access, cp, lstat, mkdir, readFile, readdir, rm, stat, } from 'node:fs/promises';
|
|
4
|
+
import { homedir } from 'node:os';
|
|
4
5
|
import { delimiter, dirname, join, resolve } from 'node:path';
|
|
5
6
|
import { fileURLToPath } from 'node:url';
|
|
6
7
|
import spawn from 'cross-spawn';
|
|
@@ -9,6 +10,23 @@ import { pathExists } from './fs.js';
|
|
|
9
10
|
import { loadGeneratorAdapter } from './generators.js';
|
|
10
11
|
import { loadProject } from './project.js';
|
|
11
12
|
const packageRoot = resolve(dirname(fileURLToPath(import.meta.url)), '..');
|
|
13
|
+
export const AGENT_CATALOG = [
|
|
14
|
+
{
|
|
15
|
+
name: 'codex',
|
|
16
|
+
displayName: 'Codex',
|
|
17
|
+
packageName: '@openai/codex',
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
name: 'claude',
|
|
21
|
+
displayName: 'Claude Code',
|
|
22
|
+
packageName: '@anthropic-ai/claude-code',
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
name: 'gemini',
|
|
26
|
+
displayName: 'Gemini',
|
|
27
|
+
packageName: '@google/gemini-cli',
|
|
28
|
+
},
|
|
29
|
+
];
|
|
12
30
|
export function parseAgent(value) {
|
|
13
31
|
if (value === undefined)
|
|
14
32
|
return undefined;
|
|
@@ -102,15 +120,56 @@ function skillDestinationsFor(root, skillName, agent) {
|
|
|
102
120
|
}
|
|
103
121
|
export async function detectAgents() {
|
|
104
122
|
const found = [];
|
|
105
|
-
for (const
|
|
106
|
-
const executable = await findExecutable(name);
|
|
123
|
+
for (const agent of AGENT_CATALOG) {
|
|
124
|
+
const executable = await findExecutable(agent.name);
|
|
107
125
|
if (executable)
|
|
108
|
-
found.push({ name, executable });
|
|
126
|
+
found.push({ name: agent.name, executable });
|
|
109
127
|
}
|
|
110
128
|
return found;
|
|
111
129
|
}
|
|
130
|
+
export async function installAgent(name, options = {}) {
|
|
131
|
+
const agent = AGENT_CATALOG.find((candidate) => candidate.name === name);
|
|
132
|
+
if (!agent)
|
|
133
|
+
throw new DoxloopError(`Unsupported agent: ${name}`);
|
|
134
|
+
const npm = await findExecutable('npm');
|
|
135
|
+
if (!npm) {
|
|
136
|
+
throw new DoxloopError(`Cannot install ${agent.displayName} because npm is not available on PATH. Install npm, then run \`npm install --global ${agent.packageName}\`.`, 2);
|
|
137
|
+
}
|
|
138
|
+
const exitCode = await new Promise((resolveInstall, rejectInstall) => {
|
|
139
|
+
const child = spawn(npm, ['install', '--global', agent.packageName], {
|
|
140
|
+
stdio: options.onOutput ? ['ignore', 'pipe', 'pipe'] : 'inherit',
|
|
141
|
+
env: process.env,
|
|
142
|
+
});
|
|
143
|
+
options.onChild?.(child);
|
|
144
|
+
if (options.onOutput) {
|
|
145
|
+
child.stdout?.on('data', (chunk) => options.onOutput?.(chunk.toString()));
|
|
146
|
+
child.stderr?.on('data', (chunk) => options.onOutput?.(chunk.toString()));
|
|
147
|
+
}
|
|
148
|
+
child.once('error', (error) => {
|
|
149
|
+
rejectInstall(new DoxloopError(`Could not start npm to install ${agent.displayName}: ${error.message}`, 2));
|
|
150
|
+
});
|
|
151
|
+
child.once('exit', (code, signal) => {
|
|
152
|
+
if (signal) {
|
|
153
|
+
rejectInstall(new DoxloopError(`${agent.displayName} installation was stopped by ${signal}.`, 2));
|
|
154
|
+
}
|
|
155
|
+
else {
|
|
156
|
+
resolveInstall(code ?? 1);
|
|
157
|
+
}
|
|
158
|
+
});
|
|
159
|
+
});
|
|
160
|
+
if (exitCode !== 0) {
|
|
161
|
+
throw new DoxloopError(`Could not install ${agent.displayName} (npm exited with code ${exitCode}). Retry with \`npm install --global ${agent.packageName}\`.`, 2);
|
|
162
|
+
}
|
|
163
|
+
const executable = await findExecutable(name);
|
|
164
|
+
if (!executable) {
|
|
165
|
+
throw new DoxloopError(`${agent.displayName} was installed, but \`${name}\` is not available on PATH. Open a new terminal and run Doxloop again.`, 2);
|
|
166
|
+
}
|
|
167
|
+
return { name, executable };
|
|
168
|
+
}
|
|
112
169
|
export async function chooseAgent(preferred) {
|
|
113
|
-
const candidates = preferred
|
|
170
|
+
const candidates = preferred
|
|
171
|
+
? [preferred]
|
|
172
|
+
: AGENT_CATALOG.map((agent) => agent.name);
|
|
114
173
|
for (const name of candidates) {
|
|
115
174
|
const executable = await findExecutable(name);
|
|
116
175
|
if (executable)
|
|
@@ -119,13 +178,9 @@ export async function chooseAgent(preferred) {
|
|
|
119
178
|
const expected = preferred ?? 'Codex, Claude Code, or Gemini';
|
|
120
179
|
throw new DoxloopError(`${expected} is not available on PATH. Install an agent or use --print to copy the prompt.`);
|
|
121
180
|
}
|
|
122
|
-
export async function agentAuthenticationStatus(agent) {
|
|
123
|
-
if (agent.name === 'gemini')
|
|
124
|
-
return
|
|
125
|
-
status: 'unknown',
|
|
126
|
-
detail: 'Gemini authentication cannot be verified automatically; it will be checked when Gemini starts.',
|
|
127
|
-
};
|
|
128
|
-
}
|
|
181
|
+
export async function agentAuthenticationStatus(agent, options = {}) {
|
|
182
|
+
if (agent.name === 'gemini')
|
|
183
|
+
return geminiAuthenticationStatus(options);
|
|
129
184
|
const args = agent.name === 'codex'
|
|
130
185
|
? ['login', 'status']
|
|
131
186
|
: ['auth', 'status', '--json'];
|
|
@@ -166,6 +221,37 @@ export async function agentAuthenticationStatus(agent) {
|
|
|
166
221
|
: 'Claude Code is not signed in. Run `claude auth login`.',
|
|
167
222
|
};
|
|
168
223
|
}
|
|
224
|
+
/**
|
|
225
|
+
* Gemini CLI has no non-interactive status command, so its sign-in is read
|
|
226
|
+
* from the places it keeps credentials: an API key or Vertex AI project in
|
|
227
|
+
* the environment, or the OAuth token file a Google sign-in leaves behind.
|
|
228
|
+
* Only presence is checked; no credential value is read into memory for
|
|
229
|
+
* longer than the parse, and none is reported.
|
|
230
|
+
*/
|
|
231
|
+
export async function geminiAuthenticationStatus(options = {}) {
|
|
232
|
+
const env = options.env ?? process.env;
|
|
233
|
+
if (env.GEMINI_API_KEY?.trim() || env.GOOGLE_API_KEY?.trim()) {
|
|
234
|
+
return { status: 'authenticated', detail: 'Gemini uses the API key from the environment.' };
|
|
235
|
+
}
|
|
236
|
+
const vertex = env.GOOGLE_GENAI_USE_VERTEXAI?.trim().toLowerCase();
|
|
237
|
+
if ((vertex === 'true' || vertex === '1') && (env.GOOGLE_CLOUD_PROJECT?.trim() || env.GOOGLE_APPLICATION_CREDENTIALS?.trim())) {
|
|
238
|
+
return { status: 'authenticated', detail: 'Gemini uses Vertex AI credentials from the environment.' };
|
|
239
|
+
}
|
|
240
|
+
const credentials = join(options.home ?? homedir(), '.gemini', 'oauth_creds.json');
|
|
241
|
+
try {
|
|
242
|
+
const parsed = JSON.parse(await readFile(credentials, 'utf8'));
|
|
243
|
+
if (typeof parsed.refresh_token === 'string' || typeof parsed.access_token === 'string') {
|
|
244
|
+
return { status: 'authenticated', detail: 'Gemini is signed in with a Google account.' };
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
catch {
|
|
248
|
+
// No token file, or one Gemini itself would not accept.
|
|
249
|
+
}
|
|
250
|
+
return {
|
|
251
|
+
status: 'unauthenticated',
|
|
252
|
+
detail: 'Gemini is not signed in. Run `gemini` once in a terminal and complete the sign-in, or set GEMINI_API_KEY.',
|
|
253
|
+
};
|
|
254
|
+
}
|
|
169
255
|
async function runAgentStatus(executable, args) {
|
|
170
256
|
return new Promise((resolveStatus) => {
|
|
171
257
|
const child = spawn(executable, args, {
|
|
@@ -194,6 +280,24 @@ async function runAgentStatus(executable, args) {
|
|
|
194
280
|
});
|
|
195
281
|
}
|
|
196
282
|
async function findExecutable(name) {
|
|
283
|
+
if (name === 'codex' || name === 'claude' || name === 'gemini') {
|
|
284
|
+
const override = process.env[`DOXLOOP_AGENT_EXECUTABLE_${name.toUpperCase()}`];
|
|
285
|
+
if (override) {
|
|
286
|
+
const candidate = resolve(override);
|
|
287
|
+
try {
|
|
288
|
+
const stats = await stat(candidate);
|
|
289
|
+
if (stats.isFile()) {
|
|
290
|
+
if (process.platform !== 'win32')
|
|
291
|
+
await access(candidate, constants.X_OK);
|
|
292
|
+
return candidate;
|
|
293
|
+
}
|
|
294
|
+
throw new Error('not a file');
|
|
295
|
+
}
|
|
296
|
+
catch {
|
|
297
|
+
throw new DoxloopError(`The test agent executable for ${name} is not runnable: ${candidate}`);
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
}
|
|
197
301
|
const path = process.env.PATH;
|
|
198
302
|
if (!path)
|
|
199
303
|
return undefined;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { ValidationIssue } from './types.js';
|
|
2
|
+
/** `METHOD /path` for every `<ApiEndpoint>` block in a page. */
|
|
3
|
+
export declare function documentedOperations(content: string): string[];
|
|
4
|
+
/**
|
|
5
|
+
* One warning per contract operation that no page documents with an
|
|
6
|
+
* `<ApiEndpoint>` block, filed against the page that mentions it (the
|
|
7
|
+
* operation itself first, then its path). Operations no page mentions are
|
|
8
|
+
* returned without a file so the caller can report them without a fix session.
|
|
9
|
+
*/
|
|
10
|
+
export declare function contractCoverageIssues(root: string, files: string[]): Promise<ValidationIssue[]>;
|
|
11
|
+
//# sourceMappingURL=api-coverage.d.ts.map
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* API contract coverage for Doxbrix sites.
|
|
3
|
+
*
|
|
4
|
+
* A RealWorld run documented 14 of the contract's 19 operations with an
|
|
5
|
+
* `<ApiEndpoint>` block; login, registration, article deletion, and the two
|
|
6
|
+
* favorite operations were written as curl walkthroughs on how-to pages, so
|
|
7
|
+
* the published reference had no "Try it" entry for them and nothing flagged
|
|
8
|
+
* the gap. The end-of-run check now names each contract operation without a
|
|
9
|
+
* reference block and hands it to the page that already talks about it.
|
|
10
|
+
*/
|
|
11
|
+
import { readFile } from 'node:fs/promises';
|
|
12
|
+
import { join } from 'node:path';
|
|
13
|
+
import { discoverDocumentationSources } from './source-discovery.js';
|
|
14
|
+
const ENDPOINT_TAG = /<ApiEndpoint\b([^>]*)>/g;
|
|
15
|
+
/** `METHOD /path` for every `<ApiEndpoint>` block in a page. */
|
|
16
|
+
export function documentedOperations(content) {
|
|
17
|
+
const found = [];
|
|
18
|
+
for (const match of content.matchAll(ENDPOINT_TAG)) {
|
|
19
|
+
const method = /\bmethod=["']([A-Za-z]+)["']/.exec(match[1])?.[1];
|
|
20
|
+
const path = /\bpath=["']([^"']+)["']/.exec(match[1])?.[1];
|
|
21
|
+
if (method && path)
|
|
22
|
+
found.push(`${method.toUpperCase()} ${normalizePath(path)}`);
|
|
23
|
+
}
|
|
24
|
+
return found;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* One warning per contract operation that no page documents with an
|
|
28
|
+
* `<ApiEndpoint>` block, filed against the page that mentions it (the
|
|
29
|
+
* operation itself first, then its path). Operations no page mentions are
|
|
30
|
+
* returned without a file so the caller can report them without a fix session.
|
|
31
|
+
*/
|
|
32
|
+
export async function contractCoverageIssues(root, files) {
|
|
33
|
+
let operations;
|
|
34
|
+
try {
|
|
35
|
+
const { inventory } = await discoverDocumentationSources(root);
|
|
36
|
+
operations = [...new Set(inventory.sources.flatMap((source) => source.evidence.filter((row) => row.contract && row.kind === 'operation').map((row) => row.label)))];
|
|
37
|
+
}
|
|
38
|
+
catch {
|
|
39
|
+
return [];
|
|
40
|
+
}
|
|
41
|
+
if (operations.length === 0)
|
|
42
|
+
return [];
|
|
43
|
+
const pages = [];
|
|
44
|
+
for (const file of files) {
|
|
45
|
+
try {
|
|
46
|
+
pages.push({ file, content: await readFile(join(root, file), 'utf8') });
|
|
47
|
+
}
|
|
48
|
+
catch { /* A page the run did not write. */ }
|
|
49
|
+
}
|
|
50
|
+
const documented = new Set(pages.flatMap((page) => documentedOperations(page.content)));
|
|
51
|
+
const issues = [];
|
|
52
|
+
for (const operation of operations) {
|
|
53
|
+
const [method = '', ...rest] = operation.split(' ');
|
|
54
|
+
const path = normalizePath(rest.join(' '));
|
|
55
|
+
if (documented.has(`${method} ${path}`))
|
|
56
|
+
continue;
|
|
57
|
+
const mentions = (page) => page.content.includes(`${method} ${rest.join(' ')}`) || page.content.includes(`${method} ${path}`);
|
|
58
|
+
const home = pages.find(mentions) ?? pages.find((page) => page.content.includes(rest.join(' ')));
|
|
59
|
+
issues.push({
|
|
60
|
+
severity: 'warning',
|
|
61
|
+
code: 'api-endpoint-missing',
|
|
62
|
+
message: `The API contract's ${operation} has no <ApiEndpoint> reference block on any page. Add one on this page where it discusses the operation: method, path, baseUrl, a <Param> for each path, query, header, and body field, and a <Response> for the success and each documented error status, all taken from the contract.`,
|
|
63
|
+
...(home ? { file: home.file } : {}),
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
return issues;
|
|
67
|
+
}
|
|
68
|
+
/** `{slug}` and `:slug` path parameters compare equal. */
|
|
69
|
+
function normalizePath(path) {
|
|
70
|
+
return path.trim().replace(/:([A-Za-z_][\w]*)/g, '{$1}').replace(/\/+$/, '') || '/';
|
|
71
|
+
}
|
|
72
|
+
//# sourceMappingURL=api-coverage.js.map
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/** Paths (or hash routes) a product uses for its sign-in screen. */
|
|
2
|
+
export declare const SIGN_IN_ROUTE: RegExp;
|
|
3
|
+
export interface SignInProbeObservation {
|
|
4
|
+
/** Where the page ended up once client routing settled. */
|
|
5
|
+
finalUrl: string;
|
|
6
|
+
/** Whether a visible password field is on the page. */
|
|
7
|
+
hasPasswordField: boolean;
|
|
8
|
+
}
|
|
9
|
+
export interface SignInWall {
|
|
10
|
+
/** The sign-in route the visitor was sent to, such as `/auth` or `/_/#/login`. */
|
|
11
|
+
signInPath: string;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Decide from what the browser saw whether the page is a sign-in wall: the
|
|
15
|
+
* final route looks like a sign-in route, or a password field is visible.
|
|
16
|
+
*/
|
|
17
|
+
export declare function signInWallFromObservation(observation: SignInProbeObservation): SignInWall | undefined;
|
|
18
|
+
export type SignInProbe = (url: string, options?: {
|
|
19
|
+
timeoutMs?: number;
|
|
20
|
+
}) => Promise<SignInWall | undefined>;
|
|
21
|
+
/**
|
|
22
|
+
* Load `url` headless and report a sign-in wall, or undefined when the page
|
|
23
|
+
* is not one or nothing could be learned (no browser, timeout, crash).
|
|
24
|
+
*/
|
|
25
|
+
export declare function probeSignInWall(url: string, options?: {
|
|
26
|
+
timeoutMs?: number;
|
|
27
|
+
}): Promise<SignInWall | undefined>;
|
|
28
|
+
//# sourceMappingURL=application-probe.d.ts.map
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A single-page application answers every route with HTTP 200 and decides in
|
|
3
|
+
* the browser whether the visitor may stay, so a plain fetch reports a sign-in
|
|
4
|
+
* wall as a reachable page. This probe loads the page in the managed headless
|
|
5
|
+
* browser, lets client routing settle, and reports where the visitor landed.
|
|
6
|
+
* It never throws: a missing browser or a slow page means "no evidence".
|
|
7
|
+
*/
|
|
8
|
+
import { loadInstalledPlaywright } from './capture.js';
|
|
9
|
+
/** Paths (or hash routes) a product uses for its sign-in screen. */
|
|
10
|
+
export const SIGN_IN_ROUTE = /(?:^|[/#])(?:login|log-in|signin|sign-in|sign_in|auth|authenticate|sso|oauth)(?:[/?#]|$)/i;
|
|
11
|
+
/**
|
|
12
|
+
* Decide from what the browser saw whether the page is a sign-in wall: the
|
|
13
|
+
* final route looks like a sign-in route, or a password field is visible.
|
|
14
|
+
*/
|
|
15
|
+
export function signInWallFromObservation(observation) {
|
|
16
|
+
let url;
|
|
17
|
+
try {
|
|
18
|
+
url = new URL(observation.finalUrl);
|
|
19
|
+
}
|
|
20
|
+
catch {
|
|
21
|
+
return observation.hasPasswordField ? { signInPath: observation.finalUrl } : undefined;
|
|
22
|
+
}
|
|
23
|
+
const hash = url.hash && url.hash !== '#' ? url.hash.replace(/\?.*$/, '') : '';
|
|
24
|
+
if (SIGN_IN_ROUTE.test(url.pathname))
|
|
25
|
+
return { signInPath: url.pathname };
|
|
26
|
+
// Hash-routed applications (PocketBase `/_/#/login`) keep the route after `#`.
|
|
27
|
+
if (hash && SIGN_IN_ROUTE.test(hash))
|
|
28
|
+
return { signInPath: `${url.pathname}${hash}` };
|
|
29
|
+
if (observation.hasPasswordField)
|
|
30
|
+
return { signInPath: `${url.pathname}${hash}` };
|
|
31
|
+
return undefined;
|
|
32
|
+
}
|
|
33
|
+
const DEFAULT_PROBE_TIMEOUT_MS = 8_000;
|
|
34
|
+
/** Client-side guards often redirect just after the first idle moment. */
|
|
35
|
+
const SETTLE_AFTER_IDLE_MS = 400;
|
|
36
|
+
/**
|
|
37
|
+
* Load `url` headless and report a sign-in wall, or undefined when the page
|
|
38
|
+
* is not one or nothing could be learned (no browser, timeout, crash).
|
|
39
|
+
*/
|
|
40
|
+
export async function probeSignInWall(url, options = {}) {
|
|
41
|
+
const timeoutMs = options.timeoutMs ?? DEFAULT_PROBE_TIMEOUT_MS;
|
|
42
|
+
let browser;
|
|
43
|
+
let timer;
|
|
44
|
+
let abandoned = false;
|
|
45
|
+
const run = async () => {
|
|
46
|
+
const playwright = await loadInstalledPlaywright();
|
|
47
|
+
if (!playwright || abandoned)
|
|
48
|
+
return undefined;
|
|
49
|
+
const launched = (await playwright.chromium.launch({ headless: true }));
|
|
50
|
+
// The overall deadline may have passed while the browser started.
|
|
51
|
+
if (abandoned) {
|
|
52
|
+
await launched.close().catch(() => { });
|
|
53
|
+
return undefined;
|
|
54
|
+
}
|
|
55
|
+
browser = launched;
|
|
56
|
+
const context = await browser.newContext({ viewport: { width: 1280, height: 800 } });
|
|
57
|
+
const page = (await context.newPage());
|
|
58
|
+
const deadline = Date.now() + timeoutMs;
|
|
59
|
+
await page.goto(url, { waitUntil: 'domcontentloaded', timeout: timeoutMs });
|
|
60
|
+
await page.waitForLoadState('networkidle', { timeout: Math.max(500, deadline - Date.now()) }).catch(() => { });
|
|
61
|
+
await page.waitForTimeout(Math.min(SETTLE_AFTER_IDLE_MS, Math.max(0, deadline - Date.now())));
|
|
62
|
+
let hasPasswordField = false;
|
|
63
|
+
try {
|
|
64
|
+
const field = page.locator('input[type="password"]');
|
|
65
|
+
hasPasswordField = (await field.count()) > 0 && (await field.first().isVisible());
|
|
66
|
+
}
|
|
67
|
+
catch {
|
|
68
|
+
hasPasswordField = false;
|
|
69
|
+
}
|
|
70
|
+
return signInWallFromObservation({ finalUrl: page.url(), hasPasswordField });
|
|
71
|
+
};
|
|
72
|
+
try {
|
|
73
|
+
return await Promise.race([
|
|
74
|
+
run().catch(() => undefined),
|
|
75
|
+
new Promise((resolve) => {
|
|
76
|
+
timer = setTimeout(() => resolve(undefined), timeoutMs + 4_000);
|
|
77
|
+
timer.unref?.();
|
|
78
|
+
}),
|
|
79
|
+
]);
|
|
80
|
+
}
|
|
81
|
+
catch {
|
|
82
|
+
return undefined;
|
|
83
|
+
}
|
|
84
|
+
finally {
|
|
85
|
+
abandoned = true;
|
|
86
|
+
if (timer)
|
|
87
|
+
clearTimeout(timer);
|
|
88
|
+
const opened = browser;
|
|
89
|
+
if (opened)
|
|
90
|
+
await opened.close().catch(() => { });
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
//# sourceMappingURL=application-probe.js.map
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Resolve an application route (`/settings`) against the configured base URL.
|
|
3
|
+
*
|
|
4
|
+
* Plain `new URL(route, base)` is wrong for a hash-routed single-page
|
|
5
|
+
* application (`https://host/_/#`): the route lives in the fragment, so
|
|
6
|
+
* `/settings` must open `https://host/_/#/settings`, not `https://host/settings`
|
|
7
|
+
* (which the server answers with 404). Every other base URL keeps the standard
|
|
8
|
+
* resolution, because some projects configure a page such as `/login` as the
|
|
9
|
+
* base and still write routes from the origin root.
|
|
10
|
+
*/
|
|
11
|
+
export declare function applicationUrl(baseUrl: string, route: string | undefined): URL;
|
|
12
|
+
/** A base URL ending in an empty or route-shaped fragment (`#`, `#/`) is hash-routed. */
|
|
13
|
+
export declare function hashRouted(base: URL | string): boolean;
|
|
14
|
+
//# sourceMappingURL=application-url.d.ts.map
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Resolve an application route (`/settings`) against the configured base URL.
|
|
3
|
+
*
|
|
4
|
+
* Plain `new URL(route, base)` is wrong for a hash-routed single-page
|
|
5
|
+
* application (`https://host/_/#`): the route lives in the fragment, so
|
|
6
|
+
* `/settings` must open `https://host/_/#/settings`, not `https://host/settings`
|
|
7
|
+
* (which the server answers with 404). Every other base URL keeps the standard
|
|
8
|
+
* resolution, because some projects configure a page such as `/login` as the
|
|
9
|
+
* base and still write routes from the origin root.
|
|
10
|
+
*/
|
|
11
|
+
export function applicationUrl(baseUrl, route) {
|
|
12
|
+
const base = new URL(baseUrl);
|
|
13
|
+
const path = route?.trim() || '/';
|
|
14
|
+
if (!hashRouted(base) || /^[a-z][a-z0-9+.-]*:/i.test(path) || path.startsWith('//'))
|
|
15
|
+
return new URL(path, base);
|
|
16
|
+
const document = new URL(base.toString());
|
|
17
|
+
const routed = path.startsWith('#') ? path.slice(1) : path;
|
|
18
|
+
document.hash = routed.startsWith('/') ? routed : `/${routed}`;
|
|
19
|
+
return document;
|
|
20
|
+
}
|
|
21
|
+
/** A base URL ending in an empty or route-shaped fragment (`#`, `#/`) is hash-routed. */
|
|
22
|
+
export function hashRouted(base) {
|
|
23
|
+
const url = typeof base === 'string' ? new URL(base) : base;
|
|
24
|
+
return url.href.includes('#') && (url.hash === '' || url.hash.startsWith('#/'));
|
|
25
|
+
}
|
|
26
|
+
//# sourceMappingURL=application-url.js.map
|
package/dist/args.js
CHANGED
|
@@ -1,16 +1,31 @@
|
|
|
1
1
|
import { UsageError } from './errors.js';
|
|
2
2
|
const BOOLEAN_FLAGS = new Set([
|
|
3
|
+
'backfill-evidence',
|
|
4
|
+
'ignore-screenshot-problems',
|
|
3
5
|
'dry-run',
|
|
4
6
|
'h',
|
|
5
7
|
'help',
|
|
6
8
|
'open',
|
|
7
9
|
'print',
|
|
8
10
|
'public',
|
|
11
|
+
'quiet',
|
|
9
12
|
'screenshots',
|
|
10
13
|
'no-screenshots',
|
|
14
|
+
'no-open',
|
|
11
15
|
'v',
|
|
12
16
|
'version',
|
|
13
17
|
'yes',
|
|
18
|
+
'offline',
|
|
19
|
+
'rendered',
|
|
20
|
+
'examples',
|
|
21
|
+
'fix',
|
|
22
|
+
'update-visuals',
|
|
23
|
+
'warnings-as-errors',
|
|
24
|
+
'approve-baseline',
|
|
25
|
+
'approve-quality-baseline',
|
|
26
|
+
'allow-related',
|
|
27
|
+
'existing',
|
|
28
|
+
'zip',
|
|
14
29
|
]);
|
|
15
30
|
export function parseArgs(argv) {
|
|
16
31
|
const flags = new Map();
|
|
@@ -6,4 +6,13 @@ export declare function deployGeneratedSite(options: {
|
|
|
6
6
|
public?: boolean;
|
|
7
7
|
apiUrl?: string;
|
|
8
8
|
}): Promise<void>;
|
|
9
|
+
export declare function containedOutput(root: string, outputDir: string): string;
|
|
10
|
+
export declare function runLocalBuild(generator: string, command: string, root: string, siteUrl: string): Promise<void>;
|
|
11
|
+
export declare function packageStaticOutput(root: string): Promise<{
|
|
12
|
+
archive: Uint8Array;
|
|
13
|
+
files: number;
|
|
14
|
+
omittedSourceMaps: number;
|
|
15
|
+
sha256: string;
|
|
16
|
+
}>;
|
|
17
|
+
export declare function writeDryRunArchive(root: string, archive: Uint8Array): Promise<string>;
|
|
9
18
|
//# sourceMappingURL=artifact-deploy.d.ts.map
|
package/dist/artifact-deploy.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { spawn } from 'node:child_process';
|
|
2
2
|
import { createHash } from 'node:crypto';
|
|
3
|
-
import { lstat, readdir, readFile } from 'node:fs/promises';
|
|
4
|
-
import { basename, extname, relative, resolve, sep } from 'node:path';
|
|
3
|
+
import { lstat, mkdir, readdir, readFile, writeFile } from 'node:fs/promises';
|
|
4
|
+
import { basename, extname, join, relative, resolve, sep } from 'node:path';
|
|
5
5
|
import { zipSync } from 'fflate';
|
|
6
6
|
import { apiUrl, authenticatedRequest, authenticatedRequestOptional } from './auth.js';
|
|
7
7
|
import { DoxloopError } from './errors.js';
|
|
@@ -65,7 +65,8 @@ export async function deployGeneratedSite(options) {
|
|
|
65
65
|
const packaged = await packageStaticOutput(outputRoot);
|
|
66
66
|
building.done(`Built ${adapter.displayName} artifact`, `${packaged.files} files, ${formatBytes(packaged.archive.byteLength)}${packaged.omittedSourceMaps ? `, omitted ${packaged.omittedSourceMaps} source maps` : ''}`);
|
|
67
67
|
if (options.dryRun) {
|
|
68
|
-
|
|
68
|
+
const archivePath = await writeDryRunArchive(options.root, packaged.archive);
|
|
69
|
+
process.stdout.write(`\nDeployment is valid.\nGenerator: ${project.generator}\nVisibility: ${visibility}\nOutput: ${adapter.build.outputDir}\nFiles: ${packaged.files}\nArtifact: ${formatBytes(packaged.archive.byteLength)}\nArchive: ${archivePath}\nSHA-256: ${packaged.sha256}\nNo data was uploaded.\n`);
|
|
69
70
|
return;
|
|
70
71
|
}
|
|
71
72
|
if (!target)
|
|
@@ -104,7 +105,7 @@ function assertArtifactProject(project, generator) {
|
|
|
104
105
|
throw new DoxloopError(`Project "${project.slug}" expects generator "${project.generator ?? 'unknown'}", but this Doxloop project uses "${generator}". Choose another --slug.`);
|
|
105
106
|
}
|
|
106
107
|
}
|
|
107
|
-
function containedOutput(root, outputDir) {
|
|
108
|
+
export function containedOutput(root, outputDir) {
|
|
108
109
|
const base = resolve(root);
|
|
109
110
|
const output = resolve(base, outputDir);
|
|
110
111
|
if (output === base || !output.startsWith(`${base}${sep}`)) {
|
|
@@ -112,10 +113,14 @@ function containedOutput(root, outputDir) {
|
|
|
112
113
|
}
|
|
113
114
|
return output;
|
|
114
115
|
}
|
|
115
|
-
async function runLocalBuild(generator, command, root, siteUrl) {
|
|
116
|
+
export async function runLocalBuild(generator, command, root, siteUrl) {
|
|
116
117
|
const environment = { ...process.env, DOXLOOP_SITE_URL: siteUrl, SITE_URL: siteUrl };
|
|
117
118
|
delete environment.DOXLOOP_TOKEN;
|
|
118
119
|
delete environment.DOXBRIX_TOKEN;
|
|
120
|
+
delete environment.DOXLOOP_NETLIFY_TOKEN;
|
|
121
|
+
delete environment.NETLIFY_AUTH_TOKEN;
|
|
122
|
+
delete environment.DOXLOOP_VERCEL_TOKEN;
|
|
123
|
+
delete environment.VERCEL_TOKEN;
|
|
119
124
|
const effectiveCommand = generator === 'hugo'
|
|
120
125
|
? `${command} --baseURL ${JSON.stringify(`${siteUrl.replace(/\/$/, '')}/`)}`
|
|
121
126
|
: generator === 'jekyll'
|
|
@@ -135,7 +140,7 @@ async function runLocalBuild(generator, command, root, siteUrl) {
|
|
|
135
140
|
throw new DoxloopError(`Local generator build failed (exit ${code}). Doxloop does not install dependencies during deploy; install them and run \`${command}\` locally to diagnose.`);
|
|
136
141
|
}
|
|
137
142
|
}
|
|
138
|
-
async function packageStaticOutput(root) {
|
|
143
|
+
export async function packageStaticOutput(root) {
|
|
139
144
|
let rootStat;
|
|
140
145
|
try {
|
|
141
146
|
rootStat = await lstat(root);
|
|
@@ -181,9 +186,16 @@ async function packageStaticOutput(root) {
|
|
|
181
186
|
if (/-----BEGIN (?:RSA |EC |OPENSSH )?PRIVATE KEY-----/.test(text)) {
|
|
182
187
|
throw new DoxloopError(`Build output appears to contain a private key: ${path}`);
|
|
183
188
|
}
|
|
184
|
-
const activeTokens = [
|
|
189
|
+
const activeTokens = [
|
|
190
|
+
process.env.DOXLOOP_TOKEN,
|
|
191
|
+
process.env.DOXBRIX_TOKEN,
|
|
192
|
+
process.env.DOXLOOP_NETLIFY_TOKEN,
|
|
193
|
+
process.env.NETLIFY_AUTH_TOKEN,
|
|
194
|
+
process.env.DOXLOOP_VERCEL_TOKEN,
|
|
195
|
+
process.env.VERCEL_TOKEN,
|
|
196
|
+
].filter((value) => Boolean(value && value.length >= 12));
|
|
185
197
|
if (activeTokens.some((token) => text.includes(token)) || /\bdxb_[A-Za-z0-9_-]{20,}\b/.test(text)) {
|
|
186
|
-
throw new DoxloopError(`Build output appears to contain a
|
|
198
|
+
throw new DoxloopError(`Build output appears to contain a deployment access token: ${path}`);
|
|
187
199
|
}
|
|
188
200
|
entries[path] = new Uint8Array(data);
|
|
189
201
|
if (path === 'index.html')
|
|
@@ -200,6 +212,14 @@ async function packageStaticOutput(root) {
|
|
|
200
212
|
throw new DoxloopError(`Compressed artifact exceeds ${formatBytes(MAX_ZIP_BYTES)}.`);
|
|
201
213
|
return { archive, files, omittedSourceMaps, sha256: createHash('sha256').update(archive).digest('hex') };
|
|
202
214
|
}
|
|
215
|
+
export async function writeDryRunArchive(root, archive) {
|
|
216
|
+
const directory = join(root, '.doxloop', 'exports');
|
|
217
|
+
await mkdir(directory, { recursive: true, mode: 0o700 });
|
|
218
|
+
const stamp = new Date().toISOString().replace(/[:.]/g, '-');
|
|
219
|
+
const path = join(directory, `${stamp}.zip`);
|
|
220
|
+
await writeFile(path, archive, { mode: 0o600 });
|
|
221
|
+
return path;
|
|
222
|
+
}
|
|
203
223
|
function assertSafeArtifactName(path) {
|
|
204
224
|
const name = basename(path).toLowerCase();
|
|
205
225
|
if (name === '.env' || name.startsWith('.env.') || name === 'id_rsa' || name === 'id_ed25519' ||
|
package/dist/assets.d.ts
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import type { GeneratorAdapter } from './generator-api.js';
|
|
2
|
+
import type { DoxloopProject } from './types.js';
|
|
3
|
+
/**
|
|
4
|
+
* Images and downloadable files that documentation pages reference. Uploads
|
|
5
|
+
* land in the generator's conventional asset directory and are referenced by
|
|
6
|
+
* a public path pages can use directly; the library lists every asset with the
|
|
7
|
+
* pages that embed it so alt text can be edited and unused files deleted.
|
|
8
|
+
*/
|
|
9
|
+
export declare const ASSET_IMAGE_EXTENSIONS: Set<string>;
|
|
10
|
+
export declare const ASSET_FILE_EXTENSIONS: Set<string>;
|
|
11
|
+
export declare const MAX_ASSET_BYTES: number;
|
|
12
|
+
export interface AssetReference {
|
|
13
|
+
page: string;
|
|
14
|
+
alt: string;
|
|
15
|
+
}
|
|
16
|
+
export interface AssetEntry {
|
|
17
|
+
/** Project-relative path. */
|
|
18
|
+
path: string;
|
|
19
|
+
name: string;
|
|
20
|
+
/** The address a page uses to embed the asset. */
|
|
21
|
+
publicPath: string;
|
|
22
|
+
kind: 'image' | 'file';
|
|
23
|
+
bytes: number;
|
|
24
|
+
modifiedAt: string;
|
|
25
|
+
references: AssetReference[];
|
|
26
|
+
}
|
|
27
|
+
export interface AssetLibrary {
|
|
28
|
+
/** Project-relative directory uploads go to. */
|
|
29
|
+
directory: string;
|
|
30
|
+
publicPrefix: string;
|
|
31
|
+
maxBytes: number;
|
|
32
|
+
assets: AssetEntry[];
|
|
33
|
+
}
|
|
34
|
+
export declare function assetLocation(project: DoxloopProject, adapter?: GeneratorAdapter): {
|
|
35
|
+
directory: string;
|
|
36
|
+
publicPrefix: string;
|
|
37
|
+
};
|
|
38
|
+
export declare function readAssetLibrary(root: string): Promise<AssetLibrary>;
|
|
39
|
+
export declare function listAssets(root: string, loaded?: DoxloopProject, loadedAdapter?: GeneratorAdapter): Promise<AssetEntry[]>;
|
|
40
|
+
export interface AssetUpload {
|
|
41
|
+
name: string;
|
|
42
|
+
/** Base64 file content, with or without a data-URL prefix. */
|
|
43
|
+
data: string;
|
|
44
|
+
/** Project-relative directory inside the content or asset folder; defaults to the generator's asset directory. */
|
|
45
|
+
directory?: string;
|
|
46
|
+
/** Overwrite an existing file with the same name. */
|
|
47
|
+
replace?: boolean;
|
|
48
|
+
}
|
|
49
|
+
export declare function uploadAsset(root: string, raw: unknown): Promise<AssetEntry>;
|
|
50
|
+
export declare function deleteAsset(root: string, raw: unknown): Promise<{
|
|
51
|
+
removed: string;
|
|
52
|
+
}>;
|
|
53
|
+
/** Rewrite the alt text of every reference to one asset across the pages that embed it. */
|
|
54
|
+
export declare function updateAssetAlt(root: string, raw: unknown): Promise<AssetEntry>;
|
|
55
|
+
/** Replace one screenshot in a proposal workspace before the proposal is accepted. */
|
|
56
|
+
export declare function replaceRunCapture(root: string, raw: unknown): Promise<{
|
|
57
|
+
run: string;
|
|
58
|
+
path: string;
|
|
59
|
+
bytes: number;
|
|
60
|
+
}>;
|
|
61
|
+
/**
|
|
62
|
+
* Markdown image references and HTML `img`/`source` tags with their alt text.
|
|
63
|
+
* Exposed so the page service and the library agree on what counts as a
|
|
64
|
+
* reference.
|
|
65
|
+
*/
|
|
66
|
+
export declare function pageAssetReferences(raw: string): Array<{
|
|
67
|
+
reference: string;
|
|
68
|
+
alt: string;
|
|
69
|
+
}>;
|
|
70
|
+
export declare function safeAssetName(raw: string): string;
|
|
71
|
+
export declare function decodeUpload(data: string, name: string): Buffer;
|
|
72
|
+
//# sourceMappingURL=assets.d.ts.map
|