@aura3d/cli 1.1.0 → 1.3.1
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/dist/animation-asset-profiles.d.ts +15 -0
- package/dist/animation-asset-profiles.d.ts.map +1 -0
- package/dist/animation-asset-profiles.js +46 -0
- package/dist/animation-asset-profiles.js.map +1 -0
- package/dist/animation-asset-validator.d.ts +22 -0
- package/dist/animation-asset-validator.d.ts.map +1 -0
- package/dist/animation-asset-validator.js +49 -0
- package/dist/animation-asset-validator.js.map +1 -0
- package/dist/cli.js +40 -17
- package/dist/cli.js.map +1 -1
- package/dist/game-asset-validator.d.ts +4 -4
- package/dist/game-asset-validator.d.ts.map +1 -1
- package/dist/game-asset-validator.js +2 -2
- package/dist/game-asset-validator.js.map +1 -1
- package/dist/index.d.ts +32 -19
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +101 -68
- package/dist/index.js.map +1 -1
- package/dist/pull-bridge.d.ts +30 -4
- package/dist/pull-bridge.d.ts.map +1 -1
- package/dist/pull-bridge.js +320 -51
- package/dist/pull-bridge.js.map +1 -1
- package/package.json +5 -5
package/dist/index.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { copyFileSync, existsSync, mkdirSync, readFileSync, readdirSync, statSync, writeFileSync } from "node:fs";
|
|
2
2
|
import { createHash } from "node:crypto";
|
|
3
3
|
import { dirname, extname, join, relative, resolve } from "node:path";
|
|
4
|
-
export {
|
|
4
|
+
export { animationCliAssetProfiles, getAnimationAssetProfileDefinition } from "./animation-asset-profiles.js";
|
|
5
|
+
export { validateAnimationAssets, parseAnimationClipMap, DEFAULT_ANIMATION_ACTIONS } from "./animation-asset-validator.js";
|
|
5
6
|
export const DEFAULT_AURA_ASSET_MANIFEST = "aura.assets.json";
|
|
6
7
|
export const DEFAULT_AURA_ASSET_OUTPUT_DIR = "public/aura-assets";
|
|
7
8
|
export const DEFAULT_AURA_ASSET_PUBLIC_PATH = "/aura-assets/";
|
|
@@ -14,6 +15,9 @@ export function addAsset(options) {
|
|
|
14
15
|
}
|
|
15
16
|
const manifestPath = resolve(projectDir, DEFAULT_AURA_ASSET_MANIFEST);
|
|
16
17
|
const current = readAssetManifest(projectDir);
|
|
18
|
+
// Re-adding (e.g. resolve overwriting) must not silently drop hand-authored
|
|
19
|
+
// provenance the new add does not re-supply (license/attribution/author/etc.).
|
|
20
|
+
const existing = current.assets.find((asset) => asset.id === options.name);
|
|
17
21
|
const outputDir = normalizeRelativePath(options.outputDir ?? current.outputDir ?? DEFAULT_AURA_ASSET_OUTPUT_DIR);
|
|
18
22
|
const publicPath = normalizePublicPath(options.publicPath ?? current.assetBasePath ?? DEFAULT_AURA_ASSET_PUBLIC_PATH);
|
|
19
23
|
const typegen = normalizeRelativePath(options.typegen ?? current.typegen ?? DEFAULT_AURA_ASSET_TYPEGEN);
|
|
@@ -47,7 +51,7 @@ export function addAsset(options) {
|
|
|
47
51
|
humanoid: inspection.humanoid,
|
|
48
52
|
skeleton: inspection.skeleton,
|
|
49
53
|
morphTargets: inspection.morphTargets,
|
|
50
|
-
provenance: createAssetProvenance(projectDir, sourcePath, options, inspection.provenance),
|
|
54
|
+
provenance: createAssetProvenance(projectDir, sourcePath, options, mergeDetectedProvenance(existing?.provenance, inspection.provenance)),
|
|
51
55
|
textures: inspection.textures,
|
|
52
56
|
dependencies: inspection.dependencies,
|
|
53
57
|
orientation: inspection.orientation,
|
|
@@ -305,8 +309,8 @@ export function checkDeploy(options = {}) {
|
|
|
305
309
|
export function validateGameAssets(options = {}) {
|
|
306
310
|
return validateAssetReadiness("game", options);
|
|
307
311
|
}
|
|
308
|
-
export function
|
|
309
|
-
return validateAssetReadiness("
|
|
312
|
+
export function validateAnimationStudioAssets(options = {}) {
|
|
313
|
+
return validateAssetReadiness("animation", options);
|
|
310
314
|
}
|
|
311
315
|
export function createCharacterAssemblyPlan(options) {
|
|
312
316
|
const projectDir = resolve(options.projectDir ?? process.cwd());
|
|
@@ -468,7 +472,7 @@ function validateAssetReadiness(profile, options) {
|
|
|
468
472
|
pushUnique(warnings, [...readinessIssues.warnings, ...profileIssues.warnings]);
|
|
469
473
|
const gameReady = asset.type === "model" && Boolean(asset.bounds) && asset.materials.length > 0 && asset.sizeBytes <= 50 * 1024 * 1024 && readinessIssues.failures.length === 0 && profileIssues.failures.length === 0;
|
|
470
474
|
const profileReady = profileTarget && gameReady;
|
|
471
|
-
const
|
|
475
|
+
const animationReady = asset.type === "model"
|
|
472
476
|
? Boolean(asset.bounds) && (asset.animations.length > 0 || /prop|set|stage|background|environment/i.test(asset.id))
|
|
473
477
|
: asset.type === "audio" || asset.type === "texture";
|
|
474
478
|
const artifactPaths = artifacts.assetFiles.find((entry) => entry.id === asset.id) ?? createReadinessAssetArtifacts(projectDir, manifest, asset);
|
|
@@ -499,7 +503,7 @@ function validateAssetReadiness(profile, options) {
|
|
|
499
503
|
nodeNames: asset.nodeNames,
|
|
500
504
|
artifactPaths,
|
|
501
505
|
gameReady,
|
|
502
|
-
|
|
506
|
+
animationReady,
|
|
503
507
|
...(gameProfile === "fighting-character" ? { profileTarget, profileReady, ...(profileSkippedReason ? { profileSkippedReason } : {}) } : {}),
|
|
504
508
|
warnings: assetWarnings
|
|
505
509
|
};
|
|
@@ -544,25 +548,25 @@ function validateAssetReadiness(profile, options) {
|
|
|
544
548
|
}
|
|
545
549
|
else {
|
|
546
550
|
if (modelAssets.length === 0)
|
|
547
|
-
failures.push("
|
|
551
|
+
failures.push("Animation readiness requires at least one typed model/set/prop GLB or GLTF.");
|
|
548
552
|
if (animatedModels.length === 0)
|
|
549
553
|
warnings.push("No animated character clips detected. Prompt-to-episode output can use transform animation, but character acting needs skeletal or pose clips.");
|
|
550
554
|
if (animatedModels.length > 0 && humanoidModels === 0)
|
|
551
|
-
warnings.push("No humanoid model metadata detected. Acting-heavy
|
|
555
|
+
warnings.push("No humanoid model metadata detected. Acting-heavy animation routes should confirm character rigs with assets inspect --humanoid.");
|
|
552
556
|
const audioAssets = manifest.assets.filter((asset) => asset.type === "audio");
|
|
553
557
|
if (audioAssets.length === 0)
|
|
554
558
|
warnings.push("No audio assets detected. AuraVoice bridge can still reference external narration manifests, but local episode proof is stronger with audio registered.");
|
|
555
559
|
}
|
|
556
|
-
const
|
|
557
|
-
?
|
|
560
|
+
const animationEpisode = profile === "animation"
|
|
561
|
+
? createAnimationEpisodeReadinessReport(manifest, assets, options)
|
|
558
562
|
: undefined;
|
|
559
|
-
if (
|
|
560
|
-
pushUnique(failures,
|
|
561
|
-
pushUnique(warnings,
|
|
563
|
+
if (animationEpisode && options.episode) {
|
|
564
|
+
pushUnique(failures, animationEpisode.failures);
|
|
565
|
+
pushUnique(warnings, animationEpisode.warnings);
|
|
562
566
|
}
|
|
563
567
|
const ok = failures.length === 0;
|
|
564
568
|
const baseMessage = ok
|
|
565
|
-
? `${profile === "game" ? "Game" : "
|
|
569
|
+
? `${profile === "game" ? "Game" : "Animation"} asset readiness report completed.`
|
|
566
570
|
: failures;
|
|
567
571
|
const messages = [
|
|
568
572
|
...(Array.isArray(baseMessage) ? baseMessage : [baseMessage]),
|
|
@@ -579,7 +583,7 @@ function validateAssetReadiness(profile, options) {
|
|
|
579
583
|
manifestPath,
|
|
580
584
|
artifacts,
|
|
581
585
|
contracts: createReadinessValidationContracts(profile),
|
|
582
|
-
...(
|
|
586
|
+
...(animationEpisode ? { animationEpisode } : {}),
|
|
583
587
|
summary: {
|
|
584
588
|
totalAssets: manifest.assets.length,
|
|
585
589
|
modelAssets: modelAssets.length,
|
|
@@ -589,14 +593,14 @@ function validateAssetReadiness(profile, options) {
|
|
|
589
593
|
environmentAssets: manifest.assets.filter((asset) => asset.type === "environment").length,
|
|
590
594
|
animationClips,
|
|
591
595
|
humanoidModels,
|
|
592
|
-
...(
|
|
596
|
+
...(animationEpisode
|
|
593
597
|
? {
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
episodeReadyCharacters:
|
|
598
|
-
mouthReadyCharacters:
|
|
599
|
-
animationReadyCharacters:
|
|
598
|
+
animationCharacters: animationEpisode.selectedCharacters.length,
|
|
599
|
+
animationSets: animationEpisode.selectedSets.length,
|
|
600
|
+
animationProps: animationEpisode.selectedProps.length,
|
|
601
|
+
episodeReadyCharacters: animationEpisode.readiness.filter((entry) => entry.role === "character" && entry.episodeReady).length,
|
|
602
|
+
mouthReadyCharacters: animationEpisode.readiness.filter((entry) => entry.role === "character" && entry.mouthReady).length,
|
|
603
|
+
animationReadyCharacters: animationEpisode.readiness.filter((entry) => entry.role === "character" && entry.animationReady).length
|
|
600
604
|
}
|
|
601
605
|
: {}),
|
|
602
606
|
...(gameProfile === "fighting-character"
|
|
@@ -660,9 +664,9 @@ function createReadinessValidatorEvidence(profile) {
|
|
|
660
664
|
label: "AuraClash game asset validator"
|
|
661
665
|
}
|
|
662
666
|
: {
|
|
663
|
-
id: "aura-voice-
|
|
664
|
-
command: "assets validate-
|
|
665
|
-
label: "AuraVoice
|
|
667
|
+
id: "aura-voice-animation-assets",
|
|
668
|
+
command: "assets validate-animation",
|
|
669
|
+
label: "AuraVoice animation asset validator"
|
|
666
670
|
};
|
|
667
671
|
}
|
|
668
672
|
function createReadinessValidationContracts(profile) {
|
|
@@ -694,11 +698,11 @@ function createReadinessValidationContracts(profile) {
|
|
|
694
698
|
}
|
|
695
699
|
return [
|
|
696
700
|
{
|
|
697
|
-
id: "auravoice-
|
|
698
|
-
label: "AuraVoice
|
|
699
|
-
profile: "
|
|
701
|
+
id: "auravoice-animation-character-asset-validation-contract",
|
|
702
|
+
label: "AuraVoice animation asset validation contract",
|
|
703
|
+
profile: "animation",
|
|
700
704
|
sourceFamily: "AuraVoice",
|
|
701
|
-
intendedUse: "
|
|
705
|
+
intendedUse: "animation-character",
|
|
702
706
|
sourceOnly: true,
|
|
703
707
|
requiredChecks: [
|
|
704
708
|
"typed Aura model, texture, audio, or environment asset entry",
|
|
@@ -706,7 +710,7 @@ function createReadinessValidationContracts(profile) {
|
|
|
706
710
|
"animation or transform-ready character metadata",
|
|
707
711
|
"audio or external AuraVoice manifest references for stronger episode proof"
|
|
708
712
|
],
|
|
709
|
-
evidenceBoundary: "This CLI contract is source-only. It does not prove
|
|
713
|
+
evidenceBoundary: "This CLI contract is source-only. It does not prove animation route readiness until validate-animation output, rendered frames, timing proof, and AuraVoice evidence are archived."
|
|
710
714
|
}
|
|
711
715
|
];
|
|
712
716
|
}
|
|
@@ -744,8 +748,8 @@ function createReadinessAnimationMetadata(animations) {
|
|
|
744
748
|
clips: animations.map((name, index) => ({ index, name }))
|
|
745
749
|
};
|
|
746
750
|
}
|
|
747
|
-
function
|
|
748
|
-
const readiness = assets.map(
|
|
751
|
+
function createAnimationEpisodeReadinessReport(manifest, assets, options) {
|
|
752
|
+
const readiness = assets.map(createAnimationEpisodeAssetReadiness);
|
|
749
753
|
const characters = readiness.filter((entry) => entry.role === "character");
|
|
750
754
|
const sets = readiness.filter((entry) => entry.role === "set");
|
|
751
755
|
const props = readiness.filter((entry) => entry.role === "prop");
|
|
@@ -756,26 +760,26 @@ function createCartoonEpisodeReadinessReport(manifest, assets, options) {
|
|
|
756
760
|
const readyCharacters = characters.filter((entry) => entry.episodeReady);
|
|
757
761
|
const readySets = sets.filter((entry) => entry.episodeReady);
|
|
758
762
|
if (characters.length < 2) {
|
|
759
|
-
failures.push(`
|
|
763
|
+
failures.push(`animation episode validation requires at least 2 typed animation character assets; found ${characters.length}${characters.length ? `: ${characters.map((entry) => entry.id).join(", ")}` : ""}.`);
|
|
760
764
|
}
|
|
761
765
|
if (characters.length >= 2 && distinctCharacterHashes.size < characters.length) {
|
|
762
|
-
failures.push("
|
|
766
|
+
failures.push("animation episode validation requires distinct character files/hashes; duplicated character assets cannot prove a two-character episode cast.");
|
|
763
767
|
}
|
|
764
768
|
if (readyCharacters.length < 2) {
|
|
765
|
-
failures.push(`
|
|
769
|
+
failures.push(`animation episode validation found only ${readyCharacters.length} episode-ready character asset${readyCharacters.length === 1 ? "" : "s"}; required 2 with license, provenance, animation, and mouth readiness.`);
|
|
766
770
|
}
|
|
767
771
|
if (sets.length < 1) {
|
|
768
|
-
failures.push("
|
|
772
|
+
failures.push("animation episode validation requires at least 1 typed animation set/location asset.");
|
|
769
773
|
}
|
|
770
774
|
if (readySets.length < 1) {
|
|
771
|
-
failures.push("
|
|
775
|
+
failures.push("animation episode validation requires at least 1 episode-ready set with license, provenance, bounds, readable materials, and walkable scale.");
|
|
772
776
|
}
|
|
773
777
|
for (const entry of readiness) {
|
|
774
778
|
pushUnique(failures, entry.failures);
|
|
775
779
|
pushUnique(warnings, entry.warnings);
|
|
776
780
|
}
|
|
777
781
|
if (audio.length === 0) {
|
|
778
|
-
warnings.push("
|
|
782
|
+
warnings.push("animation episode validation found no typed audio assets; publish-ready dialogue/audio proof will need registered dialogue, music, or SFX stems.");
|
|
779
783
|
}
|
|
780
784
|
const evidenceDirectory = options.output ? dirname(normalizeRelativePath(options.output)) : "artifacts/aura3d";
|
|
781
785
|
return {
|
|
@@ -801,56 +805,56 @@ function createCartoonEpisodeReadinessReport(manifest, assets, options) {
|
|
|
801
805
|
warnings
|
|
802
806
|
};
|
|
803
807
|
}
|
|
804
|
-
function
|
|
805
|
-
const role =
|
|
808
|
+
function createAnimationEpisodeAssetReadiness(asset) {
|
|
809
|
+
const role = inferAnimationEpisodeAssetRole(asset);
|
|
806
810
|
const failures = [];
|
|
807
811
|
const warnings = [];
|
|
808
812
|
const provenanceReady = Boolean(asset.provenance?.sourceUrl || asset.provenance?.sourceFamily || asset.provenance?.sourcePath);
|
|
809
|
-
const animationReady = role === "character" ?
|
|
810
|
-
const mouthMode = role === "character" ?
|
|
813
|
+
const animationReady = role === "character" ? isAnimationCharacterAnimationReady(asset) : asset.animations.length > 0;
|
|
814
|
+
const mouthMode = role === "character" ? inferAnimationMouthMode(asset) : undefined;
|
|
811
815
|
const mouthReady = role === "character" ? mouthMode !== "missing-mouth-motion" : false;
|
|
812
|
-
const setReady = role === "set" ?
|
|
816
|
+
const setReady = role === "set" ? isAnimationSetReady(asset) : undefined;
|
|
813
817
|
if (role === "character") {
|
|
814
818
|
if (asset.type !== "model")
|
|
815
|
-
failures.push(`${asset.id}:
|
|
819
|
+
failures.push(`${asset.id}: animation character must be a typed model asset, found ${asset.type}.`);
|
|
816
820
|
if (!asset.licenseVerified)
|
|
817
|
-
failures.push(`${asset.id}:
|
|
821
|
+
failures.push(`${asset.id}: animation character requires verified redistributable license evidence.`);
|
|
818
822
|
if (!provenanceReady)
|
|
819
|
-
failures.push(`${asset.id}:
|
|
823
|
+
failures.push(`${asset.id}: animation character requires source provenance for episode packaging.`);
|
|
820
824
|
if (!asset.placeholderFree)
|
|
821
|
-
failures.push(`${asset.id}:
|
|
825
|
+
failures.push(`${asset.id}: animation character is placeholder-tagged and cannot satisfy episode-ready validation.`);
|
|
822
826
|
if (!asset.boundsMetadata && !asset.bounds)
|
|
823
|
-
failures.push(`${asset.id}:
|
|
827
|
+
failures.push(`${asset.id}: animation character requires bounds metadata for framing and motion analysis.`);
|
|
824
828
|
if (!animationReady)
|
|
825
|
-
failures.push(`${asset.id}:
|
|
829
|
+
failures.push(`${asset.id}: animation character requires embedded animation clips or explicit segmented-rig metadata.`);
|
|
826
830
|
if (!mouthReady)
|
|
827
|
-
failures.push(`${asset.id}:
|
|
831
|
+
failures.push(`${asset.id}: animation character requires blendshape, mouth-card, viseme, talk, face, or primitive mouth fallback metadata.`);
|
|
828
832
|
if (asset.materials.length === 0)
|
|
829
|
-
failures.push(`${asset.id}:
|
|
833
|
+
failures.push(`${asset.id}: animation character requires at least one readable material.`);
|
|
830
834
|
if (asset.sizeBytes > 45 * 1024 * 1024)
|
|
831
|
-
failures.push(`${asset.id}:
|
|
835
|
+
failures.push(`${asset.id}: animation character payload is ${asset.sizeBytes} bytes; expected <= 47185920 for browser episode playback.`);
|
|
832
836
|
if (mouthMode === "amplitude-only")
|
|
833
837
|
warnings.push(`${asset.id}: mouth readiness is amplitude-only; add blendshape or mouth-card metadata for stronger lip-sync proof.`);
|
|
834
838
|
}
|
|
835
839
|
else if (role === "set") {
|
|
836
840
|
if (asset.type !== "model")
|
|
837
|
-
failures.push(`${asset.id}:
|
|
841
|
+
failures.push(`${asset.id}: animation set must be a typed model asset, found ${asset.type}.`);
|
|
838
842
|
if (!asset.licenseVerified)
|
|
839
|
-
failures.push(`${asset.id}:
|
|
843
|
+
failures.push(`${asset.id}: animation set requires verified redistributable license evidence.`);
|
|
840
844
|
if (!provenanceReady)
|
|
841
|
-
failures.push(`${asset.id}:
|
|
845
|
+
failures.push(`${asset.id}: animation set requires source provenance for episode packaging.`);
|
|
842
846
|
if (!asset.placeholderFree)
|
|
843
|
-
failures.push(`${asset.id}:
|
|
847
|
+
failures.push(`${asset.id}: animation set is placeholder-tagged and cannot satisfy episode-ready validation.`);
|
|
844
848
|
if (!setReady)
|
|
845
|
-
failures.push(`${asset.id}:
|
|
849
|
+
failures.push(`${asset.id}: animation set requires bounds, readable materials, and at least 1.5m walkable/framing scale.`);
|
|
846
850
|
if (asset.sizeBytes > 90 * 1024 * 1024)
|
|
847
|
-
failures.push(`${asset.id}:
|
|
851
|
+
failures.push(`${asset.id}: animation set payload is ${asset.sizeBytes} bytes; expected <= 94371840 for browser episode playback.`);
|
|
848
852
|
}
|
|
849
853
|
else if (role === "prop" || role === "environment") {
|
|
850
854
|
if (!asset.licenseVerified)
|
|
851
|
-
warnings.push(`${asset.id}: optional
|
|
855
|
+
warnings.push(`${asset.id}: optional animation ${role} lacks verified license evidence; publish packages should include provenance for every visible asset.`);
|
|
852
856
|
if (!provenanceReady)
|
|
853
|
-
warnings.push(`${asset.id}: optional
|
|
857
|
+
warnings.push(`${asset.id}: optional animation ${role} lacks source provenance.`);
|
|
854
858
|
}
|
|
855
859
|
return {
|
|
856
860
|
id: asset.id,
|
|
@@ -872,12 +876,12 @@ function createCartoonEpisodeAssetReadiness(asset) {
|
|
|
872
876
|
failures
|
|
873
877
|
};
|
|
874
878
|
}
|
|
875
|
-
function
|
|
879
|
+
function inferAnimationEpisodeAssetRole(asset) {
|
|
876
880
|
if (asset.type === "audio")
|
|
877
881
|
return "audio";
|
|
878
882
|
if (asset.type === "texture")
|
|
879
883
|
return "texture";
|
|
880
|
-
const text =
|
|
884
|
+
const text = animationAssetText(asset);
|
|
881
885
|
if (asset.type === "environment" || /\b(environment|skybox|backdrop|background|world|terrain|horizon)\b/i.test(text))
|
|
882
886
|
return "environment";
|
|
883
887
|
if (/\b(set|stage|room|garden|park|school|street|house|classroom|moon garden|walkable|floor|location)\b/i.test(text))
|
|
@@ -894,17 +898,17 @@ function inferCartoonEpisodeAssetRole(asset) {
|
|
|
894
898
|
return "prop";
|
|
895
899
|
return "unknown";
|
|
896
900
|
}
|
|
897
|
-
function
|
|
901
|
+
function isAnimationCharacterAnimationReady(asset) {
|
|
898
902
|
if (asset.animations.length > 0)
|
|
899
903
|
return true;
|
|
900
|
-
return /\b(segmented rig|segmented-rig|puppet rig|body parts|armature|skinned|skeleton)\b/i.test(
|
|
904
|
+
return /\b(segmented rig|segmented-rig|puppet rig|body parts|armature|skinned|skeleton)\b/i.test(animationAssetText(asset));
|
|
901
905
|
}
|
|
902
|
-
function
|
|
906
|
+
function inferAnimationMouthMode(asset) {
|
|
903
907
|
const morphNames = asset.morphTargets?.targetNames ?? [];
|
|
904
908
|
if (morphNames.some((name) => /\b(aa|oh|ee|mouth|lip|jaw|viseme|smile|frown|blink|expression)\b/i.test(name))) {
|
|
905
909
|
return "blendshape-lip-sync";
|
|
906
910
|
}
|
|
907
|
-
const text =
|
|
911
|
+
const text = animationAssetText(asset);
|
|
908
912
|
if (/\b(primitive mouth|mouth card|mouth-card|mouth sprite|face card|viseme card|2d mouth)\b/i.test(text)) {
|
|
909
913
|
return "primitive-mouth-card";
|
|
910
914
|
}
|
|
@@ -913,14 +917,14 @@ function inferCartoonMouthMode(asset) {
|
|
|
913
917
|
}
|
|
914
918
|
return "missing-mouth-motion";
|
|
915
919
|
}
|
|
916
|
-
function
|
|
920
|
+
function isAnimationSetReady(asset) {
|
|
917
921
|
const size = asset.boundsMetadata?.size ?? asset.bounds;
|
|
918
922
|
if (!size)
|
|
919
923
|
return false;
|
|
920
924
|
const largest = Math.max(...size);
|
|
921
925
|
return largest >= 1.5 && asset.materials.length > 0;
|
|
922
926
|
}
|
|
923
|
-
function
|
|
927
|
+
function animationAssetText(asset) {
|
|
924
928
|
return [
|
|
925
929
|
asset.id,
|
|
926
930
|
asset.source,
|
|
@@ -1553,10 +1557,35 @@ function createAssetProvenance(projectDir, sourcePath, options, detected) {
|
|
|
1553
1557
|
...(options.author ?? detected?.author ? { author: options.author ?? detected?.author } : {}),
|
|
1554
1558
|
...(options.sourceFamily ?? detected?.sourceFamily ? { sourceFamily: options.sourceFamily ?? detected?.sourceFamily } : {}),
|
|
1555
1559
|
...(options.attribution ?? detected?.attribution ? { attribution: options.attribution ?? detected?.attribution } : {}),
|
|
1560
|
+
...(options.sha256 ?? detected?.sha256 ? { sha256: options.sha256 ?? detected?.sha256 } : {}),
|
|
1561
|
+
...(options.retrievedAt ?? detected?.retrievedAt ? { retrievedAt: options.retrievedAt ?? detected?.retrievedAt } : {}),
|
|
1556
1562
|
...(detected?.evidence && detected.evidence.length > 0 ? { evidence: detected.evidence } : {}),
|
|
1557
1563
|
checkedAt: new Date().toISOString()
|
|
1558
1564
|
};
|
|
1559
1565
|
}
|
|
1566
|
+
/**
|
|
1567
|
+
* Merge a freshly inspected asset's auto-detected provenance over the entry that
|
|
1568
|
+
* already exists in the manifest, so a re-add/re-resolve preserves hand-authored
|
|
1569
|
+
* fields (license, attribution, author, sourceUrl, sourceFamily, evidence,
|
|
1570
|
+
* sha256) that the new pass did not re-detect. Freshly detected values win over
|
|
1571
|
+
* the prior entry; explicit `addAsset` options still win over both (applied in
|
|
1572
|
+
* {@link createAssetProvenance}). (#26)
|
|
1573
|
+
*/
|
|
1574
|
+
function mergeDetectedProvenance(existing, detected) {
|
|
1575
|
+
if (!existing)
|
|
1576
|
+
return detected;
|
|
1577
|
+
const evidence = [...(detected?.evidence ?? []), ...(existing.evidence ?? [])];
|
|
1578
|
+
return {
|
|
1579
|
+
sourceUrl: detected?.sourceUrl ?? existing.sourceUrl,
|
|
1580
|
+
license: detected?.license ?? existing.license,
|
|
1581
|
+
author: detected?.author ?? existing.author,
|
|
1582
|
+
sourceFamily: detected?.sourceFamily ?? existing.sourceFamily,
|
|
1583
|
+
attribution: detected?.attribution ?? existing.attribution,
|
|
1584
|
+
sha256: detected?.sha256 ?? existing.sha256,
|
|
1585
|
+
retrievedAt: detected?.retrievedAt ?? existing.retrievedAt,
|
|
1586
|
+
...(evidence.length > 0 ? { evidence: [...new Set(evidence)] } : {})
|
|
1587
|
+
};
|
|
1588
|
+
}
|
|
1560
1589
|
function readExternalProvenance(projectDir, provenanceFile) {
|
|
1561
1590
|
if (!provenanceFile)
|
|
1562
1591
|
return new Map();
|
|
@@ -1586,6 +1615,8 @@ function readExternalProvenance(projectDir, provenanceFile) {
|
|
|
1586
1615
|
const sourceFamily = stringValue(record.sourceFamily ?? nestedProvenance?.sourceFamily ?? nestedProvenance?.sourcePack);
|
|
1587
1616
|
const author = stringValue(record.author ?? nestedProvenance?.author);
|
|
1588
1617
|
const attribution = stringValue(record.attribution ?? record.credit ?? nestedProvenance?.attribution);
|
|
1618
|
+
const sha256 = stringValue(record.sha256 ?? nestedProvenance?.sha256);
|
|
1619
|
+
const retrievedAt = stringValue(record.retrievedAt ?? nestedProvenance?.retrievedAt);
|
|
1589
1620
|
const evidence = [
|
|
1590
1621
|
...stringArrayValue(record.evidence),
|
|
1591
1622
|
...stringArrayValue(record.intendedRouteUsage),
|
|
@@ -1598,6 +1629,8 @@ function readExternalProvenance(projectDir, provenanceFile) {
|
|
|
1598
1629
|
...(author ? { author } : {}),
|
|
1599
1630
|
...(sourceFamily ? { sourceFamily } : {}),
|
|
1600
1631
|
...(attribution ? { attribution } : {}),
|
|
1632
|
+
...(sha256 ? { sha256 } : {}),
|
|
1633
|
+
...(retrievedAt ? { retrievedAt } : {}),
|
|
1601
1634
|
...(evidence.length > 0 ? { evidence } : {}),
|
|
1602
1635
|
checkedAt
|
|
1603
1636
|
});
|