@stacksjs/defaults 0.75.360 → 0.75.362
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/ai/skills/stacks-ai/SKILL.md +22 -0
- package/ai/skills/stacks-native/CATALOG.md +4 -4
- package/ai/skills/stacks-payments/SKILL.md +28 -0
- package/ai/skills/stacks-technical-diagrams/LICENSE +202 -22
- package/ai/skills/stacks-technical-diagrams/LICENSE-MIT +22 -0
- package/ai/skills/stacks-technical-diagrams/NOTICE +26 -0
- package/ai/skills/stacks-technical-diagrams/SKILL.md +106 -266
- package/ai/skills/stacks-technical-diagrams/THIRD_PARTY_NOTICES.md +11 -2
- package/ai/skills/stacks-technical-diagrams/UPSTREAM.json +12 -0
- package/ai/skills/stacks-technical-diagrams/agents/openai.yaml +2 -2
- package/ai/skills/stacks-technical-diagrams/assets/template.html +3655 -3798
- package/ai/skills/stacks-technical-diagrams/bin/archify.mjs +7290 -0
- package/ai/skills/stacks-technical-diagrams/bin/delivery-update.mjs +7 -0
- package/ai/skills/stacks-technical-diagrams/bin/diagrams +1 -0
- package/ai/skills/stacks-technical-diagrams/bin/finalize.mjs +1056 -0
- package/ai/skills/stacks-technical-diagrams/bin/open-artifact.mjs +72 -12
- package/ai/skills/stacks-technical-diagrams/bin/preview.mjs +348 -31
- package/ai/skills/stacks-technical-diagrams/bin/recover-output.mjs +41 -0
- package/ai/skills/stacks-technical-diagrams/bin/technical-diagrams.mjs +3 -2097
- package/ai/skills/stacks-technical-diagrams/bin/visual-check.mjs +2142 -157
- package/ai/skills/stacks-technical-diagrams/brand-marks/README.md +3 -3
- package/ai/skills/stacks-technical-diagrams/delta/architecture-delta.mjs +130 -33
- package/ai/skills/stacks-technical-diagrams/examples/agent-run.lifecycle.json +25 -30
- package/ai/skills/stacks-technical-diagrams/examples/agent-tool-call.workflow.json +0 -20
- package/ai/skills/stacks-technical-diagrams/examples/archify-dev-activity.timeline.json +176 -0
- package/ai/skills/stacks-technical-diagrams/examples/archify-repository.tree.json +197 -0
- package/ai/skills/stacks-technical-diagrams/examples/async-job-roundtrip.sequence.json +1 -6
- package/ai/skills/stacks-technical-diagrams/examples/brand-aware-delivery.architecture.json +3 -16
- package/ai/skills/stacks-technical-diagrams/examples/cache-miss-request.sequence.json +2 -7
- package/ai/skills/stacks-technical-diagrams/examples/checkout-platform.base.architecture.json +2 -1
- package/ai/skills/stacks-technical-diagrams/examples/checkout-platform.head.architecture.json +2 -1
- package/ai/skills/stacks-technical-diagrams/examples/checkout-request.waterfall.json +20 -0
- package/ai/skills/stacks-technical-diagrams/examples/class-payment-processors-rendered.html +14987 -0
- package/ai/skills/stacks-technical-diagrams/examples/class-payments-rendered.html +14787 -0
- package/ai/skills/stacks-technical-diagrams/examples/dataflow-product-analytics.html +14880 -0
- package/ai/skills/stacks-technical-diagrams/examples/delivery.explainer.json +155 -0
- package/ai/skills/stacks-technical-diagrams/examples/deployment-release.lifecycle.json +57 -34
- package/ai/skills/stacks-technical-diagrams/examples/erd-orders-rendered.html +15112 -0
- package/ai/skills/stacks-technical-diagrams/examples/event-stream.dataflow.json +1 -6
- package/ai/skills/stacks-technical-diagrams/examples/example-rebuild.waterfall.json +212 -0
- package/ai/skills/stacks-technical-diagrams/examples/holiday-planning.workflow.json +143 -0
- package/ai/skills/stacks-technical-diagrams/examples/incident-response.workflow.json +1 -6
- package/ai/skills/stacks-technical-diagrams/examples/lifecycle-agent-run.html +14892 -0
- package/ai/skills/stacks-technical-diagrams/examples/locales/de.json +463 -0
- package/ai/skills/stacks-technical-diagrams/examples/locales/fr.json +463 -0
- package/ai/skills/stacks-technical-diagrams/examples/locales/fr.partial.json +22 -0
- package/ai/skills/stacks-technical-diagrams/examples/locales/it.json +463 -0
- package/ai/skills/stacks-technical-diagrams/examples/locales/ja.json +463 -0
- package/ai/skills/stacks-technical-diagrams/examples/locales/pt.json +463 -0
- package/ai/skills/stacks-technical-diagrams/examples/locales/ru.json +463 -0
- package/ai/skills/stacks-technical-diagrams/examples/orders.erd.json +337 -0
- package/ai/skills/stacks-technical-diagrams/examples/payment-incident.timeline.json +24 -0
- package/ai/skills/stacks-technical-diagrams/examples/payment-platform.tree.json +22 -0
- package/ai/skills/stacks-technical-diagrams/examples/payment-processors.class.json +177 -0
- package/ai/skills/stacks-technical-diagrams/examples/payments.class.json +85 -0
- package/ai/skills/stacks-technical-diagrams/examples/product-analytics.dataflow.json +1 -6
- package/ai/skills/stacks-technical-diagrams/examples/production-deployment.architecture.json +3 -23
- package/ai/skills/stacks-technical-diagrams/examples/release-delivery.workflow.json +1 -6
- package/ai/skills/stacks-technical-diagrams/examples/sequence-cache-miss-request.html +14895 -0
- package/ai/skills/stacks-technical-diagrams/examples/subscription-billing-rendered.html +15369 -0
- package/ai/skills/stacks-technical-diagrams/examples/subscription-billing.erd.json +548 -0
- package/ai/skills/stacks-technical-diagrams/examples/timeline-archify-dev-activity-rendered.html +15002 -0
- package/ai/skills/stacks-technical-diagrams/examples/timeline-payment-incident-rendered.html +14772 -0
- package/ai/skills/stacks-technical-diagrams/examples/tree-archify-repository-rendered.html +15071 -0
- package/ai/skills/stacks-technical-diagrams/examples/tree-payment-platform-rendered.html +14810 -0
- package/ai/skills/stacks-technical-diagrams/examples/waterfall-checkout-request-rendered.html +14754 -0
- package/ai/skills/stacks-technical-diagrams/examples/waterfall-example-rebuild-rendered.html +14940 -0
- package/ai/skills/stacks-technical-diagrams/examples/web-app-rendered.html +14889 -0
- package/ai/skills/stacks-technical-diagrams/examples/web-app.architecture.json +1 -6
- package/ai/skills/stacks-technical-diagrams/examples/workflow-agent-tool-call-rendered.html +14907 -0
- package/ai/skills/stacks-technical-diagrams/explainer/browser-check.mjs +208 -0
- package/ai/skills/stacks-technical-diagrams/explainer/charts.mjs +70 -0
- package/ai/skills/stacks-technical-diagrams/explainer/check.mjs +168 -0
- package/ai/skills/stacks-technical-diagrams/explainer/diagrams.mjs +518 -0
- package/ai/skills/stacks-technical-diagrams/explainer/diff.mjs +124 -0
- package/ai/skills/stacks-technical-diagrams/explainer/inline.mjs +155 -0
- package/ai/skills/stacks-technical-diagrams/explainer/layout.mjs +35 -0
- package/ai/skills/stacks-technical-diagrams/explainer/page-bundle.mjs +16 -0
- package/ai/skills/stacks-technical-diagrams/explainer/parse.mjs +300 -0
- package/ai/skills/stacks-technical-diagrams/explainer/patch.mjs +117 -0
- package/ai/skills/stacks-technical-diagrams/explainer/prepare.mjs +131 -0
- package/ai/skills/stacks-technical-diagrams/explainer/reader.css +472 -0
- package/ai/skills/stacks-technical-diagrams/explainer/reader.js +667 -0
- package/ai/skills/stacks-technical-diagrams/explainer/render.mjs +224 -0
- package/ai/skills/stacks-technical-diagrams/explainer/revision.mjs +20 -0
- package/ai/skills/stacks-technical-diagrams/explainer/sources.mjs +318 -0
- package/ai/skills/stacks-technical-diagrams/explainer/validate.mjs +138 -0
- package/ai/skills/stacks-technical-diagrams/explainer/vendor/pele/LICENSE +40 -0
- package/ai/skills/stacks-technical-diagrams/explainer/vendor/pele/README.md +5 -0
- package/ai/skills/stacks-technical-diagrams/explainer/vendor/pele/chunks/3DKXF73K.js +4 -0
- package/ai/skills/stacks-technical-diagrams/explainer/vendor/pele/chunks/42UTT654.js +3 -0
- package/ai/skills/stacks-technical-diagrams/explainer/vendor/pele/chunks/DKTLEL5E.js +1 -0
- package/ai/skills/stacks-technical-diagrams/explainer/vendor/pele/chunks/EEIX5ZYH.js +2 -0
- package/ai/skills/stacks-technical-diagrams/explainer/vendor/pele/chunks/EJ5KOXYD.js +25 -0
- package/ai/skills/stacks-technical-diagrams/explainer/vendor/pele/chunks/GUJTEFZK.js +1 -0
- package/ai/skills/stacks-technical-diagrams/explainer/vendor/pele/chunks/IZOKQ2GT.js +4 -0
- package/ai/skills/stacks-technical-diagrams/explainer/vendor/pele/chunks/L4LY553T.js +3 -0
- package/ai/skills/stacks-technical-diagrams/explainer/vendor/pele/chunks/MSKIJHB2.js +2 -0
- package/ai/skills/stacks-technical-diagrams/explainer/vendor/pele/chunks/RVARI57O.js +1 -0
- package/ai/skills/stacks-technical-diagrams/explainer/vendor/pele/chunks/UKSIWDDN.js +2 -0
- package/ai/skills/stacks-technical-diagrams/explainer/vendor/pele/chunks/UPTXNAXN.js +2 -0
- package/ai/skills/stacks-technical-diagrams/explainer/vendor/pele/chunks/XTDUIGUI.js +9 -0
- package/ai/skills/stacks-technical-diagrams/explainer/vendor/pele/chunks/YAYTP6L5.js +9 -0
- package/ai/skills/stacks-technical-diagrams/explainer/vendor/pele/core.js +1 -0
- package/ai/skills/stacks-technical-diagrams/explainer/vendor/pele/diagrams/pie.js +1 -0
- package/ai/skills/stacks-technical-diagrams/explainer/vendor/pele/diagrams/quadrantChart.js +1 -0
- package/ai/skills/stacks-technical-diagrams/explainer/vendor/pele/diagrams/radar.js +1 -0
- package/ai/skills/stacks-technical-diagrams/explainer/vendor/pele/diagrams/sankey.js +1 -0
- package/ai/skills/stacks-technical-diagrams/explainer/vendor/pele/diagrams/venn.js +1 -0
- package/ai/skills/stacks-technical-diagrams/explainer/vendor/pele/diagrams/xychart.js +1 -0
- package/ai/skills/stacks-technical-diagrams/explainer/writing.mjs +139 -0
- package/ai/skills/stacks-technical-diagrams/locales/README.md +40 -0
- package/ai/skills/stacks-technical-diagrams/locales/ar.json +488 -0
- package/ai/skills/stacks-technical-diagrams/locales/en.json +488 -0
- package/ai/skills/stacks-technical-diagrams/locales/es.json +488 -0
- package/ai/skills/stacks-technical-diagrams/locales/ko.json +488 -0
- package/ai/skills/stacks-technical-diagrams/locales/manifest.json +10 -0
- package/ai/skills/stacks-technical-diagrams/locales/zh-CN.json +488 -0
- package/ai/skills/stacks-technical-diagrams/locales/zh-TW.json +488 -0
- package/ai/skills/stacks-technical-diagrams/migrations/workflow-v2.mjs +1 -0
- package/ai/skills/stacks-technical-diagrams/recipes/scenarios.mjs +115 -51
- package/ai/skills/stacks-technical-diagrams/references/architecture-layout-repair.md +18 -0
- package/ai/skills/stacks-technical-diagrams/references/authoring-contract.md +453 -51
- package/ai/skills/stacks-technical-diagrams/references/authoring-defaults.md +57 -0
- package/ai/skills/stacks-technical-diagrams/references/brand-marks.md +57 -3
- package/ai/skills/stacks-technical-diagrams/references/delivery-contract.md +603 -58
- package/ai/skills/stacks-technical-diagrams/references/explainer-authoring.md +274 -0
- package/ai/skills/stacks-technical-diagrams/references/repository-authoring.md +126 -0
- package/ai/skills/stacks-technical-diagrams/references/update-awareness.md +13 -0
- package/ai/skills/stacks-technical-diagrams/references/viewer-runtime.md +18 -11
- package/ai/skills/stacks-technical-diagrams/renderers/architecture/grid.mjs +1 -1
- package/ai/skills/stacks-technical-diagrams/renderers/architecture/labels.mjs +2 -0
- package/ai/skills/stacks-technical-diagrams/renderers/architecture/render-architecture.mjs +550 -424
- package/ai/skills/stacks-technical-diagrams/renderers/architecture/routing.mjs +1469 -0
- package/ai/skills/stacks-technical-diagrams/renderers/class/README.md +129 -0
- package/ai/skills/stacks-technical-diagrams/renderers/class/render-class.mjs +1095 -0
- package/ai/skills/stacks-technical-diagrams/renderers/dataflow/README.md +89 -17
- package/ai/skills/stacks-technical-diagrams/renderers/dataflow/dataflow-compiler.mjs +1605 -0
- package/ai/skills/stacks-technical-diagrams/renderers/dataflow/joint-label-routing.mjs +165 -0
- package/ai/skills/stacks-technical-diagrams/renderers/dataflow/label-capacity.mjs +81 -0
- package/ai/skills/stacks-technical-diagrams/renderers/dataflow/render-dataflow.mjs +14 -474
- package/ai/skills/stacks-technical-diagrams/renderers/erd/README.md +246 -0
- package/ai/skills/stacks-technical-diagrams/renderers/erd/grid.mjs +150 -0
- package/ai/skills/stacks-technical-diagrams/renderers/erd/render-erd.mjs +1588 -0
- package/ai/skills/stacks-technical-diagrams/renderers/explainer/render-explainer.mjs +42 -0
- package/ai/skills/stacks-technical-diagrams/renderers/lifecycle/README.md +83 -94
- package/ai/skills/stacks-technical-diagrams/renderers/lifecycle/render-lifecycle.mjs +990 -417
- package/ai/skills/stacks-technical-diagrams/renderers/sequence/README.md +49 -17
- package/ai/skills/stacks-technical-diagrams/renderers/sequence/render-sequence.mjs +735 -94
- package/ai/skills/stacks-technical-diagrams/renderers/shared/atomic-output.mjs +1982 -0
- package/ai/skills/stacks-technical-diagrams/renderers/shared/automatic-labels.mjs +290 -0
- package/ai/skills/stacks-technical-diagrams/renderers/shared/brand-marks.mjs +169 -26
- package/ai/skills/stacks-technical-diagrams/renderers/shared/cli.mjs +266 -54
- package/ai/skills/stacks-technical-diagrams/renderers/shared/desktop-readability.mjs +106 -0
- package/ai/skills/stacks-technical-diagrams/renderers/shared/diagnostics.mjs +0 -0
- package/ai/skills/stacks-technical-diagrams/renderers/shared/generated-validators.mjs +268 -2
- package/ai/skills/stacks-technical-diagrams/renderers/shared/geometry.mjs +774 -124
- package/ai/skills/stacks-technical-diagrams/renderers/shared/i18n.mjs +289 -556
- package/ai/skills/stacks-technical-diagrams/renderers/shared/layout-report.mjs +18 -1
- package/ai/skills/stacks-technical-diagrams/renderers/shared/legend.mjs +16 -6
- package/ai/skills/stacks-technical-diagrams/renderers/shared/node-card-style.mjs +32 -0
- package/ai/skills/stacks-technical-diagrams/renderers/shared/output-path.mjs +329 -229
- package/ai/skills/stacks-technical-diagrams/renderers/shared/owned-temp-directory.mjs +82 -0
- package/ai/skills/stacks-technical-diagrams/renderers/shared/path-semantics.mjs +1017 -0
- package/ai/skills/stacks-technical-diagrams/renderers/shared/portable-path.mjs +408 -0
- package/ai/skills/stacks-technical-diagrams/renderers/shared/repository-evidence.mjs +235 -79
- package/ai/skills/stacks-technical-diagrams/renderers/shared/repository-location.mjs +17 -4
- package/ai/skills/stacks-technical-diagrams/renderers/shared/route-quality.mjs +670 -0
- package/ai/skills/stacks-technical-diagrams/renderers/shared/sidecar-path.mjs +38 -0
- package/ai/skills/stacks-technical-diagrams/renderers/shared/software-notices.mjs +6 -0
- package/ai/skills/stacks-technical-diagrams/renderers/shared/spatial-grid.mjs +66 -0
- package/ai/skills/stacks-technical-diagrams/renderers/shared/svg-path-analysis.mjs +135 -0
- package/ai/skills/stacks-technical-diagrams/renderers/shared/text-fit.mjs +59 -3
- package/ai/skills/stacks-technical-diagrams/renderers/shared/utils.mjs +63 -35
- package/ai/skills/stacks-technical-diagrams/renderers/shared/validator.mjs +2 -5
- package/ai/skills/stacks-technical-diagrams/renderers/timeline/README.md +76 -0
- package/ai/skills/stacks-technical-diagrams/renderers/timeline/render-timeline.mjs +576 -0
- package/ai/skills/stacks-technical-diagrams/renderers/tree/README.md +96 -0
- package/ai/skills/stacks-technical-diagrams/renderers/tree/render-tree.mjs +573 -0
- package/ai/skills/stacks-technical-diagrams/renderers/waterfall/README.md +73 -0
- package/ai/skills/stacks-technical-diagrams/renderers/waterfall/render-waterfall.mjs +413 -0
- package/ai/skills/stacks-technical-diagrams/renderers/workflow/README.md +137 -23
- package/ai/skills/stacks-technical-diagrams/renderers/workflow/render-workflow.mjs +18 -6
- package/ai/skills/stacks-technical-diagrams/renderers/workflow/workflow-compiler.mjs +2230 -710
- package/ai/skills/stacks-technical-diagrams/renderers/workflow/workflow-migration-geometry.mjs +17 -0
- package/ai/skills/stacks-technical-diagrams/renderers/workflow/workflow-placement-repair.mjs +94 -0
- package/ai/skills/stacks-technical-diagrams/schemas/README.md +87 -40
- package/ai/skills/stacks-technical-diagrams/schemas/architecture.schema.json +7 -33
- package/ai/skills/stacks-technical-diagrams/schemas/class.schema.json +345 -0
- package/ai/skills/stacks-technical-diagrams/schemas/common.schema.json +68 -2
- package/ai/skills/stacks-technical-diagrams/schemas/dataflow.schema.json +14 -3
- package/ai/skills/stacks-technical-diagrams/schemas/erd.schema.json +381 -0
- package/ai/skills/stacks-technical-diagrams/schemas/explainer.schema.json +927 -0
- package/ai/skills/stacks-technical-diagrams/schemas/lifecycle.schema.json +23 -110
- package/ai/skills/stacks-technical-diagrams/schemas/sequence.schema.json +15 -4
- package/ai/skills/stacks-technical-diagrams/schemas/timeline.schema.json +160 -0
- package/ai/skills/stacks-technical-diagrams/schemas/tree.schema.json +141 -0
- package/ai/skills/stacks-technical-diagrams/schemas/waterfall.schema.json +147 -0
- package/ai/skills/stacks-technical-diagrams/schemas/workflow.schema.json +14 -3
- package/ai/skills/stacks-technical-diagrams/scripts/check-render-output.mjs +876 -243
- package/ai/skills/stacks-technical-diagrams/scripts/check-update.mjs +1717 -0
- package/ai/skills/stacks-technical-diagrams/scripts/delivery-update-child.mjs +26 -0
- package/ai/skills/stacks-technical-diagrams/scripts/generate-brand-marks.mjs +141 -0
- package/ai/skills/stacks-technical-diagrams/scripts/generate-validators.mjs +105 -0
- package/ai/skills/stacks-technical-diagrams/scripts/render-examples.mjs +11 -1
- package/ai/skills/stacks-technical-diagrams/scripts/update-contract.mjs +187 -0
- package/ai/skills/stacks-technical-diagrams/skill-release.json +10 -0
- package/ide/vscode/package.json +1 -1
- package/package.json +2 -2
|
@@ -1,236 +1,359 @@
|
|
|
1
|
-
import fs from 'node:fs';
|
|
2
1
|
import path from 'node:path';
|
|
2
|
+
import {
|
|
3
|
+
containedBy,
|
|
4
|
+
isValidWindowsSmbShareName,
|
|
5
|
+
isWindowsIpcShare,
|
|
6
|
+
resolvePhysicalLocation,
|
|
7
|
+
sameLocation,
|
|
8
|
+
} from './path-semantics.mjs';
|
|
9
|
+
import { PortablePathError, validatePortablePath } from './portable-path.mjs';
|
|
3
10
|
|
|
4
|
-
|
|
5
|
-
const
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
segments: absolutePath.slice(root.length).split(path.sep).filter(Boolean),
|
|
13
|
-
};
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
function canonicalize(targetPath, depth) {
|
|
17
|
-
const absolutePath = path.resolve(targetPath);
|
|
18
|
-
const { root, segments } = splitAbsolute(absolutePath);
|
|
19
|
-
let current = root;
|
|
20
|
-
|
|
21
|
-
for (let index = 0; index < segments.length; index += 1) {
|
|
22
|
-
const candidate = path.join(current, segments[index]);
|
|
23
|
-
let stat;
|
|
24
|
-
try {
|
|
25
|
-
stat = fs.lstatSync(candidate);
|
|
26
|
-
} catch (error) {
|
|
27
|
-
if (error.code === 'ENOENT' || error.code === 'ENOTDIR') {
|
|
28
|
-
return path.resolve(current, ...segments.slice(index));
|
|
29
|
-
}
|
|
30
|
-
throw error;
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
if (stat.isSymbolicLink()) {
|
|
34
|
-
if (depth >= MAX_SYMLINK_DEPTH) {
|
|
35
|
-
const error = new Error(`Could not resolve path because a symbolic-link cycle includes "${candidate}".`);
|
|
36
|
-
error.code = 'ELOOP';
|
|
37
|
-
error.path = candidate;
|
|
38
|
-
throw error;
|
|
39
|
-
}
|
|
40
|
-
const link = fs.readlinkSync(candidate);
|
|
41
|
-
const linkTarget = path.isAbsolute(link) ? link : path.resolve(path.dirname(candidate), link);
|
|
42
|
-
return canonicalize(path.join(linkTarget, ...segments.slice(index + 1)), depth + 1);
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
current = fs.realpathSync.native(candidate);
|
|
11
|
+
export function canonicalFuturePath(targetPath) {
|
|
12
|
+
const resolution = resolvePhysicalLocation(targetPath);
|
|
13
|
+
if (resolution.status === 'resolved') {
|
|
14
|
+
if (resolution.location.kind === 'existing') return resolution.location.path;
|
|
15
|
+
return path.join(
|
|
16
|
+
resolution.location.ancestorPath,
|
|
17
|
+
...resolution.location.unresolved,
|
|
18
|
+
);
|
|
46
19
|
}
|
|
47
|
-
|
|
48
|
-
|
|
20
|
+
const output = path.resolve(targetPath);
|
|
21
|
+
if (resolution.reason.code === 'symlink-cycle') {
|
|
22
|
+
throw new OutputPathError(`Output path contains a symbolic-link cycle: "${output}".`, {
|
|
23
|
+
code: 'output/symlink-cycle',
|
|
24
|
+
message: 'Output path could not be resolved because it contains a symbolic-link cycle.',
|
|
25
|
+
subject: { output },
|
|
26
|
+
evidence: { relation: resolution.reason },
|
|
27
|
+
supportedFixes: ['remove the symbolic-link cycle or choose an output path outside it'],
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
throw new OutputPathError('Output path could not be resolved safely.', {
|
|
31
|
+
code: 'output/path-resolution-indeterminate',
|
|
32
|
+
message: 'Output path could not be resolved safely for the requested filesystem location.',
|
|
33
|
+
subject: { output },
|
|
34
|
+
evidence: { relation: resolution.reason },
|
|
35
|
+
supportedFixes: ['use an ordinary local filesystem path that can be resolved safely, then retry'],
|
|
36
|
+
});
|
|
49
37
|
}
|
|
50
38
|
|
|
51
|
-
export function
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
39
|
+
export function pathsAlias(leftPath, rightPath) {
|
|
40
|
+
const relation = sameLocation(leftPath, rightPath);
|
|
41
|
+
if (relation.status === 'match') return true;
|
|
42
|
+
if (relation.status === 'different') return false;
|
|
43
|
+
if (relation.reason.code === 'ancestor-not-directory') return false;
|
|
44
|
+
if (relation.reason.code === 'symlink-cycle') {
|
|
45
|
+
const output = path.resolve(relation.reason.side === 'right' ? rightPath : leftPath);
|
|
57
46
|
throw new OutputPathError(`Output path contains a symbolic-link cycle: "${output}".`, {
|
|
58
47
|
code: 'output/symlink-cycle',
|
|
59
48
|
message: 'Output path could not be resolved because it contains a symbolic-link cycle.',
|
|
60
49
|
subject: { output },
|
|
61
|
-
evidence: {
|
|
62
|
-
systemCode: 'ELOOP',
|
|
63
|
-
...(error.path ? { cycleAt: path.resolve(error.path) } : {}),
|
|
64
|
-
},
|
|
50
|
+
evidence: { relation: relation.reason },
|
|
65
51
|
supportedFixes: ['remove the symbolic-link cycle or choose an output path outside it'],
|
|
66
52
|
});
|
|
67
53
|
}
|
|
54
|
+
throw new OutputPathError('Path identity could not be determined safely.', {
|
|
55
|
+
code: 'output/path-identity-indeterminate',
|
|
56
|
+
message: 'Path identity could not be determined safely for the requested filesystem location.',
|
|
57
|
+
subject: { left: path.resolve(leftPath), right: path.resolve(rightPath) },
|
|
58
|
+
evidence: { relation: relation.reason },
|
|
59
|
+
supportedFixes: ['use ordinary local filesystem paths whose identity can be verified, then retry'],
|
|
60
|
+
});
|
|
68
61
|
}
|
|
69
62
|
|
|
70
|
-
function
|
|
71
|
-
|
|
63
|
+
function pathIsInside(directoryPath, targetPath) {
|
|
64
|
+
const relation = containedBy(directoryPath, targetPath);
|
|
65
|
+
if (relation.status === 'match') return true;
|
|
66
|
+
if (relation.status === 'different') return false;
|
|
67
|
+
throw new OutputPathError('Output containment could not be determined safely.', {
|
|
68
|
+
code: 'output/containment-indeterminate',
|
|
69
|
+
message: 'Output containment could not be determined safely for the requested filesystem location.',
|
|
70
|
+
subject: { output: path.resolve(targetPath), cwd: path.resolve(directoryPath) },
|
|
71
|
+
evidence: { relation: relation.reason },
|
|
72
|
+
supportedFixes: ['use an output beneath an ordinary local directory whose identity can be verified'],
|
|
73
|
+
});
|
|
72
74
|
}
|
|
73
75
|
|
|
74
|
-
function
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
76
|
+
function authoredOutputDiagnostic(error, rawOutput) {
|
|
77
|
+
const absolute = error?.reason === 'absolute';
|
|
78
|
+
const code = absolute ? 'output/meta-absolute' : 'output/meta-path-syntax';
|
|
79
|
+
const message = absolute
|
|
80
|
+
? 'meta.output must be a relative path resolved from the current working directory.'
|
|
81
|
+
: 'meta.output must be a portable POSIX-relative path.';
|
|
82
|
+
return {
|
|
83
|
+
code,
|
|
84
|
+
message,
|
|
85
|
+
subject: { output: rawOutput, path: '/meta/output' },
|
|
86
|
+
evidence: {
|
|
87
|
+
reason: error?.reason || 'invalid',
|
|
88
|
+
...(error?.segmentIndex !== undefined ? { segmentIndex: error.segmentIndex } : {}),
|
|
89
|
+
...(error?.segment !== undefined ? { segment: error.segment } : {}),
|
|
90
|
+
...(error?.utf8Bytes !== undefined ? { utf8Bytes: error.utf8Bytes } : {}),
|
|
91
|
+
...(error?.utf16CodeUnits !== undefined ? { utf16CodeUnits: error.utf16CodeUnits } : {}),
|
|
92
|
+
...(error?.limit !== undefined ? { limit: error.limit } : {}),
|
|
93
|
+
},
|
|
94
|
+
supportedFixes: ['set meta.output to a portable relative .html path such as reports/diagram.html'],
|
|
95
|
+
};
|
|
79
96
|
}
|
|
80
97
|
|
|
81
|
-
function
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
+
function nativeOutputDiagnostic(rawOutput, reason, details = {}) {
|
|
99
|
+
return {
|
|
100
|
+
code: 'output/native-path-syntax',
|
|
101
|
+
message: 'The output path is not a valid native filesystem path on this host.',
|
|
102
|
+
subject: { output: rawOutput },
|
|
103
|
+
evidence: { reason, ...details },
|
|
104
|
+
supportedFixes: ['choose an ordinary filesystem path without device names, alternate data streams, trailing dots or spaces, or overlong components'],
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function throwNativeOutputDiagnostic(rawOutput, reason, details = {}) {
|
|
109
|
+
const diagnostic = nativeOutputDiagnostic(rawOutput, reason, details);
|
|
110
|
+
throw new OutputPathError(diagnostic.message, diagnostic);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
function windowsExtendedTailComponents(rawOutput, tail) {
|
|
114
|
+
const authoredComponents = tail.split('\\');
|
|
115
|
+
// Preserve one trailing separator for directory arguments, but never repair
|
|
116
|
+
// an empty component inside the raw extended namespace.
|
|
117
|
+
if (authoredComponents.slice(0, -1).some((component) => component.length === 0)) {
|
|
118
|
+
throwNativeOutputDiagnostic(rawOutput, 'windows-extended-root');
|
|
98
119
|
}
|
|
120
|
+
return authoredComponents.filter(Boolean);
|
|
99
121
|
}
|
|
100
122
|
|
|
101
|
-
function
|
|
102
|
-
if (
|
|
103
|
-
|
|
123
|
+
function rejectWindowsIpcShare(rawOutput, share) {
|
|
124
|
+
if (isWindowsIpcShare(share)) {
|
|
125
|
+
throwNativeOutputDiagnostic(rawOutput, 'windows-ipc-namespace', { share });
|
|
126
|
+
}
|
|
104
127
|
}
|
|
105
128
|
|
|
106
|
-
function
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
const lookupPath = path.join(directoryPath, lookupName);
|
|
129
|
+
function validateWindowsNativeComponent(
|
|
130
|
+
rawOutput,
|
|
131
|
+
component,
|
|
132
|
+
componentIndex,
|
|
133
|
+
{ allowReservedName = false } = {},
|
|
134
|
+
) {
|
|
113
135
|
try {
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
}
|
|
131
|
-
try {
|
|
132
|
-
result = fs.realpathSync.native(authoredPath) === fs.realpathSync.native(lookupPath);
|
|
133
|
-
} catch {
|
|
134
|
-
result = null;
|
|
135
|
-
}
|
|
136
|
-
}
|
|
137
|
-
}
|
|
138
|
-
} catch {
|
|
139
|
-
result = null;
|
|
140
|
-
} finally {
|
|
141
|
-
if (fileDescriptor !== undefined) {
|
|
142
|
-
try {
|
|
143
|
-
fs.closeSync(fileDescriptor);
|
|
144
|
-
} catch {
|
|
145
|
-
cleaned = false;
|
|
146
|
-
}
|
|
147
|
-
}
|
|
148
|
-
if (created) {
|
|
149
|
-
try {
|
|
150
|
-
fs.unlinkSync(authoredPath);
|
|
151
|
-
} catch {
|
|
152
|
-
cleaned = false;
|
|
153
|
-
}
|
|
136
|
+
// Prefix the component so a colon is classified as an ADS separator,
|
|
137
|
+
// rather than allowing the generic URI detector to claim it first.
|
|
138
|
+
validatePortablePath(`native/${component}`, { profile: 'output' });
|
|
139
|
+
} catch (error) {
|
|
140
|
+
if (!(error instanceof PortablePathError)) throw error;
|
|
141
|
+
if (allowReservedName && error.reason === 'windows-reserved-name') return;
|
|
142
|
+
// Native Windows arguments may intentionally name an existing 8.3 alias.
|
|
143
|
+
// Portable authored/archive paths reject that ambiguous spelling, while
|
|
144
|
+
// native resolution lets the filesystem prove the existing target.
|
|
145
|
+
if (error.reason === 'windows-short-name') {
|
|
146
|
+
if (component.length <= 255) return;
|
|
147
|
+
throwNativeOutputDiagnostic(rawOutput, 'component-too-long', {
|
|
148
|
+
component,
|
|
149
|
+
componentIndex,
|
|
150
|
+
utf16CodeUnits: component.length,
|
|
151
|
+
limit: 255,
|
|
152
|
+
});
|
|
154
153
|
}
|
|
154
|
+
// Native Windows filesystems bound components in UTF-16 code units. The
|
|
155
|
+
// stricter UTF-8 bound belongs to portable authored/archive names only.
|
|
156
|
+
if (error.reason === 'component-too-long' && error.utf16CodeUnits <= 255) return;
|
|
157
|
+
throwNativeOutputDiagnostic(rawOutput, error.reason, {
|
|
158
|
+
component,
|
|
159
|
+
componentIndex,
|
|
160
|
+
...(error.character !== undefined ? { character: error.character } : {}),
|
|
161
|
+
...(error.utf8Bytes !== undefined ? { utf8Bytes: error.utf8Bytes } : {}),
|
|
162
|
+
...(error.utf16CodeUnits !== undefined ? { utf16CodeUnits: error.utf16CodeUnits } : {}),
|
|
163
|
+
...(error.limit !== undefined ? { limit: error.limit } : {}),
|
|
164
|
+
});
|
|
155
165
|
}
|
|
156
|
-
return cleaned ? result : null;
|
|
157
166
|
}
|
|
158
167
|
|
|
159
|
-
function
|
|
160
|
-
|
|
161
|
-
if (
|
|
162
|
-
|
|
168
|
+
function validateWindowsUncRootComponents(rawOutput, server, share) {
|
|
169
|
+
rejectWindowsIpcShare(rawOutput, share);
|
|
170
|
+
if (!isValidWindowsSmbShareName(share)) {
|
|
171
|
+
throwNativeOutputDiagnostic(rawOutput, 'windows-unc-share-name', {
|
|
172
|
+
share,
|
|
173
|
+
utf16CodeUnits: share.length,
|
|
174
|
+
limit: 80,
|
|
175
|
+
});
|
|
163
176
|
}
|
|
177
|
+
// UNC servers and shares are root components, not DOS file names. Keep the
|
|
178
|
+
// server's ordinary native syntax checks while allowing names such as CON.
|
|
179
|
+
validateWindowsNativeComponent(rawOutput, server, 0, { allowReservedName: true });
|
|
180
|
+
}
|
|
164
181
|
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
caseAuthored.toLowerCase(),
|
|
174
|
-
),
|
|
175
|
-
normalizationInsensitive: probeNamesAlias(
|
|
176
|
-
directory.path,
|
|
177
|
-
normalizationAuthored,
|
|
178
|
-
normalizationAuthored.normalize('NFD'),
|
|
179
|
-
),
|
|
180
|
-
};
|
|
181
|
-
if (
|
|
182
|
-
cacheKey
|
|
183
|
-
&& semantics.caseInsensitive !== null
|
|
184
|
-
&& semantics.normalizationInsensitive !== null
|
|
185
|
-
) {
|
|
186
|
-
directorySemanticsCache.set(cacheKey, semantics);
|
|
182
|
+
function windowsExtendedPathComponents(rawOutput) {
|
|
183
|
+
// The extended-length namespace deliberately bypasses Win32 normalization.
|
|
184
|
+
// Inspect its original spelling so a dot segment cannot retarget a UNC share.
|
|
185
|
+
if (rawOutput.includes('/')) {
|
|
186
|
+
throwNativeOutputDiagnostic(rawOutput, 'windows-extended-separator', { character: '/' });
|
|
187
|
+
}
|
|
188
|
+
if (/^\\\\\?\\(?:GLOBALROOT|Device)(?:\\|$)/iu.test(rawOutput)) {
|
|
189
|
+
throwNativeOutputDiagnostic(rawOutput, 'windows-device-namespace');
|
|
187
190
|
}
|
|
188
|
-
return semantics;
|
|
189
|
-
}
|
|
190
191
|
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
192
|
+
const drive = rawOutput.match(/^\\\\\?\\[A-Za-z]:\\/u);
|
|
193
|
+
if (drive) {
|
|
194
|
+
return windowsExtendedTailComponents(rawOutput, rawOutput.slice(drive[0].length));
|
|
195
|
+
}
|
|
194
196
|
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
197
|
+
const uncPrefix = rawOutput.match(/^\\\\\?\\UNC\\/iu);
|
|
198
|
+
if (uncPrefix) {
|
|
199
|
+
const authoredTail = rawOutput.slice(uncPrefix[0].length);
|
|
200
|
+
const components = authoredTail.split('\\');
|
|
201
|
+
if (components.length < 2 || components[0].length === 0 || components[1].length === 0) {
|
|
202
|
+
throwNativeOutputDiagnostic(rawOutput, 'windows-extended-root');
|
|
203
|
+
}
|
|
204
|
+
validateWindowsUncRootComponents(rawOutput, components[0], components[1]);
|
|
205
|
+
return windowsExtendedTailComponents(rawOutput, components.slice(2).join('\\'));
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
throwNativeOutputDiagnostic(rawOutput, 'windows-extended-root');
|
|
209
|
+
}
|
|
199
210
|
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
211
|
+
function validateWindowsRawUncRoot(rawOutput) {
|
|
212
|
+
if (!/^[\\/]{2}/u.test(rawOutput) || /^[\\/]{2}[.?][\\/]/u.test(rawOutput)) return;
|
|
213
|
+
// Validate the raw server/share boundary before win32.normalize can collapse
|
|
214
|
+
// an empty share or mix the two UNC separator spellings.
|
|
215
|
+
const unc = rawOutput.match(/^([\\/])\1([^\\/]+)\1([^\\/]+)(?:[\\/]|$)/u);
|
|
216
|
+
if (!unc) {
|
|
217
|
+
throwNativeOutputDiagnostic(rawOutput, 'windows-unc-root');
|
|
204
218
|
}
|
|
219
|
+
validateWindowsUncRootComponents(rawOutput, unc[2], unc[3]);
|
|
220
|
+
}
|
|
205
221
|
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
comparableLeft = comparableLeft.normalize('NFC');
|
|
211
|
-
comparableRight = comparableRight.normalize('NFC');
|
|
222
|
+
function windowsPathComponents(rawOutput, normalized) {
|
|
223
|
+
const authoredUncPrefix = /^[\\/]{2}/u.test(rawOutput);
|
|
224
|
+
if (/^\\\\\.\\/u.test(normalized)) {
|
|
225
|
+
throwNativeOutputDiagnostic(rawOutput, 'windows-device-namespace');
|
|
212
226
|
}
|
|
213
|
-
if (
|
|
214
|
-
|
|
215
|
-
comparableRight = comparableRight.toLowerCase();
|
|
227
|
+
if (/^\\\\\?\\/u.test(normalized)) {
|
|
228
|
+
throwNativeOutputDiagnostic(rawOutput, 'windows-extended-root');
|
|
216
229
|
}
|
|
217
|
-
|
|
230
|
+
if (normalized.startsWith('\\\\')) {
|
|
231
|
+
const unc = normalized.match(/^\\\\([^\\]+)\\([^\\]+)(?:\\|$)/u);
|
|
232
|
+
if (!unc) throwNativeOutputDiagnostic(rawOutput, 'windows-unc-root');
|
|
233
|
+
return normalized.slice(unc[0].length).split('\\').filter(Boolean);
|
|
234
|
+
}
|
|
235
|
+
if (authoredUncPrefix) throwNativeOutputDiagnostic(rawOutput, 'windows-unc-root');
|
|
236
|
+
if (normalized.startsWith('\\')) {
|
|
237
|
+
throwNativeOutputDiagnostic(rawOutput, 'current-drive-rooted');
|
|
238
|
+
}
|
|
239
|
+
const root = path.win32.parse(normalized).root;
|
|
240
|
+
return normalized
|
|
241
|
+
.slice(root.length)
|
|
242
|
+
.split(/[\\/]+/u)
|
|
243
|
+
// normalize() retains leading navigation for a relative path. Those dot
|
|
244
|
+
// segments are path syntax, not filename components subject to name rules.
|
|
245
|
+
.filter((component) => component && component !== '.' && component !== '..');
|
|
218
246
|
}
|
|
219
247
|
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
248
|
+
/**
|
|
249
|
+
* Validate command-line/default output paths using the active host's native
|
|
250
|
+
* syntax. Unlike authored portable paths, absolute paths and native separators
|
|
251
|
+
* remain supported. File outputs reject a trailing separator before native
|
|
252
|
+
* resolution can erase it; directory callers must opt in explicitly. The
|
|
253
|
+
* component bound also protects derived sidecars from failing after an
|
|
254
|
+
* operation has already started mutating the destination.
|
|
255
|
+
*/
|
|
256
|
+
export function validateNativeOutputPath(
|
|
257
|
+
rawOutput,
|
|
258
|
+
{ platform = process.platform, kind = 'file' } = {},
|
|
259
|
+
) {
|
|
260
|
+
if (kind !== 'file' && kind !== 'directory') {
|
|
261
|
+
throw new TypeError(`Unsupported native output path kind: ${JSON.stringify(kind)}`);
|
|
262
|
+
}
|
|
263
|
+
if (typeof rawOutput !== 'string' || rawOutput.length === 0) {
|
|
264
|
+
throwNativeOutputDiagnostic(rawOutput, 'empty');
|
|
265
|
+
}
|
|
266
|
+
if (rawOutput.includes('\0')) throwNativeOutputDiagnostic(rawOutput, 'nul-character');
|
|
267
|
+
if (/[\uD800-\uDFFF]/u.test(rawOutput)) {
|
|
268
|
+
throwNativeOutputDiagnostic(rawOutput, 'unpaired-surrogate');
|
|
269
|
+
}
|
|
270
|
+
const hasTrailingSeparator = platform === 'win32'
|
|
271
|
+
? /[\\/]$/u.test(rawOutput)
|
|
272
|
+
: rawOutput.endsWith(path.posix.sep);
|
|
273
|
+
if (kind === 'file' && hasTrailingSeparator) {
|
|
274
|
+
throwNativeOutputDiagnostic(rawOutput, 'trailing-separator', { kind });
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
let normalized;
|
|
278
|
+
let components;
|
|
279
|
+
if (platform === 'win32') {
|
|
280
|
+
if (/^[A-Za-z]:(?:$|[^\\/])/u.test(rawOutput)) {
|
|
281
|
+
throwNativeOutputDiagnostic(rawOutput, 'drive-relative');
|
|
282
|
+
}
|
|
283
|
+
const extendedPrefix = rawOutput.startsWith('\\\\?\\');
|
|
284
|
+
if (extendedPrefix) {
|
|
285
|
+
components = windowsExtendedPathComponents(rawOutput);
|
|
286
|
+
} else {
|
|
287
|
+
validateWindowsRawUncRoot(rawOutput);
|
|
288
|
+
normalized = path.win32.normalize(rawOutput);
|
|
289
|
+
components = windowsPathComponents(rawOutput, normalized);
|
|
290
|
+
}
|
|
291
|
+
for (const [componentIndex, component] of components.entries()) {
|
|
292
|
+
validateWindowsNativeComponent(rawOutput, component, componentIndex);
|
|
293
|
+
}
|
|
294
|
+
} else {
|
|
295
|
+
normalized = path.resolve(rawOutput);
|
|
296
|
+
const root = path.parse(normalized).root;
|
|
297
|
+
components = normalized.slice(root.length).split(path.sep).filter(Boolean);
|
|
298
|
+
for (const [componentIndex, component] of components.entries()) {
|
|
299
|
+
const utf8Bytes = Buffer.byteLength(component, 'utf8');
|
|
300
|
+
if (utf8Bytes > 255) {
|
|
301
|
+
throwNativeOutputDiagnostic(rawOutput, 'component-too-long', {
|
|
302
|
+
component,
|
|
303
|
+
componentIndex,
|
|
304
|
+
utf8Bytes,
|
|
305
|
+
limit: 255,
|
|
306
|
+
});
|
|
307
|
+
}
|
|
308
|
+
}
|
|
228
309
|
}
|
|
310
|
+
return rawOutput;
|
|
229
311
|
}
|
|
230
312
|
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
313
|
+
/** Validate a CLI directory argument before native resolution can normalize away its raw syntax. */
|
|
314
|
+
export function resolveNativeOutputDirectory(
|
|
315
|
+
rawDirectory,
|
|
316
|
+
{ platform = process.platform, cwd = process.cwd() } = {},
|
|
317
|
+
) {
|
|
318
|
+
validateNativeOutputPath(rawDirectory, { platform, kind: 'directory' });
|
|
319
|
+
const pathApi = platform === 'win32' ? path.win32 : path.posix;
|
|
320
|
+
return pathApi.resolve(cwd, rawDirectory);
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
export function validateAuthoredOutputPath(rawOutput, { cwd = process.cwd() } = {}) {
|
|
324
|
+
try {
|
|
325
|
+
validatePortablePath(rawOutput, { profile: 'output' });
|
|
326
|
+
} catch (error) {
|
|
327
|
+
if (!(error instanceof PortablePathError)) throw error;
|
|
328
|
+
const diagnostic = authoredOutputDiagnostic(error, rawOutput);
|
|
329
|
+
throw new OutputPathError(diagnostic.message, diagnostic);
|
|
330
|
+
}
|
|
331
|
+
if (path.posix.extname(rawOutput).toLowerCase() !== '.html') {
|
|
332
|
+
throw new OutputPathError('meta.output must target an .html file.', {
|
|
333
|
+
code: 'output/meta-extension',
|
|
334
|
+
message: 'meta.output must target an .html file.',
|
|
335
|
+
subject: { output: rawOutput, path: '/meta/output' },
|
|
336
|
+
supportedFixes: ['change meta.output to a portable path ending in .html'],
|
|
337
|
+
});
|
|
338
|
+
}
|
|
339
|
+
const outputPath = path.resolve(cwd, rawOutput);
|
|
340
|
+
if (path.extname(canonicalFuturePath(outputPath)).toLowerCase() !== '.html') {
|
|
341
|
+
throw new OutputPathError('meta.output must resolve to an .html file.', {
|
|
342
|
+
code: 'output/meta-resolved-extension',
|
|
343
|
+
message: 'meta.output must resolve to an .html file after symbolic links are followed.',
|
|
344
|
+
subject: { output: rawOutput },
|
|
345
|
+
supportedFixes: ['remove the symbolic-link alias or point it to an .html target inside the current working directory'],
|
|
346
|
+
});
|
|
347
|
+
}
|
|
348
|
+
if (!pathIsInside(cwd, outputPath)) {
|
|
349
|
+
throw new OutputPathError('meta.output must stay inside the current working directory.', {
|
|
350
|
+
code: 'output/meta-outside-cwd',
|
|
351
|
+
message: 'meta.output must stay inside the current working directory after symbolic links are resolved.',
|
|
352
|
+
subject: { output: rawOutput, cwd: path.resolve(cwd) },
|
|
353
|
+
supportedFixes: ['set meta.output to a relative .html path inside the current working directory'],
|
|
354
|
+
});
|
|
355
|
+
}
|
|
356
|
+
return rawOutput;
|
|
234
357
|
}
|
|
235
358
|
|
|
236
359
|
export class OutputPathError extends Error {
|
|
@@ -256,46 +379,17 @@ export function resolveOutputPath({
|
|
|
256
379
|
otherOutputPaths = [],
|
|
257
380
|
cwd = process.cwd(),
|
|
258
381
|
requiredExtension = '.html',
|
|
382
|
+
platform = process.platform,
|
|
259
383
|
}) {
|
|
260
|
-
|
|
261
|
-
const source = requestedOutput
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
message: 'meta.output must be a relative path resolved from the current working directory.',
|
|
269
|
-
subject: { output: rawOutput },
|
|
270
|
-
supportedFixes: ['set meta.output to a relative .html path inside the current working directory'],
|
|
271
|
-
});
|
|
272
|
-
}
|
|
273
|
-
if (source === 'meta' && path.extname(rawOutput).toLowerCase() !== '.html') {
|
|
274
|
-
throw new OutputPathError('meta.output must target an .html file.', {
|
|
275
|
-
code: 'output/meta-extension',
|
|
276
|
-
message: 'meta.output must target an .html file.',
|
|
277
|
-
subject: { output: rawOutput },
|
|
278
|
-
supportedFixes: ['change meta.output to a path ending in .html'],
|
|
279
|
-
});
|
|
280
|
-
}
|
|
384
|
+
if (authoredOutput !== undefined) validateAuthoredOutputPath(authoredOutput, { cwd });
|
|
385
|
+
const source = requestedOutput !== undefined
|
|
386
|
+
? 'cli'
|
|
387
|
+
: (authoredOutput !== undefined ? 'meta' : 'default');
|
|
388
|
+
const rawOutput = source === 'cli'
|
|
389
|
+
? requestedOutput
|
|
390
|
+
: (source === 'meta' ? authoredOutput : defaultOutput);
|
|
391
|
+
if (source !== 'meta') validateNativeOutputPath(rawOutput, { platform, kind: 'file' });
|
|
281
392
|
const outputPath = path.resolve(cwd, rawOutput);
|
|
282
|
-
if (source === 'meta' && path.extname(canonicalFuturePath(outputPath)).toLowerCase() !== '.html') {
|
|
283
|
-
throw new OutputPathError('meta.output must resolve to an .html file.', {
|
|
284
|
-
code: 'output/meta-resolved-extension',
|
|
285
|
-
message: 'meta.output must resolve to an .html file after symbolic links are followed.',
|
|
286
|
-
subject: { output: rawOutput },
|
|
287
|
-
supportedFixes: ['remove the symbolic-link alias or point it to an .html target inside the current working directory'],
|
|
288
|
-
});
|
|
289
|
-
}
|
|
290
|
-
if (source === 'meta' && !pathIsInside(cwd, outputPath)) {
|
|
291
|
-
throw new OutputPathError('meta.output must stay inside the current working directory.', {
|
|
292
|
-
code: 'output/meta-outside-cwd',
|
|
293
|
-
message: 'meta.output must stay inside the current working directory after symbolic links are resolved.',
|
|
294
|
-
subject: { output: rawOutput, cwd: path.resolve(cwd) },
|
|
295
|
-
supportedFixes: ['set meta.output to a relative .html path inside the current working directory'],
|
|
296
|
-
});
|
|
297
|
-
}
|
|
298
|
-
|
|
299
393
|
for (const inputPath of inputPaths) {
|
|
300
394
|
if (!pathsAlias(outputPath, inputPath)) continue;
|
|
301
395
|
throw new OutputPathError(`Output must not replace ${inputDescription}.`, {
|
|
@@ -319,7 +413,13 @@ export function resolveOutputPath({
|
|
|
319
413
|
// Alias checks above retain priority when a target would overwrite an input.
|
|
320
414
|
if (source === 'cli') {
|
|
321
415
|
const resolvedOutput = canonicalFuturePath(outputPath);
|
|
322
|
-
const
|
|
416
|
+
const authoredExtension = path.extname(rawOutput).toLowerCase();
|
|
417
|
+
const existingWindowsHtmlAlias = platform === 'win32'
|
|
418
|
+
&& requiredExtension === '.html'
|
|
419
|
+
&& authoredExtension === '.htm'
|
|
420
|
+
&& path.extname(resolvedOutput).toLowerCase() === requiredExtension
|
|
421
|
+
&& pathsAlias(outputPath, resolvedOutput);
|
|
422
|
+
const authoredMatches = authoredExtension === requiredExtension || existingWindowsHtmlAlias;
|
|
323
423
|
const resolvedMatches = path.extname(resolvedOutput).toLowerCase() === requiredExtension;
|
|
324
424
|
if (!authoredMatches || !resolvedMatches) {
|
|
325
425
|
const message = `CLI output must ${authoredMatches ? 'resolve to' : 'target'} a ${requiredExtension} file.`;
|