create-octane-xplat 0.1.0 → 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.
Files changed (84) hide show
  1. package/README.md +25 -0
  2. package/index.mjs +20 -17
  3. package/package.json +2 -2
  4. package/template/.gitattributes +1 -0
  5. package/template/.oxlintrc.json +25 -0
  6. package/template/App_Resources/Android/app.gradle +25 -0
  7. package/template/App_Resources/Android/before-plugins.gradle +15 -0
  8. package/template/App_Resources/Android/src/main/AndroidManifest.xml +43 -0
  9. package/template/App_Resources/Android/src/main/res/drawable/ic_launcher_foreground.xml +15 -0
  10. package/template/App_Resources/Android/src/main/res/drawable-hdpi/background.png +0 -0
  11. package/template/App_Resources/Android/src/main/res/drawable-hdpi/logo.png +0 -0
  12. package/template/App_Resources/Android/src/main/res/drawable-ldpi/background.png +0 -0
  13. package/template/App_Resources/Android/src/main/res/drawable-ldpi/logo.png +0 -0
  14. package/template/App_Resources/Android/src/main/res/drawable-mdpi/background.png +0 -0
  15. package/template/App_Resources/Android/src/main/res/drawable-mdpi/logo.png +0 -0
  16. package/template/App_Resources/Android/src/main/res/drawable-nodpi/splash_screen.xml +8 -0
  17. package/template/App_Resources/Android/src/main/res/drawable-xhdpi/background.png +0 -0
  18. package/template/App_Resources/Android/src/main/res/drawable-xhdpi/logo.png +0 -0
  19. package/template/App_Resources/Android/src/main/res/drawable-xxhdpi/background.png +0 -0
  20. package/template/App_Resources/Android/src/main/res/drawable-xxhdpi/logo.png +0 -0
  21. package/template/App_Resources/Android/src/main/res/drawable-xxxhdpi/background.png +0 -0
  22. package/template/App_Resources/Android/src/main/res/drawable-xxxhdpi/logo.png +0 -0
  23. package/template/App_Resources/Android/src/main/res/mipmap-anydpi-v26/ic_launcher.xml +5 -0
  24. package/template/App_Resources/Android/src/main/res/mipmap-hdpi/ic_launcher.png +0 -0
  25. package/template/App_Resources/Android/src/main/res/mipmap-mdpi/ic_launcher.png +0 -0
  26. package/template/App_Resources/Android/src/main/res/mipmap-xhdpi/ic_launcher.png +0 -0
  27. package/template/App_Resources/Android/src/main/res/mipmap-xxhdpi/ic_launcher.png +0 -0
  28. package/template/App_Resources/Android/src/main/res/mipmap-xxxhdpi/ic_launcher.png +0 -0
  29. package/template/App_Resources/Android/src/main/res/values/colors.xml +14 -0
  30. package/template/App_Resources/Android/src/main/res/values/ic_launcher_background.xml +4 -0
  31. package/template/App_Resources/Android/src/main/res/values/styles.xml +42 -0
  32. package/template/App_Resources/Android/src/main/res/values-v21/colors.xml +5 -0
  33. package/template/App_Resources/Android/src/main/res/values-v21/styles.xml +34 -0
  34. package/template/App_Resources/Android/src/main/res/values-v29/styles.xml +18 -0
  35. package/template/App_Resources/Android/src/main/res/xml/network_security_config.xml +14 -0
  36. package/template/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/Contents.json +122 -0
  37. package/template/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-1024.png +0 -0
  38. package/template/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-20.png +0 -0
  39. package/template/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-20@2x.png +0 -0
  40. package/template/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-20@3x.png +0 -0
  41. package/template/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-29.png +0 -0
  42. package/template/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-29@2x.png +0 -0
  43. package/template/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-29@3x.png +0 -0
  44. package/template/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-40.png +0 -0
  45. package/template/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-40@2x.png +0 -0
  46. package/template/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-40@3x.png +0 -0
  47. package/template/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-60@2x.png +0 -0
  48. package/template/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-60@3x.png +0 -0
  49. package/template/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-76.png +0 -0
  50. package/template/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-76@2x.png +0 -0
  51. package/template/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-83.5@2x.png +0 -0
  52. package/template/App_Resources/iOS/Assets.xcassets/Contents.json +6 -0
  53. package/template/App_Resources/iOS/Assets.xcassets/LaunchScreen.AspectFill.imageset/Contents.json +23 -0
  54. package/template/App_Resources/iOS/Assets.xcassets/LaunchScreen.AspectFill.imageset/LaunchScreen-AspectFill.png +0 -0
  55. package/template/App_Resources/iOS/Assets.xcassets/LaunchScreen.AspectFill.imageset/LaunchScreen-AspectFill@2x.png +0 -0
  56. package/template/App_Resources/iOS/Assets.xcassets/LaunchScreen.AspectFill.imageset/LaunchScreen-AspectFill@3x.png +0 -0
  57. package/template/App_Resources/iOS/Assets.xcassets/LaunchScreen.Center.imageset/Contents.json +23 -0
  58. package/template/App_Resources/iOS/Assets.xcassets/LaunchScreen.Center.imageset/LaunchScreen-Center.png +0 -0
  59. package/template/App_Resources/iOS/Assets.xcassets/LaunchScreen.Center.imageset/LaunchScreen-Center@2x.png +0 -0
  60. package/template/App_Resources/iOS/Assets.xcassets/LaunchScreen.Center.imageset/LaunchScreen-Center@3x.png +0 -0
  61. package/template/App_Resources/iOS/Assets.xcassets/octane.imageset/Contents.json +8 -0
  62. package/template/App_Resources/iOS/Assets.xcassets/octane.imageset/octane.png +0 -0
  63. package/template/App_Resources/iOS/Assets.xcassets/octane.imageset/octane@2x.png +0 -0
  64. package/template/App_Resources/iOS/Assets.xcassets/octane.imageset/octane@3x.png +0 -0
  65. package/template/App_Resources/iOS/Info.plist +52 -0
  66. package/template/App_Resources/iOS/LaunchScreen.storyboard +53 -0
  67. package/template/App_Resources/iOS/build.xcconfig +7 -0
  68. package/template/App_Resources/iOS/src/OctaneShaders.metal +128 -0
  69. package/template/gitignore +2 -0
  70. package/template/index.html +9 -9
  71. package/template/nativescript.config.ts +13 -0
  72. package/template/package.json +40 -18
  73. package/template/patches/@nativescript-community__vite-octane@0.2.1.patch +41 -0
  74. package/template/patches/@nativescript__vite@8.0.11.patch +294 -0
  75. package/template/pnpm-workspace.yaml +25 -3
  76. package/template/references.d.ts +1 -0
  77. package/template/src/App.tsrx +5 -3
  78. package/template/src/index.native.ts +33 -0
  79. package/template/src/style.css +21 -0
  80. package/template/tsconfig.json +4 -2
  81. package/template/tsconfig.native.json +18 -0
  82. package/template/vite.config.native.mts +8 -0
  83. package/template/vite.config.ts +14 -7
  84. /package/template/src/{main.tsrx → main.web.tsrx} +0 -0
@@ -0,0 +1,294 @@
1
+ diff --git a/hmr/server/core-bundle.js b/hmr/server/core-bundle.js
2
+ index 993097b9d861a2a7c477f4e664213efec3eb6293..7a9b95180cbd47e1d9c71180bc4582a450333a7c 100644
3
+ --- a/hmr/server/core-bundle.js
4
+ +++ b/hmr/server/core-bundle.js
5
+ @@ -607,17 +607,32 @@ export function buildCoreMainShimCode() {
6
+ * class/function values. `exportNames` comes from the same disk-based export
7
+ * discovery the per-module bridge relies on (collectStaticExportNamesFromFile).
8
+ */
9
+ -const RESERVED_EXPORT_NAMES = new Set(['default', 'break', 'case', 'catch', 'class', 'const', 'continue', 'debugger', 'delete', 'do', 'else', 'enum', 'export', 'extends', 'false', 'finally', 'for', 'function', 'if', 'import', 'in', 'instanceof', 'new', 'null', 'return', 'super', 'switch', 'this', 'throw', 'true', 'try', 'typeof', 'var', 'void', 'while', 'with', 'let', 'static', 'yield', 'await', 'eval', 'arguments', '__ns_core_sub_ns__']);
10
+ +// `default` is emitted separately (below); `__ns_core_sub_ns__` is this shim's
11
+ +// own local — a module exporting that name can't collide with it.
12
+ +const NON_DECLARABLE_EXPORT_NAMES = new Set(['default', '__ns_core_sub_ns__']);
13
+ +// Reserved words can't be declaration bindings (`export const enum` is a
14
+ +// syntax error), but they ARE legal export names — the specifier position in
15
+ +// `export { x as name }` accepts any IdentifierName. Emit those through a
16
+ +// temp + alias so a reserved-word export (zod's `enum`/`catch`/… shapes this
17
+ +// same code path) still reaches the bridged namespace.
18
+ +const RESERVED_EXPORT_NAMES = new Set(['break', 'case', 'catch', 'class', 'const', 'continue', 'debugger', 'delete', 'do', 'else', 'enum', 'export', 'extends', 'false', 'finally', 'for', 'function', 'if', 'import', 'in', 'instanceof', 'new', 'null', 'return', 'super', 'switch', 'this', 'throw', 'true', 'try', 'typeof', 'var', 'void', 'while', 'with', 'let', 'static', 'yield', 'await', 'eval', 'arguments']);
19
+ export function buildCoreSubShimCode(sub, exportNames, hasOwnDefaultExport = false) {
20
+ const canonical = normalizeCoreSub(sub);
21
+ const registryKey = `@nativescript/core/${canonical}`;
22
+ - const names = Array.from(new Set(exportNames)).filter((n) => /^[A-Za-z_$][\w$]*$/.test(n) && !RESERVED_EXPORT_NAMES.has(n));
23
+ + const names = Array.from(new Set(exportNames)).filter((n) => /^[A-Za-z_$][\w$]*$/.test(n) && !NON_DECLARABLE_EXPORT_NAMES.has(n));
24
+ const lines = [];
25
+ lines.push(`/* @nativescript/core bridge (bundle mode) — sub: ${canonical} */`);
26
+ lines.push(`import ${JSON.stringify(CORE_BUNDLE_PATH)};`);
27
+ lines.push(`const __ns_core_sub_ns__ = (globalThis.__NS_CORE_MODULES__ || {})[${JSON.stringify(registryKey)}];`);
28
+ lines.push(`if (!__ns_core_sub_ns__) { throw new Error(${JSON.stringify(`[ns-core] core bundle registry missing '${registryKey}' — restart the dev server or set NS_CORE_PER_MODULE=1`)}); }`);
29
+ + let aliasIdx = 0;
30
+ for (const name of names) {
31
+ + if (RESERVED_EXPORT_NAMES.has(name)) {
32
+ + const alias = `__ns_core_alias_${aliasIdx++}`;
33
+ + lines.push(`const ${alias} = __ns_core_sub_ns__[${JSON.stringify(name)}];`);
34
+ + lines.push(`export { ${alias} as ${name} };`);
35
+ + continue;
36
+ + }
37
+ lines.push(`export const ${name} = __ns_core_sub_ns__.${name};`);
38
+ }
39
+ // Deep-subpath contract (see isDeepCoreSubpath / per-module bridge): a sub
40
+ diff --git a/hmr/server/deps-bundle.js b/hmr/server/deps-bundle.js
41
+ index 50e5abddddf80b35044d45eb108a64dd175bf6e7..799c7257a8a730515633f19f6221ad26ff8678eb 100644
42
+ --- a/hmr/server/deps-bundle.js
43
+ +++ b/hmr/server/deps-bundle.js
44
+ @@ -219,8 +219,37 @@ function isPackageRootSpec(spec) {
45
+ return false;
46
+ return pkgSub.split('/').length === (pkgSub.startsWith('@') ? 2 : 1);
47
+ }
48
+ -function resolveSpecToFile(spec, projectRoot, workspaceRoot, platform) {
49
+ +/**
50
+ + * `/node_modules/<pkg>/<sub...>` → the exports-mapped file on disk
51
+ + * (`rouzer/http` → `rouzer/dist/http.js`). Without this, non-root specs fall
52
+ + * back to `resolveId` and land on Vite's dep-optimizer artifacts under
53
+ + * `.vite/deps/` — flat bundles whose generated shared chunks
54
+ + * (`foo-<hash>.js`) this pipeline cannot serve to the device.
55
+ + */
56
+ +function resolvePackageSubpathFromSpecRoots(spec, roots, platform) {
57
+ + const pkgSub = spec.replace(/^\/node_modules\//, '');
58
+ + const parts = pkgSub.split('/');
59
+ + const nameSegs = parts[0].startsWith('@') ? 2 : 1;
60
+ + if (parts.length <= nameSegs)
61
+ + return null;
62
+ + const subpath = parts.slice(nameSegs).join('/');
63
+ + for (const root of roots) {
64
+ + const pkgDir = path.join(root, 'node_modules', ...parts.slice(0, nameSegs));
65
+ + if (!existsSync(path.join(pkgDir, 'package.json')))
66
+ + continue;
67
+ + const resolved = resolvePackageSubpathEntry(pkgDir, subpath, platform);
68
+ + if (resolved)
69
+ + return resolved;
70
+ + }
71
+ + return null;
72
+ +}
73
+ +export function resolveSpecToFile(spec, projectRoot, workspaceRoot, platform) {
74
+ const roots = [projectRoot, ...(workspaceRoot && path.resolve(workspaceRoot) !== path.resolve(projectRoot) ? [workspaceRoot] : [])];
75
+ + // `.vite/` is Vite's dep-optimizer store — generated artifacts with
76
+ + // content-hashed names, not package files. Resolving them here would mint
77
+ + // deps-bundle entries/shims for files whose names churn per optimization.
78
+ + if (spec.includes('/.vite/'))
79
+ + return null;
80
+ // Package roots honor the declared entry (exports/module/main) FIRST —
81
+ // matching esbuild's resolution of bare imports inside the bundle. The
82
+ // index fan-out would otherwise pick stray root files like a shipped
83
+ @@ -236,6 +265,12 @@ function resolveSpecToFile(spec, projectRoot, workspaceRoot, platform) {
84
+ if (abs)
85
+ return abs;
86
+ }
87
+ + // Non-root specs (`rouzer/http`, `qubu/sqlite`): consult the package's
88
+ + // exports map so the real dist file wins — the alternative downstream is
89
+ + // the dep optimizer's `.vite/deps` artifact, whose sibling chunks 404.
90
+ + const subpathEntry = resolvePackageSubpathFromSpecRoots(spec, roots, platform);
91
+ + if (subpathEntry)
92
+ + return subpathEntry;
93
+ return isPackageRootSpec(spec) ? null : resolvePackageRootEntry(spec, roots, platform);
94
+ }
95
+ // Never bundled: `@nativescript/core` belongs to the /ns/core bridge realm,
96
+ @@ -869,7 +904,15 @@ export function collectDepsModuleExportInfo(absPath, platform, seen = new Set())
97
+ names.delete('default');
98
+ return { names: Array.from(names).sort(), hasDefault: hasExistingDefaultExport(code) };
99
+ }
100
+ -const RESERVED_EXPORT_NAMES = new Set(['default', 'break', 'case', 'catch', 'class', 'const', 'continue', 'debugger', 'delete', 'do', 'else', 'enum', 'export', 'extends', 'false', 'finally', 'for', 'function', 'if', 'import', 'in', 'instanceof', 'new', 'null', 'return', 'super', 'switch', 'this', 'throw', 'true', 'try', 'typeof', 'var', 'void', 'while', 'with', 'let', 'static', 'yield', 'await', 'eval', 'arguments', '__ns_dep_ns__']);
101
+ +// `default` is emitted separately (below); `__ns_dep_ns__` is this shim's own
102
+ +// local — a package exporting that name can't collide with it.
103
+ +const NON_DECLARABLE_EXPORT_NAMES = new Set(['default', '__ns_dep_ns__']);
104
+ +// Reserved words can't be declaration bindings (`export const enum` is a
105
+ +// syntax error), but they ARE legal export names — the specifier position in
106
+ +// `export { x as name }` accepts any IdentifierName. Emit those through a
107
+ +// temp + alias so packages like zod (`enum`, `catch`, `function`, …) keep
108
+ +// their full surface on the bridged namespace.
109
+ +const RESERVED_EXPORT_NAMES = new Set(['break', 'case', 'catch', 'class', 'const', 'continue', 'debugger', 'delete', 'do', 'else', 'enum', 'export', 'extends', 'false', 'finally', 'for', 'function', 'if', 'import', 'in', 'instanceof', 'new', 'null', 'return', 'super', 'switch', 'this', 'throw', 'true', 'try', 'typeof', 'var', 'void', 'while', 'with', 'let', 'static', 'yield', 'await', 'eval', 'arguments']);
110
+ /**
111
+ * Shim served at `/ns/m/node_modules/...` for a bundled module. The static
112
+ * bundle import guarantees the registry is populated before the body reads it
113
+ @@ -880,13 +923,20 @@ const RESERVED_EXPORT_NAMES = new Set(['default', 'break', 'case', 'catch', 'cla
114
+ export function buildDepsShimCode(key, exportNames, hasOwnDefaultExport) {
115
+ // JS_IDENTIFIER_RE (not ASCII \w): Angular exports `ɵɵdefineInjectable`-style
116
+ // names — dropping them here breaks every `import * as i0` consumer.
117
+ - const names = Array.from(new Set(exportNames)).filter((n) => JS_IDENTIFIER_RE.test(n) && !RESERVED_EXPORT_NAMES.has(n));
118
+ + const names = Array.from(new Set(exportNames)).filter((n) => JS_IDENTIFIER_RE.test(n) && !NON_DECLARABLE_EXPORT_NAMES.has(n));
119
+ const lines = [];
120
+ lines.push(`/* ns-deps bridge (bundle mode) — ${key} */`);
121
+ lines.push(`import ${JSON.stringify(DEPS_BUNDLE_PATH)};`);
122
+ lines.push(`const __ns_dep_ns__ = (globalThis.__NS_DEPS_MODULES__ || {})[${JSON.stringify(key)}];`);
123
+ lines.push(`if (!__ns_dep_ns__) { throw new Error(${JSON.stringify(`[ns-deps] deps bundle registry missing '${key}' — restart the dev server or set NS_DEPS_PER_MODULE=1`)}); }`);
124
+ + let aliasIdx = 0;
125
+ for (const name of names) {
126
+ + if (RESERVED_EXPORT_NAMES.has(name)) {
127
+ + const alias = `__ns_dep_alias_${aliasIdx++}`;
128
+ + lines.push(`const ${alias} = __ns_dep_ns__[${JSON.stringify(name)}];`);
129
+ + lines.push(`export { ${alias} as ${name} };`);
130
+ + continue;
131
+ + }
132
+ lines.push(`export const ${name} = __ns_dep_ns__.${name};`);
133
+ }
134
+ if (hasOwnDefaultExport) {
135
+ diff --git a/hmr/server/device-transform-helpers.js b/hmr/server/device-transform-helpers.js
136
+ index 9364ce9942b84fffd0e3a6eb6700d8253fa66e7e..93ee2bf2570303277d7b4350018b00fbbb31e2a4 100644
137
+ --- a/hmr/server/device-transform-helpers.js
138
+ +++ b/hmr/server/device-transform-helpers.js
139
+ @@ -81,12 +81,18 @@ function bareSpecifierFromFlatDepPath(depPath) {
140
+ }
141
+ catch { }
142
+ }
143
+ - const bareSpecifier = resolved || candidates[0];
144
+ + // Unverified decode → NOT a package: `depPath` is a generated optimizer
145
+ + // artifact (a hash-named shared chunk like `http-Dm_BQhab.js`, whose
146
+ + // base64url '_' decodes to a bogus '/'). Returning null lets the caller
147
+ + // keep the real `.vite/deps` URL instead of emitting a garbage specifier.
148
+ + if (!resolved) {
149
+ + return null;
150
+ + }
151
+ if (!warnedVendorMisses.has(depPath)) {
152
+ warnedVendorMisses.add(depPath);
153
+ - console.warn(`[ns-vite] vendor-manifest miss: prebundled dep '${depPath}' is not in the vendor bundle — serving '${bareSpecifier}' over HTTP via /ns/m/node_modules/${resolved ? '' : ' (could not verify the package on disk; the specifier is a best-effort decode of the flattened id)'}.`);
154
+ + console.warn(`[ns-vite] vendor-manifest miss: prebundled dep '${depPath}' is not in the vendor bundle — serving '${resolved}' over HTTP via /ns/m/node_modules/${resolved}.`);
155
+ }
156
+ - return bareSpecifier;
157
+ + return resolved;
158
+ }
159
+ function rewriteVitePrebundleImportsForDevice(code, preserveVendorImports) {
160
+ const imports = collectTopLevelImportRecords(code);
161
+ @@ -111,6 +117,15 @@ function rewriteVitePrebundleImportsForDevice(code, preserveVendorImports) {
162
+ if (bareSpecifier) {
163
+ replacement = imp.text.replace(source, bareSpecifier);
164
+ }
165
+ + else {
166
+ + // Generated optimizer chunk (hash-named sibling of an
167
+ + // optimized entry) — not a package and not decodable to one.
168
+ + // Route the real artifact URL back through the bridge: the
169
+ + // /ns/m route serves it from the dep-optimizer store (the
170
+ + // `?v` stamp rides along as forcedVer) or emits an
171
+ + // actionable optimizeDeps.exclude error.
172
+ + replacement = imp.text.replace(source, `/ns/m/node_modules/.vite/deps/${depPath}`);
173
+ + }
174
+ }
175
+ edits.push({
176
+ start: imp.start,
177
+ diff --git a/hmr/server/websocket-ns-m-request.js b/hmr/server/websocket-ns-m-request.js
178
+ index b54aefcc839e95906d76d02e522c052a706ca76f..d49159de94ba05dcfc372aa7a3c513e0f5c0a0d3 100644
179
+ --- a/hmr/server/websocket-ns-m-request.js
180
+ +++ b/hmr/server/websocket-ns-m-request.js
181
+ @@ -1,6 +1,9 @@
182
+ import * as path from 'path';
183
+ +import { realpathSync } from 'fs';
184
+ import { filterExistingNodeModulesTransformCandidates, getBlockedDeviceNodeModulesReason, resolveCandidateFilePath, tryReadRawExplicitJavaScriptModule } from './websocket-module-specifiers.js';
185
+ import { collapseLegacyNsMTags } from './websocket-ns-m-paths.js';
186
+ +import { resolveSpecToFile } from './deps-bundle.js';
187
+ +import { resolvePlatform } from '../../helpers/cli-flags.js';
188
+ function normalizeAbsoluteFilesystemSpec(spec, serverRoot) {
189
+ const toPosix = (value) => value.replace(/\\/g, '/');
190
+ const rootPosix = toPosix(serverRoot);
191
+ @@ -93,6 +96,29 @@ export function createNsMRequestContext(requestUrl, serverRoot, appVirtualWithSl
192
+ return { kind: 'next' };
193
+ }
194
+ }
195
+ +/**
196
+ + * Canonical request URL for a resolved node_modules file. Project-root files
197
+ + * keep the `/node_modules/...` form so module identity matches the /ns/m
198
+ + * spec space; files outside the project root (hoisted/workspace) use the
199
+ + * `/@fs` form Vite can load directly. Symlinked workspace-source packages
200
+ + * resolve to their realpath so the TS transform applies — same policy as
201
+ + * filterExistingNodeModulesTransformCandidates.
202
+ + */
203
+ +function nodeModulesFileToRequestUrl(absPath, serverRoot) {
204
+ + try {
205
+ + const real = realpathSync(absPath);
206
+ + if (real !== absPath && !real.split(path.sep).includes('node_modules')) {
207
+ + return `/@fs${real.replace(/\\/g, '/')}`;
208
+ + }
209
+ + }
210
+ + catch { }
211
+ + const rootPosix = path.resolve(serverRoot).replace(/\\/g, '/').replace(/\/+$/, '');
212
+ + const absPosix = absPath.replace(/\\/g, '/');
213
+ + if (absPosix.startsWith(`${rootPosix}/`)) {
214
+ + return absPosix.slice(rootPosix.length);
215
+ + }
216
+ + return `/@fs${absPosix}`;
217
+ +}
218
+ async function tryTransformRequest(transformRequest, url, timeoutMs) {
219
+ try {
220
+ return await transformRequest(url, timeoutMs);
221
+ @@ -146,6 +172,38 @@ export async function resolveNsMTransformedModule(options) {
222
+ return { transformed, resolvedCandidate };
223
+ }
224
+ }
225
+ + // Optimizer-artifact requests (`/node_modules/.vite/deps/<file>`): flat
226
+ + // entries and hash-named shared chunks live in the dep-optimizer's store
227
+ + // (often memory-only), keyed by the `?v=<browserHash>` stamp Vite emits —
228
+ + // captured inbound as `forcedVer`. Replay it so Vite resolves the artifact
229
+ + // as-is; a miss falls through to the route's transform-miss branch, which
230
+ + // names optimizeDeps.exclude as the fix.
231
+ + if (spec.includes('/.vite/deps/')) {
232
+ + const depUrl = context.forcedVer ? `${spec}?v=${context.forcedVer}` : spec;
233
+ + transformed = await tryTransformRequest(transformRequest, depUrl, timeoutMs);
234
+ + if (transformed?.code) {
235
+ + return { transformed, resolvedCandidate: depUrl };
236
+ + }
237
+ + return { transformed: null, resolvedCandidate };
238
+ + }
239
+ + // Bare or subpath node_modules specs (`rouzer/http`, `qubu/sqlite`) that
240
+ + // survive the on-disk candidate fan-out would otherwise hit `resolveId`
241
+ + // and land on Vite's dep-optimizer output: `/node_modules/.vite/deps/
242
+ + // <flat>.js` bundles whose generated sibling chunks (`foo-<hash>.js`)
243
+ + // this pipeline cannot serve — and whose flattened ids decode to bogus
244
+ + // bare specifiers downstream (`http-Dm/BQhab`). Prefer the real package
245
+ + // file (exports-map aware) so the device gets the same per-module source
246
+ + // an `optimizeDeps.exclude` entry produces.
247
+ + if (spec.includes('/node_modules/')) {
248
+ + const realFile = resolveSpecToFile(spec.slice(spec.lastIndexOf('/node_modules/')), serverRoot, workspaceRoot, resolvePlatform() || 'ios');
249
+ + const realUrl = realFile ? nodeModulesFileToRequestUrl(realFile, serverRoot) : null;
250
+ + if (realUrl) {
251
+ + transformed = await tryTransformRequest(transformRequest, realUrl, timeoutMs);
252
+ + if (transformed?.code) {
253
+ + return { transformed, resolvedCandidate: realUrl };
254
+ + }
255
+ + }
256
+ + }
257
+ const resolvedId = await resolveId(spec);
258
+ if (resolvedId) {
259
+ transformed = await tryTransformRequest(transformRequest, resolvedId, timeoutMs);
260
+ diff --git a/hmr/server/websocket-ns-m.js b/hmr/server/websocket-ns-m.js
261
+ index 8eeb51ab59497e7f775da3117c4207e05f1814c7..346b676fbb67ce6bebe81cc379fdbe0d0aeac876 100644
262
+ --- a/hmr/server/websocket-ns-m.js
263
+ +++ b/hmr/server/websocket-ns-m.js
264
+ @@ -265,6 +265,30 @@ export function registerNsModuleServerRoute(server, options) {
265
+ catch { }
266
+ // If transformRequest failed, handle bare-module vendor shims for 'vue' and 'pinia'
267
+ if (!transformed?.code) {
268
+ + // Dep-optimizer artifacts (`.vite/deps` entries + generated
269
+ + // shared chunks) aren't package files — when one reaches the
270
+ + // bridge unserved, the fix is keeping that dep out of
271
+ + // optimizeDeps. Name the optimized set so the on-device
272
+ + // error is actionable instead of a generic transform miss.
273
+ + if (spec.includes('/.vite/deps/')) {
274
+ + const optimizedIds = [];
275
+ + try {
276
+ + for (const env of Object.values(server.environments || {})) {
277
+ + const meta = env?.depsOptimizer?.metadata;
278
+ + for (const record of [meta?.optimized, meta?.discovered]) {
279
+ + if (record)
280
+ + optimizedIds.push(...Object.keys(record));
281
+ + }
282
+ + }
283
+ + }
284
+ + catch { }
285
+ + const hint = optimizedIds.length
286
+ + ? ` Currently optimized: ${Array.from(new Set(optimizedIds)).join(', ')} — add the package that pulls in this chunk to optimizeDeps.exclude in the native vite config (or restart with NS_DEPS_PER_MODULE=1).`
287
+ + : ' Add the importing package to optimizeDeps.exclude in the native vite config.';
288
+ + res.statusCode = 404;
289
+ + res.end(`throw new Error(${JSON.stringify(`[ns/m] Vite optimizeDeps artifact '${spec}' is not servable on device — its shared chunks are generated files outside the package tree.${hint}`)});\nexport {};\n`);
290
+ + return;
291
+ + }
292
+ const bare = spec.startsWith('/') ? spec.slice(1) : spec;
293
+ const isBare = bare && !bare.includes('/') && !/\.(ts|tsx|js|jsx|mjs|mts|cts|vue)$/i.test(bare);
294
+ if (isBare && (bare === 'vue' || bare === 'nativescript-vue' || bare === 'pinia')) {
@@ -1,7 +1,29 @@
1
1
  # pnpm 11 settings live here, not in package.json.
2
- # esbuild needs its install script to place the platform binary.
3
- # @nativescript/core lands via optional peers — its scripts are native
4
- # toolchain prep the ns CLI does itself; skipping keeps installs clean.
2
+ #
3
+ # nodeLinker hoisted: Node ESM resolves package imports relative to the
4
+ # symlink path (node_modules/octane/...), not the realpath — so octane's
5
+ # transitive deps (@tsrx/core etc.) must sit at top-level node_modules.
6
+ # NativeScript's bundler wants a flat tree for the same reason.
7
+ nodeLinker: hoisted
8
+ #
9
+ # esbuild + @parcel/watcher need install scripts to place binaries;
10
+ # nativescript's postinstall sets up the local CLI. @nativescript/core's
11
+ # scripts are native toolchain prep the ns CLI does itself.
5
12
  allowBuilds:
6
13
  esbuild: true
14
+ '@parcel/watcher': true
15
+ nativescript: true
7
16
  '@nativescript/core': false
17
+ #
18
+ # vite-octane's octaneServerStrategy early-returns for `.tsrx` files (its
19
+ # SCRIPT_FILE_RE only covers [jt]sx?), so component edits never emit the
20
+ # deferred ns:hmr-delta broadcast — on-device HMR looks dead. The patch also
21
+ # counts open /ns-hmr sockets so `recipients=N` in update logs is real.
22
+ # Drop once a release carries the fix.
23
+ patchedDependencies:
24
+ '@nativescript-community/vite-octane@0.2.1': patches/@nativescript-community__vite-octane@0.2.1.patch
25
+ # Dep/core shims dropped reserved-word named exports (zod's `enum`, `catch`,
26
+ # `function`, …) because `export const enum` is a syntax error — emit them
27
+ # via temp+alias so bridged namespaces keep their full surface. Without it,
28
+ # any app importing such a dep fails module eval on boot/graph-reload.
29
+ '@nativescript/vite@8.0.11': patches/@nativescript__vite@8.0.11.patch
@@ -0,0 +1 @@
1
+ /// <reference types="@nativescript/types" />
@@ -13,10 +13,12 @@ const FEATURES = [
13
13
  * (.web.tsrx / .native.tsrx), never inside a component. */
14
14
  export function App() @{
15
15
  const [count, setCount] = useState(0);
16
- const [dark, setDark] = useState(false);
16
+ // null = follow the system; the toggle overrides it for this session.
17
+ const [override, setOverride] = useState<boolean | null>(null);
17
18
  const scheme = useColorScheme();
19
+ const dark = override ?? scheme === 'dark';
18
20
 
19
- <View className={['app', dark ? 'dark' : '']}>
21
+ <View className={['app', dark ? 'dark' : 'light']}>
20
22
  <Text className="title">octane-xplat</Text>
21
23
  <Text className="muted">one codebase — web · iOS · android</Text>
22
24
 
@@ -28,7 +30,7 @@ export function App() @{
28
30
 
29
31
  <List items={FEATURES} renderItem={(f) => <Text className="chip">{f}</Text>} />
30
32
 
31
- <Pressable className="link" onPress={() => setDark(!dark)}>
33
+ <Pressable className="link" onPress={() => setOverride(!dark)}>
32
34
  <Text>{dark ? 'light' : 'dark'} mode (system: {scheme})</Text>
33
35
  </Pressable>
34
36
  </View>
@@ -0,0 +1,33 @@
1
+ // Native entry — the iOS/Android twin of main.web.tsrx. Same App.tsrx,
2
+ // different shell: a Frame hosting a Page that renders the universal root.
3
+ import { Application, Frame, Page } from '@nativescript/core'
4
+ import { renderNativeScriptApp } from '@nativescript-community/octane'
5
+ import { App } from './App.tsrx'
6
+ import '@octane-xplat/ui/theme/tokens.css'
7
+ import './style.css'
8
+
9
+ const entry = {
10
+ create: () => {
11
+ // Root is a Frame so pushRoute() can push Pages later — a Frame
12
+ // window root doubles as the 'root' stack with no registration;
13
+ // named stacks come from TabSpec.stack or registerStack. Route
14
+ // pushes also need registerScreens({name: Component}) at boot.
15
+ const frame = new Frame()
16
+ const page = new Page()
17
+ page.actionBarHidden = true
18
+ renderNativeScriptApp(page, App)
19
+ frame.navigate({ create: () => page })
20
+ return frame
21
+ },
22
+ }
23
+
24
+ // Under the vite dev session the placeholder bootstrap has already run
25
+ // Application.run(), and a module-graph reload re-evaluates this entry.
26
+ // Android's run() throws "Application is already started" on re-entry
27
+ // (iOS core handles the placeholder handoff internally); resetRootView is
28
+ // the documented way to swap the root of a running app.
29
+ if (Application.started) {
30
+ Application.resetRootView(entry)
31
+ } else {
32
+ Application.run(entry)
33
+ }
@@ -6,6 +6,25 @@
6
6
  body {
7
7
  margin: 0;
8
8
  font-family: system-ui, sans-serif;
9
+ background-color: var(--color-surface);
10
+ }
11
+
12
+ /* System-dark before JS mounts — no white flash. Same values tokens.css
13
+ * sets under .dark; .app.light below is the manual override back. */
14
+ @media (prefers-color-scheme: dark) {
15
+ :root {
16
+ --color-surface: #0f172a;
17
+ --color-text: #f8fafc;
18
+ }
19
+ body {
20
+ background-color: var(--color-surface);
21
+ }
22
+ }
23
+
24
+ /* Manual light override when the system is dark (JS adds .light). */
25
+ .app.light {
26
+ --color-surface: #ffffff;
27
+ --color-text: #111827;
9
28
  }
10
29
 
11
30
  .app {
@@ -49,6 +68,7 @@ body {
49
68
  color: var(--color-on-primary);
50
69
  padding: 10px 20px;
51
70
  border-radius: 10px;
71
+ corner-shape: squircle;
52
72
  font-size: 20px;
53
73
  }
54
74
 
@@ -57,6 +77,7 @@ body {
57
77
  color: var(--color-on-primary);
58
78
  padding: 6px 14px;
59
79
  border-radius: 14px;
80
+ corner-shape: squircle;
60
81
  font-size: 14px;
61
82
  }
62
83
 
@@ -10,7 +10,9 @@
10
10
  "customConditions": ["web"],
11
11
  "lib": ["ESNext", "DOM", "DOM.Iterable"],
12
12
  "types": ["vite/client"],
13
- "noEmit": true
13
+ "noEmit": true,
14
+ "skipLibCheck": true
14
15
  },
15
- "include": ["src/**/*"]
16
+ "include": ["src/**/*"],
17
+ "exclude": ["node_modules", "src/index.native.ts"]
16
18
  }
@@ -0,0 +1,18 @@
1
+ {
2
+ "compilerOptions": {
3
+ "strict": true,
4
+ "target": "ESNext",
5
+ "module": "ESNext",
6
+ "moduleResolution": "bundler",
7
+ "jsx": "preserve",
8
+ "jsxImportSource": "@nativescript-community/octane",
9
+ "moduleSuffixes": [".ios", ".android", ".native", ""],
10
+ "customConditions": ["native"],
11
+ "lib": ["ESNext"],
12
+ "types": ["@nativescript/types"],
13
+ "noEmit": true,
14
+ "skipLibCheck": true
15
+ },
16
+ "include": ["src/**/*", "references.d.ts"],
17
+ "exclude": ["node_modules", "platforms", "**/*.web.*"]
18
+ }
@@ -0,0 +1,8 @@
1
+ import { defineConfig } from 'vite'
2
+ import { xplatNative } from '@octane-xplat/cli/vite'
3
+
4
+ // The shared preset owns the renderer rules, octane→universal/native alias,
5
+ // suffix extension chain, deps-bundle plugin exclusions, the HMR watchdog,
6
+ // and the px→dip CSS rewrite. App-specific additions go through the second
7
+ // argument's `extra` (or mergeConfig).
8
+ export default defineConfig(({ mode }) => xplatNative(mode))
@@ -1,5 +1,5 @@
1
- import { defineConfig } from 'vite';
2
- import { octane } from '@octanejs/vite-plugin';
1
+ import { defineConfig } from 'vite'
2
+ import { octane } from '@octanejs/vite-plugin'
3
3
 
4
4
  // The web build. For native (iOS/Android), the same source runs through
5
5
  // @nativescript-community/vite-octane with `conditions: ['native']` and the
@@ -12,10 +12,17 @@ export default defineConfig({
12
12
  conditions: ['web'],
13
13
  // Suffix chain (first match wins): .web → shared → fallback.
14
14
  extensions: [
15
- '.web.tsrx', '.tsrx',
16
- '.web.tsx', '.tsx',
17
- '.web.ts', '.mjs', '.mts', '.ts',
18
- '.jsx', '.js', '.json',
15
+ '.web.tsrx',
16
+ '.tsrx',
17
+ '.web.tsx',
18
+ '.tsx',
19
+ '.web.ts',
20
+ '.mjs',
21
+ '.mts',
22
+ '.ts',
23
+ '.jsx',
24
+ '.js',
25
+ '.json',
19
26
  ],
20
27
  },
21
- });
28
+ })
File without changes