@alpaca-software/40kdc-data 0.1.1 → 0.1.2
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/abilities-resolver/index.d.ts +9 -0
- package/dist/abilities-resolver/index.d.ts.map +1 -0
- package/dist/abilities-resolver/index.js +9 -0
- package/dist/abilities-resolver/index.js.map +1 -0
- package/dist/abilities-resolver/resolver.d.ts +64 -0
- package/dist/abilities-resolver/resolver.d.ts.map +1 -0
- package/dist/abilities-resolver/resolver.js +135 -0
- package/dist/abilities-resolver/resolver.js.map +1 -0
- package/dist/bundle-schemas.d.ts +1 -0
- package/dist/bundle-schemas.d.ts.map +1 -0
- package/dist/bundle-schemas.js +1 -0
- package/dist/bundle-schemas.js.map +1 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +2 -0
- package/dist/cli.js.map +1 -0
- package/dist/codegen-data.d.ts +1 -0
- package/dist/codegen-data.d.ts.map +1 -0
- package/dist/codegen-data.js +2 -0
- package/dist/codegen-data.js.map +1 -0
- package/dist/commands/import.d.ts +1 -0
- package/dist/commands/import.d.ts.map +1 -0
- package/dist/commands/import.js +1 -0
- package/dist/commands/import.js.map +1 -0
- package/dist/commands/translate.d.ts +1 -0
- package/dist/commands/translate.d.ts.map +1 -0
- package/dist/commands/translate.js +1 -0
- package/dist/commands/translate.js.map +1 -0
- package/dist/commands/validate-all.d.ts +1 -0
- package/dist/commands/validate-all.d.ts.map +1 -0
- package/dist/commands/validate-all.js +1 -0
- package/dist/commands/validate-all.js.map +1 -0
- package/dist/commands/validate-core.d.ts +1 -0
- package/dist/commands/validate-core.d.ts.map +1 -0
- package/dist/commands/validate-core.js +1 -0
- package/dist/commands/validate-core.js.map +1 -0
- package/dist/commands/validate-enrichment.d.ts +1 -0
- package/dist/commands/validate-enrichment.d.ts.map +1 -0
- package/dist/commands/validate-enrichment.js +1 -0
- package/dist/commands/validate-enrichment.js.map +1 -0
- package/dist/convert-faction.d.ts +1 -0
- package/dist/convert-faction.d.ts.map +1 -0
- package/dist/convert-faction.js +1 -0
- package/dist/convert-faction.js.map +1 -0
- package/dist/converters/configs/adepta-sororitas.d.ts +1 -0
- package/dist/converters/configs/adepta-sororitas.d.ts.map +1 -0
- package/dist/converters/configs/adepta-sororitas.js +1 -0
- package/dist/converters/configs/adepta-sororitas.js.map +1 -0
- package/dist/converters/configs/adeptus-astartes.d.ts +1 -0
- package/dist/converters/configs/adeptus-astartes.d.ts.map +1 -0
- package/dist/converters/configs/adeptus-astartes.js +1 -0
- package/dist/converters/configs/adeptus-astartes.js.map +1 -0
- package/dist/converters/configs/adeptus-custodes.d.ts +1 -0
- package/dist/converters/configs/adeptus-custodes.d.ts.map +1 -0
- package/dist/converters/configs/adeptus-custodes.js +1 -0
- package/dist/converters/configs/adeptus-custodes.js.map +1 -0
- package/dist/converters/configs/adeptus-mechanicus.d.ts +1 -0
- package/dist/converters/configs/adeptus-mechanicus.d.ts.map +1 -0
- package/dist/converters/configs/adeptus-mechanicus.js +1 -0
- package/dist/converters/configs/adeptus-mechanicus.js.map +1 -0
- package/dist/converters/configs/aeldari.d.ts +1 -0
- package/dist/converters/configs/aeldari.d.ts.map +1 -0
- package/dist/converters/configs/aeldari.js +1 -0
- package/dist/converters/configs/aeldari.js.map +1 -0
- package/dist/converters/configs/agents-of-the-imperium.d.ts +1 -0
- package/dist/converters/configs/agents-of-the-imperium.d.ts.map +1 -0
- package/dist/converters/configs/agents-of-the-imperium.js +1 -0
- package/dist/converters/configs/agents-of-the-imperium.js.map +1 -0
- package/dist/converters/configs/astra-militarum.d.ts +1 -0
- package/dist/converters/configs/astra-militarum.d.ts.map +1 -0
- package/dist/converters/configs/astra-militarum.js +1 -0
- package/dist/converters/configs/astra-militarum.js.map +1 -0
- package/dist/converters/configs/black-templars.d.ts +1 -0
- package/dist/converters/configs/black-templars.d.ts.map +1 -0
- package/dist/converters/configs/black-templars.js +1 -0
- package/dist/converters/configs/black-templars.js.map +1 -0
- package/dist/converters/configs/blood-angels.d.ts +1 -0
- package/dist/converters/configs/blood-angels.d.ts.map +1 -0
- package/dist/converters/configs/blood-angels.js +1 -0
- package/dist/converters/configs/blood-angels.js.map +1 -0
- package/dist/converters/configs/chaos-daemons.d.ts +1 -0
- package/dist/converters/configs/chaos-daemons.d.ts.map +1 -0
- package/dist/converters/configs/chaos-daemons.js +1 -0
- package/dist/converters/configs/chaos-daemons.js.map +1 -0
- package/dist/converters/configs/chaos-knights.d.ts +1 -0
- package/dist/converters/configs/chaos-knights.d.ts.map +1 -0
- package/dist/converters/configs/chaos-knights.js +1 -0
- package/dist/converters/configs/chaos-knights.js.map +1 -0
- package/dist/converters/configs/chaos-space-marines.d.ts +1 -0
- package/dist/converters/configs/chaos-space-marines.d.ts.map +1 -0
- package/dist/converters/configs/chaos-space-marines.js +1 -0
- package/dist/converters/configs/chaos-space-marines.js.map +1 -0
- package/dist/converters/configs/crimson-fists.d.ts +1 -0
- package/dist/converters/configs/crimson-fists.d.ts.map +1 -0
- package/dist/converters/configs/crimson-fists.js +1 -0
- package/dist/converters/configs/crimson-fists.js.map +1 -0
- package/dist/converters/configs/dark-angels.d.ts +1 -0
- package/dist/converters/configs/dark-angels.d.ts.map +1 -0
- package/dist/converters/configs/dark-angels.js +1 -0
- package/dist/converters/configs/dark-angels.js.map +1 -0
- package/dist/converters/configs/death-guard.d.ts +1 -0
- package/dist/converters/configs/death-guard.d.ts.map +1 -0
- package/dist/converters/configs/death-guard.js +1 -0
- package/dist/converters/configs/death-guard.js.map +1 -0
- package/dist/converters/configs/deathwatch.d.ts +1 -0
- package/dist/converters/configs/deathwatch.d.ts.map +1 -0
- package/dist/converters/configs/deathwatch.js +1 -0
- package/dist/converters/configs/deathwatch.js.map +1 -0
- package/dist/converters/configs/drukhari.d.ts +1 -0
- package/dist/converters/configs/drukhari.d.ts.map +1 -0
- package/dist/converters/configs/drukhari.js +1 -0
- package/dist/converters/configs/drukhari.js.map +1 -0
- package/dist/converters/configs/emperors-children.d.ts +1 -0
- package/dist/converters/configs/emperors-children.d.ts.map +1 -0
- package/dist/converters/configs/emperors-children.js +1 -0
- package/dist/converters/configs/emperors-children.js.map +1 -0
- package/dist/converters/configs/genestealer-cults.d.ts +1 -0
- package/dist/converters/configs/genestealer-cults.d.ts.map +1 -0
- package/dist/converters/configs/genestealer-cults.js +1 -0
- package/dist/converters/configs/genestealer-cults.js.map +1 -0
- package/dist/converters/configs/grey-knights.d.ts +1 -0
- package/dist/converters/configs/grey-knights.d.ts.map +1 -0
- package/dist/converters/configs/grey-knights.js +1 -0
- package/dist/converters/configs/grey-knights.js.map +1 -0
- package/dist/converters/configs/imperial-fists.d.ts +1 -0
- package/dist/converters/configs/imperial-fists.d.ts.map +1 -0
- package/dist/converters/configs/imperial-fists.js +1 -0
- package/dist/converters/configs/imperial-fists.js.map +1 -0
- package/dist/converters/configs/imperial-knights.d.ts +1 -0
- package/dist/converters/configs/imperial-knights.d.ts.map +1 -0
- package/dist/converters/configs/imperial-knights.js +1 -0
- package/dist/converters/configs/imperial-knights.js.map +1 -0
- package/dist/converters/configs/iron-hands.d.ts +1 -0
- package/dist/converters/configs/iron-hands.d.ts.map +1 -0
- package/dist/converters/configs/iron-hands.js +1 -0
- package/dist/converters/configs/iron-hands.js.map +1 -0
- package/dist/converters/configs/leagues-of-votann.d.ts +1 -0
- package/dist/converters/configs/leagues-of-votann.d.ts.map +1 -0
- package/dist/converters/configs/leagues-of-votann.js +1 -0
- package/dist/converters/configs/leagues-of-votann.js.map +1 -0
- package/dist/converters/configs/necrons.d.ts +1 -0
- package/dist/converters/configs/necrons.d.ts.map +1 -0
- package/dist/converters/configs/necrons.js +1 -0
- package/dist/converters/configs/necrons.js.map +1 -0
- package/dist/converters/configs/orks.d.ts +1 -0
- package/dist/converters/configs/orks.d.ts.map +1 -0
- package/dist/converters/configs/orks.js +1 -0
- package/dist/converters/configs/orks.js.map +1 -0
- package/dist/converters/configs/raven-guard.d.ts +1 -0
- package/dist/converters/configs/raven-guard.d.ts.map +1 -0
- package/dist/converters/configs/raven-guard.js +1 -0
- package/dist/converters/configs/raven-guard.js.map +1 -0
- package/dist/converters/configs/salamanders.d.ts +1 -0
- package/dist/converters/configs/salamanders.d.ts.map +1 -0
- package/dist/converters/configs/salamanders.js +1 -0
- package/dist/converters/configs/salamanders.js.map +1 -0
- package/dist/converters/configs/space-wolves.d.ts +1 -0
- package/dist/converters/configs/space-wolves.d.ts.map +1 -0
- package/dist/converters/configs/space-wolves.js +1 -0
- package/dist/converters/configs/space-wolves.js.map +1 -0
- package/dist/converters/configs/tau-empire.d.ts +1 -0
- package/dist/converters/configs/tau-empire.d.ts.map +1 -0
- package/dist/converters/configs/tau-empire.js +1 -0
- package/dist/converters/configs/tau-empire.js.map +1 -0
- package/dist/converters/configs/thousand-sons.d.ts +1 -0
- package/dist/converters/configs/thousand-sons.d.ts.map +1 -0
- package/dist/converters/configs/thousand-sons.js +1 -0
- package/dist/converters/configs/thousand-sons.js.map +1 -0
- package/dist/converters/configs/tyranids.d.ts +1 -0
- package/dist/converters/configs/tyranids.d.ts.map +1 -0
- package/dist/converters/configs/tyranids.js +1 -0
- package/dist/converters/configs/tyranids.js.map +1 -0
- package/dist/converters/configs/ultramarines.d.ts +1 -0
- package/dist/converters/configs/ultramarines.d.ts.map +1 -0
- package/dist/converters/configs/ultramarines.js +1 -0
- package/dist/converters/configs/ultramarines.js.map +1 -0
- package/dist/converters/configs/white-scars.d.ts +1 -0
- package/dist/converters/configs/white-scars.d.ts.map +1 -0
- package/dist/converters/configs/white-scars.js +1 -0
- package/dist/converters/configs/white-scars.js.map +1 -0
- package/dist/converters/configs/world-eaters.d.ts +1 -0
- package/dist/converters/configs/world-eaters.d.ts.map +1 -0
- package/dist/converters/configs/world-eaters.js +1 -0
- package/dist/converters/configs/world-eaters.js.map +1 -0
- package/dist/converters/faction-config.d.ts +1 -0
- package/dist/converters/faction-config.d.ts.map +1 -0
- package/dist/converters/faction-config.js +1 -0
- package/dist/converters/faction-config.js.map +1 -0
- package/dist/converters/id-generator.d.ts +1 -0
- package/dist/converters/id-generator.d.ts.map +1 -0
- package/dist/converters/id-generator.js +1 -0
- package/dist/converters/id-generator.js.map +1 -0
- package/dist/converters/keyword-filter.d.ts +1 -0
- package/dist/converters/keyword-filter.d.ts.map +1 -0
- package/dist/converters/keyword-filter.js +1 -0
- package/dist/converters/keyword-filter.js.map +1 -0
- package/dist/converters/stat-parser.d.ts +1 -0
- package/dist/converters/stat-parser.d.ts.map +1 -0
- package/dist/converters/stat-parser.js +1 -0
- package/dist/converters/stat-parser.js.map +1 -0
- package/dist/converters/view-selector.d.ts +1 -0
- package/dist/converters/view-selector.d.ts.map +1 -0
- package/dist/converters/view-selector.js +1 -0
- package/dist/converters/view-selector.js.map +1 -0
- package/dist/converters/weapon-dedup.d.ts +1 -0
- package/dist/converters/weapon-dedup.d.ts.map +1 -0
- package/dist/converters/weapon-dedup.js +1 -0
- package/dist/converters/weapon-dedup.js.map +1 -0
- package/dist/cruncher/buffs.d.ts +184 -0
- package/dist/cruncher/buffs.d.ts.map +1 -0
- package/dist/cruncher/buffs.js +150 -0
- package/dist/cruncher/buffs.js.map +1 -0
- package/dist/cruncher/engine.d.ts +50 -0
- package/dist/cruncher/engine.d.ts.map +1 -0
- package/dist/cruncher/engine.js +312 -0
- package/dist/cruncher/engine.js.map +1 -0
- package/dist/cruncher/from-dsl.d.ts +69 -0
- package/dist/cruncher/from-dsl.d.ts.map +1 -0
- package/dist/cruncher/from-dsl.js +523 -0
- package/dist/cruncher/from-dsl.js.map +1 -0
- package/dist/cruncher/from-keyword.d.ts +35 -0
- package/dist/cruncher/from-keyword.d.ts.map +1 -0
- package/dist/cruncher/from-keyword.js +159 -0
- package/dist/cruncher/from-keyword.js.map +1 -0
- package/dist/cruncher/get-buffs.d.ts +12 -0
- package/dist/cruncher/get-buffs.d.ts.map +1 -0
- package/dist/cruncher/get-buffs.js +7 -0
- package/dist/cruncher/get-buffs.js.map +1 -0
- package/dist/cruncher/index.d.ts +11 -0
- package/dist/cruncher/index.d.ts.map +1 -0
- package/dist/cruncher/index.js +11 -0
- package/dist/cruncher/index.js.map +1 -0
- package/dist/data/bundle.generated.d.ts +1 -0
- package/dist/data/bundle.generated.d.ts.map +1 -0
- package/dist/data/bundle.generated.js +2 -1
- package/dist/data/bundle.generated.js.map +1 -0
- package/dist/data/collection.d.ts +1 -0
- package/dist/data/collection.d.ts.map +1 -0
- package/dist/data/collection.js +1 -0
- package/dist/data/collection.js.map +1 -0
- package/dist/data/dataset.d.ts +54 -2
- package/dist/data/dataset.d.ts.map +1 -0
- package/dist/data/dataset.js +111 -1
- package/dist/data/dataset.js.map +1 -0
- package/dist/data/entities.d.ts +70 -2
- package/dist/data/entities.d.ts.map +1 -0
- package/dist/data/entities.js +122 -0
- package/dist/data/entities.js.map +1 -0
- package/dist/data/index.d.ts +9 -1
- package/dist/data/index.d.ts.map +1 -0
- package/dist/data/index.js +14 -1
- package/dist/data/index.js.map +1 -0
- package/dist/data/normalize.d.ts +1 -0
- package/dist/data/normalize.d.ts.map +1 -0
- package/dist/data/normalize.js +1 -0
- package/dist/data/normalize.js.map +1 -0
- package/dist/data/roster-resolve.d.ts +33 -0
- package/dist/data/roster-resolve.d.ts.map +1 -0
- package/dist/data/roster-resolve.js +36 -0
- package/dist/data/roster-resolve.js.map +1 -0
- package/dist/data/types.d.ts +4 -1
- package/dist/data/types.d.ts.map +1 -0
- package/dist/data/types.js +2 -0
- package/dist/data/types.js.map +1 -0
- package/dist/export/helpers.d.ts +33 -0
- package/dist/export/helpers.d.ts.map +1 -0
- package/dist/export/helpers.js +57 -0
- package/dist/export/helpers.js.map +1 -0
- package/dist/export/index.d.ts +21 -0
- package/dist/export/index.d.ts.map +1 -0
- package/dist/export/index.js +25 -0
- package/dist/export/index.js.map +1 -0
- package/dist/export/newrecruit-json.d.ts +3 -0
- package/dist/export/newrecruit-json.d.ts.map +1 -0
- package/dist/export/newrecruit-json.js +140 -0
- package/dist/export/newrecruit-json.js.map +1 -0
- package/dist/export/newrecruit-simple.d.ts +3 -0
- package/dist/export/newrecruit-simple.d.ts.map +1 -0
- package/dist/export/newrecruit-simple.js +76 -0
- package/dist/export/newrecruit-simple.js.map +1 -0
- package/dist/export/newrecruit-wtc.d.ts +4 -0
- package/dist/export/newrecruit-wtc.d.ts.map +1 -0
- package/dist/export/newrecruit-wtc.js +142 -0
- package/dist/export/newrecruit-wtc.js.map +1 -0
- package/dist/export/roster-json.d.ts +3 -0
- package/dist/export/roster-json.d.ts.map +1 -0
- package/dist/export/roster-json.js +8 -0
- package/dist/export/roster-json.js.map +1 -0
- package/dist/export/serializer.d.ts +27 -0
- package/dist/export/serializer.d.ts.map +1 -0
- package/dist/export/serializer.js +2 -0
- package/dist/export/serializer.js.map +1 -0
- package/dist/gen-conformance.d.ts +1 -0
- package/dist/gen-conformance.d.ts.map +1 -0
- package/dist/gen-conformance.js +73 -12
- package/dist/gen-conformance.js.map +1 -0
- package/dist/generated.d.ts +194 -118
- package/dist/generated.d.ts.map +1 -0
- package/dist/generated.js +1 -0
- package/dist/generated.js.map +1 -0
- package/dist/import/adapter.d.ts +4 -3
- package/dist/import/adapter.d.ts.map +1 -0
- package/dist/import/adapter.js +1 -0
- package/dist/import/adapter.js.map +1 -0
- package/dist/import/decode.d.ts +1 -0
- package/dist/import/decode.d.ts.map +1 -0
- package/dist/import/decode.js +1 -0
- package/dist/import/decode.js.map +1 -0
- package/dist/import/import-roster.d.ts +35 -0
- package/dist/import/import-roster.d.ts.map +1 -0
- package/dist/import/import-roster.js +97 -0
- package/dist/import/import-roster.js.map +1 -0
- package/dist/import/index.d.ts +7 -3
- package/dist/import/index.d.ts.map +1 -0
- package/dist/import/index.js +5 -1
- package/dist/import/index.js.map +1 -0
- package/dist/import/listforge.d.ts +1 -0
- package/dist/import/listforge.d.ts.map +1 -0
- package/dist/import/listforge.js +7 -1
- package/dist/import/listforge.js.map +1 -0
- package/dist/import/newrecruit-json.d.ts +31 -0
- package/dist/import/newrecruit-json.d.ts.map +1 -0
- package/dist/import/newrecruit-json.js +224 -0
- package/dist/import/newrecruit-json.js.map +1 -0
- package/dist/import/newrecruit-simple.d.ts +29 -0
- package/dist/import/newrecruit-simple.d.ts.map +1 -0
- package/dist/import/newrecruit-simple.js +200 -0
- package/dist/import/newrecruit-simple.js.map +1 -0
- package/dist/import/newrecruit-text.d.ts +48 -0
- package/dist/import/newrecruit-text.d.ts.map +1 -0
- package/dist/import/newrecruit-text.js +96 -0
- package/dist/import/newrecruit-text.js.map +1 -0
- package/dist/import/newrecruit-wtc.d.ts +36 -0
- package/dist/import/newrecruit-wtc.d.ts.map +1 -0
- package/dist/import/newrecruit-wtc.js +334 -0
- package/dist/import/newrecruit-wtc.js.map +1 -0
- package/dist/import/resolve.d.ts +3 -2
- package/dist/import/resolve.d.ts.map +1 -0
- package/dist/import/resolve.js +5 -2
- package/dist/import/resolve.js.map +1 -0
- package/dist/import/types.d.ts +11 -1
- package/dist/import/types.d.ts.map +1 -0
- package/dist/import/types.js +1 -0
- package/dist/import/types.js.map +1 -0
- package/dist/index.d.ts +5 -2
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +4 -1
- package/dist/index.js.map +1 -0
- package/dist/known-support-10e.d.ts +1 -0
- package/dist/known-support-10e.d.ts.map +1 -0
- package/dist/known-support-10e.js +1 -0
- package/dist/known-support-10e.js.map +1 -0
- package/dist/link-abilities.d.ts +41 -0
- package/dist/link-abilities.d.ts.map +1 -0
- package/dist/link-abilities.js +159 -0
- package/dist/link-abilities.js.map +1 -0
- package/dist/migrations/2026-weapon-keywords.d.ts +2 -0
- package/dist/migrations/2026-weapon-keywords.d.ts.map +1 -0
- package/dist/migrations/2026-weapon-keywords.js +243 -0
- package/dist/migrations/2026-weapon-keywords.js.map +1 -0
- package/dist/port-10e-faction.d.ts +1 -0
- package/dist/port-10e-faction.d.ts.map +1 -0
- package/dist/port-10e-faction.js +1 -0
- package/dist/port-10e-faction.js.map +1 -0
- package/dist/report.d.ts +1 -0
- package/dist/report.d.ts.map +1 -0
- package/dist/report.js +1 -0
- package/dist/report.js.map +1 -0
- package/dist/rube-goldberg.d.ts +3 -0
- package/dist/rube-goldberg.d.ts.map +1 -0
- package/dist/rube-goldberg.js +109 -0
- package/dist/rube-goldberg.js.map +1 -0
- package/dist/schema-loader.d.ts +1 -0
- package/dist/schema-loader.d.ts.map +1 -0
- package/dist/schema-loader.js +1 -0
- package/dist/schema-loader.js.map +1 -0
- package/dist/validate.d.ts +1 -0
- package/dist/validate.d.ts.map +1 -0
- package/dist/validate.js +2 -0
- package/dist/validate.js.map +1 -0
- package/package.json +7 -2
- package/schemas/core/roster.schema.json +17 -4
- package/schemas/core/weapon-keyword.schema.json +31 -0
- package/schemas/core/weapon.schema.json +22 -1
- package/schemas/enrichment/ability-dsl/effect.schema.json +23 -1
- package/dist/import/import-listforge.d.ts +0 -23
- package/dist/import/import-listforge.js +0 -32
package/dist/report.js
CHANGED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"report.js","sourceRoot":"","sources":["../src/report.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAK1B,MAAM,UAAU,YAAY,CAAC,MAAwB,EAAE,IAAkB;IACvE,IAAI,IAAI,KAAK,MAAM,EAAE,CAAC;QACpB,OAAO,IAAI,CAAC,SAAS,CAAC,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC;IACzC,CAAC;IAED,MAAM,KAAK,GAAa,EAAE,CAAC;IAC3B,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IACf,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,8BAA8B,CAAC,CAAC,CAAC;IACvD,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;IACvC,KAAK,CAAC,IAAI,CAAC,mBAAmB,MAAM,CAAC,UAAU,EAAE,CAAC,CAAC;IACnD,KAAK,CAAC,IAAI,CAAC,oBAAoB,MAAM,CAAC,UAAU,EAAE,CAAC,CAAC;IACpD,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,WAAW,MAAM,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC;IAEpD,IAAI,MAAM,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACtB,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,WAAW,MAAM,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC;QAClD,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;QAEf,KAAK,MAAM,GAAG,IAAI,MAAM,CAAC,MAAM,EAAE,CAAC;YAChC,MAAM,GAAG,GAAG,GAAG,CAAC,KAAK,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,GAAG,CAAC,KAAK,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC;YACnD,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,OAAO,GAAG,CAAC,IAAI,GAAG,GAAG,EAAE,CAAC,CAAC,CAAC;YAC/C,KAAK,MAAM,CAAC,IAAI,GAAG,CAAC,MAAM,EAAE,CAAC;gBAC3B,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,IAAI,KAAK,CAAC,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC;YAC1D,CAAC;QACH,CAAC;IACH,CAAC;SAAM,CAAC;QACN,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,WAAW,CAAC,CAAC,CAAC;QACrC,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;QACf,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,yBAAyB,CAAC,CAAC,CAAC;IACrD,CAAC;IAED,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IACf,OAAO,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AAC1B,CAAC","sourcesContent":["import chalk from \"chalk\";\nimport type { ValidationResult } from \"./validate.js\";\n\nexport type ReporterMode = \"pretty\" | \"json\";\n\nexport function formatReport(result: ValidationResult, mode: ReporterMode): string {\n if (mode === \"json\") {\n return JSON.stringify(result, null, 2);\n }\n\n const lines: string[] = [];\n lines.push(\"\");\n lines.push(chalk.bold(\"40kdc Data Validation Report\"));\n lines.push(chalk.gray(\"─\".repeat(40)));\n lines.push(`Files scanned: ${result.totalFiles}`);\n lines.push(`Items validated: ${result.totalItems}`);\n lines.push(chalk.green(`Passed: ${result.passed}`));\n\n if (result.failed > 0) {\n lines.push(chalk.red(`Failed: ${result.failed}`));\n lines.push(\"\");\n\n for (const err of result.errors) {\n const loc = err.index >= 0 ? `[${err.index}]` : \"\";\n lines.push(chalk.red(` ✗ ${err.file}${loc}`));\n for (const e of err.errors) {\n lines.push(chalk.yellow(` ${e.path}: ${e.message}`));\n }\n }\n } else {\n lines.push(chalk.green(`Failed: 0`));\n lines.push(\"\");\n lines.push(chalk.green(\"All validations passed.\"));\n }\n\n lines.push(\"\");\n return lines.join(\"\\n\");\n}\n"]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"rube-goldberg.d.ts","sourceRoot":"","sources":["../src/rube-goldberg.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
#!/usr/bin/env tsx
|
|
2
|
+
/**
|
|
3
|
+
* Rube Goldberg machine — round-trips a NewRecruit list through every
|
|
4
|
+
* supported format and walks the linked Dataset API at each end.
|
|
5
|
+
*
|
|
6
|
+
* Loop:
|
|
7
|
+
* ```
|
|
8
|
+
* seed (newrecruit-json) → import → Roster₀
|
|
9
|
+
* → export wtc-compact → import
|
|
10
|
+
* → export wtc-full → import
|
|
11
|
+
* → export simple → import
|
|
12
|
+
* → export newrecruit-json → import → Rosterₙ
|
|
13
|
+
* ```
|
|
14
|
+
*
|
|
15
|
+
* Asserts `Roster₀ == Rosterₙ` at the Roster level (after stripping
|
|
16
|
+
* `source` + `diagnostics`, which legitimately change across hops). Exits
|
|
17
|
+
* non-zero on divergence. Walks `unit.faction.units` / `weapon.units` /
|
|
18
|
+
* `ability.phases` at each end to demonstrate the cross-reference loops.
|
|
19
|
+
*
|
|
20
|
+
* Strict IP rule: only ids, names, and counts may be printed. No ability
|
|
21
|
+
* description text, no rules text — none of that is stored in the
|
|
22
|
+
* dataset, so we can't accidentally leak it, but be careful adding fields.
|
|
23
|
+
*
|
|
24
|
+
* @packageDocumentation
|
|
25
|
+
*/
|
|
26
|
+
import { readFileSync } from "node:fs";
|
|
27
|
+
import { dirname, join } from "node:path";
|
|
28
|
+
import { fileURLToPath } from "node:url";
|
|
29
|
+
import { Dataset } from "./data/dataset.js";
|
|
30
|
+
import { exportRoster } from "./export/index.js";
|
|
31
|
+
import { importRoster } from "./import/import-roster.js";
|
|
32
|
+
const SEED_PATH = join(dirname(fileURLToPath(import.meta.url)), "..", "..", "conformance", "roster", "chaos-knights-houndpack", "input.newrecruit-json.json");
|
|
33
|
+
const HOPS = [
|
|
34
|
+
"newrecruit-wtc-compact",
|
|
35
|
+
"newrecruit-wtc-full",
|
|
36
|
+
"newrecruit-simple",
|
|
37
|
+
"newrecruit-json",
|
|
38
|
+
];
|
|
39
|
+
/** Strip the fields that legitimately change across format hops so the
|
|
40
|
+
* fixed-point comparison can focus on the structural Roster shape. */
|
|
41
|
+
function stable(r) {
|
|
42
|
+
const x = JSON.parse(JSON.stringify(r));
|
|
43
|
+
delete x.source;
|
|
44
|
+
delete x.diagnostics;
|
|
45
|
+
return x;
|
|
46
|
+
}
|
|
47
|
+
function rule(title) {
|
|
48
|
+
console.log(`\n── ${title} ${"─".repeat(Math.max(0, 60 - title.length))}`);
|
|
49
|
+
}
|
|
50
|
+
function walkLinkedApi(label, roster, ds) {
|
|
51
|
+
rule(`${label}: walking the linked Dataset API`);
|
|
52
|
+
console.log(`Roster: ${roster.name} (${roster.units.length} units)`);
|
|
53
|
+
console.log(` faction_id = ${roster.faction_id ?? "—"}`);
|
|
54
|
+
console.log(` detachment_id = ${roster.detachment_id ?? "—"}`);
|
|
55
|
+
console.log(` total_computed = ${roster.points.total_computed}`);
|
|
56
|
+
for (const u of roster.units) {
|
|
57
|
+
if (!u.ref.id) {
|
|
58
|
+
console.log(` · ${u.ref.raw_name} [unresolved]`);
|
|
59
|
+
continue;
|
|
60
|
+
}
|
|
61
|
+
const unit = ds.units.get(u.ref.id);
|
|
62
|
+
if (!unit) {
|
|
63
|
+
console.log(` · ${u.ref.id} [not in dataset]`);
|
|
64
|
+
continue;
|
|
65
|
+
}
|
|
66
|
+
const fac = unit.faction;
|
|
67
|
+
const facUnits = fac?.units.length ?? 0;
|
|
68
|
+
console.log(` · ${unit.id} → faction=${fac?.id ?? "—"} (faction has ${facUnits} units)`);
|
|
69
|
+
for (const w of unit.weapons) {
|
|
70
|
+
const carriers = w.units.length;
|
|
71
|
+
console.log(` weapon=${w.id} carried by ${carriers} units`);
|
|
72
|
+
}
|
|
73
|
+
for (const a of unit.abilities) {
|
|
74
|
+
const phaseIds = a.phases.join(",") || "—";
|
|
75
|
+
const carriers = a.units.length;
|
|
76
|
+
console.log(` ability=${a.id} phases=[${phaseIds}] on ${carriers} units`);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
function main() {
|
|
81
|
+
const ds = Dataset.embedded();
|
|
82
|
+
const seedJson = JSON.parse(readFileSync(SEED_PATH, "utf8"));
|
|
83
|
+
rule("seed");
|
|
84
|
+
console.log(`Loading: conformance/roster/chaos-knights-houndpack/input.newrecruit-json.json`);
|
|
85
|
+
const roster0 = importRoster(seedJson, { dataset: ds });
|
|
86
|
+
walkLinkedApi("Roster₀", roster0, ds);
|
|
87
|
+
let current = roster0;
|
|
88
|
+
for (const [i, fmt] of HOPS.entries()) {
|
|
89
|
+
const text = exportRoster(current, fmt);
|
|
90
|
+
const bytes = Buffer.byteLength(text, "utf8");
|
|
91
|
+
const isJson = fmt === "newrecruit-json";
|
|
92
|
+
const reimported = importRoster(isJson ? JSON.parse(text) : text, { dataset: ds });
|
|
93
|
+
console.log(`[hop ${i + 1}/${HOPS.length}] ${fmt} → ${bytes.toLocaleString()} bytes → reimport: ${reimported.units.length} units (computed ${reimported.points.total_computed} pts)`);
|
|
94
|
+
current = reimported;
|
|
95
|
+
}
|
|
96
|
+
walkLinkedApi("Rosterₙ", current, ds);
|
|
97
|
+
rule("fixed-point check");
|
|
98
|
+
const before = stable(roster0);
|
|
99
|
+
const after = stable(current);
|
|
100
|
+
const equal = JSON.stringify(before) === JSON.stringify(after);
|
|
101
|
+
if (equal) {
|
|
102
|
+
console.log(`Roster₀ == Rosterₙ after ${HOPS.length} format hops (source/diagnostics excluded).`);
|
|
103
|
+
return 0;
|
|
104
|
+
}
|
|
105
|
+
console.error(`Roster₀ ≠ Rosterₙ — round-trip diverged.`);
|
|
106
|
+
return 1;
|
|
107
|
+
}
|
|
108
|
+
process.exit(main());
|
|
109
|
+
//# sourceMappingURL=rube-goldberg.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"rube-goldberg.js","sourceRoot":"","sources":["../src/rube-goldberg.ts"],"names":[],"mappings":"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","sourcesContent":["#!/usr/bin/env tsx\n/**\n * Rube Goldberg machine — round-trips a NewRecruit list through every\n * supported format and walks the linked Dataset API at each end.\n *\n * Loop:\n * ```\n * seed (newrecruit-json) → import → Roster₀\n * → export wtc-compact → import\n * → export wtc-full → import\n * → export simple → import\n * → export newrecruit-json → import → Rosterₙ\n * ```\n *\n * Asserts `Roster₀ == Rosterₙ` at the Roster level (after stripping\n * `source` + `diagnostics`, which legitimately change across hops). Exits\n * non-zero on divergence. Walks `unit.faction.units` / `weapon.units` /\n * `ability.phases` at each end to demonstrate the cross-reference loops.\n *\n * Strict IP rule: only ids, names, and counts may be printed. No ability\n * description text, no rules text — none of that is stored in the\n * dataset, so we can't accidentally leak it, but be careful adding fields.\n *\n * @packageDocumentation\n */\nimport { readFileSync } from \"node:fs\";\nimport { dirname, join } from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\n\nimport { Dataset } from \"./data/dataset.js\";\nimport { exportRoster, type ExportFormat } from \"./export/index.js\";\nimport { importRoster } from \"./import/import-roster.js\";\nimport type { Roster } from \"./import/types.js\";\n\nconst SEED_PATH = join(\n dirname(fileURLToPath(import.meta.url)),\n \"..\",\n \"..\",\n \"conformance\",\n \"roster\",\n \"chaos-knights-houndpack\",\n \"input.newrecruit-json.json\",\n);\n\nconst HOPS: ExportFormat[] = [\n \"newrecruit-wtc-compact\",\n \"newrecruit-wtc-full\",\n \"newrecruit-simple\",\n \"newrecruit-json\",\n];\n\n/** Strip the fields that legitimately change across format hops so the\n * fixed-point comparison can focus on the structural Roster shape. */\nfunction stable(r: Roster): Record<string, unknown> {\n const x = JSON.parse(JSON.stringify(r)) as Record<string, unknown>;\n delete x.source;\n delete x.diagnostics;\n return x;\n}\n\nfunction rule(title: string): void {\n console.log(`\\n── ${title} ${\"─\".repeat(Math.max(0, 60 - title.length))}`);\n}\n\nfunction walkLinkedApi(label: string, roster: Roster, ds: Dataset): void {\n rule(`${label}: walking the linked Dataset API`);\n console.log(`Roster: ${roster.name} (${roster.units.length} units)`);\n console.log(` faction_id = ${roster.faction_id ?? \"—\"}`);\n console.log(` detachment_id = ${roster.detachment_id ?? \"—\"}`);\n console.log(` total_computed = ${roster.points.total_computed}`);\n\n for (const u of roster.units) {\n if (!u.ref.id) {\n console.log(` · ${u.ref.raw_name} [unresolved]`);\n continue;\n }\n const unit = ds.units.get(u.ref.id);\n if (!unit) {\n console.log(` · ${u.ref.id} [not in dataset]`);\n continue;\n }\n const fac = unit.faction;\n const facUnits = fac?.units.length ?? 0;\n console.log(` · ${unit.id} → faction=${fac?.id ?? \"—\"} (faction has ${facUnits} units)`);\n\n for (const w of unit.weapons) {\n const carriers = w.units.length;\n console.log(` weapon=${w.id} carried by ${carriers} units`);\n }\n for (const a of unit.abilities) {\n const phaseIds = a.phases.join(\",\") || \"—\";\n const carriers = a.units.length;\n console.log(` ability=${a.id} phases=[${phaseIds}] on ${carriers} units`);\n }\n }\n}\n\nfunction main(): number {\n const ds = Dataset.embedded();\n const seedJson = JSON.parse(readFileSync(SEED_PATH, \"utf8\"));\n\n rule(\"seed\");\n console.log(`Loading: conformance/roster/chaos-knights-houndpack/input.newrecruit-json.json`);\n\n const roster0 = importRoster(seedJson, { dataset: ds });\n walkLinkedApi(\"Roster₀\", roster0, ds);\n\n let current: Roster = roster0;\n for (const [i, fmt] of HOPS.entries()) {\n const text = exportRoster(current, fmt);\n const bytes = Buffer.byteLength(text, \"utf8\");\n const isJson = fmt === \"newrecruit-json\";\n const reimported = importRoster(isJson ? JSON.parse(text) : text, { dataset: ds });\n console.log(\n `[hop ${i + 1}/${HOPS.length}] ${fmt} → ${bytes.toLocaleString()} bytes → reimport: ${reimported.units.length} units (computed ${reimported.points.total_computed} pts)`,\n );\n current = reimported;\n }\n\n walkLinkedApi(\"Rosterₙ\", current, ds);\n\n rule(\"fixed-point check\");\n const before = stable(roster0);\n const after = stable(current);\n const equal = JSON.stringify(before) === JSON.stringify(after);\n if (equal) {\n console.log(`Roster₀ == Rosterₙ after ${HOPS.length} format hops (source/diagnostics excluded).`);\n return 0;\n }\n console.error(`Roster₀ ≠ Rosterₙ — round-trip diverged.`);\n return 1;\n}\n\nprocess.exit(main());\n"]}
|
package/dist/schema-loader.d.ts
CHANGED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"schema-loader.d.ts","sourceRoot":"","sources":["../src/schema-loader.ts"],"names":[],"mappings":"AAAA,OAAO,GAAG,MAAM,KAAK,CAAC;AAyBtB,eAAO,MAAM,YAAY,QAAuB,CAAC;AAEjD;;GAEG;AACH,wBAAgB,eAAe,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,EAAE,CAWrD;AAED;;;GAGG;AACH,wBAAgB,eAAe,IAAI,GAAG,CAuBrC;AAED;;GAEG;AACH,wBAAgB,aAAa,IAAI,MAAM,EAAE,CAWxC"}
|
package/dist/schema-loader.js
CHANGED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"schema-loader.js","sourceRoot":"","sources":["../src/schema-loader.ts"],"names":[],"mappings":"AAAA,OAAO,GAAG,MAAM,KAAK,CAAC;AACtB,OAAO,UAAU,MAAM,aAAa,CAAC;AACrC,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,WAAW,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AAC1E,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAC1C,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAEzC,MAAM,SAAS,GAAG,aAAa,CAAC,IAAI,GAAG,CAAC,GAAG,EAAE,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC;AAE/D;;;;;;;;;;GAUG;AACH,SAAS,kBAAkB;IACzB,MAAM,IAAI,GAAG,OAAO,CAAC,SAAS,EAAE,eAAe,CAAC,CAAC;IACjD,MAAM,QAAQ,GAAG,OAAO,CAAC,SAAS,EAAE,YAAY,CAAC,CAAC;IAClD,OAAO,UAAU,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,QAAQ,CAAC;AAC5C,CAAC;AAED,MAAM,CAAC,MAAM,YAAY,GAAG,kBAAkB,EAAE,CAAC;AAEjD;;GAEG;AACH,MAAM,UAAU,eAAe,CAAC,GAAW;IACzC,MAAM,OAAO,GAAa,EAAE,CAAC;IAC7B,KAAK,MAAM,KAAK,IAAI,WAAW,CAAC,GAAG,CAAC,EAAE,CAAC;QACrC,MAAM,IAAI,GAAG,IAAI,CAAC,GAAG,EAAE,KAAK,CAAC,CAAC;QAC9B,IAAI,QAAQ,CAAC,IAAI,CAAC,CAAC,WAAW,EAAE,EAAE,CAAC;YACjC,OAAO,CAAC,IAAI,CAAC,GAAG,eAAe,CAAC,IAAI,CAAC,CAAC,CAAC;QACzC,CAAC;aAAM,IAAI,KAAK,CAAC,QAAQ,CAAC,cAAc,CAAC,EAAE,CAAC;YAC1C,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACrB,CAAC;IACH,CAAC;IACD,OAAO,OAAO,CAAC;AACjB,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,eAAe;IAC7B,MAAM,GAAG,GAAG,IAAI,GAAG,CAAC;QAClB,MAAM,EAAE,KAAK;QACb,SAAS,EAAE,IAAI;QACf,cAAc,EAAE,KAAK;KACtB,CAAC,CAAC;IACH,UAAU,CAAC,GAAG,CAAC,CAAC;IAEhB,MAAM,WAAW,GAAG,eAAe,CAAC,YAAY,CAAC,CAAC;IAClD,MAAM,OAAO,GAAG,WAAW,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE;QACvC,MAAM,GAAG,GAAG,YAAY,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;QACxC,OAAO,IAAI,CAAC,KAAK,CAAC,GAAG,CAA4B,CAAC;IACpD,CAAC,CAAC,CAAC;IAEH,8BAA8B;IAC9B,KAAK,MAAM,MAAM,IAAI,OAAO,EAAE,CAAC;QAC7B,MAAM,EAAE,GAAG,MAAM,CAAC,KAAK,CAAuB,CAAC;QAC/C,IAAI,EAAE,EAAE,CAAC;YACP,GAAG,CAAC,SAAS,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC;QAC5B,CAAC;IACH,CAAC;IAED,OAAO,GAAG,CAAC;AACb,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,aAAa;IAC3B,MAAM,WAAW,GAAG,eAAe,CAAC,YAAY,CAAC,CAAC;IAClD,MAAM,GAAG,GAAa,EAAE,CAAC;IACzB,KAAK,MAAM,IAAI,IAAI,WAAW,EAAE,CAAC;QAC/B,MAAM,GAAG,GAAG,YAAY,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;QACxC,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAA4B,CAAC;QAC1D,IAAI,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC;YAClB,GAAG,CAAC,IAAI,CAAC,MAAM,CAAC,KAAK,CAAW,CAAC,CAAC;QACpC,CAAC;IACH,CAAC;IACD,OAAO,GAAG,CAAC;AACb,CAAC","sourcesContent":["import Ajv from \"ajv\";\nimport addFormats from \"ajv-formats\";\nimport { existsSync, readFileSync, readdirSync, statSync } from \"node:fs\";\nimport { join, resolve } from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\n\nconst __dirname = fileURLToPath(new URL(\".\", import.meta.url));\n\n/**\n * Resolve the schema tree across both layouts:\n * - in-repo / dev: schemas live in the sibling top-level `schemas/` dir\n * (`../../schemas` from `dist/` or `src/`) — always the live source.\n * - packaged: once published, schemas are copied into the package root, so\n * `<pkg>/schemas` sits one level above the compiled file in `dist/`\n * (`../schemas`). When installed, the repo path resolves outside the\n * package and doesn't exist, so this branch is taken.\n * Repo path wins when present so dev runs never read a stale `copy:schemas`\n * artifact; the packaged copy is the fallback for installed consumers.\n */\nfunction resolveSchemasRoot(): string {\n const repo = resolve(__dirname, \"../../schemas\");\n const packaged = resolve(__dirname, \"../schemas\");\n return existsSync(repo) ? repo : packaged;\n}\n\nexport const SCHEMAS_ROOT = resolveSchemasRoot();\n\n/**\n * Recursively find all .schema.json files under a directory.\n */\nexport function findSchemaFiles(dir: string): string[] {\n const results: string[] = [];\n for (const entry of readdirSync(dir)) {\n const full = join(dir, entry);\n if (statSync(full).isDirectory()) {\n results.push(...findSchemaFiles(full));\n } else if (entry.endsWith(\".schema.json\")) {\n results.push(full);\n }\n }\n return results;\n}\n\n/**\n * Create and configure an AJV instance with all project schemas loaded.\n * Schemas are registered by their $id so $ref resolution works across files.\n */\nexport function createValidator(): Ajv {\n const ajv = new Ajv({\n strict: false,\n allErrors: true,\n validateSchema: false,\n });\n addFormats(ajv);\n\n const schemaFiles = findSchemaFiles(SCHEMAS_ROOT);\n const schemas = schemaFiles.map((file) => {\n const raw = readFileSync(file, \"utf-8\");\n return JSON.parse(raw) as Record<string, unknown>;\n });\n\n // Register all schemas by $id\n for (const schema of schemas) {\n const id = schema[\"$id\"] as string | undefined;\n if (id) {\n ajv.addSchema(schema, id);\n }\n }\n\n return ajv;\n}\n\n/**\n * Return the list of all loaded schema $id values.\n */\nexport function listSchemaIds(): string[] {\n const schemaFiles = findSchemaFiles(SCHEMAS_ROOT);\n const ids: string[] = [];\n for (const file of schemaFiles) {\n const raw = readFileSync(file, \"utf-8\");\n const schema = JSON.parse(raw) as Record<string, unknown>;\n if (schema[\"$id\"]) {\n ids.push(schema[\"$id\"] as string);\n }\n }\n return ids;\n}\n"]}
|
package/dist/validate.d.ts
CHANGED
|
@@ -19,3 +19,4 @@ export interface ValidationResult {
|
|
|
19
19
|
* Each data file is expected to be a JSON array; each element is validated individually.
|
|
20
20
|
*/
|
|
21
21
|
export declare function validateFiles(ajv: Ajv, pattern: string, cwd?: string): Promise<ValidationResult>;
|
|
22
|
+
//# sourceMappingURL=validate.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"validate.d.ts","sourceRoot":"","sources":["../src/validate.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,GAAG,MAAM,KAAK,CAAC;AAS3B,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;CAClD;AAED,MAAM,WAAW,gBAAgB;IAC/B,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC;IACnB,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,eAAe,EAAE,CAAC;CAC3B;AA4CD;;;GAGG;AACH,wBAAsB,aAAa,CACjC,GAAG,EAAE,GAAG,EACR,OAAO,EAAE,MAAM,EACf,GAAG,CAAC,EAAE,MAAM,GACX,OAAO,CAAC,gBAAgB,CAAC,CA+E3B"}
|
package/dist/validate.js
CHANGED
|
@@ -11,6 +11,7 @@ const SCHEMA_MAP = {
|
|
|
11
11
|
factions: "https://40kdc.dev/schemas/core/faction.schema.json",
|
|
12
12
|
units: "https://40kdc.dev/schemas/core/unit.schema.json",
|
|
13
13
|
weapons: "https://40kdc.dev/schemas/core/weapon.schema.json",
|
|
14
|
+
"weapon-keywords": "https://40kdc.dev/schemas/core/weapon-keyword.schema.json",
|
|
14
15
|
"game-versions": "https://40kdc.dev/schemas/core/game-version.schema.json",
|
|
15
16
|
detachments: "https://40kdc.dev/schemas/core/detachment.schema.json",
|
|
16
17
|
enhancements: "https://40kdc.dev/schemas/core/enhancement.schema.json",
|
|
@@ -122,3 +123,4 @@ export async function validateFiles(ajv, pattern, cwd) {
|
|
|
122
123
|
}
|
|
123
124
|
return result;
|
|
124
125
|
}
|
|
126
|
+
//# sourceMappingURL=validate.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"validate.js","sourceRoot":"","sources":["../src/validate.ts"],"names":[],"mappings":"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","sourcesContent":["import type Ajv from \"ajv\";\nimport { readFileSync } from \"node:fs\";\nimport { glob } from \"glob\";\nimport { resolve, basename } from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\n\nconst __dirname = fileURLToPath(new URL(\".\", import.meta.url));\nconst DATA_ROOT = resolve(__dirname, \"../../data\");\n\nexport interface ValidationError {\n file: string;\n index: number;\n errors: Array<{ path: string; message: string }>;\n}\n\nexport interface ValidationResult {\n totalFiles: number;\n totalItems: number;\n passed: number;\n failed: number;\n errors: ValidationError[];\n}\n\n/**\n * Map from data file base-name prefix to schema $id.\n */\nconst SCHEMA_MAP: Record<string, string> = {\n factions: \"https://40kdc.dev/schemas/core/faction.schema.json\",\n units: \"https://40kdc.dev/schemas/core/unit.schema.json\",\n weapons: \"https://40kdc.dev/schemas/core/weapon.schema.json\",\n \"weapon-keywords\": \"https://40kdc.dev/schemas/core/weapon-keyword.schema.json\",\n \"game-versions\": \"https://40kdc.dev/schemas/core/game-version.schema.json\",\n detachments: \"https://40kdc.dev/schemas/core/detachment.schema.json\",\n enhancements: \"https://40kdc.dev/schemas/core/enhancement.schema.json\",\n stratagems: \"https://40kdc.dev/schemas/core/stratagem.schema.json\",\n \"wargear-options\": \"https://40kdc.dev/schemas/core/wargear-option.schema.json\",\n \"leader-attachments\": \"https://40kdc.dev/schemas/core/leader-attachment.schema.json\",\n \"unit-compositions\": \"https://40kdc.dev/schemas/core/unit-composition.schema.json\",\n \"force-dispositions\": \"https://40kdc.dev/schemas/core/force-disposition.schema.json\",\n \"deployment-patterns\": \"https://40kdc.dev/schemas/core/deployment-pattern.schema.json\",\n \"mission-matchups\": \"https://40kdc.dev/schemas/core/mission-matchup.schema.json\",\n missions: \"https://40kdc.dev/schemas/core/mission.schema.json\",\n \"secondary-cards\": \"https://40kdc.dev/schemas/core/secondary-card.schema.json\",\n \"terrain-layouts\": \"https://40kdc.dev/schemas/core/terrain-layout.schema.json\",\n \"phase-mappings\": \"https://40kdc.dev/schemas/enrichment/phase-mapping.schema.json\",\n \"timing-flags\": \"https://40kdc.dev/schemas/enrichment/timing-flag.schema.json\",\n \"interaction-flags\": \"https://40kdc.dev/schemas/enrichment/interaction-flag.schema.json\",\n abilities: \"https://40kdc.dev/schemas/enrichment/ability-dsl/ability.schema.json\",\n \"resource-pools\": \"https://40kdc.dev/schemas/enrichment/resource-pool.schema.json\",\n};\n\n/**\n * Determine which schema $id to use for a given data file path.\n * Convention: the file's base name prefix (before the first dot) maps to a schema.\n */\nfunction resolveSchemaId(filePath: string): string | null {\n const base = basename(filePath);\n for (const [prefix, schemaId] of Object.entries(SCHEMA_MAP)) {\n if (base.startsWith(prefix)) {\n return schemaId;\n }\n }\n return null;\n}\n\n/**\n * Validate all data files matching the given glob pattern.\n * Each data file is expected to be a JSON array; each element is validated individually.\n */\nexport async function validateFiles(\n ajv: Ajv,\n pattern: string,\n cwd?: string,\n): Promise<ValidationResult> {\n const root = cwd ?? DATA_ROOT;\n const files = await glob(pattern, { cwd: root, absolute: true });\n\n const result: ValidationResult = {\n totalFiles: files.length,\n totalItems: 0,\n passed: 0,\n failed: 0,\n errors: [],\n };\n\n for (const file of files) {\n const schemaId = resolveSchemaId(file);\n if (!schemaId) {\n result.errors.push({\n file,\n index: -1,\n errors: [{ path: \"\", message: `No schema mapping found for file: ${basename(file)}` }],\n });\n result.failed++;\n continue;\n }\n\n const validate = ajv.getSchema(schemaId);\n if (!validate) {\n result.errors.push({\n file,\n index: -1,\n errors: [{ path: \"\", message: `Schema not found: ${schemaId}` }],\n });\n result.failed++;\n continue;\n }\n\n let data: unknown;\n try {\n const raw = readFileSync(file, \"utf-8\");\n data = JSON.parse(raw);\n } catch (err) {\n result.errors.push({\n file,\n index: -1,\n errors: [{ path: \"\", message: `Failed to parse JSON: ${(err as Error).message}` }],\n });\n result.failed++;\n continue;\n }\n\n if (!Array.isArray(data)) {\n result.errors.push({\n file,\n index: -1,\n errors: [{ path: \"\", message: \"Data file must be a JSON array\" }],\n });\n result.failed++;\n continue;\n }\n\n for (let i = 0; i < data.length; i++) {\n result.totalItems++;\n const valid = validate(data[i]);\n if (valid) {\n result.passed++;\n } else {\n result.failed++;\n result.errors.push({\n file,\n index: i,\n errors: (validate.errors ?? []).map((e) => ({\n path: e.instancePath || \"/\",\n message: e.message ?? \"Unknown validation error\",\n })),\n });\n }\n }\n }\n\n return result;\n}\n"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@alpaca-software/40kdc-data",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "The 40kdc Warhammer 40K dataset behind a linked, typed API — find units, follow them to their weapons, abilities, phases, and factions. Also validates data against the canonical JSON Schemas.",
|
|
6
6
|
"keywords": [
|
|
@@ -43,11 +43,15 @@
|
|
|
43
43
|
"access": "public",
|
|
44
44
|
"registry": "https://registry.npmjs.org"
|
|
45
45
|
},
|
|
46
|
+
"engines": {
|
|
47
|
+
"node": ">=20"
|
|
48
|
+
},
|
|
46
49
|
"scripts": {
|
|
47
50
|
"build": "npm run codegen:data && tsc",
|
|
48
51
|
"codegen:data": "tsx src/codegen-data.ts",
|
|
49
52
|
"gen:conformance": "tsx src/gen-conformance.ts",
|
|
50
53
|
"docs:api": "typedoc",
|
|
54
|
+
"link:abilities": "tsx src/link-abilities.ts",
|
|
51
55
|
"validate": "tsx src/cli.ts validate-all",
|
|
52
56
|
"validate:core": "tsx src/cli.ts validate-core",
|
|
53
57
|
"validate:enrichment": "tsx src/cli.ts validate-enrichment",
|
|
@@ -57,7 +61,8 @@
|
|
|
57
61
|
"copy:schemas": "rm -rf schemas && cp -R ../schemas schemas",
|
|
58
62
|
"prepack": "npm run build && npm run copy:schemas",
|
|
59
63
|
"pretest": "npm run codegen:data",
|
|
60
|
-
"test": "vitest run"
|
|
64
|
+
"test": "vitest run",
|
|
65
|
+
"demo:rube-goldberg": "tsx src/rube-goldberg.ts"
|
|
61
66
|
},
|
|
62
67
|
"dependencies": {
|
|
63
68
|
"ajv": "^8.17.0",
|
|
@@ -8,12 +8,21 @@
|
|
|
8
8
|
"name": { "type": "string", "minLength": 1, "maxLength": 256 },
|
|
9
9
|
"source": {
|
|
10
10
|
"type": "object",
|
|
11
|
-
"description": "Provenance of the imported list.",
|
|
11
|
+
"description": "Provenance of the imported list. `format` identifies which adapter parsed the source payload; new adapters extend this enum.",
|
|
12
12
|
"properties": {
|
|
13
|
-
"format": {
|
|
13
|
+
"format": {
|
|
14
|
+
"type": "string",
|
|
15
|
+
"enum": [
|
|
16
|
+
"listforge",
|
|
17
|
+
"newrecruit-json",
|
|
18
|
+
"newrecruit-wtc-compact",
|
|
19
|
+
"newrecruit-wtc-full",
|
|
20
|
+
"newrecruit-simple"
|
|
21
|
+
]
|
|
22
|
+
},
|
|
14
23
|
"generated_by": {
|
|
15
24
|
"oneOf": [{ "type": "string" }, { "type": "null" }],
|
|
16
|
-
"description": "The `generatedBy` field reported by the source payload (e.g. 'List Forge'), if present."
|
|
25
|
+
"description": "The `generatedBy` field reported by the source payload (e.g. 'List Forge', 'https://newrecruit.eu'), if present."
|
|
17
26
|
}
|
|
18
27
|
},
|
|
19
28
|
"required": ["format", "generated_by"],
|
|
@@ -123,6 +132,10 @@
|
|
|
123
132
|
"oneOf": [{ "$ref": "#/$defs/roster-resolved-ref" }, { "type": "null" }],
|
|
124
133
|
"description": "Attached enhancement, or null when none."
|
|
125
134
|
},
|
|
135
|
+
"enhancement_points": {
|
|
136
|
+
"oneOf": [{ "type": "integer", "minimum": 0 }, { "type": "null" }],
|
|
137
|
+
"description": "Points cost of the attached enhancement when the source reported one. Null when there is no enhancement or when the source didn't carry a cost line for it. Lets a Roster round-trip cleanly through formats that print enhancements as a separate `+N pts` line."
|
|
138
|
+
},
|
|
126
139
|
"wargear": {
|
|
127
140
|
"type": "array",
|
|
128
141
|
"description": "Weapons/wargear selected on the unit. Always lists the raw name and count; `ref.id` is null when the weapon could not be matched.",
|
|
@@ -152,7 +165,7 @@
|
|
|
152
165
|
]
|
|
153
166
|
}
|
|
154
167
|
},
|
|
155
|
-
"required": ["ref", "model_count", "points", "is_warlord", "enhancement", "wargear", "leader_attachment"],
|
|
168
|
+
"required": ["ref", "model_count", "points", "is_warlord", "enhancement", "enhancement_points", "wargear", "leader_attachment"],
|
|
156
169
|
"additionalProperties": false
|
|
157
170
|
},
|
|
158
171
|
"roster-diagnostics": {
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
+
"$id": "https://40kdc.dev/schemas/core/weapon-keyword.schema.json",
|
|
4
|
+
"title": "Weapon Keyword",
|
|
5
|
+
"description": "Catalog entry for a weapon keyword (Lethal Hits, Sustained Hits N, Anti-X N+, etc.). Each weapon profile references entries here via {keyword_id, parameters?} instead of carrying free-text strings. The optional `effect` describes the keyword's game mechanic in the Ability DSL; null when the behaviour is faction-specific flavour not yet modelled.",
|
|
6
|
+
"type": "object",
|
|
7
|
+
"properties": {
|
|
8
|
+
"id": { "$ref": "../defs/common.schema.json#/$defs/entity-id" },
|
|
9
|
+
"name": { "type": "string", "minLength": 1, "maxLength": 128 },
|
|
10
|
+
"required_parameters": {
|
|
11
|
+
"type": "array",
|
|
12
|
+
"description": "Parameter keys that must be supplied at each reference site, in the order they would appear in a printed datasheet (e.g. Anti-INFANTRY 4+ → ['target_keyword', 'threshold']).",
|
|
13
|
+
"items": {
|
|
14
|
+
"type": "string",
|
|
15
|
+
"enum": ["value", "target_keyword", "threshold"]
|
|
16
|
+
},
|
|
17
|
+
"uniqueItems": true,
|
|
18
|
+
"maxItems": 3
|
|
19
|
+
},
|
|
20
|
+
"effect": {
|
|
21
|
+
"oneOf": [
|
|
22
|
+
{ "$ref": "../enrichment/ability-dsl/effect.schema.json" },
|
|
23
|
+
{ "type": "null" }
|
|
24
|
+
],
|
|
25
|
+
"description": "Mechanical effect of this keyword. Null when the behaviour is faction-specific flavour not yet expressible in the DSL — engines treat such references as no-op buffs and may surface them as 'cannot auto-apply'."
|
|
26
|
+
},
|
|
27
|
+
"game_version": { "$ref": "../defs/game-version-ref.schema.json" }
|
|
28
|
+
},
|
|
29
|
+
"required": ["id", "name", "required_parameters", "effect", "game_version"],
|
|
30
|
+
"additionalProperties": false
|
|
31
|
+
}
|
|
@@ -42,7 +42,28 @@
|
|
|
42
42
|
},
|
|
43
43
|
"required": ["A", "S", "AP", "D"]
|
|
44
44
|
},
|
|
45
|
-
"keywords": {
|
|
45
|
+
"keywords": {
|
|
46
|
+
"type": "array",
|
|
47
|
+
"description": "References into the weapon-keyword catalog. Each entry names the catalog id and supplies parameter values (e.g. `Sustained Hits 1` → `{keyword_id: 'sustained-hits', parameters: {value: 1}}`).",
|
|
48
|
+
"items": {
|
|
49
|
+
"type": "object",
|
|
50
|
+
"properties": {
|
|
51
|
+
"keyword_id": { "$ref": "../defs/common.schema.json#/$defs/entity-id" },
|
|
52
|
+
"parameters": {
|
|
53
|
+
"type": "object",
|
|
54
|
+
"description": "Reference-site parameters conforming to the catalog entry's required_parameters. Only the three documented keys are accepted; any other key is invalid.",
|
|
55
|
+
"properties": {
|
|
56
|
+
"value": { "$ref": "../defs/common.schema.json#/$defs/stat-value" },
|
|
57
|
+
"target_keyword": { "type": "string", "minLength": 1, "maxLength": 64 },
|
|
58
|
+
"threshold": { "type": "integer", "minimum": 2, "maximum": 6 }
|
|
59
|
+
},
|
|
60
|
+
"additionalProperties": false
|
|
61
|
+
}
|
|
62
|
+
},
|
|
63
|
+
"required": ["keyword_id"],
|
|
64
|
+
"additionalProperties": false
|
|
65
|
+
}
|
|
66
|
+
}
|
|
46
67
|
},
|
|
47
68
|
"required": ["name", "stats"],
|
|
48
69
|
"additionalProperties": false
|
|
@@ -44,7 +44,29 @@
|
|
|
44
44
|
},
|
|
45
45
|
"modifier": { "type": "object", "additionalProperties": true }
|
|
46
46
|
},
|
|
47
|
-
"required": ["type", "target"]
|
|
47
|
+
"required": ["type", "target"],
|
|
48
|
+
"$comment": "When `type` is `re-roll`, `modifier` must carry `roll` (string) and `subset` (`ones` | `all-failures`). Rerolls always target failures; the subset decides whether only 1s are rerolled or every failed die. The constraint is enforced by AJV at validation time and stripped from the codegen bundle (typify can't model if/then/else) — the generated TS/Rust types therefore see `modifier` as an open object, matching its other-`type` callers.",
|
|
49
|
+
"allOf": [
|
|
50
|
+
{
|
|
51
|
+
"if": {
|
|
52
|
+
"properties": { "type": { "const": "re-roll" } },
|
|
53
|
+
"required": ["type"]
|
|
54
|
+
},
|
|
55
|
+
"then": {
|
|
56
|
+
"properties": {
|
|
57
|
+
"modifier": {
|
|
58
|
+
"type": "object",
|
|
59
|
+
"properties": {
|
|
60
|
+
"roll": { "type": "string", "minLength": 1 },
|
|
61
|
+
"subset": { "type": "string", "enum": ["ones", "all-failures"] }
|
|
62
|
+
},
|
|
63
|
+
"required": ["roll", "subset"]
|
|
64
|
+
}
|
|
65
|
+
},
|
|
66
|
+
"required": ["modifier"]
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
]
|
|
48
70
|
},
|
|
49
71
|
"choice-effect": {
|
|
50
72
|
"type": "object",
|
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Orchestrates a ListForge import: decode → parse → resolve.
|
|
3
|
-
*
|
|
4
|
-
* @packageDocumentation
|
|
5
|
-
*/
|
|
6
|
-
import { Dataset } from "../data/dataset.js";
|
|
7
|
-
import type { Roster } from "./types.js";
|
|
8
|
-
export interface ImportOptions {
|
|
9
|
-
/** Dataset to resolve against. Defaults to the package's embedded dataset. */
|
|
10
|
-
dataset?: Dataset;
|
|
11
|
-
}
|
|
12
|
-
/**
|
|
13
|
-
* Import a ListForge army-list export into a resolved 40kdc {@link Roster}.
|
|
14
|
-
*
|
|
15
|
-
* `input` may be a full ListForge URL, a bare base64 segment, or an
|
|
16
|
-
* already-decoded JSON string — all are handled transparently.
|
|
17
|
-
*/
|
|
18
|
-
export declare function importListForge(input: string, opts?: ImportOptions): Roster;
|
|
19
|
-
/**
|
|
20
|
-
* Import an already-decoded payload. Selects the matching format adapter and
|
|
21
|
-
* resolves the result against the dataset.
|
|
22
|
-
*/
|
|
23
|
-
export declare function importRoster(decoded: unknown, opts?: ImportOptions): Roster;
|
|
@@ -1,32 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Orchestrates a ListForge import: decode → parse → resolve.
|
|
3
|
-
*
|
|
4
|
-
* @packageDocumentation
|
|
5
|
-
*/
|
|
6
|
-
import { Dataset } from "../data/dataset.js";
|
|
7
|
-
import { selectAdapter } from "./adapter.js";
|
|
8
|
-
import { decodeListForge } from "./decode.js";
|
|
9
|
-
import { listForgeAdapter } from "./listforge.js";
|
|
10
|
-
import { resolve } from "./resolve.js";
|
|
11
|
-
/** Adapters available to {@link importRoster}, in match-priority order. */
|
|
12
|
-
const ADAPTERS = [listForgeAdapter];
|
|
13
|
-
/**
|
|
14
|
-
* Import a ListForge army-list export into a resolved 40kdc {@link Roster}.
|
|
15
|
-
*
|
|
16
|
-
* `input` may be a full ListForge URL, a bare base64 segment, or an
|
|
17
|
-
* already-decoded JSON string — all are handled transparently.
|
|
18
|
-
*/
|
|
19
|
-
export function importListForge(input, opts = {}) {
|
|
20
|
-
const decoded = decodeListForge(input);
|
|
21
|
-
return importRoster(decoded, opts);
|
|
22
|
-
}
|
|
23
|
-
/**
|
|
24
|
-
* Import an already-decoded payload. Selects the matching format adapter and
|
|
25
|
-
* resolves the result against the dataset.
|
|
26
|
-
*/
|
|
27
|
-
export function importRoster(decoded, opts = {}) {
|
|
28
|
-
const ds = opts.dataset ?? Dataset.embedded();
|
|
29
|
-
const adapter = selectAdapter(decoded, ADAPTERS);
|
|
30
|
-
const parsed = adapter.parse(decoded);
|
|
31
|
-
return resolve(parsed, ds);
|
|
32
|
-
}
|