Browse Source

refactor(assets): convert asset management scripts to TypeScript with tsx

- Replace JavaScript asset scripts with TypeScript equivalents
- Install tsx for direct TypeScript execution without compilation
- Add proper TypeScript interfaces for AssetConfig and validation
- Update package.json scripts to use tsx instead of node
- Remove old JavaScript files (assets-config.js, assets-validator.js)
- Maintain all existing functionality while improving type safety
- Fix module syntax issues that caused build failures on macOS

Scripts affected:
- assets:config: node → tsx scripts/assets-config.ts
- assets:validate: node → tsx scripts/assets-validator.ts

Benefits:
- Eliminates CommonJS/ES module syntax conflicts
- Provides better type safety and IntelliSense
- Modernizes development tooling
- Ensures cross-platform compatibility
pull/165/head
Matthew Raymer 4 days ago
parent
commit
495a94827a
  1. 14
      config/assets/capacitor-assets.config.json
  2. 528
      package-lock.json
  3. 5
      package.json
  4. 82
      scripts/assets-config.ts
  5. 103
      scripts/assets-validator.ts

14
config/assets/capacitor-assets.config.json

@ -1,10 +1,9 @@
{ {
"icon": { "icon": {
"source": "resources/icon.png",
"android": { "android": {
"adaptive": { "adaptive": {
"foreground": "resources/icon.png",
"background": "#121212", "background": "#121212",
"foreground": "resources/icon.png",
"monochrome": "resources/icon.png" "monochrome": "resources/icon.png"
}, },
"target": "android/app/src/main/res" "target": "android/app/src/main/res"
@ -13,20 +12,21 @@
"padding": 0, "padding": 0,
"target": "ios/App/App/Assets.xcassets/AppIcon.appiconset" "target": "ios/App/App/Assets.xcassets/AppIcon.appiconset"
}, },
"source": "resources/icon.png",
"web": { "web": {
"target": "public/img/icons" "target": "public/img/icons"
} }
}, },
"splash": { "splash": {
"source": "resources/splash.png",
"darkSource": "resources/splash_dark.png",
"android": { "android": {
"scale": "cover", "scale": "cover",
"target": "android/app/src/main/res" "target": "android/app/src/main/res"
}, },
"darkSource": "resources/splash_dark.png",
"ios": { "ios": {
"useStoryBoard": true, "target": "ios/App/App/Assets.xcassets",
"target": "ios/App/App/Assets.xcassets" "useStoryBoard": true
} },
"source": "resources/splash.png"
} }
} }

528
package-lock.json

@ -133,6 +133,7 @@
"rimraf": "^6.0.1", "rimraf": "^6.0.1",
"tailwindcss": "^3.4.1", "tailwindcss": "^3.4.1",
"ts-jest": "^29.4.0", "ts-jest": "^29.4.0",
"tsx": "^4.20.4",
"typescript": "~5.2.2", "typescript": "~5.2.2",
"vite": "^5.2.0" "vite": "^5.2.0"
} }
@ -3900,6 +3901,23 @@
"node": ">=12" "node": ">=12"
} }
}, },
"node_modules/@esbuild/netbsd-arm64": {
"version": "0.25.9",
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.9.tgz",
"integrity": "sha512-9jNJl6FqaUG+COdQMjSCGW4QiMHH88xWbvZ+kRVblZsWrkXlABuGdFJ1E9L7HK+T0Yqd4akKNa/lO0+jDxQD4Q==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"netbsd"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/netbsd-x64": { "node_modules/@esbuild/netbsd-x64": {
"version": "0.21.5", "version": "0.21.5",
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz", "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz",
@ -3917,6 +3935,23 @@
"node": ">=12" "node": ">=12"
} }
}, },
"node_modules/@esbuild/openbsd-arm64": {
"version": "0.25.9",
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.9.tgz",
"integrity": "sha512-YaFBlPGeDasft5IIM+CQAhJAqS3St3nJzDEgsgFixcfZeyGPCd6eJBWzke5piZuZ7CtL656eOSYKk4Ls2C0FRQ==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"openbsd"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/openbsd-x64": { "node_modules/@esbuild/openbsd-x64": {
"version": "0.21.5", "version": "0.21.5",
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz", "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz",
@ -3934,6 +3969,23 @@
"node": ">=12" "node": ">=12"
} }
}, },
"node_modules/@esbuild/openharmony-arm64": {
"version": "0.25.9",
"resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.9.tgz",
"integrity": "sha512-4Xd0xNiMVXKh6Fa7HEJQbrpP3m3DDn43jKxMjxLLRjWnRsfxjORYJlXPO4JNcXtOyfajXorRKY9NkOpTHptErg==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"openharmony"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/sunos-x64": { "node_modules/@esbuild/sunos-x64": {
"version": "0.21.5", "version": "0.21.5",
"resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz", "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz",
@ -17406,6 +17458,19 @@
"url": "https://github.com/sponsors/sindresorhus" "url": "https://github.com/sponsors/sindresorhus"
} }
}, },
"node_modules/get-tsconfig": {
"version": "4.10.1",
"resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.10.1.tgz",
"integrity": "sha512-auHyJ4AgMz7vgS8Hp3N6HXSmlMdUyhSUrfBF16w153rxtLIEOE+HGqaBppczZvnHLqQJfiHotCYpNhl0lUROFQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"resolve-pkg-maps": "^1.0.0"
},
"funding": {
"url": "https://github.com/privatenumber/get-tsconfig?sponsor=1"
}
},
"node_modules/getenv": { "node_modules/getenv": {
"version": "2.0.0", "version": "2.0.0",
"resolved": "https://registry.npmjs.org/getenv/-/getenv-2.0.0.tgz", "resolved": "https://registry.npmjs.org/getenv/-/getenv-2.0.0.tgz",
@ -26798,6 +26863,16 @@
"node": ">=4" "node": ">=4"
} }
}, },
"node_modules/resolve-pkg-maps": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz",
"integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==",
"dev": true,
"license": "MIT",
"funding": {
"url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1"
}
},
"node_modules/resolve-workspace-root": { "node_modules/resolve-workspace-root": {
"version": "2.0.0", "version": "2.0.0",
"resolved": "https://registry.npmjs.org/resolve-workspace-root/-/resolve-workspace-root-2.0.0.tgz", "resolved": "https://registry.npmjs.org/resolve-workspace-root/-/resolve-workspace-root-2.0.0.tgz",
@ -29272,6 +29347,459 @@
"dev": true, "dev": true,
"license": "0BSD" "license": "0BSD"
}, },
"node_modules/tsx": {
"version": "4.20.4",
"resolved": "https://registry.npmjs.org/tsx/-/tsx-4.20.4.tgz",
"integrity": "sha512-yyxBKfORQ7LuRt/BQKBXrpcq59ZvSW0XxwfjAt3w2/8PmdxaFzijtMhTawprSHhpzeM5BgU2hXHG3lklIERZXg==",
"dev": true,
"license": "MIT",
"dependencies": {
"esbuild": "~0.25.0",
"get-tsconfig": "^4.7.5"
},
"bin": {
"tsx": "dist/cli.mjs"
},
"engines": {
"node": ">=18.0.0"
},
"optionalDependencies": {
"fsevents": "~2.3.3"
}
},
"node_modules/tsx/node_modules/@esbuild/aix-ppc64": {
"version": "0.25.9",
"resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.9.tgz",
"integrity": "sha512-OaGtL73Jck6pBKjNIe24BnFE6agGl+6KxDtTfHhy1HmhthfKouEcOhqpSL64K4/0WCtbKFLOdzD/44cJ4k9opA==",
"cpu": [
"ppc64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"aix"
],
"engines": {
"node": ">=18"
}
},
"node_modules/tsx/node_modules/@esbuild/android-arm": {
"version": "0.25.9",
"resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.9.tgz",
"integrity": "sha512-5WNI1DaMtxQ7t7B6xa572XMXpHAaI/9Hnhk8lcxF4zVN4xstUgTlvuGDorBguKEnZO70qwEcLpfifMLoxiPqHQ==",
"cpu": [
"arm"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"android"
],
"engines": {
"node": ">=18"
}
},
"node_modules/tsx/node_modules/@esbuild/android-arm64": {
"version": "0.25.9",
"resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.9.tgz",
"integrity": "sha512-IDrddSmpSv51ftWslJMvl3Q2ZT98fUSL2/rlUXuVqRXHCs5EUF1/f+jbjF5+NG9UffUDMCiTyh8iec7u8RlTLg==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"android"
],
"engines": {
"node": ">=18"
}
},
"node_modules/tsx/node_modules/@esbuild/android-x64": {
"version": "0.25.9",
"resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.9.tgz",
"integrity": "sha512-I853iMZ1hWZdNllhVZKm34f4wErd4lMyeV7BLzEExGEIZYsOzqDWDf+y082izYUE8gtJnYHdeDpN/6tUdwvfiw==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"android"
],
"engines": {
"node": ">=18"
}
},
"node_modules/tsx/node_modules/@esbuild/darwin-arm64": {
"version": "0.25.9",
"resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.9.tgz",
"integrity": "sha512-XIpIDMAjOELi/9PB30vEbVMs3GV1v2zkkPnuyRRURbhqjyzIINwj+nbQATh4H9GxUgH1kFsEyQMxwiLFKUS6Rg==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": ">=18"
}
},
"node_modules/tsx/node_modules/@esbuild/darwin-x64": {
"version": "0.25.9",
"resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.9.tgz",
"integrity": "sha512-jhHfBzjYTA1IQu8VyrjCX4ApJDnH+ez+IYVEoJHeqJm9VhG9Dh2BYaJritkYK3vMaXrf7Ogr/0MQ8/MeIefsPQ==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": ">=18"
}
},
"node_modules/tsx/node_modules/@esbuild/freebsd-arm64": {
"version": "0.25.9",
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.9.tgz",
"integrity": "sha512-z93DmbnY6fX9+KdD4Ue/H6sYs+bhFQJNCPZsi4XWJoYblUqT06MQUdBCpcSfuiN72AbqeBFu5LVQTjfXDE2A6Q==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"freebsd"
],
"engines": {
"node": ">=18"
}
},
"node_modules/tsx/node_modules/@esbuild/freebsd-x64": {
"version": "0.25.9",
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.9.tgz",
"integrity": "sha512-mrKX6H/vOyo5v71YfXWJxLVxgy1kyt1MQaD8wZJgJfG4gq4DpQGpgTB74e5yBeQdyMTbgxp0YtNj7NuHN0PoZg==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"freebsd"
],
"engines": {
"node": ">=18"
}
},
"node_modules/tsx/node_modules/@esbuild/linux-arm": {
"version": "0.25.9",
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.9.tgz",
"integrity": "sha512-HBU2Xv78SMgaydBmdor38lg8YDnFKSARg1Q6AT0/y2ezUAKiZvc211RDFHlEZRFNRVhcMamiToo7bDx3VEOYQw==",
"cpu": [
"arm"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=18"
}
},
"node_modules/tsx/node_modules/@esbuild/linux-arm64": {
"version": "0.25.9",
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.9.tgz",
"integrity": "sha512-BlB7bIcLT3G26urh5Dmse7fiLmLXnRlopw4s8DalgZ8ef79Jj4aUcYbk90g8iCa2467HX8SAIidbL7gsqXHdRw==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=18"
}
},
"node_modules/tsx/node_modules/@esbuild/linux-ia32": {
"version": "0.25.9",
"resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.9.tgz",
"integrity": "sha512-e7S3MOJPZGp2QW6AK6+Ly81rC7oOSerQ+P8L0ta4FhVi+/j/v2yZzx5CqqDaWjtPFfYz21Vi1S0auHrap3Ma3A==",
"cpu": [
"ia32"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=18"
}
},
"node_modules/tsx/node_modules/@esbuild/linux-loong64": {
"version": "0.25.9",
"resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.9.tgz",
"integrity": "sha512-Sbe10Bnn0oUAB2AalYztvGcK+o6YFFA/9829PhOCUS9vkJElXGdphz0A3DbMdP8gmKkqPmPcMJmJOrI3VYB1JQ==",
"cpu": [
"loong64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=18"
}
},
"node_modules/tsx/node_modules/@esbuild/linux-mips64el": {
"version": "0.25.9",
"resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.9.tgz",
"integrity": "sha512-YcM5br0mVyZw2jcQeLIkhWtKPeVfAerES5PvOzaDxVtIyZ2NUBZKNLjC5z3/fUlDgT6w89VsxP2qzNipOaaDyA==",
"cpu": [
"mips64el"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=18"
}
},
"node_modules/tsx/node_modules/@esbuild/linux-ppc64": {
"version": "0.25.9",
"resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.9.tgz",
"integrity": "sha512-++0HQvasdo20JytyDpFvQtNrEsAgNG2CY1CLMwGXfFTKGBGQT3bOeLSYE2l1fYdvML5KUuwn9Z8L1EWe2tzs1w==",
"cpu": [
"ppc64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=18"
}
},
"node_modules/tsx/node_modules/@esbuild/linux-riscv64": {
"version": "0.25.9",
"resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.9.tgz",
"integrity": "sha512-uNIBa279Y3fkjV+2cUjx36xkx7eSjb8IvnL01eXUKXez/CBHNRw5ekCGMPM0BcmqBxBcdgUWuUXmVWwm4CH9kg==",
"cpu": [
"riscv64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=18"
}
},
"node_modules/tsx/node_modules/@esbuild/linux-s390x": {
"version": "0.25.9",
"resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.9.tgz",
"integrity": "sha512-Mfiphvp3MjC/lctb+7D287Xw1DGzqJPb/J2aHHcHxflUo+8tmN/6d4k6I2yFR7BVo5/g7x2Monq4+Yew0EHRIA==",
"cpu": [
"s390x"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=18"
}
},
"node_modules/tsx/node_modules/@esbuild/linux-x64": {
"version": "0.25.9",
"resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.9.tgz",
"integrity": "sha512-iSwByxzRe48YVkmpbgoxVzn76BXjlYFXC7NvLYq+b+kDjyyk30J0JY47DIn8z1MO3K0oSl9fZoRmZPQI4Hklzg==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=18"
}
},
"node_modules/tsx/node_modules/@esbuild/netbsd-x64": {
"version": "0.25.9",
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.9.tgz",
"integrity": "sha512-RLLdkflmqRG8KanPGOU7Rpg829ZHu8nFy5Pqdi9U01VYtG9Y0zOG6Vr2z4/S+/3zIyOxiK6cCeYNWOFR9QP87g==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"netbsd"
],
"engines": {
"node": ">=18"
}
},
"node_modules/tsx/node_modules/@esbuild/openbsd-x64": {
"version": "0.25.9",
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.9.tgz",
"integrity": "sha512-1MkgTCuvMGWuqVtAvkpkXFmtL8XhWy+j4jaSO2wxfJtilVCi0ZE37b8uOdMItIHz4I6z1bWWtEX4CJwcKYLcuA==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"openbsd"
],
"engines": {
"node": ">=18"
}
},
"node_modules/tsx/node_modules/@esbuild/sunos-x64": {
"version": "0.25.9",
"resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.9.tgz",
"integrity": "sha512-WjH4s6hzo00nNezhp3wFIAfmGZ8U7KtrJNlFMRKxiI9mxEK1scOMAaa9i4crUtu+tBr+0IN6JCuAcSBJZfnphw==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"sunos"
],
"engines": {
"node": ">=18"
}
},
"node_modules/tsx/node_modules/@esbuild/win32-arm64": {
"version": "0.25.9",
"resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.9.tgz",
"integrity": "sha512-mGFrVJHmZiRqmP8xFOc6b84/7xa5y5YvR1x8djzXpJBSv/UsNK6aqec+6JDjConTgvvQefdGhFDAs2DLAds6gQ==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"win32"
],
"engines": {
"node": ">=18"
}
},
"node_modules/tsx/node_modules/@esbuild/win32-ia32": {
"version": "0.25.9",
"resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.9.tgz",
"integrity": "sha512-b33gLVU2k11nVx1OhX3C8QQP6UHQK4ZtN56oFWvVXvz2VkDoe6fbG8TOgHFxEvqeqohmRnIHe5A1+HADk4OQww==",
"cpu": [
"ia32"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"win32"
],
"engines": {
"node": ">=18"
}
},
"node_modules/tsx/node_modules/@esbuild/win32-x64": {
"version": "0.25.9",
"resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.9.tgz",
"integrity": "sha512-PPOl1mi6lpLNQxnGoyAfschAodRFYXJ+9fs6WHXz7CSWKbOqiMZsubC+BQsVKuul+3vKLuwTHsS2c2y9EoKwxQ==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"win32"
],
"engines": {
"node": ">=18"
}
},
"node_modules/tsx/node_modules/esbuild": {
"version": "0.25.9",
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.9.tgz",
"integrity": "sha512-CRbODhYyQx3qp7ZEwzxOk4JBqmD/seJrzPa/cGjY1VtIn5E09Oi9/dB4JwctnfZ8Q8iT7rioVv5k/FNT/uf54g==",
"dev": true,
"hasInstallScript": true,
"license": "MIT",
"bin": {
"esbuild": "bin/esbuild"
},
"engines": {
"node": ">=18"
},
"optionalDependencies": {
"@esbuild/aix-ppc64": "0.25.9",
"@esbuild/android-arm": "0.25.9",
"@esbuild/android-arm64": "0.25.9",
"@esbuild/android-x64": "0.25.9",
"@esbuild/darwin-arm64": "0.25.9",
"@esbuild/darwin-x64": "0.25.9",
"@esbuild/freebsd-arm64": "0.25.9",
"@esbuild/freebsd-x64": "0.25.9",
"@esbuild/linux-arm": "0.25.9",
"@esbuild/linux-arm64": "0.25.9",
"@esbuild/linux-ia32": "0.25.9",
"@esbuild/linux-loong64": "0.25.9",
"@esbuild/linux-mips64el": "0.25.9",
"@esbuild/linux-ppc64": "0.25.9",
"@esbuild/linux-riscv64": "0.25.9",
"@esbuild/linux-s390x": "0.25.9",
"@esbuild/linux-x64": "0.25.9",
"@esbuild/netbsd-arm64": "0.25.9",
"@esbuild/netbsd-x64": "0.25.9",
"@esbuild/openbsd-arm64": "0.25.9",
"@esbuild/openbsd-x64": "0.25.9",
"@esbuild/openharmony-arm64": "0.25.9",
"@esbuild/sunos-x64": "0.25.9",
"@esbuild/win32-arm64": "0.25.9",
"@esbuild/win32-ia32": "0.25.9",
"@esbuild/win32-x64": "0.25.9"
}
},
"node_modules/tunnel-agent": { "node_modules/tunnel-agent": {
"version": "0.6.0", "version": "0.6.0",
"resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz",

5
package.json

@ -26,8 +26,8 @@
"build:capacitor": "VITE_GIT_HASH=`git log -1 --pretty=format:%h` vite build --mode capacitor --config vite.config.capacitor.mts", "build:capacitor": "VITE_GIT_HASH=`git log -1 --pretty=format:%h` vite build --mode capacitor --config vite.config.capacitor.mts",
"build:capacitor:sync": "npm run build:capacitor && npx cap sync", "build:capacitor:sync": "npm run build:capacitor && npx cap sync",
"build:native": "vite build && npx cap sync && npx capacitor-assets generate", "build:native": "vite build && npx cap sync && npx capacitor-assets generate",
"assets:config": "node scripts/assets-config.js", "assets:config": "tsx scripts/assets-config.ts",
"assets:validate": "node scripts/assets-validator.js", "assets:validate": "tsx scripts/assets-validator.ts",
"assets:clean": "rimraf android/app/src/main/res/mipmap-* ios/App/App/Assets.xcassets/**/AppIcon*.png ios/App/App/Assets.xcassets/**/Splash*.png || true", "assets:clean": "rimraf android/app/src/main/res/mipmap-* ios/App/App/Assets.xcassets/**/AppIcon*.png ios/App/App/Assets.xcassets/**/Splash*.png || true",
"build:ios": "./scripts/build-ios.sh", "build:ios": "./scripts/build-ios.sh",
"build:ios:dev": "./scripts/build-ios.sh --dev", "build:ios:dev": "./scripts/build-ios.sh --dev",
@ -245,6 +245,7 @@
"rimraf": "^6.0.1", "rimraf": "^6.0.1",
"tailwindcss": "^3.4.1", "tailwindcss": "^3.4.1",
"ts-jest": "^29.4.0", "ts-jest": "^29.4.0",
"tsx": "^4.20.4",
"typescript": "~5.2.2", "typescript": "~5.2.2",
"vite": "^5.2.0" "vite": "^5.2.0"
} }

82
scripts/assets-config.js → scripts/assets-config.ts

@ -1,11 +1,11 @@
#!/usr/bin/env node #!/usr/bin/env tsx
/** /**
* TimeSafari Asset Configuration Generator * TimeSafari Asset Configuration Generator
* Generates capacitor-assets configuration files with deterministic outputs * Generates capacitor-assets configuration files with deterministic outputs
* Author: Matthew Raymer * Author: Matthew Raymer
* *
* Usage: node scripts/assets-config.js * Usage: tsx scripts/assets-config.ts
*/ */
import fs from 'fs'; import fs from 'fs';
@ -16,12 +16,62 @@ const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename); const __dirname = path.dirname(__filename);
const PROJECT_ROOT = path.dirname(__dirname); const PROJECT_ROOT = path.dirname(__dirname);
// TypeScript interfaces for asset configuration
interface AdaptiveIconConfig {
foreground: string;
background: string;
monochrome: string;
}
interface AndroidIconConfig {
adaptive: AdaptiveIconConfig;
target: string;
}
interface IOSIconConfig {
padding: number;
target: string;
}
interface WebIconConfig {
target: string;
}
interface IconConfig {
source: string;
android: AndroidIconConfig;
ios: IOSIconConfig;
web: WebIconConfig;
}
interface AndroidSplashConfig {
scale: string;
target: string;
}
interface IOSSplashConfig {
useStoryBoard: boolean;
target: string;
}
interface SplashConfig {
source: string;
darkSource: string;
android: AndroidSplashConfig;
ios: IOSSplashConfig;
}
interface AssetConfig {
icon: IconConfig;
splash: SplashConfig;
}
/** /**
* Generate deterministic capacitor-assets configuration * Generate deterministic capacitor-assets configuration
* @returns {Object} Sorted, stable configuration object * @returns Sorted, stable configuration object
*/ */
function generateAssetConfig() { function generateAssetConfig(): AssetConfig {
const config = { const config: AssetConfig = {
icon: { icon: {
source: "resources/icon.png", source: "resources/icon.png",
android: { android: {
@ -59,10 +109,10 @@ function generateAssetConfig() {
/** /**
* Sort object keys recursively for deterministic output * Sort object keys recursively for deterministic output
* @param {Object} obj - Object to sort * @param obj - Object to sort
* @returns {Object} Object with sorted keys * @returns Object with sorted keys
*/ */
function sortObjectKeys(obj) { function sortObjectKeys(obj: any): any {
if (obj === null || typeof obj !== 'object') { if (obj === null || typeof obj !== 'object') {
return obj; return obj;
} }
@ -71,7 +121,7 @@ function sortObjectKeys(obj) {
return obj.map(sortObjectKeys); return obj.map(sortObjectKeys);
} }
const sorted = {}; const sorted: any = {};
Object.keys(obj) Object.keys(obj)
.sort() .sort()
.forEach(key => { .forEach(key => {
@ -84,7 +134,7 @@ function sortObjectKeys(obj) {
/** /**
* Validate that required source files exist * Validate that required source files exist
*/ */
function validateSourceFiles() { function validateSourceFiles(): void {
const requiredFiles = [ const requiredFiles = [
'resources/icon.png', 'resources/icon.png',
'resources/splash.png', 'resources/splash.png',
@ -107,10 +157,10 @@ function validateSourceFiles() {
/** /**
* Write configuration to file with consistent formatting * Write configuration to file with consistent formatting
* @param {Object} config - Configuration object * @param config - Configuration object
* @param {string} outputPath - Output file path * @param outputPath - Output file path
*/ */
function writeConfig(config, outputPath) { function writeConfig(config: AssetConfig, outputPath: string): void {
const jsonString = JSON.stringify(config, null, 2); const jsonString = JSON.stringify(config, null, 2);
// Ensure consistent line endings and no trailing whitespace // Ensure consistent line endings and no trailing whitespace
@ -126,7 +176,7 @@ function writeConfig(config, outputPath) {
/** /**
* Main execution function * Main execution function
*/ */
function main() { function main(): void {
console.log('🔄 Generating TimeSafari asset configuration...'); console.log('🔄 Generating TimeSafari asset configuration...');
console.log(`📁 Project root: ${PROJECT_ROOT}`); console.log(`📁 Project root: ${PROJECT_ROOT}`);
console.log(`📅 Generated: ${new Date().toISOString()}`); console.log(`📅 Generated: ${new Date().toISOString()}`);
@ -161,7 +211,7 @@ function main() {
console.log(' 4. Use "npm run build:native" for builds'); console.log(' 4. Use "npm run build:native" for builds');
} catch (error) { } catch (error) {
console.error('❌ Configuration generation failed:', error.message); console.error('❌ Configuration generation failed:', error instanceof Error ? error.message : String(error));
process.exit(1); process.exit(1);
} }
} }
@ -171,4 +221,4 @@ if (import.meta.url === `file://${process.argv[1]}`) {
main(); main();
} }
export { generateAssetConfig, sortObjectKeys, validateSourceFiles }; export { generateAssetConfig, sortObjectKeys, validateSourceFiles, AssetConfig };

103
scripts/assets-validator.js → scripts/assets-validator.ts

@ -1,11 +1,11 @@
#!/usr/bin/env node #!/usr/bin/env tsx
/** /**
* TimeSafari Asset Configuration Validator * TimeSafari Asset Configuration Validator
* Validates capacitor-assets configuration against schema and source files * Validates capacitor-assets configuration against schema and source files
* Author: Matthew Raymer * Author: Matthew Raymer
* *
* Usage: node scripts/assets-validator.js [config-path] * Usage: tsx scripts/assets-validator.ts [config-path]
*/ */
import fs from 'fs'; import fs from 'fs';
@ -16,50 +16,71 @@ const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename); const __dirname = path.dirname(__filename);
const PROJECT_ROOT = path.dirname(__dirname); const PROJECT_ROOT = path.dirname(__dirname);
// TypeScript interfaces for validation
interface ValidationError {
message: string;
}
interface AssetConfig {
icon?: {
source?: string;
android?: {
adaptive?: {
foreground?: string;
background?: string;
monochrome?: string;
};
};
};
splash?: {
source?: string;
darkSource?: string;
};
}
/** /**
* Load and parse JSON file * Load and parse JSON file
* @param {string} filePath - Path to JSON file * @param filePath - Path to JSON file
* @returns {Object} Parsed JSON object * @returns Parsed JSON object
*/ */
function loadJsonFile(filePath) { function loadJsonFile(filePath: string): AssetConfig {
try { try {
const content = fs.readFileSync(filePath, 'utf8'); const content = fs.readFileSync(filePath, 'utf8');
return JSON.parse(content); return JSON.parse(content);
} catch (error) { } catch (error) {
throw new Error(`Failed to load ${filePath}: ${error.message}`); throw new Error(`Failed to load ${filePath}: ${error instanceof Error ? error.message : String(error)}`);
} }
} }
/** /**
* Validate configuration against schema * Validate configuration against schema
* @param {Object} config - Configuration object to validate * @param config - Configuration object to validate
* @param {Object} schema - JSON schema for validation * @returns Array of validation errors
* @returns {Array} Array of validation errors
*/ */
function validateAgainstSchema(config, schema) { function validateAgainstSchema(config: AssetConfig): ValidationError[] {
const errors = []; const errors: ValidationError[] = [];
// Basic structure validation // Basic structure validation
if (!config.icon || !config.splash) { if (!config.icon || !config.splash) {
errors.push('Configuration must contain both "icon" and "splash" sections'); errors.push({ message: 'Configuration must contain both "icon" and "splash" sections' });
} }
// Icon validation // Icon validation
if (config.icon) { if (config.icon) {
if (!config.icon.source) { if (!config.icon.source) {
errors.push('Icon section must contain "source" field'); errors.push({ message: 'Icon section must contain "source" field' });
} else if (!/^resources\/.*\.(png|svg)$/.test(config.icon.source)) { } else if (!/^resources\/.*\.(png|svg)$/.test(config.icon.source)) {
errors.push('Icon source must be a PNG or SVG file in resources/ directory'); errors.push({ message: 'Icon source must be a PNG or SVG file in resources/ directory' });
} }
// Android adaptive icon validation // Android adaptive icon validation
if (config.icon.android?.adaptive) { if (config.icon.android?.adaptive) {
const adaptive = config.icon.android.adaptive; const adaptive = config.icon.android.adaptive;
if (!adaptive.foreground || !adaptive.background) { if (!adaptive.foreground || !adaptive.background) {
errors.push('Android adaptive icon must have both foreground and background'); errors.push({ message: 'Android adaptive icon must have both foreground and background' });
} }
if (adaptive.foreground && !/^resources\/.*\.(png|svg)$/.test(adaptive.foreground)) { if (adaptive.foreground && !/^resources\/.*\.(png|svg)$/.test(adaptive.foreground)) {
errors.push('Android adaptive foreground must be a PNG or SVG file in resources/ directory'); errors.push({ message: 'Android adaptive foreground must be a PNG or SVG file in resources/ directory' });
} }
} }
} }
@ -67,13 +88,13 @@ function validateAgainstSchema(config, schema) {
// Splash validation // Splash validation
if (config.splash) { if (config.splash) {
if (!config.splash.source) { if (!config.splash.source) {
errors.push('Splash section must contain "source" field'); errors.push({ message: 'Splash section must contain "source" field' });
} else if (!/^resources\/.*\.(png|svg)$/.test(config.splash.source)) { } else if (!/^resources\/.*\.(png|svg)$/.test(config.splash.source)) {
errors.push('Splash source must be a PNG or SVG file in resources/ directory'); errors.push({ message: 'Splash source must be a PNG or SVG file in resources/ directory' });
} }
if (config.splash.darkSource && !/^resources\/.*\.(png|svg)$/.test(config.splash.darkSource)) { if (config.splash.darkSource && !/^resources\/.*\.(png|svg)$/.test(config.splash.darkSource)) {
errors.push('Dark splash source must be a PNG or SVG file in resources/ directory'); errors.push({ message: 'Dark splash source must be a PNG or SVG file in resources/ directory' });
} }
} }
@ -82,12 +103,12 @@ function validateAgainstSchema(config, schema) {
/** /**
* Validate that source files exist * Validate that source files exist
* @param {Object} config - Configuration object * @param config - Configuration object
* @returns {Array} Array of missing file errors * @returns Array of missing file errors
*/ */
function validateSourceFiles(config) { function validateSourceFiles(config: AssetConfig): ValidationError[] {
const errors = []; const errors: ValidationError[] = [];
const requiredFiles = new Set(); const requiredFiles = new Set<string>();
// Collect all required source files // Collect all required source files
if (config.icon?.source) requiredFiles.add(config.icon.source); if (config.icon?.source) requiredFiles.add(config.icon.source);
@ -100,7 +121,7 @@ function validateSourceFiles(config) {
requiredFiles.forEach(file => { requiredFiles.forEach(file => {
const filePath = path.join(PROJECT_ROOT, file); const filePath = path.join(PROJECT_ROOT, file);
if (!fs.existsSync(filePath)) { if (!fs.existsSync(filePath)) {
errors.push(`Source file not found: ${file}`); errors.push({ message: `Source file not found: ${file}` });
} }
}); });
@ -109,12 +130,12 @@ function validateSourceFiles(config) {
/** /**
* Validate target directories are writable * Validate target directories are writable
* @param {Object} config - Configuration object * @param config - Configuration object
* @returns {Array} Array of directory validation errors * @returns Array of directory validation errors
*/ */
function validateTargetDirectories(config) { function validateTargetDirectories(config: AssetConfig): ValidationError[] {
const errors = []; const errors: ValidationError[] = [];
const targetDirs = new Set(); const targetDirs = new Set<string>();
// Collect all target directories // Collect all target directories
if (config.icon?.android?.target) targetDirs.add(config.icon.android.target); if (config.icon?.android?.target) targetDirs.add(config.icon.android.target);
@ -129,9 +150,9 @@ function validateTargetDirectories(config) {
const parentDir = path.dirname(dirPath); const parentDir = path.dirname(dirPath);
if (!fs.existsSync(parentDir)) { if (!fs.existsSync(parentDir)) {
errors.push(`Parent directory does not exist: ${parentDir}`); errors.push({ message: `Parent directory does not exist: ${parentDir}` });
} else if (!fs.statSync(parentDir).isDirectory()) { } else if (!fs.statSync(parentDir).isDirectory()) {
errors.push(`Parent path is not a directory: ${parentDir}`); errors.push({ message: `Parent path is not a directory: ${parentDir}` });
} }
}); });
@ -140,10 +161,10 @@ function validateTargetDirectories(config) {
/** /**
* Main validation function * Main validation function
* @param {string} configPath - Path to configuration file * @param configPath - Path to configuration file
* @returns {boolean} True if validation passes * @returns True if validation passes
*/ */
function validateConfiguration(configPath) { function validateConfiguration(configPath: string): boolean {
console.log('🔍 Validating TimeSafari asset configuration...'); console.log('🔍 Validating TimeSafari asset configuration...');
console.log(`📁 Config file: ${configPath}`); console.log(`📁 Config file: ${configPath}`);
console.log(`📁 Project root: ${PROJECT_ROOT}`); console.log(`📁 Project root: ${PROJECT_ROOT}`);
@ -159,7 +180,7 @@ function validateConfiguration(configPath) {
console.log('✅ Schema file loaded successfully'); console.log('✅ Schema file loaded successfully');
// Perform validations // Perform validations
const schemaErrors = validateAgainstSchema(config, schema); const schemaErrors = validateAgainstSchema(config);
const fileErrors = validateSourceFiles(config); const fileErrors = validateSourceFiles(config);
const dirErrors = validateTargetDirectories(config); const dirErrors = validateTargetDirectories(config);
@ -179,13 +200,13 @@ function validateConfiguration(configPath) {
} else { } else {
console.error('❌ Validation failed with the following errors:'); console.error('❌ Validation failed with the following errors:');
allErrors.forEach((error, index) => { allErrors.forEach((error, index) => {
console.error(` ${index + 1}. ${error}`); console.error(` ${index + 1}. ${error.message}`);
}); });
return false; return false;
} }
} catch (error) { } catch (error) {
console.error('❌ Validation failed:', error.message); console.error('❌ Validation failed:', error instanceof Error ? error.message : String(error));
return false; return false;
} }
} }
@ -193,7 +214,7 @@ function validateConfiguration(configPath) {
/** /**
* Main execution function * Main execution function
*/ */
function main() { function main(): void {
const configPath = process.argv[2] || path.join(PROJECT_ROOT, 'capacitor-assets.config.json'); const configPath = process.argv[2] || path.join(PROJECT_ROOT, 'capacitor-assets.config.json');
if (!fs.existsSync(configPath)) { if (!fs.existsSync(configPath)) {
@ -201,7 +222,7 @@ function main() {
console.log(''); console.log('');
console.log('💡 Available options:'); console.log('💡 Available options:');
console.log(' - Use default: capacitor-assets.config.json'); console.log(' - Use default: capacitor-assets.config.json');
console.log(' - Specify path: node scripts/assets-validator.js path/to/config.json'); console.log(' - Specify path: tsx scripts/assets-validator.ts path/to/config.json');
console.log(' - Generate config: npm run assets:config'); console.log(' - Generate config: npm run assets:config');
process.exit(1); process.exit(1);
} }
@ -215,4 +236,4 @@ if (import.meta.url === `file://${process.argv[1]}`) {
main(); main();
} }
export { validateConfiguration, validateAgainstSchema, validateSourceFiles, validateTargetDirectories }; export { validateConfiguration, validateAgainstSchema, validateSourceFiles, validateTargetDirectories, AssetConfig };
Loading…
Cancel
Save