@warpgogol/werkstatt-shared 0.14.0 → 0.15.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/README.md +31 -13
- package/README.uk.md +25 -15
- package/package.json +519 -762
- package/src/{share/agent → agent}/ard-catalog.ts +6 -1
- package/src/{share/agent → agent}/dns-aid.test.ts +2 -2
- package/src/{share/agent → agent}/manifest.ts +15 -1
- package/src/{share/agent → agent}/search.ts +6 -1
- package/src/{share → agent}/tests/api-catalog.test.ts +2 -2
- package/src/{share → agent}/tests/ard-catalog.test.ts +2 -2
- package/src/{share → agent}/tests/capability.test.ts +1 -1
- package/src/{share → agent}/tests/fleet-catalog.test.ts +1 -1
- package/src/{share → agent}/tests/knowledge.test.ts +2 -2
- package/src/{share → agent}/tests/manifest.test.ts +1 -1
- package/src/{share → agent}/tests/mcp-card.test.ts +2 -2
- package/src/agent/tests/omit-empty.test.ts +33 -0
- package/src/{share → agent}/tests/openapi.test.ts +2 -2
- package/src/{share → agent}/tests/proof.test.ts +1 -1
- package/src/{share → attribution}/material-credits.ts +4 -4
- package/src/{share → checks}/check-hints.ts +6 -1
- package/src/checks/index.ts +4 -1
- package/src/checks/lib/astro-site-url.ts +6 -1
- package/src/checks/lib/i18n.ts +14 -0
- package/src/checks/result-helpers.ts +22 -5
- package/src/checks/tests/remediation-auto-populate.test.ts +83 -0
- package/src/{share → client-scripts}/counter-utils.ts +6 -2
- package/src/{share/scripts → client-scripts}/external-link-qr.ts +11 -16
- package/src/{share/scripts → client-scripts}/external-links.ts +12 -1
- package/src/{share/scripts → client-scripts}/gsap-counter.ts +8 -3
- package/src/client-scripts/index.ts +35 -0
- package/src/{share/scripts → client-scripts}/inline-number-animation.ts +1 -1
- package/src/{share/scripts → client-scripts}/lenis.ts +6 -1
- package/src/{share/scripts → client-scripts}/lordicon.ts +6 -1
- package/src/{share/scripts → client-scripts}/orchestrator.ts +10 -1
- package/src/{share/scripts → client-scripts}/scheduler.ts +6 -1
- package/src/client-scripts/scroll-spy.ts +106 -0
- package/src/client-scripts/tests/external-link-qr.test.ts +8 -0
- package/src/client-scripts/tests/external-links.test.ts +25 -0
- package/src/client-scripts/tests/gsap-counter.test.ts +8 -0
- package/src/client-scripts/tests/gsap-mountain-journey.test.ts +8 -0
- package/src/client-scripts/tests/gsap-parallax.test.ts +8 -0
- package/src/client-scripts/tests/gsap-reveal.test.ts +8 -0
- package/src/client-scripts/tests/gsap-stagger.test.ts +8 -0
- package/src/client-scripts/tests/inline-number-animation.test.ts +8 -0
- package/src/client-scripts/tests/lenis.test.ts +8 -0
- package/src/client-scripts/tests/live-photos.test.ts +8 -0
- package/src/client-scripts/tests/lordicon.test.ts +8 -0
- package/src/client-scripts/tests/orchestrator.test.ts +47 -0
- package/src/client-scripts/tests/scheduler.test.ts +23 -0
- package/src/client-scripts/tests/scroll-spy.test.ts +204 -0
- package/src/{share/scripts → client-scripts/tests}/video-player.test.ts +51 -31
- package/src/component/contracts.ts +206 -0
- package/src/component/index.ts +11 -0
- package/src/{share → content}/content-reference.ts +3 -3
- package/src/{share/content → content}/entity-id.ts +6 -3
- package/src/content/index.ts +3 -0
- package/src/{share/content → content}/merge.ts +6 -1
- package/src/content/system-manifest.test.ts +107 -0
- package/src/content/system-manifest.ts +12 -94
- package/src/{share → content}/tests/resolve-field-path.test.ts +1 -1
- package/src/{share → content}/tests/substitute-deep.test.ts +1 -1
- package/src/content-discipline/index.ts +19 -0
- package/src/{share/content-discipline → content-discipline}/parsers.ts +2 -2
- package/src/{share/env.d.ts → env.d.ts} +6 -3
- package/src/fingerprint/canonical-json.ts +731 -0
- package/src/fingerprint/index.ts +12 -0
- package/src/fingerprint/primitives.ts +72 -0
- package/src/{share/i18n → i18n}/localization.ts +6 -2
- package/src/integration/adapters.ts +10 -71
- package/src/integration/dispatch.ts +1 -1
- package/src/integration/funnel.ts +6 -1
- package/src/integration/index.ts +101 -4
- package/src/integration/lagebild-destination-adapter.ts +108 -0
- package/src/integration/lagebild-ingress.ts +7 -12
- package/src/integration/orchestration.ts +11 -10
- package/src/integration/tests/deliver-event.test.ts +1 -1
- package/src/integration/tests/dispatch.test.ts +4 -4
- package/src/integration/tests/hub-runtime.test.ts +22 -14
- package/src/integration/vitest.config.ts +8 -0
- package/src/kernel/desired-state.ts +227 -0
- package/src/kernel/diagnostic.ts +289 -0
- package/src/kernel/fs-atomic.ts +131 -0
- package/src/kernel/index.ts +15 -0
- package/src/kernel/tests/fs-atomic.test.ts +150 -0
- package/src/kernel/tests/workspace-io.test.ts +187 -0
- package/src/kernel/types.ts +459 -0
- package/src/kernel/workspace-io.ts +248 -0
- package/src/{share → knowledge}/tests/claim.test.ts +1 -1
- package/src/{share → knowledge}/tests/derivation.test.ts +1 -1
- package/src/{share → knowledge}/tests/freshness.test.ts +1 -1
- package/src/{share → knowledge}/tests/ledger.test.ts +1 -1
- package/src/{share → knowledge}/tests/plan.test.ts +1 -1
- package/src/{share → knowledge}/tests/source.test.ts +1 -1
- package/src/legal/index.ts +15 -0
- package/src/{share/middleware → middleware}/access-protection.ts +8 -8
- package/src/{share → node}/dev-props-validator.ts +6 -1
- package/src/{share → node}/import-scan.ts +23 -3
- package/src/{share → node}/semantic/derived-prices-loader.ts +4 -2
- package/src/{share → node}/semantic/markdown-twin-provenance.ts +1 -1
- package/src/node/tests/derived-prices-loader.test.ts +59 -0
- package/src/node/tests/markdown-twin-provenance.test.ts +152 -0
- package/src/observability/env-resolver.ts +6 -1
- package/src/observability/index.ts +6 -1
- package/src/observability/otlp-converter.ts +6 -1
- package/src/observability/otlp-transport.ts +6 -1
- package/src/observability/pusher.ts +6 -1
- package/src/observability/redact.ts +6 -1
- package/src/observability/typed-refs.ts +6 -2
- package/src/observability/vitest.config.ts +1 -0
- package/src/{share → offers}/formula-eval.ts +3 -3
- package/src/{share → offers}/tests/formula-eval.test.ts +2 -2
- package/src/onboarding/index.ts +1 -0
- package/src/{share → onboarding}/tests/onboarding-yaml.test.ts +1 -1
- package/src/ontology/archetype-registry.ts +6 -1
- package/src/ontology/archetypes/index.json +33 -1
- package/src/ontology/archetypes/index.yaml +27 -1
- package/src/ontology/archetypes/sections/entity-grid.yaml +46 -0
- package/src/ontology/archetypes/sections/exit-diagram.yaml +43 -0
- package/src/ontology/biome-token-projection.ts +6 -1
- package/src/ontology/cosmic/index.ts +6 -1
- package/src/ontology/cosmic/moon-catalog.ts +6 -1
- package/src/ontology/cosmic/planet-catalog.ts +6 -1
- package/src/ontology/cosmic/star-catalog.ts +6 -1
- package/src/ontology/enums.ts +6 -1
- package/src/ontology/external-surfaces/index.ts +1 -1
- package/src/ontology/index.ts +6 -2
- package/src/ontology/manifest.ts +26 -4
- package/src/ontology/operations/artifact-store.ts +45 -0
- package/src/ontology/operations/dht.ts +98 -0
- package/src/ontology/operations/handoff.ts +92 -0
- package/src/ontology/operations/index.ts +187 -1
- package/src/ontology/operations/leitstand.ts +99 -0
- package/src/ontology/operations/materialization.ts +90 -0
- package/src/ontology/operations/mission.ts +111 -0
- package/src/ontology/operations/naming-policy.ts +132 -0
- package/src/ontology/operations/notausgang.ts +64 -0
- package/src/ontology/operations/release.ts +121 -0
- package/src/ontology/operations/sternsystem.ts +192 -0
- package/src/ontology/operations/werkstatt.ts +48 -0
- package/src/ontology/schemas/biome.ts +7 -2
- package/src/ontology/schemas/capability.ts +1 -1
- package/src/ontology/schemas/constellation.ts +6 -1
- package/src/ontology/schemas/index.ts +6 -2
- package/src/ontology/schemas/manifest-resolver.ts +6 -1
- package/src/ontology/schemas/page-entry.ts +9 -5
- package/src/ontology/schemas/system/growth.ts +6 -1
- package/src/ontology/schemas/system/manifest.ts +40 -1
- package/src/ontology/schemas/system/page-output.ts +7 -6
- package/src/ontology/schemas/system/tests/growth.test.ts +22 -0
- package/src/ontology/schemas/system/tests/integrations.test.ts +12 -0
- package/src/ontology/schemas/system/tests/manifest.test.ts +22 -0
- package/src/ontology/schemas/system/tests/page-output.test.ts +13 -0
- package/src/ontology/schemas/system/tests/release.test.ts +22 -0
- package/src/ontology/schemas/system/tests/text.test.ts +18 -0
- package/src/ontology/schemas/system/tests/verification.test.ts +12 -0
- package/src/ontology/schemas/system/text.ts +15 -0
- package/src/ontology/schemas/system.ts +6 -2
- package/src/ontology/schemas/tests/biome.test.ts +19 -0
- package/src/ontology/schemas/tests/capability.test.ts +22 -0
- package/src/ontology/schemas/tests/constellation.test.ts +22 -0
- package/src/ontology/schemas/tests/dns-records.test.ts +13 -0
- package/src/ontology/schemas/tests/manifest-resolver.test.ts +13 -0
- package/src/ontology/schemas/tests/page-entry.test.ts +22 -0
- package/src/ontology/schemas/tests/section-archetype.test.ts +12 -0
- package/src/ontology/schemas/tests/site-family.test.ts +18 -0
- package/src/ontology/schemas/tests/system.test.ts +20 -0
- package/src/ontology/shared-section-props/body-fragments.ts +2 -0
- package/src/ontology/tests/sternsystem-owner.test.ts +1 -1
- package/src/ontology/vitest.config.ts +1 -0
- package/src/{share → page}/page.ts +13 -9
- package/src/{share → page}/tests/dev-props-validator.test.ts +1 -1
- package/src/{share → page}/tests/hero-section-numbering-parity.test.ts +1 -1
- package/src/passport/index.ts +6 -1
- package/src/passport/key-rotate.ts +10 -2
- package/src/passport/schema.ts +6 -1
- package/src/passport/sign.ts +8 -3
- package/src/passport/vitest.config.ts +1 -0
- package/src/pbt/index.test.ts +95 -0
- package/src/pbt/index.ts +251 -0
- package/src/plugin/plugin-contract.ts +1 -1
- package/src/{share → policy}/entitlement.ts +24 -4
- package/src/{share → policy}/feature-policy.ts +8 -3
- package/src/{share → policy}/visibility.ts +7 -2
- package/src/redirects/redirects.ts +117 -0
- package/src/{share → redirects/tests}/redirects.test.ts +1 -1
- package/src/remediation/remediation-catalog.ts +316 -0
- package/src/{share/routes → routes}/template-filter.ts +1 -1
- package/src/{share → runtime}/runtime-context.ts +7 -2
- package/src/{share → runtime}/shared-context.ts +1 -1
- package/src/{share/schemas → schemas}/claims.ts +2 -2
- package/src/{share/schemas → schemas}/effects.ts +6 -1
- package/src/{share/schemas → schemas}/features.ts +1 -1
- package/src/{share/schemas → schemas}/index.ts +3 -3
- package/src/{share/schemas → schemas}/navigation.ts +6 -1
- package/src/{share/schemas → schemas}/page-base.ts +6 -2
- package/src/{share/schemas → schemas}/person.ts +1 -1
- package/src/{share/schemas → schemas}/standard-list-item.ts +1 -1
- package/src/schemas/tests/claim-records.test.ts +52 -0
- package/src/schemas/tests/claims.test.ts +23 -0
- package/src/schemas/tests/effects.test.ts +116 -0
- package/src/schemas/tests/features.test.ts +61 -0
- package/src/schemas/tests/horizontal-align.test.ts +16 -0
- package/src/schemas/tests/icon-color.test.ts +16 -0
- package/src/schemas/tests/live-photo.test.ts +40 -0
- package/src/schemas/tests/material-credit.test.ts +51 -0
- package/src/schemas/tests/media.test.ts +26 -0
- package/src/schemas/tests/navigation.test.ts +58 -0
- package/src/schemas/tests/page-base.test.ts +19 -0
- package/src/schemas/tests/participant.test.ts +26 -0
- package/src/schemas/tests/person.test.ts +12 -0
- package/src/schemas/tests/print.test.ts +30 -0
- package/src/schemas/tests/section-background.test.ts +31 -0
- package/src/schemas/tests/section-body.test.ts +16 -0
- package/src/schemas/tests/section-cards.test.ts +17 -0
- package/src/schemas/tests/section-cta.test.ts +41 -0
- package/src/schemas/tests/section-header.test.ts +31 -0
- package/src/schemas/tests/section-image.test.ts +24 -0
- package/src/schemas/tests/section-motion.test.ts +29 -0
- package/src/schemas/tests/section-shell.test.ts +42 -0
- package/src/schemas/tests/section-stats.test.ts +17 -0
- package/src/schemas/tests/site-background.test.ts +33 -0
- package/src/schemas/tests/standard-list-item.test.ts +16 -0
- package/src/{share → sections}/rfc0042-utils.ts +1 -1
- package/src/{share/semantic → semantic}/block-extractors/index.ts +20 -1
- package/src/{share/semantic → semantic}/business-projection.ts +95 -1
- package/src/semantic/canonical-uri.ts +54 -0
- package/src/{share/semantic → semantic}/extract.ts +100 -1
- package/src/semantic/fact-extraction.test.ts +47 -0
- package/src/semantic/fact-extraction.ts +46 -0
- package/src/semantic/freshness.ts +67 -0
- package/src/{share/semantic → semantic}/ids.ts +6 -1
- package/src/{share/semantic → semantic}/index.ts +9 -2
- package/src/{share/semantic → semantic}/jsonld/breadcrumb.ts +6 -2
- package/src/{share/semantic → semantic}/jsonld/context.ts +6 -2
- package/src/{share/semantic → semantic}/jsonld/faq.ts +6 -2
- package/src/{share/semantic → semantic}/jsonld/initiative.ts +6 -1
- package/src/{share/semantic → semantic}/jsonld/organization.ts +6 -1
- package/src/{share/semantic → semantic}/jsonld/person.ts +6 -2
- package/src/{share/semantic → semantic}/jsonld/service.ts +6 -2
- package/src/semantic/jsonld/shared.ts +36 -0
- package/src/semantic/jsonld/tests/article.test.ts +89 -0
- package/src/semantic/jsonld/tests/breadcrumb.test.ts +62 -0
- package/src/semantic/jsonld/tests/collection-list.test.ts +39 -0
- package/src/semantic/jsonld/tests/faq.test.ts +60 -0
- package/src/semantic/jsonld/tests/initiative.test.ts +56 -0
- package/src/semantic/jsonld/tests/organization.test.ts +123 -0
- package/src/semantic/jsonld/tests/person.test.ts +89 -0
- package/src/semantic/jsonld/tests/service.test.ts +65 -0
- package/src/semantic/jsonld/tests/shared.test.ts +41 -0
- package/src/semantic/jsonld/tests/video.test.ts +84 -0
- package/src/semantic/jsonld/tests/webpage.test.ts +93 -0
- package/src/semantic/jsonld/tests/website.test.ts +27 -0
- package/src/semantic/jsonld/types.ts +24 -0
- package/src/{share/semantic → semantic}/jsonld/webpage.ts +6 -1
- package/src/semantic/jsonld/website.ts +32 -0
- package/src/{share/semantic → semantic}/jsonld.ts +6 -1
- package/src/{share/semantic → semantic}/llms.ts +41 -3
- package/src/{share/semantic → semantic}/models.ts +27 -38
- package/src/{share/semantic → semantic}/organization-profile.ts +3 -0
- package/src/{share/semantic → semantic}/page-builders/markdown-page.ts +6 -3
- package/src/{share/semantic → semantic}/page-utils.ts +6 -3
- package/src/semantic/tests/ai.test.ts +55 -0
- package/src/semantic/tests/block-extraction.test.ts +34 -0
- package/src/{share/tests/breadcrumbs.test.ts → semantic/tests/breadcrumbs-0229.test.ts} +1 -1
- package/src/semantic/tests/breadcrumbs.test.ts +127 -0
- package/src/{share → semantic}/tests/build-page-price-markers.test.ts +2 -2
- package/src/semantic/tests/build-page.test.ts +122 -0
- package/src/semantic/tests/business-projection.test.ts +367 -0
- package/src/semantic/tests/canonical-uri.test.ts +56 -0
- package/src/semantic/tests/extract.test.ts +209 -0
- package/src/semantic/tests/feed.test.ts +113 -0
- package/src/semantic/tests/freshness.pbt.test.ts +108 -0
- package/src/semantic/tests/helpers.ts +32 -0
- package/src/semantic/tests/ids.test.ts +118 -0
- package/src/{share/tests/image-sitemap.test.ts → semantic/tests/image-sitemap-0150.test.ts} +1 -1
- package/src/semantic/tests/image-sitemap.test.ts +109 -0
- package/src/{share → semantic}/tests/jsonld-service.test.ts +2 -2
- package/src/{share → semantic}/tests/jsonld-video.test.ts +3 -3
- package/src/{share → semantic}/tests/jsonld-webpage.test.ts +3 -3
- package/src/semantic/tests/jsonld.test.ts +61 -0
- package/src/{share → semantic}/tests/llms-0789.test.ts +2 -2
- package/src/semantic/tests/llms-policy.test.ts +60 -0
- package/src/semantic/tests/llms.test.ts +164 -0
- package/src/semantic/tests/markdown-hygiene.test.ts +72 -0
- package/src/semantic/tests/markdown-page.test.ts +97 -0
- package/src/{share → semantic}/tests/organization-jsonld.test.ts +3 -3
- package/src/semantic/tests/organization-profile.test.ts +107 -0
- package/src/{share/tests/output-projection.test.ts → semantic/tests/output-projection-0143.test.ts} +1 -1
- package/src/semantic/tests/output-projection.test.ts +77 -0
- package/src/semantic/tests/page-markdown.test.ts +98 -0
- package/src/semantic/tests/page-utils.test.ts +88 -0
- package/src/{share/tests/price-marker-resolver.test.ts → semantic/tests/price-marker-resolver-0767.test.ts} +2 -2
- package/src/semantic/tests/price-marker-resolver.test.ts +90 -0
- package/src/semantic/tests/project-claims.pbt.test.ts +60 -0
- package/src/semantic/tests/robots.test.ts +70 -0
- package/src/{share/semantic → semantic}/tests/split-sentences.test.ts +1 -1
- package/src/{share → semantic}/tests/twin-path.test.ts +1 -1
- package/src/semantic/tests/update-stamp.test.ts +102 -0
- package/src/signing/index.ts +13 -0
- package/src/signing/key.ts +139 -0
- package/src/signing/sign.ts +66 -0
- package/src/signing/types.ts +29 -0
- package/src/{share/slug → slug}/slug-url.ts +4 -1
- package/src/stack/run-tool.ts +108 -0
- package/src/stack/stack-checks.ts +124 -0
- package/src/stack/tests/run-tool.test.ts +138 -0
- package/src/stack/tests/stack-checks.test.ts +141 -0
- package/src/stack/tests/walk-files.test.ts +131 -0
- package/src/stack/walk-files.ts +114 -0
- package/src/surface/blueprint.ts +342 -66
- package/src/surface/decision-composer.ts +6 -1
- package/src/surface/geo.ts +6 -1
- package/src/surface/index.ts +11 -7
- package/src/surface/types.ts +2 -2
- package/src/surface/vitest.config.ts +1 -0
- package/src/tests/signing-core.test.ts +129 -0
- package/src/{share → text}/css-value-normalize.ts +6 -1
- package/src/text/string-utils.ts +24 -0
- package/src/{share → text}/tests/text-normalize.test.ts +5 -0
- package/src/{share → text}/text-normalize.ts +4 -3
- package/src/{share → text}/wrap-inline-numbers.ts +1 -1
- package/src/types/axiom-study.d.ts +36 -0
- package/src/types/hls-js-light.d.ts +21 -0
- package/src/types/qrcode-browser.d.ts +23 -0
- package/src/typography/fix.pbt.test.ts +208 -0
- package/src/typography/fix.ts +148 -0
- package/src/typography/index.ts +54 -0
- package/src/typography/locale-defaults.ts +97 -0
- package/src/typography/rules-tier1.test.ts +459 -0
- package/src/typography/rules-tier1.ts +925 -0
- package/src/typography/rules-tier2-locale.pbt.test.ts +190 -0
- package/src/typography/rules-tier2-locale.test.ts +286 -0
- package/src/typography/rules-tier2-locale.ts +399 -0
- package/src/typography/rules-tier2-structure.test.ts +330 -0
- package/src/typography/rules-tier2-structure.ts +378 -0
- package/src/typography/rules-tier3-advisory.test.ts +290 -0
- package/src/typography/rules-tier3-advisory.ts +239 -0
- package/src/typography/text-surface.test.ts +244 -0
- package/src/typography/text-surface.ts +448 -0
- package/src/index.ts +0 -14
- package/src/integration/port-barrel.ts +0 -105
- package/src/share/content/index.ts +0 -16
- package/src/share/content-discipline/index.ts +0 -14
- package/src/share/index.ts +0 -86
- package/src/share/legal/index.ts +0 -1
- package/src/share/redirects.ts +0 -54
- package/src/share/scripts/index.ts +0 -26
- package/src/share/semantic/jsonld/shared.ts +0 -31
- package/src/share/semantic/jsonld/types.ts +0 -18
- package/src/share/semantic/jsonld/website.ts +0 -27
- package/src/share/string-utils.ts +0 -19
- package/src/share/types/axiom-study.d.ts +0 -22
- package/src/share/types/hls-js-light.d.ts +0 -16
- package/src/share/types/qrcode-browser.d.ts +0 -18
- package/src/share/utility-registry.yaml +0 -36
- package/src/share/vitest.config.ts +0 -8
- package/src/surface/blueprint-schema.ts +0 -314
- package/src/surface/blueprint-types.ts +0 -256
- /package/src/{share/agent → agent}/api-catalog.ts +0 -0
- /package/src/{share/agent → agent}/capability.ts +0 -0
- /package/src/{share/agent → agent}/dns-aid.ts +0 -0
- /package/src/{share/agent → agent}/fleet-catalog.ts +0 -0
- /package/src/{share/agent → agent}/index.ts +0 -0
- /package/src/{share/agent → agent}/knowledge.ts +0 -0
- /package/src/{share/agent → agent}/mcp-card.ts +0 -0
- /package/src/{share/agent → agent}/omit-empty.ts +0 -0
- /package/src/{share/agent → agent}/openapi.ts +0 -0
- /package/src/{share/agent → agent}/proof.ts +0 -0
- /package/src/{share/agent → agent}/search.test.ts +0 -0
- /package/src/{share → attribution}/attribution-display.ts +0 -0
- /package/src/{share/scripts → client-scripts}/gsap-mountain-journey.ts +0 -0
- /package/src/{share/scripts → client-scripts}/gsap-parallax.ts +0 -0
- /package/src/{share/scripts → client-scripts}/gsap-reveal.ts +0 -0
- /package/src/{share/scripts → client-scripts}/gsap-stagger.ts +0 -0
- /package/src/{share/scripts → client-scripts}/live-photos.ts +0 -0
- /package/src/{share/scripts → client-scripts}/video-player.ts +0 -0
- /package/src/{share/content → content}/resolve-field-path.ts +0 -0
- /package/src/{share/content → content}/substitute-deep.ts +0 -0
- /package/src/{share → content}/tests/content-ref-index.test.ts +0 -0
- /package/src/{share/content-discipline → content-discipline}/types.ts +0 -0
- /package/src/{share → image-provider}/image-provider.ts +0 -0
- /package/src/{share/knowledge → knowledge}/claim.ts +0 -0
- /package/src/{share/knowledge → knowledge}/derivation.ts +0 -0
- /package/src/{share/knowledge → knowledge}/freshness.ts +0 -0
- /package/src/{share/knowledge → knowledge}/index.ts +0 -0
- /package/src/{share/knowledge → knowledge}/ledger.ts +0 -0
- /package/src/{share/knowledge → knowledge}/plan.ts +0 -0
- /package/src/{share/knowledge → knowledge}/source.ts +0 -0
- /package/src/{share/legal → legal}/translation-policy.ts +0 -0
- /package/src/{share/middleware → middleware}/language-redirect.ts +0 -0
- /package/src/{share/middleware → middleware}/tests/access-protection.test.ts +0 -0
- /package/src/{share → node}/fs/index.ts +0 -0
- /package/src/{share/tests/markdown-twin-provenance.test.ts → node/tests/markdown-twin-provenance-0613.test.ts} +0 -0
- /package/src/{share → offers}/offer-capacity.ts +0 -0
- /package/src/{share/onboarding-yaml/index.ts → onboarding/yaml.ts} +0 -0
- /package/src/{share → ontology}/tests/ontology-role-by-cosmic-name.test.ts +0 -0
- /package/src/{share/routes → routes}/tests/template-filter.test.ts +0 -0
- /package/src/{share/schemas → schemas}/claim-records.ts +0 -0
- /package/src/{share/schemas → schemas}/horizontal-align.ts +0 -0
- /package/src/{share/schemas → schemas}/icon-color.ts +0 -0
- /package/src/{share/schemas → schemas}/live-photo.ts +0 -0
- /package/src/{share/schemas → schemas}/material-credit.ts +0 -0
- /package/src/{share/schemas → schemas}/media.ts +0 -0
- /package/src/{share/schemas → schemas}/participant.ts +0 -0
- /package/src/{share/schemas → schemas}/print.ts +0 -0
- /package/src/{share/schemas → schemas}/section-background.ts +0 -0
- /package/src/{share/schemas → schemas}/section-body.ts +0 -0
- /package/src/{share/schemas → schemas}/section-cards.ts +0 -0
- /package/src/{share/schemas → schemas}/section-cta.ts +0 -0
- /package/src/{share/schemas → schemas}/section-header.ts +0 -0
- /package/src/{share/schemas → schemas}/section-image.ts +0 -0
- /package/src/{share/schemas → schemas}/section-motion.ts +0 -0
- /package/src/{share/schemas → schemas}/section-shell.ts +0 -0
- /package/src/{share/schemas → schemas}/section-stats.ts +0 -0
- /package/src/{share/schemas → schemas}/site-background.ts +0 -0
- /package/src/{share/semantic → semantic}/ai.ts +0 -0
- /package/src/{share/semantic → semantic}/block-extraction.ts +0 -0
- /package/src/{share/semantic → semantic}/breadcrumbs.ts +0 -0
- /package/src/{share/semantic → semantic}/build-page.ts +0 -0
- /package/src/{share/semantic → semantic}/feed.ts +0 -0
- /package/src/{share/semantic → semantic}/image-sitemap.ts +0 -0
- /package/src/{share/semantic → semantic}/jsonld/article.ts +0 -0
- /package/src/{share/semantic → semantic}/jsonld/collection-list.ts +0 -0
- /package/src/{share/semantic → semantic}/jsonld/video.ts +0 -0
- /package/src/{share/semantic → semantic}/llms-policy.ts +0 -0
- /package/src/{share/semantic → semantic}/markdown-hygiene.ts +0 -0
- /package/src/{share/semantic → semantic}/output-projection.ts +0 -0
- /package/src/{share/semantic → semantic}/page-markdown.ts +0 -0
- /package/src/{share/semantic → semantic}/price-marker-resolver.ts +0 -0
- /package/src/{share/semantic → semantic}/robots.ts +0 -0
- /package/src/{share/semantic → semantic}/update-stamp.ts +0 -0
- /package/src/{share/slug → slug}/heading-slugger.ts +0 -0
- /package/src/{share/slug → slug}/index.ts +0 -0
- /package/src/{share/slug → slug}/slug-id.ts +0 -0
- /package/src/{share/slug → slug}/strategies.ts +0 -0
- /package/src/{share/slug → slug}/tests/slug.test.ts +0 -0
- /package/src/{share → text}/tests/text-normalize.pbt.test.ts +0 -0
- /package/src/{share → text}/text-position.ts +0 -0
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
import { describe, it, expect, beforeEach, afterEach } from "vitest";
|
|
2
|
+
import { mkdtemp, rm, mkdir } from "node:fs/promises";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
import { tmpdir } from "node:os";
|
|
5
|
+
import { runTool } from "../run-tool.ts";
|
|
6
|
+
import type { ToolSpec, ToolResult, ToolExecutor } from "../run-tool.ts";
|
|
7
|
+
|
|
8
|
+
function recordingExecutor(
|
|
9
|
+
calls: ToolSpec[],
|
|
10
|
+
result: ToolResult = { success: true, stdout: "ok" },
|
|
11
|
+
): ToolExecutor {
|
|
12
|
+
return (spec) => {
|
|
13
|
+
calls.push(spec);
|
|
14
|
+
return result;
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
describe("runTool", () => {
|
|
19
|
+
let cwd: string;
|
|
20
|
+
|
|
21
|
+
beforeEach(async () => {
|
|
22
|
+
cwd = await mkdtemp(join(tmpdir(), "run-tool-"));
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
afterEach(async () => {
|
|
26
|
+
await rm(cwd, { recursive: true, force: true });
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
it("passes the spec to the executor and returns its result", () => {
|
|
30
|
+
const calls: ToolSpec[] = [];
|
|
31
|
+
const result = runTool(
|
|
32
|
+
{ bin: "npx", args: ["vite", "build"], cwd, timeoutMs: 5_000 },
|
|
33
|
+
recordingExecutor(calls),
|
|
34
|
+
);
|
|
35
|
+
|
|
36
|
+
expect(result.success).toBe(true);
|
|
37
|
+
expect(result.stdout).toBe("ok");
|
|
38
|
+
expect(calls).toHaveLength(1);
|
|
39
|
+
expect(calls[0]!.bin).toBe("npx");
|
|
40
|
+
expect(calls[0]!.args).toEqual(["vite", "build"]);
|
|
41
|
+
expect(calls[0]!.cwd).toBe(cwd);
|
|
42
|
+
expect(calls[0]!.timeoutMs).toBe(5_000);
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
it("fails preflight when requireDist and dist/ is missing", () => {
|
|
46
|
+
const calls: ToolSpec[] = [];
|
|
47
|
+
const result = runTool(
|
|
48
|
+
{ bin: "npx", args: ["deploy"], cwd, requireDist: true },
|
|
49
|
+
recordingExecutor(calls),
|
|
50
|
+
);
|
|
51
|
+
|
|
52
|
+
expect(result.success).toBe(false);
|
|
53
|
+
expect(result.failedAt).toBe("preflight");
|
|
54
|
+
expect(result.errors?.[0]).toContain("dist/ directory not found");
|
|
55
|
+
expect(calls).toHaveLength(0);
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
it("passes preflight when requireDist and dist/ exists", async () => {
|
|
59
|
+
await mkdir(join(cwd, "dist"), { recursive: true });
|
|
60
|
+
const calls: ToolSpec[] = [];
|
|
61
|
+
const result = runTool(
|
|
62
|
+
{ bin: "npx", args: ["deploy"], cwd, requireDist: true },
|
|
63
|
+
recordingExecutor(calls),
|
|
64
|
+
);
|
|
65
|
+
|
|
66
|
+
expect(result.success).toBe(true);
|
|
67
|
+
expect(calls).toHaveLength(1);
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
it("fails preflight when a required env var is missing", () => {
|
|
71
|
+
const calls: ToolSpec[] = [];
|
|
72
|
+
const result = runTool(
|
|
73
|
+
{ bin: "npx", args: ["x"], cwd, requireEnv: ["DEFINITELY_MISSING_ENV_VAR"] },
|
|
74
|
+
recordingExecutor(calls),
|
|
75
|
+
);
|
|
76
|
+
|
|
77
|
+
expect(result.success).toBe(false);
|
|
78
|
+
expect(result.failedAt).toBe("preflight");
|
|
79
|
+
expect(result.errors?.[0]).toContain("DEFINITELY_MISSING_ENV_VAR");
|
|
80
|
+
expect(calls).toHaveLength(0);
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
it("passes preflight when a required env var comes from spec.env", () => {
|
|
84
|
+
const calls: ToolSpec[] = [];
|
|
85
|
+
const result = runTool(
|
|
86
|
+
{
|
|
87
|
+
bin: "npx",
|
|
88
|
+
args: ["x"],
|
|
89
|
+
cwd,
|
|
90
|
+
env: { MY_TOKEN: "secret" },
|
|
91
|
+
requireEnv: ["MY_TOKEN"],
|
|
92
|
+
},
|
|
93
|
+
recordingExecutor(calls),
|
|
94
|
+
);
|
|
95
|
+
|
|
96
|
+
expect(result.success).toBe(true);
|
|
97
|
+
expect(calls[0]!.env?.MY_TOKEN).toBe("secret");
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
it("fails preflight when a required env var is empty after merge", () => {
|
|
101
|
+
const calls: ToolSpec[] = [];
|
|
102
|
+
const result = runTool(
|
|
103
|
+
{
|
|
104
|
+
bin: "npx",
|
|
105
|
+
args: ["x"],
|
|
106
|
+
cwd,
|
|
107
|
+
env: { EMPTY_TOKEN: "" },
|
|
108
|
+
requireEnv: ["EMPTY_TOKEN"],
|
|
109
|
+
},
|
|
110
|
+
recordingExecutor(calls),
|
|
111
|
+
);
|
|
112
|
+
|
|
113
|
+
expect(result.success).toBe(false);
|
|
114
|
+
expect(result.failedAt).toBe("preflight");
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
it("maps an executor throw to an exec failure", () => {
|
|
118
|
+
const throwing: ToolExecutor = () => {
|
|
119
|
+
throw new Error("spawn npx ENOENT");
|
|
120
|
+
};
|
|
121
|
+
const result = runTool({ bin: "npx", args: ["x"], cwd }, throwing);
|
|
122
|
+
|
|
123
|
+
expect(result.success).toBe(false);
|
|
124
|
+
expect(result.failedAt).toBe("exec");
|
|
125
|
+
expect(result.errors?.[0]).toContain("spawn npx ENOENT");
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
it("propagates executor failure results unchanged", () => {
|
|
129
|
+
const calls: ToolSpec[] = [];
|
|
130
|
+
const result = runTool(
|
|
131
|
+
{ bin: "npx", args: ["x"], cwd },
|
|
132
|
+
recordingExecutor(calls, { success: false, errors: ["exit 1"], failedAt: "exec" }),
|
|
133
|
+
);
|
|
134
|
+
|
|
135
|
+
expect(result.success).toBe(false);
|
|
136
|
+
expect(result.errors).toEqual(["exit 1"]);
|
|
137
|
+
});
|
|
138
|
+
});
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
import { describe, it, expect } from "vitest";
|
|
2
|
+
import { defineStackChecks } from "../stack-checks.ts";
|
|
3
|
+
import type { StackCheckData, StackCheckSpec, StackCheckViolation } from "../stack-checks.ts";
|
|
4
|
+
import type { PluginHookContext } from "../../plugin/plugin-contract.ts";
|
|
5
|
+
import type {
|
|
6
|
+
KernelCommandInput,
|
|
7
|
+
KernelCommandResult,
|
|
8
|
+
KernelRuntimeContext,
|
|
9
|
+
} from "@warpgogol/werkstatt-shared/kernel";
|
|
10
|
+
|
|
11
|
+
const PASS_SPEC: StackCheckSpec = {
|
|
12
|
+
name: "stack.alpha.validate",
|
|
13
|
+
description: "alpha check",
|
|
14
|
+
contract: "stack",
|
|
15
|
+
rules: ["STACK-01"],
|
|
16
|
+
reads: ["src/**"],
|
|
17
|
+
check: async () => [],
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
const FAIL_VIOLATION: StackCheckViolation = {
|
|
21
|
+
ruleId: "STACK-02",
|
|
22
|
+
file: "src/bad.ts",
|
|
23
|
+
message: "bad thing",
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
const FAIL_SPEC: StackCheckSpec = {
|
|
27
|
+
name: "stack.beta.validate",
|
|
28
|
+
description: "beta check",
|
|
29
|
+
contract: "stack",
|
|
30
|
+
rules: ["STACK-02"],
|
|
31
|
+
reads: ["src/**"],
|
|
32
|
+
check: async () => [FAIL_VIOLATION],
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
function makeCtx(projectRoot: string): PluginHookContext {
|
|
36
|
+
return {
|
|
37
|
+
workspaceRoot: projectRoot,
|
|
38
|
+
logger: { info: () => {}, warn: () => {}, error: () => {} },
|
|
39
|
+
} as unknown as PluginHookContext;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function makeRuntimeContext(projectRoot: string): KernelRuntimeContext {
|
|
43
|
+
return { workspaceRoot: projectRoot } as unknown as KernelRuntimeContext;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const INPUT: KernelCommandInput = { argv: [], flags: {} };
|
|
47
|
+
|
|
48
|
+
describe("defineStackChecks", () => {
|
|
49
|
+
it("emits one command per spec with metadata from the spec", () => {
|
|
50
|
+
const { commands } = defineStackChecks([PASS_SPEC, FAIL_SPEC]);
|
|
51
|
+
|
|
52
|
+
expect(commands).toHaveLength(2);
|
|
53
|
+
expect(commands[0]!.name).toBe("stack.alpha.validate");
|
|
54
|
+
expect(commands[0]!.description).toBe("alpha check");
|
|
55
|
+
expect(commands[0]!.contract).toBe("stack");
|
|
56
|
+
expect(commands[0]!.rules).toEqual(["STACK-01"]);
|
|
57
|
+
expect(commands[0]!.reads).toEqual(["src/**"]);
|
|
58
|
+
expect(commands[0]!.scope).toBe("workspace");
|
|
59
|
+
expect(commands[0]!.cacheable).toBe(false);
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
it("command execute returns the uniform pass envelope", async () => {
|
|
63
|
+
const { commands } = defineStackChecks([PASS_SPEC]);
|
|
64
|
+
const result = (await commands[0]!.execute(
|
|
65
|
+
INPUT,
|
|
66
|
+
makeRuntimeContext("/tmp/x"),
|
|
67
|
+
)) as KernelCommandResult<StackCheckData>;
|
|
68
|
+
|
|
69
|
+
expect(result?.exitCode).toBe(0);
|
|
70
|
+
expect(result?.data?.command).toBe("stack.alpha.validate");
|
|
71
|
+
expect(result?.data?.status).toBe("pass");
|
|
72
|
+
expect(result?.data?.violations).toEqual([]);
|
|
73
|
+
expect(result?.summary).toContain("stack.alpha.validate");
|
|
74
|
+
expect(result?.summary).toContain("pass");
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
it("command execute returns the uniform fail envelope with violations", async () => {
|
|
78
|
+
const { commands } = defineStackChecks([FAIL_SPEC]);
|
|
79
|
+
const result = (await commands[0]!.execute(
|
|
80
|
+
INPUT,
|
|
81
|
+
makeRuntimeContext("/tmp/x"),
|
|
82
|
+
)) as KernelCommandResult<StackCheckData>;
|
|
83
|
+
|
|
84
|
+
expect(result?.exitCode).toBe(1);
|
|
85
|
+
expect(result?.data?.status).toBe("fail");
|
|
86
|
+
expect(result?.data?.violations).toEqual([FAIL_VIOLATION]);
|
|
87
|
+
expect(result?.summary).toContain("1 violations");
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
it("runCheckGate succeeds when all checks pass", async () => {
|
|
91
|
+
const { runCheckGate } = defineStackChecks([PASS_SPEC]);
|
|
92
|
+
const result = await runCheckGate(makeCtx("/tmp/x"));
|
|
93
|
+
|
|
94
|
+
expect(result.success).toBe(true);
|
|
95
|
+
expect(result.errors).toBeUndefined();
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
it("runCheckGate aggregates failures across specs", async () => {
|
|
99
|
+
const { runCheckGate } = defineStackChecks([PASS_SPEC, FAIL_SPEC]);
|
|
100
|
+
const result = await runCheckGate(makeCtx("/tmp/x"));
|
|
101
|
+
|
|
102
|
+
expect(result.success).toBe(false);
|
|
103
|
+
expect(result.errors).toEqual(["stack.beta.validate: 1 violations"]);
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
it("runCheckGate prefers workpiecePath over workspaceRoot", async () => {
|
|
107
|
+
let seen = "";
|
|
108
|
+
const spec: StackCheckSpec = {
|
|
109
|
+
...PASS_SPEC,
|
|
110
|
+
check: async (root) => {
|
|
111
|
+
seen = root;
|
|
112
|
+
return [];
|
|
113
|
+
},
|
|
114
|
+
};
|
|
115
|
+
const { runCheckGate } = defineStackChecks([spec]);
|
|
116
|
+
const ctx = {
|
|
117
|
+
...makeCtx("/workspace"),
|
|
118
|
+
workpiecePath: "/workpiece",
|
|
119
|
+
};
|
|
120
|
+
await runCheckGate(ctx);
|
|
121
|
+
|
|
122
|
+
expect(seen).toBe("/workpiece");
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
it("derives invariantRows from rules[0] by default", () => {
|
|
126
|
+
const { invariantRows } = defineStackChecks([PASS_SPEC, FAIL_SPEC]);
|
|
127
|
+
|
|
128
|
+
expect(invariantRows).toEqual([
|
|
129
|
+
{ id: "STACK-01", command: "stack.alpha.validate" },
|
|
130
|
+
{ id: "STACK-02", command: "stack.beta.validate" },
|
|
131
|
+
]);
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
it("honours explicit invariantId and skips rule-less specs", () => {
|
|
135
|
+
const custom: StackCheckSpec = { ...PASS_SPEC, invariantId: "STACK-CUSTOM" };
|
|
136
|
+
const ruleless: StackCheckSpec = { ...FAIL_SPEC, rules: [] };
|
|
137
|
+
const { invariantRows } = defineStackChecks([custom, ruleless]);
|
|
138
|
+
|
|
139
|
+
expect(invariantRows).toEqual([{ id: "STACK-CUSTOM", command: "stack.alpha.validate" }]);
|
|
140
|
+
});
|
|
141
|
+
});
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
import { describe, it, expect, beforeEach, afterEach } from "vitest";
|
|
2
|
+
import { mkdtemp, rm, mkdir, writeFile } from "node:fs/promises";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
import { tmpdir } from "node:os";
|
|
5
|
+
import {
|
|
6
|
+
walkFiles,
|
|
7
|
+
readTextFile,
|
|
8
|
+
readTextFiles,
|
|
9
|
+
readBinaryFiles,
|
|
10
|
+
} from "../walk-files.ts";
|
|
11
|
+
|
|
12
|
+
describe("walkFiles", () => {
|
|
13
|
+
let root: string;
|
|
14
|
+
|
|
15
|
+
beforeEach(async () => {
|
|
16
|
+
root = await mkdtemp(join(tmpdir(), "walk-files-"));
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
afterEach(async () => {
|
|
20
|
+
await rm(root, { recursive: true, force: true });
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
it("returns relative paths with forward slashes, recursively by default", async () => {
|
|
24
|
+
await mkdir(join(root, "a", "b"), { recursive: true });
|
|
25
|
+
await writeFile(join(root, "top.ts"), "");
|
|
26
|
+
await writeFile(join(root, "a", "mid.ts"), "");
|
|
27
|
+
await writeFile(join(root, "a", "b", "deep.ts"), "");
|
|
28
|
+
|
|
29
|
+
const files = await walkFiles(root);
|
|
30
|
+
|
|
31
|
+
expect(files.sort()).toEqual(["a/b/deep.ts", "a/mid.ts", "top.ts"]);
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
it("returns only top-level files when recursive is false", async () => {
|
|
35
|
+
await mkdir(join(root, "sub"), { recursive: true });
|
|
36
|
+
await writeFile(join(root, "top.ts"), "");
|
|
37
|
+
await writeFile(join(root, "sub", "nested.ts"), "");
|
|
38
|
+
|
|
39
|
+
const files = await walkFiles(root, { recursive: false });
|
|
40
|
+
|
|
41
|
+
expect(files).toEqual(["top.ts"]);
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
it("applies the filter to relative paths", async () => {
|
|
45
|
+
await mkdir(join(root, "sub"), { recursive: true });
|
|
46
|
+
await writeFile(join(root, "keep.ts"), "");
|
|
47
|
+
await writeFile(join(root, "skip.js"), "");
|
|
48
|
+
await writeFile(join(root, "sub", "keep.ts"), "");
|
|
49
|
+
|
|
50
|
+
const files = await walkFiles(root, { filter: (rel) => rel.endsWith(".ts") });
|
|
51
|
+
|
|
52
|
+
expect(files.sort()).toEqual(["keep.ts", "sub/keep.ts"]);
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
it("returns an empty array for a missing directory", async () => {
|
|
56
|
+
expect(await walkFiles(join(root, "nope"))).toEqual([]);
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
it("skips non-file entries", async () => {
|
|
60
|
+
await mkdir(join(root, "dir-only"), { recursive: true });
|
|
61
|
+
await writeFile(join(root, "file.ts"), "");
|
|
62
|
+
|
|
63
|
+
const files = await walkFiles(root);
|
|
64
|
+
|
|
65
|
+
expect(files).toEqual(["file.ts"]);
|
|
66
|
+
});
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
describe("readTextFile", () => {
|
|
70
|
+
let root: string;
|
|
71
|
+
|
|
72
|
+
beforeEach(async () => {
|
|
73
|
+
root = await mkdtemp(join(tmpdir(), "walk-read-"));
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
afterEach(async () => {
|
|
77
|
+
await rm(root, { recursive: true, force: true });
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
it("reads utf-8 content", async () => {
|
|
81
|
+
await writeFile(join(root, "a.txt"), "hello ünïcode");
|
|
82
|
+
|
|
83
|
+
expect(await readTextFile(join(root, "a.txt"))).toBe("hello ünïcode");
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
it("returns null for a missing file", async () => {
|
|
87
|
+
expect(await readTextFile(join(root, "missing.txt"))).toBeNull();
|
|
88
|
+
});
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
describe("readTextFiles / readBinaryFiles", () => {
|
|
92
|
+
let root: string;
|
|
93
|
+
|
|
94
|
+
beforeEach(async () => {
|
|
95
|
+
root = await mkdtemp(join(tmpdir(), "walk-reads-"));
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
afterEach(async () => {
|
|
99
|
+
await rm(root, { recursive: true, force: true });
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
it("reads multiple files keyed by relative path", async () => {
|
|
103
|
+
await mkdir(join(root, "sub"), { recursive: true });
|
|
104
|
+
await writeFile(join(root, "a.ts"), "aaa");
|
|
105
|
+
await writeFile(join(root, "sub", "b.ts"), "bbb");
|
|
106
|
+
|
|
107
|
+
const contents = await readTextFiles(root, ["a.ts", "sub/b.ts"]);
|
|
108
|
+
|
|
109
|
+
expect(contents.get("a.ts")).toBe("aaa");
|
|
110
|
+
expect(contents.get("sub/b.ts")).toBe("bbb");
|
|
111
|
+
expect(contents.size).toBe(2);
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
it("omits unreadable entries", async () => {
|
|
115
|
+
await writeFile(join(root, "a.ts"), "aaa");
|
|
116
|
+
|
|
117
|
+
const contents = await readTextFiles(root, ["a.ts", "missing.ts"]);
|
|
118
|
+
|
|
119
|
+
expect(contents.size).toBe(1);
|
|
120
|
+
expect(contents.has("missing.ts")).toBe(false);
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
it("reads binary content without utf-8 corruption", async () => {
|
|
124
|
+
const bytes = Buffer.from([0x00, 0xff, 0xfe, 0x42]);
|
|
125
|
+
await writeFile(join(root, "bin.dat"), bytes);
|
|
126
|
+
|
|
127
|
+
const contents = await readBinaryFiles(root, ["bin.dat"]);
|
|
128
|
+
|
|
129
|
+
expect(contents.get("bin.dat")).toEqual(bytes);
|
|
130
|
+
});
|
|
131
|
+
});
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
/*
|
|
2
|
+
<MODULE_CONTRACT>
|
|
3
|
+
<purpose>walkFiles — the single file-discovery and file-reading seam for stack plugins
|
|
4
|
+
(RFC-1100). Returns workspace-relative paths with forward slashes and reads file
|
|
5
|
+
contents in bulk, so validators never import node:fs directly.</purpose>
|
|
6
|
+
|
|
7
|
+
<non-goals>
|
|
8
|
+
<item>Does not write files — writes flow through writeFileIfChanged / WorkspaceIO.</item>
|
|
9
|
+
<item>Does not apply implicit ignore rules (no dotfile or prefix skipping) — callers scope by directory and filter.</item>
|
|
10
|
+
<item>Does not run in the browser — server-only (Node fs/promises).</item>
|
|
11
|
+
</non-goals>
|
|
12
|
+
</MODULE_CONTRACT>
|
|
13
|
+
<CHANGE_SUMMARY>
|
|
14
|
+
<item>RFC-1100: initial shared walker — walkFiles plus readTextFile/readTextFiles/readBinaryFiles companions; replaces per-validator private readdir walkers in stack plugins.</item>
|
|
15
|
+
<item>RFC-1100: step 1 — shared tool/fs/check seams (walkFiles, runTool, defineStackChecks)</item>
|
|
16
|
+
</CHANGE_SUMMARY>
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
// Server-only. Do not import from browser/client scripts.
|
|
20
|
+
|
|
21
|
+
import { readdir, readFile } from "node:fs/promises";
|
|
22
|
+
import { join } from "node:path";
|
|
23
|
+
|
|
24
|
+
export interface WalkFilesOptions {
|
|
25
|
+
/** Recurse into subdirectories. Default true. */
|
|
26
|
+
recursive?: boolean;
|
|
27
|
+
/** Return true to include a file by its directory-relative path (forward slashes). */
|
|
28
|
+
filter?: (relPath: string) => boolean;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Lists files under `dir` as relative paths with forward slashes.
|
|
33
|
+
* Missing or unreadable directories yield an empty array.
|
|
34
|
+
*/
|
|
35
|
+
export async function walkFiles(
|
|
36
|
+
dir: string,
|
|
37
|
+
options: WalkFilesOptions = {},
|
|
38
|
+
): Promise<string[]> {
|
|
39
|
+
const { recursive = true, filter } = options;
|
|
40
|
+
const results: string[] = [];
|
|
41
|
+
|
|
42
|
+
async function walk(current: string, relPrefix: string): Promise<void> {
|
|
43
|
+
let entries;
|
|
44
|
+
try {
|
|
45
|
+
entries = await readdir(current, { withFileTypes: true });
|
|
46
|
+
} catch {
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
for (const entry of entries) {
|
|
50
|
+
const relPath = relPrefix ? `${relPrefix}/${entry.name}` : entry.name;
|
|
51
|
+
if (entry.isDirectory()) {
|
|
52
|
+
if (recursive) {
|
|
53
|
+
await walk(join(current, entry.name), relPath);
|
|
54
|
+
}
|
|
55
|
+
continue;
|
|
56
|
+
}
|
|
57
|
+
if (!entry.isFile()) continue;
|
|
58
|
+
if (filter && !filter(relPath)) continue;
|
|
59
|
+
results.push(relPath);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
await walk(dir, "");
|
|
64
|
+
return results;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Reads a single file as utf-8. Returns null when the file is missing or unreadable.
|
|
69
|
+
*/
|
|
70
|
+
export async function readTextFile(path: string): Promise<string | null> {
|
|
71
|
+
try {
|
|
72
|
+
return await readFile(path, "utf-8");
|
|
73
|
+
} catch {
|
|
74
|
+
return null;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Reads multiple files under `dir` as utf-8, keyed by their walkFiles-relative path.
|
|
80
|
+
* Unreadable entries are omitted from the result.
|
|
81
|
+
*/
|
|
82
|
+
export async function readTextFiles(
|
|
83
|
+
dir: string,
|
|
84
|
+
relPaths: string[],
|
|
85
|
+
): Promise<Map<string, string>> {
|
|
86
|
+
const contents = new Map<string, string>();
|
|
87
|
+
for (const relPath of relPaths) {
|
|
88
|
+
const content = await readTextFile(join(dir, relPath));
|
|
89
|
+
if (content !== null) {
|
|
90
|
+
contents.set(relPath, content);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
return contents;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Reads multiple files under `dir` as raw buffers, keyed by their walkFiles-relative
|
|
98
|
+
* path. Use for byte-accurate work (hashing, gzip measurement) where utf-8 decoding
|
|
99
|
+
* would corrupt binary content. Unreadable entries are omitted from the result.
|
|
100
|
+
*/
|
|
101
|
+
export async function readBinaryFiles(
|
|
102
|
+
dir: string,
|
|
103
|
+
relPaths: string[],
|
|
104
|
+
): Promise<Map<string, Buffer>> {
|
|
105
|
+
const contents = new Map<string, Buffer>();
|
|
106
|
+
for (const relPath of relPaths) {
|
|
107
|
+
try {
|
|
108
|
+
contents.set(relPath, await readFile(join(dir, relPath)));
|
|
109
|
+
} catch {
|
|
110
|
+
// Skip unreadable files — same tolerance as the walkers this replaces.
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
return contents;
|
|
114
|
+
}
|