@pixel-point/toolcraft 0.0.18 → 0.0.19
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/package.json +1 -1
- package/src/generate-demand-only-lifecycle.test.mjs +9 -1
- package/src/generate-performance-iteration-execution.test.mjs +3 -3
- package/src/generate-test-delivery-architecture-assertions.mjs +11 -73
- package/src/generate-test-demand-only-lifecycle.mjs +34 -36
- package/src/generate-test-dependency-sandbox.mjs +4 -1
- package/src/generate-test-doc-assertions.mjs +55 -53
- package/src/generate-test-doc-assertions.test.mjs +1 -1
- package/src/generate-test-doc-policy-assertions.mjs +1 -1
- package/src/generate-test-export-doc-assertions.mjs +53 -0
- package/src/generate-test-file-assertions.mjs +51 -8
- package/src/generate-test-integrity-assertions.mjs +5 -2
- package/src/generate-test-integrity-manifest-assertions.mjs +16 -5
- package/src/generate-test-integrity-ownership-assertions.mjs +36 -4
- package/src/generate-test-package-assertions.mjs +4 -0
- package/src/generate-test-product-fixture.mjs +1 -1
- package/src/generate-test-product-fixture.test.mjs +35 -38
- package/src/generate-test-product-iteration.mjs +0 -1
- package/src/generate-test-product-iteration.test.mjs +0 -1
- package/src/generate-test-runtime-boundary-assertions.mjs +20 -0
- package/src/generate-test-standalone-assertions.mjs +0 -16
- package/src/generate-test-standalone-delivery-stages.mjs +2 -86
- package/src/generate.test.mjs +4 -0
- package/src/generated-integrity-manifest.test.mjs +5 -2
- package/src/generated-source-policy.test.mjs +0 -4
- package/src/package-json.mjs +5 -0
- package/src/package-json.test.mjs +21 -0
- package/src/package-manager.mjs +2 -0
- package/src/package-manager.test.mjs +8 -0
- package/src/packaged-template-fallback-helpers.mjs +254 -0
- package/src/packaged-template-fallback.test.mjs +121 -46
- package/src/packaged-template-runtime-assertions.mjs +150 -0
- package/templates/runtime/contracts/component-contracts.choices.test.ts +18 -3
- package/templates/runtime/contracts/component-contracts.choices.ts +8 -3
- package/templates/runtime/contracts/component-contracts.inputs.ts +2 -1
- package/templates/runtime/contracts/component-contracts.runtime-acceptance.test.ts +18 -4
- package/templates/runtime/contracts/component-contracts.runtime-shell.test.ts +1 -1
- package/templates/runtime/contracts/component-contracts.runtime.ts +7 -4
- package/templates/runtime/contracts/component-contracts.visual.test.ts +20 -0
- package/templates/runtime/contracts/component-contracts.visual.ts +4 -0
- package/templates/runtime/contracts/decision-contracts.test.ts +18 -12
- package/templates/runtime/contracts/decision-contracts.ts +4 -4
- package/templates/runtime/contracts/performance-verification-policy.test.ts +12 -12
- package/templates/runtime/contracts/performance-verification-policy.ts +8 -8
- package/templates/runtime/export/artifact-download.ts +1 -0
- package/templates/runtime/export/artifact-frame-renderer.ts +2 -15
- package/templates/runtime/export/artifact-frame-state.ts +16 -0
- package/templates/runtime/export/artifact-scene-frame.test.ts +3 -3
- package/templates/runtime/export/artifact-scene-frame.ts +3 -3
- package/templates/runtime/export/export-error.ts +5 -0
- package/templates/runtime/export/export-renderer-coverage.ts +54 -0
- package/templates/runtime/export/image-artifact-export.ts +2 -2
- package/templates/runtime/export/index.ts +13 -0
- package/templates/runtime/export/product-export-renderer.test.ts +38 -4
- package/templates/runtime/export/product-export-renderer.ts +0 -41
- package/templates/runtime/export/product-svg-export-renderer.ts +21 -0
- package/templates/runtime/export/svg-artifact-export.test.ts +98 -0
- package/templates/runtime/export/svg-artifact-export.ts +120 -0
- package/templates/runtime/export/svg-document.test.ts +221 -0
- package/templates/runtime/export/svg-document.ts +400 -0
- package/templates/runtime/export/svg-policy.ts +47 -0
- package/templates/runtime/index.ts +1 -0
- package/templates/runtime/react/app-shell/runtime-public-api.test.tsx +10 -0
- package/templates/runtime/react/app-shell/settings-transfer.test.ts +21 -0
- package/templates/runtime/react/app-shell/toolcraft-app.scene-export.test.tsx +46 -1
- package/templates/runtime/react/app-shell/toolcraft-app.tsx +7 -2
- package/templates/runtime/react/canvas/canvas-shell-interactions.test.tsx +30 -0
- package/templates/runtime/react/canvas/canvas-shell-pinch-interactions.test.tsx +155 -0
- package/templates/runtime/react/canvas/canvas-shell-rendering.test.tsx +1 -1
- package/templates/runtime/react/canvas/canvas-shell.tsx +16 -5
- package/templates/runtime/react/canvas/canvas-viewport-geometry.ts +133 -0
- package/templates/runtime/react/canvas/use-canvas-viewport-interactions.ts +285 -36
- package/templates/runtime/react/controls-panel/__tests__/controls-panel.color-applicability-layout.test.tsx +134 -0
- package/templates/runtime/react/controls-panel/__tests__/controls-panel.color-layout.test.tsx +16 -1
- package/templates/runtime/react/controls-panel/__tests__/controls-panel.render-isolation.test.tsx +2 -0
- package/templates/runtime/react/controls-panel/__tests__/controls-panel.scene-export-actions.test.tsx +39 -0
- package/templates/runtime/react/controls-panel/__tests__/controls-panel.setup-controls.test.tsx +3 -3
- package/templates/runtime/react/controls-panel/__tests__/controls-panel.source-collection.test.tsx +1 -4
- package/templates/runtime/react/controls-panel/__tests__/controls-panel.source-color-bank.test.tsx +2 -2
- package/templates/runtime/react/controls-panel/__tests__/controls-panel.vector-interaction.test.tsx +6 -6
- package/templates/runtime/react/controls-panel/actions/controls-panel-actions.ts +2 -2
- package/templates/runtime/react/controls-panel/actions/export-action-runner.ts +40 -11
- package/templates/runtime/react/controls-panel/layout/controls-panel-layout.tsx +31 -2
- package/templates/runtime/react/controls-panel/layout/controls-panel-section.tsx +4 -1
- package/templates/runtime/react/controls-panel/renderers/controls-panel-action-renderer.tsx +2 -1
- package/templates/runtime/react/controls-panel/renderers/controls-panel-basic-renderers.tsx +2 -52
- package/templates/runtime/react/controls-panel/values/controls-panel-color-values.ts +1 -9
- package/templates/runtime/react/controls-panel/values/controls-panel-spatial-values.ts +7 -2
- package/templates/runtime/react/orientation-gizmo/orientation-gizmo-math.ts +9 -69
- package/templates/runtime/schema/artifact-export-actions.test.ts +47 -0
- package/templates/runtime/schema/artifact-export-actions.ts +49 -0
- package/templates/runtime/schema/types.ts +1 -0
- package/templates/runtime/state/canvas-background-state.ts +1 -16
- package/templates/runtime/state/canvas-state.ts +32 -20
- package/templates/runtime/state/control-value-codecs.test.ts +98 -0
- package/templates/runtime/state/control-value-codecs.ts +700 -0
- package/templates/runtime/state/control-value-normalization.test.ts +1 -1
- package/templates/runtime/state/control-value-normalization.ts +127 -135
- package/templates/runtime/state/controls-reducer.ts +26 -3
- package/templates/runtime/state/create-template-state.test.ts +30 -0
- package/templates/runtime/state/create-template-state.ts +52 -31
- package/templates/runtime/state/orientation-pose.ts +80 -0
- package/templates/runtime/state/persistence-control-values.test.ts +59 -0
- package/templates/runtime/state/reducer-controls.test.ts +45 -0
- package/templates/runtime/state/reducer-media-reset.test.ts +1 -1
- package/templates/runtime/state/reducer-timeline.test.ts +70 -0
- package/templates/runtime/state/timeline-reducer.ts +40 -2
- package/templates/runtime/testing/performance-schema-queries.ts +2 -2
- package/templates/starter/AGENTS.md +21 -22
- package/templates/starter/docs/toolcraft/README.md +6 -6
- package/templates/starter/docs/toolcraft/acceptance-testing.md +20 -20
- package/templates/starter/docs/toolcraft/agent-worklog.md +88 -1
- package/templates/starter/docs/toolcraft/assembly-workflow.md +7 -6
- package/templates/starter/docs/toolcraft/component-rules.md +2 -2
- package/templates/starter/docs/toolcraft/core/layout.md +6 -1
- package/templates/starter/docs/toolcraft/core/media-upload.md +1 -1
- package/templates/starter/docs/toolcraft/core/performance.md +8 -10
- package/templates/starter/docs/toolcraft/core/reference-study.md +80 -19
- package/templates/starter/docs/toolcraft/core/runtime-boundary.md +2 -1
- package/templates/starter/docs/toolcraft/core/setup-export.md +43 -13
- package/templates/starter/docs/toolcraft/decision-contract.md +2 -2
- package/templates/starter/docs/toolcraft/performance.md +8 -10
- package/templates/starter/docs/toolcraft/renderer-technique.md +2 -2
- package/templates/starter/docs/toolcraft/schema-reference.md +8 -8
- package/templates/starter/docs/toolcraft/workflow.md +14 -14
- package/templates/starter/e2e/app-browser-infinity-canvas-evidence.spec.ts +38 -0
- package/templates/starter/e2e/app-browser-motion-reference-requirements.spec.ts +411 -0
- package/templates/starter/e2e/app-browser-proof-session.spec.ts +85 -0
- package/templates/starter/e2e/app-browser-runtime-requirements.spec.ts +62 -0
- package/templates/starter/e2e/app-browser-selection-scope-evidence.spec.ts +137 -0
- package/templates/starter/e2e/browser-infinity-canvas-svg-evidence.ts +42 -0
- package/templates/starter/e2e/browser-layer-evidence-helpers.ts +15 -36
- package/templates/starter/e2e/browser-product-raster-snapshot.ts +213 -0
- package/templates/starter/e2e/browser-proof-session.ts +43 -15
- package/templates/starter/e2e/browser-proof-surface-action.ts +79 -0
- package/templates/starter/e2e/browser-proof-types.ts +15 -0
- package/templates/starter/e2e/browser-runtime-evidence-requirements.ts +22 -6
- package/templates/starter/e2e/browser-selection-scope-evidence.ts +269 -0
- package/templates/starter/e2e/browser-semantic-evidence-test-helpers.ts +97 -0
- package/templates/starter/e2e/browser-svg-export-evidence.spec.ts +79 -0
- package/templates/starter/e2e/browser-svg-export-evidence.ts +152 -0
- package/templates/starter/e2e/export-artifact-helpers.ts +70 -0
- package/templates/starter/e2e/product-observable-helpers.ts +8 -129
- package/templates/starter/e2e/svg-artifact-inspection.spec.ts +136 -0
- package/templates/starter/e2e/svg-artifact-inspection.ts +331 -0
- package/templates/starter/e2e/svg-download-test-fixtures.ts +34 -0
- package/templates/starter/e2e/toolcraft-delivery-catalog-reporter.ts +198 -15
- package/templates/starter/e2e/toolcraft-feature-verification-reporter.spec.ts +149 -0
- package/templates/starter/e2e/toolcraft-feature-verification-reporter.ts +223 -0
- package/templates/starter/package.json +2 -0
- package/templates/starter/scripts/check-toolcraft-docs.mjs +1 -0
- package/templates/starter/scripts/create-toolcraft-motion-reference-study.mjs +233 -0
- package/templates/starter/scripts/create-toolcraft-motion-reference-study.test.mjs +459 -0
- package/templates/starter/scripts/playwright-test-title-selection.d.mts +45 -0
- package/templates/starter/scripts/run-browser-performance-test-helpers.mjs +0 -27
- package/templates/starter/scripts/run-browser-performance.mjs +0 -6
- package/templates/starter/scripts/run-browser-performance.test.mjs +5 -13
- package/templates/starter/scripts/run-delivery-verification-test-helpers.mjs +0 -34
- package/templates/starter/scripts/run-delivery-verification.test.mjs +135 -31
- package/templates/starter/scripts/run-feature-verification.mjs +212 -0
- package/templates/starter/scripts/run-feature-verification.test.mjs +226 -0
- package/templates/starter/scripts/toolcraft-checkpoint-delivery-state.mjs +21 -8
- package/templates/starter/scripts/toolcraft-checkpoint-delivery-state.test.mjs +32 -92
- package/templates/starter/scripts/toolcraft-checkpoint-transaction.mjs +28 -6
- package/templates/starter/scripts/toolcraft-checkpoint-transaction.test.mjs +8 -63
- package/templates/starter/scripts/toolcraft-delivery-anchor.mjs +28 -8
- package/templates/starter/scripts/toolcraft-delivery-anchor.test.mjs +50 -61
- package/templates/starter/scripts/toolcraft-delivery-architecture-inventory.mjs +0 -4
- package/templates/starter/scripts/toolcraft-delivery-architecture-policy.mjs +0 -1
- package/templates/starter/scripts/toolcraft-delivery-evidence.test.mjs +2 -3
- package/templates/starter/scripts/toolcraft-delivery-executor.mjs +5 -1
- package/templates/starter/scripts/toolcraft-delivery-executor.test.mjs +5 -12
- package/templates/starter/scripts/toolcraft-delivery-functional-context.d.mts +1 -48
- package/templates/starter/scripts/toolcraft-delivery-functional-context.mjs +2 -30
- package/templates/starter/scripts/toolcraft-delivery-functional-context.test.mjs +4 -166
- package/templates/starter/scripts/toolcraft-delivery-functional-initial.test.mjs +4 -20
- package/templates/starter/scripts/toolcraft-delivery-lifecycle.mjs +43 -192
- package/templates/starter/scripts/toolcraft-delivery-performance-iteration-lifecycle.test.mjs +79 -440
- package/templates/starter/scripts/toolcraft-delivery-plan-authority.test.mjs +102 -297
- package/templates/starter/scripts/toolcraft-delivery-plan-construction.mjs +46 -97
- package/templates/starter/scripts/toolcraft-delivery-plan-test-helpers.mjs +125 -0
- package/templates/starter/scripts/toolcraft-delivery-plan-validation.test.mjs +58 -0
- package/templates/starter/scripts/toolcraft-delivery-plan.d.mts +19 -31
- package/templates/starter/scripts/toolcraft-delivery-plan.mjs +254 -252
- package/templates/starter/scripts/toolcraft-delivery-plan.test.mjs +29 -457
- package/templates/starter/scripts/toolcraft-delivery-receipt-performance.test.mjs +1 -1
- package/templates/starter/scripts/toolcraft-delivery-receipt-strictness.test.mjs +3 -72
- package/templates/starter/scripts/toolcraft-delivery-receipt-test-helpers.mjs +7 -36
- package/templates/starter/scripts/toolcraft-delivery-receipt.d.mts +3 -5
- package/templates/starter/scripts/toolcraft-delivery-receipt.mjs +15 -31
- package/templates/starter/scripts/toolcraft-delivery-receipt.test.mjs +28 -20
- package/templates/starter/scripts/toolcraft-delivery-receipt.types.test.mts +22 -30
- package/templates/starter/scripts/toolcraft-feature-verification-plan.d.mts +20 -0
- package/templates/starter/scripts/toolcraft-feature-verification-plan.mjs +146 -0
- package/templates/starter/scripts/toolcraft-feature-verification-plan.test.mjs +96 -0
- package/templates/starter/scripts/toolcraft-functional-proof-model.d.mts +3 -18
- package/templates/starter/scripts/toolcraft-functional-proof-model.mjs +39 -127
- package/templates/starter/scripts/toolcraft-functional-proof-model.test.mjs +25 -362
- package/templates/starter/scripts/toolcraft-functional-proof-model.types.test.mts +6 -83
- package/templates/starter/scripts/toolcraft-functional-proof-primitives.d.mts +11 -0
- package/templates/starter/scripts/toolcraft-functional-proof-primitives.mjs +61 -0
- package/templates/starter/scripts/toolcraft-functional-proof-primitives.test.mjs +43 -0
- package/templates/starter/scripts/toolcraft-integrity-policy.mjs +13 -0
- package/templates/starter/scripts/toolcraft-integrity-test-utils.mjs +1 -1
- package/templates/starter/scripts/toolcraft-layer-evidence-facade.test.mjs +58 -22
- package/templates/starter/scripts/toolcraft-motion-reference-analysis-protocol.mjs +111 -0
- package/templates/starter/scripts/toolcraft-motion-reference-analysis-protocol.test.mjs +116 -0
- package/templates/starter/scripts/toolcraft-motion-reference-artifacts.d.mts +37 -0
- package/templates/starter/scripts/toolcraft-motion-reference-artifacts.mjs +308 -0
- package/templates/starter/scripts/toolcraft-motion-reference-artifacts.test.mjs +420 -0
- package/templates/starter/scripts/toolcraft-motion-reference-artifacts.types.test.mts +50 -0
- package/templates/starter/scripts/toolcraft-motion-reference-command-options.mjs +195 -0
- package/templates/starter/scripts/toolcraft-motion-reference-command-options.test.mjs +224 -0
- package/templates/starter/scripts/toolcraft-motion-reference-contact-sheet.mjs +122 -0
- package/templates/starter/scripts/toolcraft-motion-reference-contact-sheet.test.mjs +153 -0
- package/templates/starter/scripts/toolcraft-motion-reference-evidence-assembly.mjs +270 -0
- package/templates/starter/scripts/toolcraft-motion-reference-evidence.test.mjs +432 -0
- package/templates/starter/scripts/toolcraft-motion-reference-ffmpeg-output.mjs +275 -0
- package/templates/starter/scripts/toolcraft-motion-reference-ffmpeg-output.test.mjs +294 -0
- package/templates/starter/scripts/toolcraft-motion-reference-group-inventory.mjs +148 -0
- package/templates/starter/scripts/toolcraft-motion-reference-image-filter-graph.mjs +101 -0
- package/templates/starter/scripts/toolcraft-motion-reference-image-filter-graph.test.mjs +124 -0
- package/templates/starter/scripts/toolcraft-motion-reference-image-source.mjs +259 -0
- package/templates/starter/scripts/toolcraft-motion-reference-image-source.test.mjs +338 -0
- package/templates/starter/scripts/toolcraft-motion-reference-image-timing.mjs +96 -0
- package/templates/starter/scripts/toolcraft-motion-reference-image-timing.test.mjs +81 -0
- package/templates/starter/scripts/toolcraft-motion-reference-lock-recovery.test.mjs +144 -0
- package/templates/starter/scripts/toolcraft-motion-reference-media-source.mjs +165 -0
- package/templates/starter/scripts/toolcraft-motion-reference-media-source.test.mjs +299 -0
- package/templates/starter/scripts/toolcraft-motion-reference-publication-discovery.test.mjs +142 -0
- package/templates/starter/scripts/toolcraft-motion-reference-publication-isolation.test.mjs +234 -0
- package/templates/starter/scripts/toolcraft-motion-reference-publication-journal-schema.mjs +122 -0
- package/templates/starter/scripts/toolcraft-motion-reference-publication-journal-store.mjs +89 -0
- package/templates/starter/scripts/toolcraft-motion-reference-publication-journal-store.test.mjs +74 -0
- package/templates/starter/scripts/toolcraft-motion-reference-publication-journal.mjs +264 -0
- package/templates/starter/scripts/toolcraft-motion-reference-publication-journal.test.mjs +377 -0
- package/templates/starter/scripts/toolcraft-motion-reference-publication-lease.mjs +254 -0
- package/templates/starter/scripts/toolcraft-motion-reference-publication-lease.test.mjs +130 -0
- package/templates/starter/scripts/toolcraft-motion-reference-publication-staging.mjs +128 -0
- package/templates/starter/scripts/toolcraft-motion-reference-publication-staging.test.mjs +209 -0
- package/templates/starter/scripts/toolcraft-motion-reference-publication.fixtures.mjs +88 -0
- package/templates/starter/scripts/toolcraft-motion-reference-publication.mjs +249 -0
- package/templates/starter/scripts/toolcraft-motion-reference-publication.test.mjs +433 -0
- package/templates/starter/scripts/toolcraft-motion-reference-review-output.mjs +38 -0
- package/templates/starter/scripts/toolcraft-motion-reference-review-output.test.mjs +29 -0
- package/templates/starter/scripts/toolcraft-motion-reference-root-safety.test.mjs +84 -0
- package/templates/starter/scripts/toolcraft-motion-reference-roots.mjs +83 -0
- package/templates/starter/scripts/toolcraft-motion-reference-sampling.mjs +283 -0
- package/templates/starter/scripts/toolcraft-motion-reference-sampling.test.mjs +480 -0
- package/templates/starter/scripts/toolcraft-motion-reference-stage-registry.mjs +39 -0
- package/templates/starter/scripts/toolcraft-motion-reference-stage-registry.test.mjs +59 -0
- package/templates/starter/scripts/toolcraft-motion-reference-stage.mjs +71 -0
- package/templates/starter/scripts/toolcraft-motion-reference-stage.test.mjs +61 -0
- package/templates/starter/scripts/toolcraft-motion-review-numeric.mjs +74 -0
- package/templates/starter/scripts/toolcraft-motion-review-numeric.test.mjs +65 -0
- package/templates/starter/scripts/toolcraft-motion-review-partitioning.mjs +268 -0
- package/templates/starter/scripts/toolcraft-motion-review-partitioning.test.mjs +329 -0
- package/templates/starter/scripts/toolcraft-motion-review-protected-units.mjs +120 -0
- package/templates/starter/scripts/toolcraft-motion-review-protected-units.test.mjs +111 -0
- package/templates/starter/scripts/toolcraft-performance-escalation-policy.test.mjs +77 -345
- package/templates/starter/scripts/toolcraft-performance-receipt-validation.test.mjs +19 -21
- package/templates/starter/scripts/toolcraft-product-boundary-ast.mjs +1 -1
- package/templates/starter/scripts/toolcraft-product-export-boundary.mjs +10 -1
- package/templates/starter/scripts/toolcraft-product-export-boundary.test.mjs +5 -1
- package/templates/starter/scripts/toolcraft-product-observable-facade.test.mjs +46 -1
- package/templates/starter/scripts/toolcraft-receipt-boundary.test.mjs +0 -1
- package/templates/starter/scripts/toolcraft-source-inventory.d.mts +5 -0
- package/templates/starter/scripts/toolcraft-source-inventory.mjs +25 -14
- package/templates/starter/scripts/toolcraft-source-inventory.test.mjs +48 -0
- package/templates/starter/scripts/toolcraft-source-ownership.mjs +0 -1
- package/templates/starter/scripts/toolcraft-source-ownership.test.mjs +30 -5
- package/templates/starter/scripts/toolcraft-verification-receipt-test-helpers.mjs +5 -26
- package/templates/starter/scripts/toolcraft-verification-receipt.d.mts +0 -7
- package/templates/starter/scripts/toolcraft-verification-receipt.mjs +3 -47
- package/templates/starter/scripts/toolcraft-verification-receipt.test.mjs +6 -87
- package/templates/starter/scripts/toolcraft-vitest-runtime-contract.d.mts +19 -0
- package/templates/starter/scripts/toolcraft-vitest-runtime-contract.mjs +107 -0
- package/templates/starter/scripts/toolcraft-vitest-runtime-contract.test.mjs +114 -0
- package/templates/starter/scripts/toolcraft-vitest-runtime-evidence-reporter.mjs +13 -2
- package/templates/starter/scripts/toolcraft-vitest-runtime-evidence-reporter.test.mjs +110 -0
- package/templates/starter/src/app/acceptance/artifact-export-intent.ts +51 -20
- package/templates/starter/src/app/acceptance/canvas-coverage.ts +1 -0
- package/templates/starter/src/app/acceptance/color-bank-labels.ts +11 -15
- package/templates/starter/src/app/acceptance/color-row-grouping.ts +33 -0
- package/templates/starter/src/app/acceptance/control-acceptance-coverage.ts +0 -11
- package/templates/starter/src/app/acceptance/control-acceptance-policy.ts +2 -3
- package/templates/starter/src/app/acceptance/control-component-rules.ts +0 -10
- package/templates/starter/src/app/acceptance/control-labels.ts +30 -1
- package/templates/starter/src/app/acceptance/control-layout-section-rules.ts +18 -0
- package/templates/starter/src/app/acceptance/export-artifact-coverage.ts +25 -17
- package/templates/starter/src/app/acceptance/feature-verification-selection.test.ts +295 -0
- package/templates/starter/src/app/acceptance/feature-verification-selection.ts +161 -0
- package/templates/starter/src/app/acceptance/infinity-canvas.ts +10 -0
- package/templates/starter/src/app/acceptance/interaction-ownership-intent.ts +19 -1
- package/templates/starter/src/app/acceptance/interaction-ownership.ts +18 -2
- package/templates/starter/src/app/acceptance/motion-reference-evidence-validation.mjs +65 -0
- package/templates/starter/src/app/acceptance/motion-reference-evidence-windows.mjs +301 -0
- package/templates/starter/src/app/acceptance/motion-reference-evidence.d.mts +11 -0
- package/templates/starter/src/app/acceptance/motion-reference-evidence.mjs +145 -0
- package/templates/starter/src/app/acceptance/motion-reference-study-behaviors.ts +162 -0
- package/templates/starter/src/app/acceptance/motion-reference-study-coverage.ts +206 -0
- package/templates/starter/src/app/acceptance/motion-reference-study-identity.ts +129 -0
- package/templates/starter/src/app/acceptance/motion-reference-study-partitions.ts +186 -0
- package/templates/starter/src/app/acceptance/motion-reference-study.ts +90 -0
- package/templates/starter/src/app/acceptance/output-export-actions.test.ts +9 -1
- package/templates/starter/src/app/acceptance/output-export-actions.ts +14 -22
- package/templates/starter/src/app/acceptance/output-export-layout-rules.ts +5 -1
- package/templates/starter/src/app/acceptance/output-export-model.ts +4 -0
- package/templates/starter/src/app/acceptance/output-export.ts +4 -1
- package/templates/starter/src/app/acceptance/reference-study-types.ts +167 -17
- package/templates/starter/src/app/acceptance/runtime-coverage.ts +6 -1
- package/templates/starter/src/app/acceptance/selection-scope.test.ts +200 -0
- package/templates/starter/src/app/acceptance/selection-scope.ts +151 -0
- package/templates/starter/src/app/acceptance/transfer-mode-types.ts +114 -0
- package/templates/starter/src/app/acceptance/types.ts +43 -118
- package/templates/starter/src/app/acceptance/validate-coverage.ts +14 -3
- package/templates/starter/src/app/starter-acceptance-data.ts +1 -0
- package/templates/starter/src/app/starter-acceptance.animation-intent.test.ts +3 -0
- package/templates/starter/src/app/starter-acceptance.artifact-export-intent.test.ts +81 -8
- package/templates/starter/src/app/starter-acceptance.background-export.test.ts +2 -0
- package/templates/starter/src/app/starter-acceptance.canvas-sizing.test.ts +2 -0
- package/templates/starter/src/app/starter-acceptance.color-row-grouping.test.ts +118 -0
- package/templates/starter/src/app/starter-acceptance.compound-part-coverage.test.ts +1 -1
- package/templates/starter/src/app/starter-acceptance.contract-fixtures.ts +1 -0
- package/templates/starter/src/app/starter-acceptance.control-applicability.test.ts +1 -0
- package/templates/starter/src/app/starter-acceptance.control-label-context.test.ts +1 -0
- package/templates/starter/src/app/starter-acceptance.control-labels.test.ts +78 -3
- package/templates/starter/src/app/starter-acceptance.control-naming-rules.test.ts +77 -1
- package/templates/starter/src/app/starter-acceptance.custom-control-fit-check.test.ts +1 -1
- package/templates/starter/src/app/starter-acceptance.export-artifact.test.ts +74 -3
- package/templates/starter/src/app/starter-acceptance.export-test-utils.ts +5 -1
- package/templates/starter/src/app/starter-acceptance.image-export-settings.test.ts +2 -0
- package/templates/starter/src/app/starter-acceptance.interaction-ownership.test.ts +19 -3
- package/templates/starter/src/app/starter-acceptance.layers.test.ts +2 -1
- package/templates/starter/src/app/starter-acceptance.media-upload.test.ts +28 -0
- package/templates/starter/src/app/starter-acceptance.motion-reference-evidence.test.ts +17 -0
- package/templates/starter/src/app/starter-acceptance.motion-reference-identity.test.ts +163 -0
- package/templates/starter/src/app/starter-acceptance.motion-reference-mapping.test.ts +133 -0
- package/templates/starter/src/app/starter-acceptance.motion-reference-ownership.test.ts +121 -0
- package/templates/starter/src/app/starter-acceptance.motion-reference-partitions.test.ts +210 -0
- package/templates/starter/src/app/starter-acceptance.motion-reference-test-utils.ts +313 -0
- package/templates/starter/src/app/starter-acceptance.output-export-intent.test.ts +78 -4
- package/templates/starter/src/app/starter-acceptance.product-output-export.test.ts +6 -1
- package/templates/starter/src/app/starter-acceptance.product-readiness.test.ts +18 -1
- package/templates/starter/src/app/starter-acceptance.product-worklog.test.ts +123 -2
- package/templates/starter/src/app/starter-acceptance.reference-custom-timeline.test.ts +2 -0
- package/templates/starter/src/app/starter-acceptance.reference-foundation.test.ts +8 -0
- package/templates/starter/src/app/starter-acceptance.reference-timeline.test.ts +7 -0
- package/templates/starter/src/app/starter-acceptance.section-cohesion.test.ts +2 -0
- package/templates/starter/src/app/starter-acceptance.timeline-playback.test.ts +5 -0
- package/templates/starter/src/app/starter-acceptance.ts +34 -8
- package/templates/starter/src/app/starter-acceptance.video-export-settings.test.ts +2 -0
- package/templates/starter/src/app/starter-acceptance.video-reference-study.test.ts +34 -236
- package/templates/starter/src/app/starter-acceptance.view-interaction.test.ts +4 -0
- package/templates/starter/src/app/starter-acceptance.worklog-test-utils.ts +122 -6
- package/templates/starter/src/app/starter-acceptance.worklog.test.ts +152 -16
- package/templates/starter/src/app/starter-performance.gates.test.ts +20 -18
- package/templates/starter/src/app/starter-performance.lifecycle.test.ts +22 -6
- package/templates/starter/src/app/test-evidence/browser-runtime-contract.ts +3 -0
- package/templates/starter/tsconfig.json +1 -0
- package/templates/starter/scripts/toolcraft-browser-impact-selection.mjs +0 -26
- package/templates/starter/scripts/toolcraft-delivery-framework-refresh.test.mjs +0 -141
- package/templates/starter/scripts/toolcraft-delivery-functional-changed.test.mjs +0 -468
- package/templates/starter/scripts/toolcraft-delivery-functional-model-lifecycle.test.mjs +0 -254
- package/templates/starter/scripts/toolcraft-delivery-functional-semantic-regression.test.mjs +0 -135
- package/templates/starter/scripts/toolcraft-delivery-performance-iteration-durability.test.mjs +0 -329
- package/templates/starter/scripts/toolcraft-delivery-plan-errors.test.mjs +0 -430
- package/templates/starter/scripts/toolcraft-delivery-plan-functional-model.test.mjs +0 -358
- package/templates/starter/scripts/toolcraft-functional-proof-regression-fixtures.mjs +0 -298
- package/templates/starter/scripts/toolcraft-verification-impact-inventory.d.mts +0 -62
- package/templates/starter/scripts/toolcraft-verification-impact-inventory.mjs +0 -344
- package/templates/starter/scripts/toolcraft-verification-impact-inventory.test.mjs +0 -204
- package/templates/starter/scripts/toolcraft-verification-impact-performance-candidates.test.mjs +0 -210
- package/templates/starter/scripts/toolcraft-verification-impact-resolution.mjs +0 -344
- package/templates/starter/scripts/toolcraft-verification-impact-resolution.test.mjs +0 -270
- package/templates/starter/scripts/toolcraft-verification-impact-semantic-resolution.test.mjs +0 -449
- package/templates/starter/scripts/toolcraft-verification-impact-support-resolution.test.mjs +0 -195
- package/templates/starter/scripts/toolcraft-verification-impact.d.mts +0 -132
- package/templates/starter/scripts/toolcraft-verification-impact.mjs +0 -169
- package/templates/starter/scripts/toolcraft-verification-impact.test.mjs +0 -300
- package/templates/starter/scripts/toolcraft-verification-input-roles.mjs +0 -197
- package/templates/starter/scripts/toolcraft-verification-input-roles.test.mjs +0 -225
- package/templates/starter/src/app/acceptance/video-reference-study.ts +0 -217
- package/templates/starter/src/app/starter-verification-impact.json +0 -15
|
@@ -0,0 +1,433 @@
|
|
|
1
|
+
import assert from "node:assert/strict";
|
|
2
|
+
import fs from "node:fs/promises";
|
|
3
|
+
import os from "node:os";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
import test from "node:test";
|
|
6
|
+
|
|
7
|
+
import {
|
|
8
|
+
publishToolcraftMotionReferenceStudies,
|
|
9
|
+
} from "./toolcraft-motion-reference-publication.mjs";
|
|
10
|
+
import {
|
|
11
|
+
getToolcraftMotionReferenceBackupPath,
|
|
12
|
+
} from "./toolcraft-motion-reference-publication-journal-schema.mjs";
|
|
13
|
+
import {
|
|
14
|
+
createToolcraftMotionPublicationEvidenceGroup,
|
|
15
|
+
} from "./toolcraft-motion-reference-publication.fixtures.mjs";
|
|
16
|
+
|
|
17
|
+
const sourceIdentity = createToolcraftMotionPublicationEvidenceGroup().referenceId;
|
|
18
|
+
|
|
19
|
+
async function exists(target) {
|
|
20
|
+
try { await fs.stat(target); return true; } catch (error) {
|
|
21
|
+
if (error.code === "ENOENT") return false;
|
|
22
|
+
throw error;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
async function fixture(context, count = 1) {
|
|
26
|
+
const root = await fs.mkdtemp(path.join(os.tmpdir(), "motion-publication-"));
|
|
27
|
+
context.after(() => fs.rm(root, { force: true, recursive: true }));
|
|
28
|
+
const temporaryRoot = path.join(root, ".toolcraft", "tmp");
|
|
29
|
+
const lockDirectory = path.join(temporaryRoot, `${sourceIdentity}.lock`);
|
|
30
|
+
const finalRoot = path.join(root, "src", "app", "reference-studies");
|
|
31
|
+
const group = createToolcraftMotionPublicationEvidenceGroup({ count });
|
|
32
|
+
const stagingRoot = path.join(
|
|
33
|
+
temporaryRoot, "work", `${sourceIdentity}.staging`,
|
|
34
|
+
);
|
|
35
|
+
await fs.mkdir(temporaryRoot, { recursive: true });
|
|
36
|
+
await fs.mkdir(finalRoot, { recursive: true });
|
|
37
|
+
await fs.mkdir(stagingRoot, { recursive: true });
|
|
38
|
+
const pairs = [];
|
|
39
|
+
for (const [index, evidence] of group.evidences.entries()) {
|
|
40
|
+
const stagingDirectory = path.join(stagingRoot, evidence.studyId);
|
|
41
|
+
const finalDirectory = path.join(finalRoot, evidence.studyId);
|
|
42
|
+
await fs.mkdir(stagingDirectory);
|
|
43
|
+
await fs.writeFile(path.join(stagingDirectory, "version"), `new-${index}`);
|
|
44
|
+
pairs.push({ finalDirectory, stagingDirectory });
|
|
45
|
+
}
|
|
46
|
+
return {
|
|
47
|
+
evidences: group.evidences, finalRoot, lockDirectory, pairs, root,
|
|
48
|
+
stagingRoot, temporaryRoot,
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
async function version(directory) {
|
|
52
|
+
return fs.readFile(path.join(directory, "version"), "utf8");
|
|
53
|
+
}
|
|
54
|
+
function options(fixture, overrides = {}) {
|
|
55
|
+
const { stagingRoot = fixture.stagingRoot, ...settings } = overrides;
|
|
56
|
+
return {
|
|
57
|
+
discoverPreviousGroup: async () => ({
|
|
58
|
+
obsoleteStudyIds: [],
|
|
59
|
+
}),
|
|
60
|
+
lockLeaseMilliseconds: 500,
|
|
61
|
+
lockDirectory: fixture.lockDirectory,
|
|
62
|
+
now: () => 1_000,
|
|
63
|
+
pid: 444,
|
|
64
|
+
sourceIdentity,
|
|
65
|
+
tokenFactory: () => "holder-token",
|
|
66
|
+
publicationGroup: {
|
|
67
|
+
evidences: fixture.evidences,
|
|
68
|
+
referenceStudiesRoot: fixture.finalRoot,
|
|
69
|
+
rootDir: fixture.root,
|
|
70
|
+
stagingRoot,
|
|
71
|
+
},
|
|
72
|
+
...settings,
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
const transactionRoot = (value) => path.join(
|
|
76
|
+
value.temporaryRoot, `${sourceIdentity}.transaction`,
|
|
77
|
+
);
|
|
78
|
+
const backupPath = (value, finalDirectory) =>
|
|
79
|
+
getToolcraftMotionReferenceBackupPath(transactionRoot(value), finalDirectory);
|
|
80
|
+
|
|
81
|
+
test("publishes one or more new staged studies as one group", async (context) => {
|
|
82
|
+
const value = await fixture(context, 2);
|
|
83
|
+
const result = await publishToolcraftMotionReferenceStudies(
|
|
84
|
+
value.pairs, options(value),
|
|
85
|
+
);
|
|
86
|
+
assert.deepEqual(result, { committed: true, retainedBackups: [], warnings: [] });
|
|
87
|
+
assert.deepEqual(await Promise.all(value.pairs.map(({ finalDirectory }) =>
|
|
88
|
+
version(finalDirectory))), ["new-0", "new-1"]);
|
|
89
|
+
assert.equal(await exists(value.lockDirectory), false);
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
test("atomically replaces old studies and removes complete backups", async (context) => {
|
|
93
|
+
const value = await fixture(context, 2);
|
|
94
|
+
for (const [index, pair] of value.pairs.entries()) {
|
|
95
|
+
await fs.mkdir(pair.finalDirectory);
|
|
96
|
+
await fs.writeFile(path.join(pair.finalDirectory, "version"), `old-${index}`);
|
|
97
|
+
}
|
|
98
|
+
await publishToolcraftMotionReferenceStudies(value.pairs, options(value));
|
|
99
|
+
assert.deepEqual(await Promise.all(value.pairs.map(({ finalDirectory }) =>
|
|
100
|
+
version(finalDirectory))), ["new-0", "new-1"]);
|
|
101
|
+
assert.deepEqual(await Promise.all(value.pairs.map(({ finalDirectory }) =>
|
|
102
|
+
exists(backupPath(value, finalDirectory)))), [false, false]);
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
test("a pre-commit staged rename failure restores the complete old group", async (context) => {
|
|
106
|
+
const value = await fixture(context, 2);
|
|
107
|
+
for (const [index, pair] of value.pairs.entries()) {
|
|
108
|
+
await fs.mkdir(pair.finalDirectory);
|
|
109
|
+
await fs.writeFile(path.join(pair.finalDirectory, "version"), `old-${index}`);
|
|
110
|
+
}
|
|
111
|
+
let stagedRenames = 0;
|
|
112
|
+
const fileSystem = {
|
|
113
|
+
...fs,
|
|
114
|
+
rename: async (from, to) => {
|
|
115
|
+
if (value.pairs.some(({ stagingDirectory }) => from === stagingDirectory) &&
|
|
116
|
+
++stagedRenames === 2) throw Object.assign(new Error("injected rename failure"), {
|
|
117
|
+
code: "EIO",
|
|
118
|
+
});
|
|
119
|
+
return fs.rename(from, to);
|
|
120
|
+
},
|
|
121
|
+
};
|
|
122
|
+
await assert.rejects(() => publishToolcraftMotionReferenceStudies(
|
|
123
|
+
value.pairs, options(value, { fileSystem }),
|
|
124
|
+
), /publication.*injected rename failure/iu);
|
|
125
|
+
assert.deepEqual(await Promise.all(value.pairs.map(({ finalDirectory }) =>
|
|
126
|
+
version(finalDirectory))), ["old-0", "old-1"]);
|
|
127
|
+
assert.deepEqual(await Promise.all(value.pairs.map(({ finalDirectory }) =>
|
|
128
|
+
exists(backupPath(value, finalDirectory)))), [false, false]);
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
test("rejects an unexplained backup instead of guessing pair state", async (context) => {
|
|
132
|
+
const value = await fixture(context);
|
|
133
|
+
const pair = value.pairs[0];
|
|
134
|
+
const backup = backupPath(value, pair.finalDirectory);
|
|
135
|
+
await fs.mkdir(transactionRoot(value));
|
|
136
|
+
await fs.mkdir(backup);
|
|
137
|
+
await fs.writeFile(path.join(backup, "version"), "old");
|
|
138
|
+
await assert.rejects(() => publishToolcraftMotionReferenceStudies(
|
|
139
|
+
value.pairs, options(value),
|
|
140
|
+
), /unexplained backup|journal/iu);
|
|
141
|
+
assert.equal(await version(backup), "old");
|
|
142
|
+
assert.equal(await exists(pair.finalDirectory), false);
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
test("journal recovery restores the complete old group before a failing next publish", async (context) => {
|
|
146
|
+
const value = await fixture(context, 2);
|
|
147
|
+
const [first, second] = value.pairs;
|
|
148
|
+
const firstBackup = backupPath(value, first.finalDirectory);
|
|
149
|
+
const secondBackup = backupPath(value, second.finalDirectory);
|
|
150
|
+
await fs.mkdir(transactionRoot(value));
|
|
151
|
+
await fs.mkdir(first.finalDirectory);
|
|
152
|
+
await fs.writeFile(path.join(first.finalDirectory, "version"), "interrupted-new-a");
|
|
153
|
+
for (const [backup, content] of [[firstBackup, "old-a"], [secondBackup, "old-b"]]) {
|
|
154
|
+
await fs.mkdir(backup);
|
|
155
|
+
await fs.writeFile(path.join(backup, "version"), content);
|
|
156
|
+
}
|
|
157
|
+
const previousStagingRoot = path.join(
|
|
158
|
+
value.temporaryRoot, "previous", `${sourceIdentity}.staging`,
|
|
159
|
+
);
|
|
160
|
+
await fs.writeFile(`${value.lockDirectory}.publication.json`, `${JSON.stringify({
|
|
161
|
+
operation: null,
|
|
162
|
+
phase: "preparing",
|
|
163
|
+
records: [
|
|
164
|
+
{ backedUp: true, backupDirectory: firstBackup,
|
|
165
|
+
finalDirectory: first.finalDirectory, hadOld: true, kind: "publish", published: true,
|
|
166
|
+
stagingDirectory: path.join(
|
|
167
|
+
previousStagingRoot, path.basename(first.finalDirectory),
|
|
168
|
+
) },
|
|
169
|
+
{ backedUp: true, backupDirectory: secondBackup,
|
|
170
|
+
finalDirectory: second.finalDirectory, hadOld: true, kind: "publish", published: false,
|
|
171
|
+
stagingDirectory: path.join(
|
|
172
|
+
previousStagingRoot, path.basename(second.finalDirectory),
|
|
173
|
+
) },
|
|
174
|
+
],
|
|
175
|
+
sourceIdentity,
|
|
176
|
+
version: 4,
|
|
177
|
+
}, null, 2)}\n`);
|
|
178
|
+
let stagedRenames = 0;
|
|
179
|
+
const fileSystem = {
|
|
180
|
+
...fs,
|
|
181
|
+
rename: async (from, to) => {
|
|
182
|
+
if (value.pairs.some(({ stagingDirectory }) => from === stagingDirectory) &&
|
|
183
|
+
++stagedRenames === 2) throw Object.assign(new Error("next publish failed"), {
|
|
184
|
+
code: "EIO",
|
|
185
|
+
});
|
|
186
|
+
return fs.rename(from, to);
|
|
187
|
+
},
|
|
188
|
+
};
|
|
189
|
+
await assert.rejects(() => publishToolcraftMotionReferenceStudies(
|
|
190
|
+
value.pairs, options(value, { fileSystem }),
|
|
191
|
+
), /next publish failed/iu);
|
|
192
|
+
assert.deepEqual(await Promise.all(value.pairs.map(({ finalDirectory }) =>
|
|
193
|
+
version(finalDirectory))), ["old-a", "old-b"]);
|
|
194
|
+
assert.equal(await exists(`${value.lockDirectory}.publication.json`), false);
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
test("post-commit backup cleanup failure is success with a retained warning", async (context) => {
|
|
198
|
+
const value = await fixture(context);
|
|
199
|
+
const pair = value.pairs[0];
|
|
200
|
+
await fs.mkdir(pair.finalDirectory);
|
|
201
|
+
await fs.writeFile(path.join(pair.finalDirectory, "version"), "old");
|
|
202
|
+
const backup = backupPath(value, pair.finalDirectory);
|
|
203
|
+
let failCleanup = true;
|
|
204
|
+
const fileSystem = {
|
|
205
|
+
...fs,
|
|
206
|
+
rm: async (target, settings) => {
|
|
207
|
+
if (target === backup && failCleanup) {
|
|
208
|
+
failCleanup = false;
|
|
209
|
+
throw Object.assign(new Error("cleanup unavailable"), { code: "EBUSY" });
|
|
210
|
+
}
|
|
211
|
+
return fs.rm(target, settings);
|
|
212
|
+
},
|
|
213
|
+
};
|
|
214
|
+
const result = await publishToolcraftMotionReferenceStudies(
|
|
215
|
+
value.pairs, options(value, { fileSystem }),
|
|
216
|
+
);
|
|
217
|
+
assert.equal(result.committed, true);
|
|
218
|
+
assert.deepEqual(result.retainedBackups, [backup]);
|
|
219
|
+
assert.match(result.warnings.join("\n"), /retained.*backup/iu);
|
|
220
|
+
assert.equal(await version(pair.finalDirectory), "new-0");
|
|
221
|
+
assert.equal(await exists(`${value.lockDirectory}.publication.json`), true);
|
|
222
|
+
|
|
223
|
+
const nextStaging = path.join(
|
|
224
|
+
value.stagingRoot, path.basename(pair.finalDirectory),
|
|
225
|
+
);
|
|
226
|
+
await fs.mkdir(nextStaging);
|
|
227
|
+
await fs.writeFile(path.join(nextStaging, "version"), "newer");
|
|
228
|
+
await publishToolcraftMotionReferenceStudies(
|
|
229
|
+
[{ ...pair, stagingDirectory: nextStaging }], options(value),
|
|
230
|
+
);
|
|
231
|
+
assert.equal(await exists(backup), false);
|
|
232
|
+
assert.equal(await version(pair.finalDirectory), "newer");
|
|
233
|
+
assert.equal(await exists(`${value.lockDirectory}.publication.json`), false);
|
|
234
|
+
});
|
|
235
|
+
|
|
236
|
+
test("an unexpired lease fails actionably without mutating staging or final", async (context) => {
|
|
237
|
+
const value = await fixture(context);
|
|
238
|
+
await fs.mkdir(value.lockDirectory);
|
|
239
|
+
await fs.writeFile(path.join(value.lockDirectory, "owner.json"), JSON.stringify({
|
|
240
|
+
leaseExpiresAt: 1_500, pid: 91, sourceIdentity,
|
|
241
|
+
token: "live-owner",
|
|
242
|
+
}));
|
|
243
|
+
await assert.rejects(() => publishToolcraftMotionReferenceStudies(
|
|
244
|
+
value.pairs, options(value),
|
|
245
|
+
), /unexpired.*lease/iu);
|
|
246
|
+
assert.equal(await exists(value.pairs[0].stagingDirectory), true);
|
|
247
|
+
assert.equal(await exists(value.pairs[0].finalDirectory), false);
|
|
248
|
+
});
|
|
249
|
+
|
|
250
|
+
test("an expired owner lease is reclaimed before publication", async (context) => {
|
|
251
|
+
const value = await fixture(context);
|
|
252
|
+
await fs.mkdir(value.lockDirectory);
|
|
253
|
+
await fs.writeFile(path.join(value.lockDirectory, "owner.json"), JSON.stringify({
|
|
254
|
+
leaseExpiresAt: 999, pid: 91, sourceIdentity,
|
|
255
|
+
token: "stale-owner",
|
|
256
|
+
}));
|
|
257
|
+
await publishToolcraftMotionReferenceStudies(value.pairs, options(value));
|
|
258
|
+
assert.equal(await version(value.pairs[0].finalDirectory), "new-0");
|
|
259
|
+
});
|
|
260
|
+
|
|
261
|
+
test("two stale-lock contenders allow only one publisher and preserve the winner lock", async (context) => {
|
|
262
|
+
const value = await fixture(context);
|
|
263
|
+
const secondStagingRoot = path.join(
|
|
264
|
+
value.temporaryRoot, "second-work", `${sourceIdentity}.staging`,
|
|
265
|
+
);
|
|
266
|
+
const secondStaging = path.join(
|
|
267
|
+
secondStagingRoot, path.basename(value.pairs[0].finalDirectory),
|
|
268
|
+
);
|
|
269
|
+
await fs.mkdir(secondStaging, { recursive: true });
|
|
270
|
+
await fs.writeFile(path.join(secondStaging, "version"), "second-new");
|
|
271
|
+
await fs.mkdir(value.lockDirectory);
|
|
272
|
+
await fs.writeFile(path.join(value.lockDirectory, "owner.json"), JSON.stringify({
|
|
273
|
+
leaseExpiresAt: 999, pid: 91, sourceIdentity,
|
|
274
|
+
token: "stale-owner",
|
|
275
|
+
}));
|
|
276
|
+
let winnerEntered;
|
|
277
|
+
let releaseWinner;
|
|
278
|
+
const winnerBlocked = new Promise((resolve) => { winnerEntered = resolve; });
|
|
279
|
+
const winnerGate = new Promise((resolve) => { releaseWinner = resolve; });
|
|
280
|
+
const stagingDirectories = new Set([value.pairs[0].stagingDirectory, secondStaging]);
|
|
281
|
+
const fileSystem = {
|
|
282
|
+
...fs,
|
|
283
|
+
rename: async (from, to) => {
|
|
284
|
+
if (stagingDirectories.has(from)) {
|
|
285
|
+
winnerEntered();
|
|
286
|
+
await winnerGate;
|
|
287
|
+
}
|
|
288
|
+
return fs.rename(from, to);
|
|
289
|
+
},
|
|
290
|
+
};
|
|
291
|
+
let loserRejected;
|
|
292
|
+
const loser = new Promise((resolve) => { loserRejected = resolve; });
|
|
293
|
+
const attempts = [
|
|
294
|
+
publishToolcraftMotionReferenceStudies(value.pairs, options(value, {
|
|
295
|
+
fileSystem, tokenFactory: () => "contender-a",
|
|
296
|
+
})),
|
|
297
|
+
publishToolcraftMotionReferenceStudies([{ ...value.pairs[0],
|
|
298
|
+
stagingDirectory: secondStaging }], options(value, {
|
|
299
|
+
fileSystem, stagingRoot: secondStagingRoot,
|
|
300
|
+
tokenFactory: () => "contender-b",
|
|
301
|
+
})),
|
|
302
|
+
].map((attempt) => attempt.then(
|
|
303
|
+
(value) => ({ status: "fulfilled", value }),
|
|
304
|
+
(reason) => {
|
|
305
|
+
loserRejected(reason);
|
|
306
|
+
return { reason, status: "rejected" };
|
|
307
|
+
},
|
|
308
|
+
));
|
|
309
|
+
await winnerBlocked;
|
|
310
|
+
const loserError = await loser;
|
|
311
|
+
assert.match(loserError.message, /reclaim|another command|lock/iu);
|
|
312
|
+
const activeOwner = JSON.parse(await fs.readFile(
|
|
313
|
+
path.join(value.lockDirectory, "owner.json"), "utf8",
|
|
314
|
+
));
|
|
315
|
+
assert.ok(["contender-a", "contender-b"].includes(activeOwner.token));
|
|
316
|
+
assert.equal(await exists(value.lockDirectory), true);
|
|
317
|
+
releaseWinner();
|
|
318
|
+
const results = await Promise.all(attempts);
|
|
319
|
+
assert.equal(results.filter(({ status }) => status === "fulfilled").length, 1);
|
|
320
|
+
assert.equal(results.filter(({ status }) => status === "rejected").length, 1);
|
|
321
|
+
assert.ok(["new-0", "second-new"].includes(await version(value.pairs[0].finalDirectory)));
|
|
322
|
+
assert.equal(await exists(value.lockDirectory), false);
|
|
323
|
+
});
|
|
324
|
+
|
|
325
|
+
test("an active publisher renews before a staged mutation so an expired original lease cannot be reclaimed",
|
|
326
|
+
async (context) => {
|
|
327
|
+
const value = await fixture(context, 2);
|
|
328
|
+
const contenderStagingRoot = path.join(
|
|
329
|
+
value.temporaryRoot, "contender-work", `${sourceIdentity}.staging`,
|
|
330
|
+
);
|
|
331
|
+
await fs.mkdir(contenderStagingRoot, { recursive: true });
|
|
332
|
+
const contenderPairs = [];
|
|
333
|
+
for (const [index, pair] of value.pairs.entries()) {
|
|
334
|
+
const stagingDirectory = path.join(
|
|
335
|
+
contenderStagingRoot, path.basename(pair.finalDirectory),
|
|
336
|
+
);
|
|
337
|
+
await fs.mkdir(stagingDirectory);
|
|
338
|
+
await fs.writeFile(path.join(stagingDirectory, "version"), `contender-${index}`);
|
|
339
|
+
contenderPairs.push({ ...pair, stagingDirectory });
|
|
340
|
+
}
|
|
341
|
+
let clock = 1_000;
|
|
342
|
+
let journalWrites = 0;
|
|
343
|
+
let stagedRenameEntered;
|
|
344
|
+
let releaseStagedRename;
|
|
345
|
+
const stagedRenameStarted = new Promise((resolve) => {
|
|
346
|
+
stagedRenameEntered = resolve;
|
|
347
|
+
});
|
|
348
|
+
const stagedRenameGate = new Promise((resolve) => {
|
|
349
|
+
releaseStagedRename = resolve;
|
|
350
|
+
});
|
|
351
|
+
const journalPath = `${value.lockDirectory}.publication.json`;
|
|
352
|
+
const fileSystem = {
|
|
353
|
+
...fs,
|
|
354
|
+
rename: async (from, to) => {
|
|
355
|
+
if (to === journalPath && ++journalWrites === 2) clock = 1_090;
|
|
356
|
+
if (from === value.pairs[0].stagingDirectory) {
|
|
357
|
+
stagedRenameEntered();
|
|
358
|
+
await stagedRenameGate;
|
|
359
|
+
}
|
|
360
|
+
return fs.rename(from, to);
|
|
361
|
+
},
|
|
362
|
+
};
|
|
363
|
+
const publisher = publishToolcraftMotionReferenceStudies(
|
|
364
|
+
value.pairs, options(value, {
|
|
365
|
+
fileSystem,
|
|
366
|
+
lockLeaseMilliseconds: 100,
|
|
367
|
+
now: () => clock,
|
|
368
|
+
tokenFactory: () => "active-publisher",
|
|
369
|
+
}),
|
|
370
|
+
).then((result) => ({ result, status: "fulfilled" }),
|
|
371
|
+
(reason) => ({ reason, status: "rejected" }));
|
|
372
|
+
await stagedRenameStarted;
|
|
373
|
+
clock = 1_150;
|
|
374
|
+
const contender = await publishToolcraftMotionReferenceStudies(
|
|
375
|
+
contenderPairs, options(value, {
|
|
376
|
+
lockLeaseMilliseconds: 100,
|
|
377
|
+
now: () => clock,
|
|
378
|
+
stagingRoot: contenderStagingRoot,
|
|
379
|
+
tokenFactory: () => "late-contender",
|
|
380
|
+
}),
|
|
381
|
+
).then((result) => ({ result, status: "fulfilled" }),
|
|
382
|
+
(reason) => ({ reason, status: "rejected" }));
|
|
383
|
+
releaseStagedRename();
|
|
384
|
+
const published = await publisher;
|
|
385
|
+
|
|
386
|
+
assert.equal(contender.status, "rejected");
|
|
387
|
+
assert.match(contender.reason.message, /lease|lock|publisher/iu);
|
|
388
|
+
assert.equal(published.status, "fulfilled");
|
|
389
|
+
assert.deepEqual(await Promise.all(value.pairs.map(({ finalDirectory }) =>
|
|
390
|
+
version(finalDirectory))), ["new-0", "new-1"]);
|
|
391
|
+
assert.deepEqual(await Promise.all(contenderPairs.map(({ stagingDirectory }) =>
|
|
392
|
+
version(stagingDirectory))), ["contender-0", "contender-1"]);
|
|
393
|
+
});
|
|
394
|
+
|
|
395
|
+
test("token loss fails closed before finals and never removes the replacement lock", async (context) => {
|
|
396
|
+
const value = await fixture(context);
|
|
397
|
+
const fileSystem = {
|
|
398
|
+
...fs,
|
|
399
|
+
readFile: async (target, ...args) => {
|
|
400
|
+
if (target === path.join(value.lockDirectory, "owner.json")) {
|
|
401
|
+
return JSON.stringify({ leaseExpiresAt: 1_500, pid: 999, sourceIdentity,
|
|
402
|
+
token: "replacement-owner" });
|
|
403
|
+
}
|
|
404
|
+
return fs.readFile(target, ...args);
|
|
405
|
+
},
|
|
406
|
+
};
|
|
407
|
+
await assert.rejects(() => publishToolcraftMotionReferenceStudies(
|
|
408
|
+
value.pairs, options(value, { fileSystem, tokenFactory: () => "holder-token" }),
|
|
409
|
+
), /ownership|token|journal/iu);
|
|
410
|
+
assert.equal(await exists(value.lockDirectory), true);
|
|
411
|
+
assert.equal(await exists(value.pairs[0].stagingDirectory), true);
|
|
412
|
+
assert.equal(await exists(value.pairs[0].finalDirectory), false);
|
|
413
|
+
await fs.rm(value.lockDirectory, { force: true, recursive: true });
|
|
414
|
+
});
|
|
415
|
+
|
|
416
|
+
test("rejects cross-device staging before any rename", async (context) => {
|
|
417
|
+
const value = await fixture(context);
|
|
418
|
+
const renames = [];
|
|
419
|
+
const fileSystem = {
|
|
420
|
+
...fs,
|
|
421
|
+
rename: async (...args) => { renames.push(args); return fs.rename(...args); },
|
|
422
|
+
stat: async (target) => {
|
|
423
|
+
const status = await fs.stat(target);
|
|
424
|
+
return Object.create(status, {
|
|
425
|
+
dev: { value: target === value.pairs[0].stagingDirectory ? 20 : 10 },
|
|
426
|
+
});
|
|
427
|
+
},
|
|
428
|
+
};
|
|
429
|
+
await assert.rejects(() => publishToolcraftMotionReferenceStudies(
|
|
430
|
+
value.pairs, options(value, { fileSystem }),
|
|
431
|
+
), /same filesystem/iu);
|
|
432
|
+
assert.deepEqual(renames, []);
|
|
433
|
+
});
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
|
|
3
|
+
function fail(message) {
|
|
4
|
+
throw new TypeError(`Invalid Toolcraft motion review output: ${message}`);
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
function validateOutputDirectory(outputDirectory) {
|
|
8
|
+
if (typeof outputDirectory !== "string" || outputDirectory.length === 0) {
|
|
9
|
+
fail("an output directory is required");
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function validateToolcraftMotionReviewFrameIndices(indices, frameCount) {
|
|
14
|
+
if (!Number.isInteger(frameCount) || frameCount <= 0 ||
|
|
15
|
+
!Array.isArray(indices) || indices.length === 0) {
|
|
16
|
+
fail("at least one review frame index and a positive frame count are required");
|
|
17
|
+
}
|
|
18
|
+
indices.forEach((index, position) => {
|
|
19
|
+
if (!Number.isInteger(index) || index < 0 || index >= frameCount ||
|
|
20
|
+
(position > 0 && index <= indices[position - 1])) {
|
|
21
|
+
fail("review frame indices must be unique, ordered, and in range");
|
|
22
|
+
}
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function createToolcraftMotionReviewOutputPattern(outputDirectory) {
|
|
27
|
+
validateOutputDirectory(outputDirectory);
|
|
28
|
+
return path.join(outputDirectory, "review-%06d.png");
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export function createToolcraftMotionReviewOutputPath(outputDirectory, ordinal) {
|
|
32
|
+
validateOutputDirectory(outputDirectory);
|
|
33
|
+
if (!Number.isInteger(ordinal) || ordinal < 0) {
|
|
34
|
+
fail("a nonnegative review output ordinal is required");
|
|
35
|
+
}
|
|
36
|
+
return path.join(outputDirectory,
|
|
37
|
+
`review-${String(ordinal).padStart(6, "0")}.png`);
|
|
38
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import assert from "node:assert/strict";
|
|
2
|
+
import test from "node:test";
|
|
3
|
+
|
|
4
|
+
import {
|
|
5
|
+
createToolcraftMotionReviewOutputPath,
|
|
6
|
+
createToolcraftMotionReviewOutputPattern,
|
|
7
|
+
validateToolcraftMotionReviewFrameIndices,
|
|
8
|
+
} from "./toolcraft-motion-reference-review-output.mjs";
|
|
9
|
+
|
|
10
|
+
test("validates strictly ordered unique in-range review indices", () => {
|
|
11
|
+
assert.doesNotThrow(() => validateToolcraftMotionReviewFrameIndices([0, 2], 3));
|
|
12
|
+
for (const indices of [[], [0, 0], [2, 1], [-1], [3], [0.5]]) {
|
|
13
|
+
assert.throws(() => validateToolcraftMotionReviewFrameIndices(indices, 3),
|
|
14
|
+
/review frame indices|at least one/iu);
|
|
15
|
+
}
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
test("creates deterministic ordinal PNG patterns and paths", () => {
|
|
19
|
+
assert.equal(createToolcraftMotionReviewOutputPattern("/tmp/review"),
|
|
20
|
+
"/tmp/review/review-%06d.png");
|
|
21
|
+
assert.equal(createToolcraftMotionReviewOutputPath("/tmp/review", 0),
|
|
22
|
+
"/tmp/review/review-000000.png");
|
|
23
|
+
assert.equal(createToolcraftMotionReviewOutputPath("/tmp/review", 12),
|
|
24
|
+
"/tmp/review/review-000012.png");
|
|
25
|
+
assert.throws(() => createToolcraftMotionReviewOutputPattern(""),
|
|
26
|
+
/output directory/iu);
|
|
27
|
+
assert.throws(() => createToolcraftMotionReviewOutputPath("/tmp", -1),
|
|
28
|
+
/ordinal/iu);
|
|
29
|
+
});
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import assert from "node:assert/strict";
|
|
2
|
+
import fs from "node:fs/promises";
|
|
3
|
+
import os from "node:os";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
import test from "node:test";
|
|
6
|
+
|
|
7
|
+
import {
|
|
8
|
+
createToolcraftMotionReferenceStudy,
|
|
9
|
+
} from "./create-toolcraft-motion-reference-study.mjs";
|
|
10
|
+
import {
|
|
11
|
+
requireToolcraftMotionReferenceStudiesRoot,
|
|
12
|
+
resolveToolcraftMotionReferenceRoots,
|
|
13
|
+
} from "./toolcraft-motion-reference-roots.mjs";
|
|
14
|
+
|
|
15
|
+
async function exists(target) {
|
|
16
|
+
return fs.lstat(target).then(() => true, (error) => {
|
|
17
|
+
if (error.code === "ENOENT") return false;
|
|
18
|
+
throw error;
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
test("rejects every reference-studies ancestor symlink before any mutation",
|
|
23
|
+
async (context) => {
|
|
24
|
+
for (const linkedPath of ["src", "src/app", "src/app/reference-studies"]) {
|
|
25
|
+
await context.test(linkedPath, async (subtest) => {
|
|
26
|
+
const fixture = await fs.mkdtemp(path.join(os.tmpdir(), "motion-root-safety-"));
|
|
27
|
+
subtest.after(() => fs.rm(fixture, { force: true, recursive: true }));
|
|
28
|
+
const rootDir = path.join(fixture, "root");
|
|
29
|
+
const outside = path.join(fixture, "outside");
|
|
30
|
+
const link = path.join(rootDir, linkedPath);
|
|
31
|
+
const outsideTarget = path.join(outside, ...linkedPath.split("/"));
|
|
32
|
+
await fs.mkdir(path.dirname(link), { recursive: true });
|
|
33
|
+
await fs.mkdir(outsideTarget, { recursive: true });
|
|
34
|
+
try {
|
|
35
|
+
await fs.symlink(outsideTarget, link, "dir");
|
|
36
|
+
} catch (error) {
|
|
37
|
+
if (["EACCES", "ENOSYS", "EPERM"].includes(error?.code)) {
|
|
38
|
+
subtest.skip(`symbolic links are unavailable: ${error.code}`);
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
throw error;
|
|
42
|
+
}
|
|
43
|
+
const before = await fs.readdir(outside, { recursive: true });
|
|
44
|
+
|
|
45
|
+
await assert.rejects(() => createToolcraftMotionReferenceStudy({
|
|
46
|
+
kind: "video",
|
|
47
|
+
source: path.join(rootDir, "reference.mkv"),
|
|
48
|
+
}, {
|
|
49
|
+
execute: async ({ binary }) => ({ stdout: `${binary} version 8.0` }),
|
|
50
|
+
rootDir,
|
|
51
|
+
}), /root|resource|outside|boundary/iu);
|
|
52
|
+
|
|
53
|
+
assert.deepEqual(await fs.readdir(outside, { recursive: true }), before);
|
|
54
|
+
assert.equal(await exists(path.join(rootDir, ".toolcraft")), false);
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
test("resolves a nonexistent canonical studies root without creating it",
|
|
60
|
+
async (context) => {
|
|
61
|
+
const rootDir = await fs.mkdtemp(path.join(os.tmpdir(), "motion-root-create-"));
|
|
62
|
+
context.after(() => fs.rm(rootDir, { force: true, recursive: true }));
|
|
63
|
+
const expectedRoot = path.join(rootDir, "src", "app", "reference-studies");
|
|
64
|
+
|
|
65
|
+
assert.equal(
|
|
66
|
+
resolveToolcraftMotionReferenceRoots(rootDir).referenceStudiesRoot,
|
|
67
|
+
expectedRoot,
|
|
68
|
+
);
|
|
69
|
+
assert.equal(await exists(expectedRoot), false);
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
test("rejects a lexically noncanonical studies root containing parent traversal",
|
|
73
|
+
async (context) => {
|
|
74
|
+
const rootDir = await fs.mkdtemp(path.join(os.tmpdir(), "motion-root-parent-"));
|
|
75
|
+
context.after(() => fs.rm(rootDir, { force: true, recursive: true }));
|
|
76
|
+
const expectedRoot = path.join(rootDir, "src", "app", "reference-studies");
|
|
77
|
+
await fs.mkdir(expectedRoot, { recursive: true });
|
|
78
|
+
|
|
79
|
+
await assert.rejects(() => requireToolcraftMotionReferenceStudiesRoot({
|
|
80
|
+
referenceStudiesRoot:
|
|
81
|
+
`${rootDir}/src/app/../app/reference-studies`,
|
|
82
|
+
rootDir,
|
|
83
|
+
}), /canonical app-owned directory/iu);
|
|
84
|
+
});
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import fs from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
|
|
4
|
+
import {
|
|
5
|
+
resolveToolcraftRootResourcePath,
|
|
6
|
+
} from "./toolcraft-source-inventory.mjs";
|
|
7
|
+
|
|
8
|
+
const REFERENCE_ID = /^motion-reference-v1-[a-f0-9]{64}$/u;
|
|
9
|
+
const REFERENCE_STUDIES_RESOURCE_PATH = "src/app/reference-studies";
|
|
10
|
+
const TEMPORARY_RESOURCE_PATH = ".toolcraft/tmp";
|
|
11
|
+
|
|
12
|
+
function fail(message, cause) {
|
|
13
|
+
throw new Error(`Toolcraft motion reference roots failed: ${message}`, { cause });
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
async function requireDirectRoot({ fileSystem, label, requestedRoot, resolvedRoot }) {
|
|
17
|
+
if (typeof requestedRoot !== "string" || !path.isAbsolute(requestedRoot) ||
|
|
18
|
+
requestedRoot !== resolvedRoot) {
|
|
19
|
+
fail(`${label} must be the canonical app-owned directory`);
|
|
20
|
+
}
|
|
21
|
+
const status = await fileSystem.lstat(resolvedRoot);
|
|
22
|
+
if (!status.isDirectory() || status.isSymbolicLink()) {
|
|
23
|
+
fail(`${label} must be a direct directory, not a link`);
|
|
24
|
+
}
|
|
25
|
+
return resolvedRoot;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function resolveToolcraftMotionReferenceRoots(rootDir) {
|
|
29
|
+
return {
|
|
30
|
+
referenceStudiesRoot: resolveToolcraftRootResourcePath(
|
|
31
|
+
REFERENCE_STUDIES_RESOURCE_PATH, rootDir,
|
|
32
|
+
),
|
|
33
|
+
temporaryRoot: resolveToolcraftRootResourcePath(
|
|
34
|
+
TEMPORARY_RESOURCE_PATH, rootDir,
|
|
35
|
+
),
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export function requireToolcraftMotionReferenceStudiesRoot({
|
|
40
|
+
fileSystem = fs,
|
|
41
|
+
referenceStudiesRoot,
|
|
42
|
+
rootDir,
|
|
43
|
+
}) {
|
|
44
|
+
return requireDirectRoot({
|
|
45
|
+
fileSystem,
|
|
46
|
+
label: "reference studies root",
|
|
47
|
+
requestedRoot: referenceStudiesRoot,
|
|
48
|
+
resolvedRoot: resolveToolcraftMotionReferenceRoots(rootDir).referenceStudiesRoot,
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export function requireToolcraftMotionReferenceTemporaryRoot({
|
|
53
|
+
fileSystem = fs,
|
|
54
|
+
rootDir,
|
|
55
|
+
temporaryRoot,
|
|
56
|
+
}) {
|
|
57
|
+
return requireDirectRoot({
|
|
58
|
+
fileSystem,
|
|
59
|
+
label: "temporary root",
|
|
60
|
+
requestedRoot: temporaryRoot,
|
|
61
|
+
resolvedRoot: resolveToolcraftMotionReferenceRoots(rootDir).temporaryRoot,
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export function requireToolcraftMotionReferenceSourceIdentity(sourceIdentity) {
|
|
66
|
+
if (!REFERENCE_ID.test(sourceIdentity ?? "")) {
|
|
67
|
+
fail("source identity must be a canonical motion reference id");
|
|
68
|
+
}
|
|
69
|
+
return sourceIdentity;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export function defineToolcraftMotionReferenceTransactionPaths(
|
|
73
|
+
checkedTemporaryRoot,
|
|
74
|
+
sourceIdentity,
|
|
75
|
+
) {
|
|
76
|
+
requireToolcraftMotionReferenceSourceIdentity(sourceIdentity);
|
|
77
|
+
return {
|
|
78
|
+
lockDirectory: path.join(checkedTemporaryRoot, `${sourceIdentity}.lock`),
|
|
79
|
+
transactionRoot: path.join(
|
|
80
|
+
checkedTemporaryRoot, `${sourceIdentity}.transaction`,
|
|
81
|
+
),
|
|
82
|
+
};
|
|
83
|
+
}
|