@jgengine/assets 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +96 -0
- package/dist/aliases.d.ts +2 -0
- package/dist/aliases.js +11 -0
- package/dist/catalogs/build.d.ts +12 -0
- package/dist/catalogs/build.js +37 -0
- package/dist/catalogs/starter.d.ts +2 -0
- package/dist/catalogs/starter.js +4 -0
- package/dist/cli/pull.d.ts +2 -0
- package/dist/cli/pull.js +163 -0
- package/dist/download.d.ts +10 -0
- package/dist/download.js +68 -0
- package/dist/generated/index.d.ts +3 -0
- package/dist/generated/index.js +9 -0
- package/dist/generated/kenney-blaster.json +362 -0
- package/dist/generated/kenney-nature.json +3292 -0
- package/dist/generated/kenney-space.json +1532 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.js +8 -0
- package/dist/indexGen.d.ts +12 -0
- package/dist/indexGen.js +81 -0
- package/dist/manifest.d.ts +37 -0
- package/dist/manifest.js +3 -0
- package/dist/singles.d.ts +2 -0
- package/dist/singles.js +2 -0
- package/dist/singles.json +1 -0
- package/dist/sources/index.d.ts +7 -0
- package/dist/sources/index.js +10 -0
- package/dist/sources/kaykit.d.ts +2 -0
- package/dist/sources/kaykit.js +16 -0
- package/dist/sources/kenney.d.ts +2 -0
- package/dist/sources/kenney.js +32 -0
- package/dist/sources/quaternius.d.ts +2 -0
- package/dist/sources/quaternius.js +18 -0
- package/dist/verify.d.ts +13 -0
- package/dist/verify.js +34 -0
- package/package.json +42 -0
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export * from "./manifest.js";
|
|
2
|
+
export { sources, sourceById, kenneySources, quaterniusSources, kaykitSources } from "./sources/index.js";
|
|
3
|
+
export { singles } from "./singles.js";
|
|
4
|
+
export { aliases } from "./aliases.js";
|
|
5
|
+
export { generatedIndex, generatedBySource } from "./generated/index.js";
|
|
6
|
+
export { buildCatalog, entryUrl, type BuildCatalogOptions } from "./catalogs/build.js";
|
|
7
|
+
export { createStarterCatalog } from "./catalogs/starter.js";
|
|
8
|
+
export { verifyManifest, type VerifyResult } from "./verify.js";
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export * from "./manifest.js";
|
|
2
|
+
export { sources, sourceById, kenneySources, quaterniusSources, kaykitSources } from "./sources/index.js";
|
|
3
|
+
export { singles } from "./singles.js";
|
|
4
|
+
export { aliases } from "./aliases.js";
|
|
5
|
+
export { generatedIndex, generatedBySource } from "./generated/index.js";
|
|
6
|
+
export { buildCatalog, entryUrl } from "./catalogs/build.js";
|
|
7
|
+
export { createStarterCatalog } from "./catalogs/starter.js";
|
|
8
|
+
export { verifyManifest } from "./verify.js";
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { AssetSource, IndexEntry } from "./manifest.js";
|
|
2
|
+
export declare function keyFromFile(file: string): string;
|
|
3
|
+
export declare function entryForFile(source: AssetSource, file: string): IndexEntry;
|
|
4
|
+
export declare function indexSourceDir(source: AssetSource, dir: string): IndexEntry[];
|
|
5
|
+
export interface ReindexResult {
|
|
6
|
+
perSource: {
|
|
7
|
+
source: string;
|
|
8
|
+
count: number;
|
|
9
|
+
}[];
|
|
10
|
+
total: number;
|
|
11
|
+
}
|
|
12
|
+
export declare function reindex(modelsDir: string, outDir: string): ReindexResult;
|
package/dist/indexGen.js
ADDED
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { existsSync, mkdirSync, readdirSync, statSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import { sourceById } from "./sources/index.js";
|
|
4
|
+
export function keyFromFile(file) {
|
|
5
|
+
return file.replace(/\.glb$/i, "");
|
|
6
|
+
}
|
|
7
|
+
export function entryForFile(source, file) {
|
|
8
|
+
return {
|
|
9
|
+
id: `${source.id}/${keyFromFile(file)}`,
|
|
10
|
+
source: source.id,
|
|
11
|
+
categories: source.categories,
|
|
12
|
+
file,
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
function collectGlbFiles(dir) {
|
|
16
|
+
const found = [];
|
|
17
|
+
for (const name of readdirSync(dir)) {
|
|
18
|
+
const full = join(dir, name);
|
|
19
|
+
if (statSync(full).isDirectory()) {
|
|
20
|
+
found.push(...collectGlbFiles(full));
|
|
21
|
+
}
|
|
22
|
+
else if (/\.glb$/i.test(name)) {
|
|
23
|
+
found.push(name);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
return found;
|
|
27
|
+
}
|
|
28
|
+
export function indexSourceDir(source, dir) {
|
|
29
|
+
const seen = new Set();
|
|
30
|
+
const entries = [];
|
|
31
|
+
for (const file of collectGlbFiles(dir).sort((a, b) => a.localeCompare(b))) {
|
|
32
|
+
if (seen.has(file))
|
|
33
|
+
continue;
|
|
34
|
+
seen.add(file);
|
|
35
|
+
entries.push(entryForFile(source, file));
|
|
36
|
+
}
|
|
37
|
+
return entries;
|
|
38
|
+
}
|
|
39
|
+
function safeIdentifier(sourceId) {
|
|
40
|
+
return `s_${sourceId.replace(/[^a-zA-Z0-9]/g, "_")}`;
|
|
41
|
+
}
|
|
42
|
+
function renderBarrel(sourceIds) {
|
|
43
|
+
const ordered = [...sourceIds].sort((a, b) => a.localeCompare(b));
|
|
44
|
+
const imports = ordered
|
|
45
|
+
.map((id) => `import ${safeIdentifier(id)} from "./${id}.json" with { type: "json" };`)
|
|
46
|
+
.join("\n");
|
|
47
|
+
const table = ordered
|
|
48
|
+
.map((id) => ` ${JSON.stringify(id)}: ${safeIdentifier(id)} as IndexEntry[],`)
|
|
49
|
+
.join("\n");
|
|
50
|
+
return `// AUTO-GENERATED by \`assets reindex\`. Do not edit by hand.
|
|
51
|
+
import type { IndexEntry } from "../manifest";
|
|
52
|
+
${imports}
|
|
53
|
+
|
|
54
|
+
export const generatedBySource: Record<string, IndexEntry[]> = {
|
|
55
|
+
${table}
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
export const generatedIndex: IndexEntry[] = Object.values(generatedBySource).flat();
|
|
59
|
+
`;
|
|
60
|
+
}
|
|
61
|
+
export function reindex(modelsDir, outDir) {
|
|
62
|
+
mkdirSync(outDir, { recursive: true });
|
|
63
|
+
const perSource = [];
|
|
64
|
+
const sourceIds = [];
|
|
65
|
+
const subdirs = existsSync(modelsDir)
|
|
66
|
+
? readdirSync(modelsDir).filter((name) => statSync(join(modelsDir, name)).isDirectory())
|
|
67
|
+
: [];
|
|
68
|
+
for (const sourceId of subdirs.sort((a, b) => a.localeCompare(b))) {
|
|
69
|
+
const source = sourceById.get(sourceId);
|
|
70
|
+
if (source === undefined) {
|
|
71
|
+
perSource.push({ source: `${sourceId} (unknown, skipped)`, count: 0 });
|
|
72
|
+
continue;
|
|
73
|
+
}
|
|
74
|
+
const entries = indexSourceDir(source, join(modelsDir, sourceId));
|
|
75
|
+
writeFileSync(join(outDir, `${sourceId}.json`), `${JSON.stringify(entries, null, 2)}\n`);
|
|
76
|
+
sourceIds.push(sourceId);
|
|
77
|
+
perSource.push({ source: sourceId, count: entries.length });
|
|
78
|
+
}
|
|
79
|
+
writeFileSync(join(outDir, "index.ts"), renderBarrel(sourceIds));
|
|
80
|
+
return { perSource, total: perSource.reduce((sum, entry) => sum + entry.count, 0) };
|
|
81
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
export type AssetProvider = "kenney" | "quaternius" | "kaykit" | "polypizza" | "itch" | "custom";
|
|
2
|
+
export interface PinnedDownload {
|
|
3
|
+
url: string;
|
|
4
|
+
sha256?: string;
|
|
5
|
+
}
|
|
6
|
+
export interface ScrapeDownload {
|
|
7
|
+
scrape: string;
|
|
8
|
+
}
|
|
9
|
+
export type AssetDownload = PinnedDownload | ScrapeDownload;
|
|
10
|
+
export declare function isScrapeDownload(download: AssetDownload): download is ScrapeDownload;
|
|
11
|
+
export interface AssetSource {
|
|
12
|
+
id: string;
|
|
13
|
+
provider: AssetProvider;
|
|
14
|
+
title: string;
|
|
15
|
+
license: string;
|
|
16
|
+
author: string;
|
|
17
|
+
categories: readonly string[];
|
|
18
|
+
download: AssetDownload;
|
|
19
|
+
homepage?: string;
|
|
20
|
+
}
|
|
21
|
+
export interface IndexEntry {
|
|
22
|
+
id: string;
|
|
23
|
+
source: string;
|
|
24
|
+
categories: readonly string[];
|
|
25
|
+
file: string;
|
|
26
|
+
}
|
|
27
|
+
export interface AssetAlias {
|
|
28
|
+
key: string;
|
|
29
|
+
target: string;
|
|
30
|
+
}
|
|
31
|
+
export interface SingleAsset {
|
|
32
|
+
id: string;
|
|
33
|
+
url: string;
|
|
34
|
+
license: string;
|
|
35
|
+
author: string;
|
|
36
|
+
categories: readonly string[];
|
|
37
|
+
}
|
package/dist/manifest.js
ADDED
package/dist/singles.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
[]
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { AssetSource } from "../manifest.js";
|
|
2
|
+
import { kaykitSources } from "./kaykit.js";
|
|
3
|
+
import { kenneySources } from "./kenney.js";
|
|
4
|
+
import { quaterniusSources } from "./quaternius.js";
|
|
5
|
+
export declare const sources: readonly AssetSource[];
|
|
6
|
+
export declare const sourceById: ReadonlyMap<string, AssetSource>;
|
|
7
|
+
export { kaykitSources, kenneySources, quaterniusSources };
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { kaykitSources } from "./kaykit.js";
|
|
2
|
+
import { kenneySources } from "./kenney.js";
|
|
3
|
+
import { quaterniusSources } from "./quaternius.js";
|
|
4
|
+
export const sources = [
|
|
5
|
+
...kenneySources,
|
|
6
|
+
...quaterniusSources,
|
|
7
|
+
...kaykitSources,
|
|
8
|
+
];
|
|
9
|
+
export const sourceById = new Map(sources.map((source) => [source.id, source]));
|
|
10
|
+
export { kaykitSources, kenneySources, quaterniusSources };
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
const KAYKIT_PACKS = [
|
|
2
|
+
{ id: "kaykit-adventurers", slug: "kaykit-adventurers", title: "KayKit Adventurers", categories: ["character", "rigged", "fantasy"] },
|
|
3
|
+
{ id: "kaykit-skeletons", slug: "kaykit-skeletons", title: "KayKit Skeletons", categories: ["character", "rigged", "halloween"] },
|
|
4
|
+
{ id: "kaykit-dungeon", slug: "kaykit-dungeon-remastered", title: "KayKit Dungeon (Remastered)", categories: ["dungeon", "environment", "prop"] },
|
|
5
|
+
{ id: "kaykit-medieval-hexagon", slug: "kaykit-medieval-hexagon-pack", title: "KayKit Medieval Hexagon Pack", categories: ["hexagon", "medieval", "environment"] },
|
|
6
|
+
];
|
|
7
|
+
export const kaykitSources = KAYKIT_PACKS.map((pack) => ({
|
|
8
|
+
id: pack.id,
|
|
9
|
+
provider: "kaykit",
|
|
10
|
+
title: pack.title,
|
|
11
|
+
license: "CC0-1.0",
|
|
12
|
+
author: "Kay Lousberg",
|
|
13
|
+
categories: pack.categories,
|
|
14
|
+
download: { scrape: `https://kaylousberg.itch.io/${pack.slug}` },
|
|
15
|
+
homepage: `https://kaylousberg.itch.io/${pack.slug}`,
|
|
16
|
+
}));
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
const KENNEY_PACKS = [
|
|
2
|
+
{ id: "kenney-nature", slug: "nature-kit", title: "Nature Kit", categories: ["nature", "environment", "prop"] },
|
|
3
|
+
{ id: "kenney-castle", slug: "castle-kit", title: "Castle Kit", categories: ["building", "medieval", "environment"] },
|
|
4
|
+
{ id: "kenney-city-commercial", slug: "city-kit-commercial", title: "City Kit (Commercial)", categories: ["building", "city", "environment"] },
|
|
5
|
+
{ id: "kenney-city-suburban", slug: "city-kit-suburban", title: "City Kit (Suburban)", categories: ["building", "city", "environment"] },
|
|
6
|
+
{ id: "kenney-city-roads", slug: "city-kit-roads", title: "City Kit (Roads)", categories: ["road", "city", "environment"] },
|
|
7
|
+
{ id: "kenney-fantasy-town", slug: "fantasy-town-kit", title: "Fantasy Town Kit", categories: ["building", "fantasy", "environment"] },
|
|
8
|
+
{ id: "kenney-graveyard", slug: "graveyard-kit", title: "Graveyard Kit", categories: ["environment", "halloween", "prop"] },
|
|
9
|
+
{ id: "kenney-holiday", slug: "holiday-kit", title: "Holiday Kit", categories: ["prop", "holiday", "environment"] },
|
|
10
|
+
{ id: "kenney-mini-arena", slug: "mini-arena", title: "Mini Arena", categories: ["character", "arena", "prop"] },
|
|
11
|
+
{ id: "kenney-mini-dungeon", slug: "mini-dungeon", title: "Mini Dungeon", categories: ["dungeon", "environment", "prop"] },
|
|
12
|
+
{ id: "kenney-mini-skate", slug: "mini-skate", title: "Mini Skate", categories: ["character", "vehicle", "prop"] },
|
|
13
|
+
{ id: "kenney-pirate", slug: "pirate-kit", title: "Pirate Kit", categories: ["ship", "pirate", "environment"] },
|
|
14
|
+
{ id: "kenney-platformer", slug: "platformer-kit", title: "Platformer Kit", categories: ["platform", "environment", "prop"] },
|
|
15
|
+
{ id: "kenney-prototype", slug: "prototype-kit", title: "Prototype Kit", categories: ["prototype", "environment"] },
|
|
16
|
+
{ id: "kenney-racing", slug: "racing-kit", title: "Racing Kit", categories: ["vehicle", "racing", "environment"] },
|
|
17
|
+
{ id: "kenney-space", slug: "space-kit", title: "Space Kit", categories: ["space", "vehicle", "prop"] },
|
|
18
|
+
{ id: "kenney-survival", slug: "survival-kit", title: "Survival Kit", categories: ["survival", "prop", "environment"] },
|
|
19
|
+
{ id: "kenney-tower-defense", slug: "tower-defense-kit", title: "Tower Defense Kit", categories: ["tower", "environment", "prop"] },
|
|
20
|
+
{ id: "kenney-food", slug: "food-kit", title: "Food Kit", categories: ["food", "prop"] },
|
|
21
|
+
{ id: "kenney-blaster", slug: "blaster-kit", title: "Blaster Kit", categories: ["weapon", "prop"] },
|
|
22
|
+
];
|
|
23
|
+
export const kenneySources = KENNEY_PACKS.map((pack) => ({
|
|
24
|
+
id: pack.id,
|
|
25
|
+
provider: "kenney",
|
|
26
|
+
title: pack.title,
|
|
27
|
+
license: "CC0-1.0",
|
|
28
|
+
author: "Kenney",
|
|
29
|
+
categories: pack.categories,
|
|
30
|
+
download: { scrape: `https://kenney.nl/assets/${pack.slug}` },
|
|
31
|
+
homepage: `https://kenney.nl/assets/${pack.slug}`,
|
|
32
|
+
}));
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
const QUATERNIUS_PACKS = [
|
|
2
|
+
{ id: "quaternius-stylized-nature", slug: "stylizednaturemegakit", title: "Stylized Nature MegaKit", categories: ["nature", "environment", "prop"] },
|
|
3
|
+
{ id: "quaternius-medieval-village", slug: "medievalvillagemegakit", title: "Medieval Village MegaKit", categories: ["building", "medieval", "environment"] },
|
|
4
|
+
{ id: "quaternius-downtown-city", slug: "downtowncitymegakit", title: "Downtown City MegaKit", categories: ["building", "city", "environment"] },
|
|
5
|
+
{ id: "quaternius-modular-scifi", slug: "modularscifimegakit", title: "Modular SciFi MegaKit", categories: ["scifi", "environment", "prop"] },
|
|
6
|
+
{ id: "quaternius-fantasy-props", slug: "fantasypropsmegakit", title: "Fantasy Props MegaKit", categories: ["fantasy", "prop"] },
|
|
7
|
+
{ id: "quaternius-base-characters", slug: "universalbasecharacters", title: "Universal Base Characters", categories: ["character", "rigged"] },
|
|
8
|
+
];
|
|
9
|
+
export const quaterniusSources = QUATERNIUS_PACKS.map((pack) => ({
|
|
10
|
+
id: pack.id,
|
|
11
|
+
provider: "quaternius",
|
|
12
|
+
title: pack.title,
|
|
13
|
+
license: "CC0-1.0",
|
|
14
|
+
author: "Quaternius",
|
|
15
|
+
categories: pack.categories,
|
|
16
|
+
download: { scrape: `https://quaternius.com/packs/${pack.slug}.html` },
|
|
17
|
+
homepage: `https://quaternius.com/packs/${pack.slug}.html`,
|
|
18
|
+
}));
|
package/dist/verify.d.ts
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { AssetAlias, AssetSource, IndexEntry, SingleAsset } from "./manifest.js";
|
|
2
|
+
export interface VerifyResult {
|
|
3
|
+
ok: boolean;
|
|
4
|
+
errors: string[];
|
|
5
|
+
}
|
|
6
|
+
export interface VerifyInput {
|
|
7
|
+
sources: readonly AssetSource[];
|
|
8
|
+
singles: readonly SingleAsset[];
|
|
9
|
+
aliases: readonly AssetAlias[];
|
|
10
|
+
index: readonly IndexEntry[];
|
|
11
|
+
}
|
|
12
|
+
export declare function verifyData(input: VerifyInput): VerifyResult;
|
|
13
|
+
export declare function verifyManifest(): VerifyResult;
|
package/dist/verify.js
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { aliases } from "./aliases.js";
|
|
2
|
+
import { generatedIndex } from "./generated/index.js";
|
|
3
|
+
import { singles } from "./singles.js";
|
|
4
|
+
import { sources } from "./sources/index.js";
|
|
5
|
+
export function verifyData(input) {
|
|
6
|
+
const errors = [];
|
|
7
|
+
for (const source of input.sources) {
|
|
8
|
+
if (source.license.trim().length === 0)
|
|
9
|
+
errors.push(`source ${source.id}: missing license`);
|
|
10
|
+
if (source.author.trim().length === 0)
|
|
11
|
+
errors.push(`source ${source.id}: missing author`);
|
|
12
|
+
}
|
|
13
|
+
for (const single of input.singles) {
|
|
14
|
+
if (single.license.trim().length === 0)
|
|
15
|
+
errors.push(`single ${single.id}: missing license`);
|
|
16
|
+
if (single.author.trim().length === 0)
|
|
17
|
+
errors.push(`single ${single.id}: missing author`);
|
|
18
|
+
if (single.url.trim().length === 0)
|
|
19
|
+
errors.push(`single ${single.id}: missing url`);
|
|
20
|
+
}
|
|
21
|
+
const knownIds = new Set([
|
|
22
|
+
...input.index.map((entry) => entry.id),
|
|
23
|
+
...input.singles.map((single) => single.id),
|
|
24
|
+
]);
|
|
25
|
+
for (const alias of input.aliases) {
|
|
26
|
+
if (!knownIds.has(alias.target)) {
|
|
27
|
+
errors.push(`alias ${alias.key}: target "${alias.target}" not found in index or singles`);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
return { ok: errors.length === 0, errors };
|
|
31
|
+
}
|
|
32
|
+
export function verifyManifest() {
|
|
33
|
+
return verifyData({ sources, singles, aliases, index: generatedIndex });
|
|
34
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@jgengine/assets",
|
|
3
|
+
"version": "0.4.0",
|
|
4
|
+
"description": "Self-generating, license-verified index of thousands of CC0 3D models for JGengine. Sources fetch from providers' own CDNs at pull time; the npm tarball ships only the typed index and CLI (no GLB bytes).",
|
|
5
|
+
"license": "AGPL-3.0-only",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"sideEffects": false,
|
|
8
|
+
"repository": {
|
|
9
|
+
"type": "git",
|
|
10
|
+
"url": "git+https://github.com/Noisemaker111/jgengine.git",
|
|
11
|
+
"directory": "packages/assets"
|
|
12
|
+
},
|
|
13
|
+
"files": ["dist"],
|
|
14
|
+
"bin": {
|
|
15
|
+
"assets": "dist/cli/pull.js"
|
|
16
|
+
},
|
|
17
|
+
"exports": {
|
|
18
|
+
".": {
|
|
19
|
+
"types": "./dist/index.d.ts",
|
|
20
|
+
"default": "./dist/index.js"
|
|
21
|
+
},
|
|
22
|
+
"./*": {
|
|
23
|
+
"types": "./dist/*.d.ts",
|
|
24
|
+
"default": "./dist/*.js"
|
|
25
|
+
}
|
|
26
|
+
},
|
|
27
|
+
"scripts": {
|
|
28
|
+
"build": "tsgo -p tsconfig.build.json && bun ../../scripts/fix-extensions.ts dist && bun scripts/postbuild.ts",
|
|
29
|
+
"check-types": "tsgo --noEmit -p tsconfig.json",
|
|
30
|
+
"test": "bun test src"
|
|
31
|
+
},
|
|
32
|
+
"dependencies": {
|
|
33
|
+
"@jgengine/core": "^0.4.0",
|
|
34
|
+
"fflate": "^0.8.2"
|
|
35
|
+
},
|
|
36
|
+
"devDependencies": {
|
|
37
|
+
"@types/node": "^24"
|
|
38
|
+
},
|
|
39
|
+
"publishConfig": {
|
|
40
|
+
"access": "public"
|
|
41
|
+
}
|
|
42
|
+
}
|