@doxbrix/doxloop 0.1.5 → 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 +1145 -0
- package/README.md +422 -227
- 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 +24 -5
- package/dist/agents.js +60 -9
- 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 +3 -0
- 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 +25 -0
- 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 +49 -32
- 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';
|
|
@@ -126,7 +127,7 @@ export async function detectAgents() {
|
|
|
126
127
|
}
|
|
127
128
|
return found;
|
|
128
129
|
}
|
|
129
|
-
export async function installAgent(name) {
|
|
130
|
+
export async function installAgent(name, options = {}) {
|
|
130
131
|
const agent = AGENT_CATALOG.find((candidate) => candidate.name === name);
|
|
131
132
|
if (!agent)
|
|
132
133
|
throw new DoxloopError(`Unsupported agent: ${name}`);
|
|
@@ -136,9 +137,14 @@ export async function installAgent(name) {
|
|
|
136
137
|
}
|
|
137
138
|
const exitCode = await new Promise((resolveInstall, rejectInstall) => {
|
|
138
139
|
const child = spawn(npm, ['install', '--global', agent.packageName], {
|
|
139
|
-
stdio: 'inherit',
|
|
140
|
+
stdio: options.onOutput ? ['ignore', 'pipe', 'pipe'] : 'inherit',
|
|
140
141
|
env: process.env,
|
|
141
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
|
+
}
|
|
142
148
|
child.once('error', (error) => {
|
|
143
149
|
rejectInstall(new DoxloopError(`Could not start npm to install ${agent.displayName}: ${error.message}`, 2));
|
|
144
150
|
});
|
|
@@ -172,13 +178,9 @@ export async function chooseAgent(preferred) {
|
|
|
172
178
|
const expected = preferred ?? 'Codex, Claude Code, or Gemini';
|
|
173
179
|
throw new DoxloopError(`${expected} is not available on PATH. Install an agent or use --print to copy the prompt.`);
|
|
174
180
|
}
|
|
175
|
-
export async function agentAuthenticationStatus(agent) {
|
|
176
|
-
if (agent.name === 'gemini')
|
|
177
|
-
return
|
|
178
|
-
status: 'unknown',
|
|
179
|
-
detail: 'Gemini authentication cannot be verified automatically; it will be checked when Gemini starts.',
|
|
180
|
-
};
|
|
181
|
-
}
|
|
181
|
+
export async function agentAuthenticationStatus(agent, options = {}) {
|
|
182
|
+
if (agent.name === 'gemini')
|
|
183
|
+
return geminiAuthenticationStatus(options);
|
|
182
184
|
const args = agent.name === 'codex'
|
|
183
185
|
? ['login', 'status']
|
|
184
186
|
: ['auth', 'status', '--json'];
|
|
@@ -219,6 +221,37 @@ export async function agentAuthenticationStatus(agent) {
|
|
|
219
221
|
: 'Claude Code is not signed in. Run `claude auth login`.',
|
|
220
222
|
};
|
|
221
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
|
+
}
|
|
222
255
|
async function runAgentStatus(executable, args) {
|
|
223
256
|
return new Promise((resolveStatus) => {
|
|
224
257
|
const child = spawn(executable, args, {
|
|
@@ -247,6 +280,24 @@ async function runAgentStatus(executable, args) {
|
|
|
247
280
|
});
|
|
248
281
|
}
|
|
249
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
|
+
}
|
|
250
301
|
const path = process.env.PATH;
|
|
251
302
|
if (!path)
|
|
252
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
|