CRAFT_VERSION looks for package.json one directory above the module, and the built module is three directories deep, so the read always throws and the fallback always wins.
dist/ios/src/index.js:
var CRAFT_VERSION = (() => {
try {
return JSON.parse(readFileSync2(join2(dirname2(import.meta.dir), "package.json"), "utf8")).version ?? "0.0.0";
} catch {
return "0.0.0";
}
})();import.meta.dir is <pkg>/dist/ios/src, so dirname(...) is <pkg>/dist/ios. The package's own package.json is at <pkg>/package.json, two levels further up. Nothing is there, readFileSync throws, and the catch returns 0.0.0 every time.
Checked on craft-native 0.0.158 in stacksjs/hq.training:
node_modules/craft-native/dist/ios package.json: no
node_modules/craft-native/dist package.json: no
node_modules/craft-native package.json: yes ← 0.0.158and the Swift it generated:
return [base ?? "", "Craft/0.0.0", "\(name.isEmpty ? "App" : name)/\(version)"]What it costs. The User-Agent is what a server sees, so no Craft app can be told apart by Craft version in logs, analytics, or a server-side workaround for a version-specific bug — they all look like 0.0.0. It is baked into the generated Swift at build time, so an app already shipped keeps saying it.
Fix. Walk up to the package root rather than guessing a depth. join(dirname(dirname(dirname(import.meta.dir))), 'package.json') works today but breaks again if the build layout moves; resolving it once at build time, or searching upward for the nearest package.json named craft-native, survives a move.
Worth a test asserting CRAFT_VERSION !== '0.0.0', since the silent catch is what let this sit: nothing fails, the string is just wrong.