create-octane-xplat 0.15.0 → 0.16.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 +57 -0
- package/compatibility.json +199 -0
- package/compatibility.ts +109 -0
- package/dist/compatibility.js +66 -0
- package/dist/index.js +132 -0
- package/dist/scaffold.js +439 -0
- package/{index.mjs → index.ts} +3 -4
- package/library-template/AGENTS.md +3 -1
- package/library-template/README.md +20 -0
- package/library-template/package.json +22 -19
- package/library-template/pnpm-workspace.yaml +150 -11
- package/library-template/src/Badge.tsx +1 -1
- package/library-template/src/Badge.web.tsx +1 -1
- package/library-template/tests/packed-consumer.mjs +2 -1
- package/library-template/tsconfig.envs.json +3 -0
- package/library-template/tsconfig.json +1 -1
- package/library-template/tsrx-typegen.json +1 -1
- package/package.json +23 -6
- package/{scaffold.mjs → scaffold.ts} +51 -40
- package/template/.agents/skills/xplat/SKILL.md +10 -2
- package/template/.agents/skills/xplat/references/components.md +2 -2
- package/template/.agents/skills/xplat/references/platform.md +2 -2
- package/template/AGENTS.md +47 -0
- package/template/App_Resources/Android/app.gradle +1 -1
- package/template/package.json +33 -31
- package/template/pnpm-workspace.yaml +64 -29
- package/template/src/app.web.test.tsx +20 -0
- package/template/src/main.web.tsrx +1 -0
- package/template/src/test-setup.web.ts +17 -0
- package/template/tsconfig.json +1 -1
- package/template/vite.config.ts +2 -2
- package/template/vitest.web.config.mts +13 -0
- package/library-template/.oxlintrc.json +0 -24
- package/template/.oxlintrc.json +0 -24
|
@@ -4,14 +4,153 @@ allowBuilds:
|
|
|
4
4
|
esbuild: true
|
|
5
5
|
'@parcel/watcher': true
|
|
6
6
|
'@nativescript/core': false
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
7
|
+
catalogs:
|
|
8
|
+
xplat:
|
|
9
|
+
"@nativescript-community/octane": 0.2.5
|
|
10
|
+
"@nativescript/core": 9.1.3
|
|
11
|
+
"@nativescript/types": 9.1.4
|
|
12
|
+
"@octane-xplat/cli": 0.16.0
|
|
13
|
+
"@octane-xplat/jsx": 0.16.0
|
|
14
|
+
"@octane-xplat/ui": 0.16.0
|
|
15
|
+
"@octanejs/vite-plugin": 0.1.61
|
|
16
|
+
"@tsrx/oxc": 0.20.0
|
|
17
|
+
"@tsrx/typescript-plugin": 0.4.11
|
|
18
|
+
octane: 0.11.0
|
|
19
|
+
tsrx-typegen: 0.2.2
|
|
20
|
+
typescript: 5.9.3
|
|
21
|
+
vite: 8.3.0
|
|
22
|
+
vitest: 4.1.11
|
|
23
|
+
overrides:
|
|
24
|
+
"@nativescript/vite>esbuild": 0.27.7
|
|
25
|
+
"@nativescript-community/vite-octane>esbuild": 0.27.7
|
|
26
|
+
vite>esbuild: 0.27.7
|
|
27
|
+
configDependencies:
|
|
28
|
+
"@octane-xplat/patches": 0.16.0
|
|
29
|
+
allowUnusedPatches: true
|
|
30
|
+
# Framework patches are supplied by the @octane-xplat/patches config dependency and
|
|
31
|
+
# applied via pnpm patchedDependencies. Managed upstream by @octane-xplat/cli (`xplat
|
|
32
|
+
# patches apply` refreshes, `xplat patches check` verifies); do not edit by hand.
|
|
33
|
+
patchedDependencies:
|
|
34
|
+
# Published 0.11.0 still ships none of the retained fixes: it adds the document guard
|
|
35
|
+
# context around installDefaultSignalOwner and reflows the compile.js hook-classification
|
|
36
|
+
# sites, but every patched feature remains absent upstream. Retains renderer-owned
|
|
37
|
+
# Children.toArray/map with explicit universal-native re-exports for older runtimes,
|
|
38
|
+
# universal signal-read tracking and local hooks, root event-error forwarding, the
|
|
39
|
+
# complete universal imperative-ref lifecycle, the Descriptor declaring-owner reader
|
|
40
|
+
# fallback, and the signal-owner/enclosingOwner chain so child reads reuse instance
|
|
41
|
+
# declarations. Verified against the registry 0.11.0 tarball; all hunks re-anchored to the
|
|
42
|
+
# rewritten compile.js and document-owner call sites. Disposes DOM provider descendants
|
|
43
|
+
# when children become null or undefined, releasing their refs and effects.
|
|
44
|
+
octane@0.11.0: node_modules/.pnpm-config/@octane-xplat/patches/patches/octane@0.11.0.patch
|
|
45
|
+
# Published 9.1.3 ships the #11446 nested-tab-frame lifecycle fix and iOS explicit-size
|
|
46
|
+
# flex alignment, so those hunks are removed. Retains the unshipped parent-padding
|
|
47
|
+
# measure-spec fix and Android explicit-size flex alignment, iOS continuous corner
|
|
48
|
+
# clipping, CSS @layer cascade support, custom Android variable-font weight mapping, and
|
|
49
|
+
# percentage translate parsing, native view, and animation support. Verified against the
|
|
50
|
+
# registry 9.1.3 tarball; it has no Windows implementation, so the separate Windows
|
|
51
|
+
# preview patch remains.
|
|
52
|
+
"@nativescript/core@9.1.3": node_modules/.pnpm-config/@octane-xplat/patches/patches/@nativescript__core@9.1.3.patch
|
|
53
|
+
# @nativescript/vite serializes bundled CSS and dev app.css through reworkcss `css`@3.0.0,
|
|
54
|
+
# which mis-parses @layer into garbage selectors — rules die before reaching
|
|
55
|
+
# _populateRules. Adds atlayer() covering block and statement forms. Applies to a
|
|
56
|
+
# transitive dep — no app declares it directly.
|
|
57
|
+
css@3.0.0: node_modules/.pnpm-config/@octane-xplat/patches/patches/css@3.0.0.patch
|
|
58
|
+
# Dev-server fixes still missing after @nativescript/vite 8.0.17 added percent decoding
|
|
59
|
+
# for path-style /ns/m and Vue /ns/sfc requests (NativeScript#11483): collect re-export
|
|
60
|
+
# specifiers in the .vite/deps rewrite, route /@fs paths outside both roots through /ns/m
|
|
61
|
+
# (linked checkouts bypass the device transform upstream), and probe the non-active
|
|
62
|
+
# platform + `native` suffix tags in module candidates. The deps-bundle esbuild passes
|
|
63
|
+
# ignore vite resolve.alias, so a dep importing bare `octane` bundles the DOM runtime
|
|
64
|
+
# beside the universal one (hooks crash on boot, octane-xplat#3) — an onResolve plugin
|
|
65
|
+
# redirects `octane` → `octane/universal/native`. tsconfig-paths and the generated
|
|
66
|
+
# build-time checker moduleSuffixes also need the `.mobile` tier for iOS/Android/visionOS
|
|
67
|
+
# so `.mobile` leaves resolve under mapped imports and `ns build`. Served `/node_modules/`
|
|
68
|
+
# tails now also scan pnpm's `.pnpm` store and `link:`/`file:`/`portal:` dep roots — a
|
|
69
|
+
# linked package's transitive deps (e.g. @iconify/utils under @octane-xplat/icons) resolve
|
|
70
|
+
# nowhere else and 404 the dev session. findMonorepoWorkspaceRoot no longer treats a lone
|
|
71
|
+
# turbo.json as a workspace root — Turborepo's per-package task configs marked apps/* as
|
|
72
|
+
# roots, masking the real pnpm root so served `/node_modules/` tails never reached the
|
|
73
|
+
# `.pnpm` store scan and non-hoisted transitive deps like source-map-js 404'd dev boot.
|
|
74
|
+
# Linked dependency fallbacks also discover each linked package's enclosing workspace and
|
|
75
|
+
# reuse the cached, bounded workspace node_modules scan so transitive workspace siblings
|
|
76
|
+
# (auth to secure-storage to a private native plugin) resolve without the consumer
|
|
77
|
+
# supplying that workspace root. Transform candidates now keep root-relative URLs only
|
|
78
|
+
# when they identify the discovered file; nested linked-dependency and pnpm-store matches
|
|
79
|
+
# inside the app root use their actual filesystem paths, preserving query suffixes. The
|
|
80
|
+
# /ns/m import rewriter now resolves `#`-prefixed package `imports` specifiers through the
|
|
81
|
+
# importer's own package.json — raw-read node_modules files (e.g. octane's
|
|
82
|
+
# dist/signals/graph.js importing `#octane/dist/signal-actions/graph`) otherwise reach the
|
|
83
|
+
# device verbatim and the HTTP loader throws `Cannot find module`, deadlocking dev boot
|
|
84
|
+
# before any boot recording can bundle the dep. NativeScript#11551: preserve user virtual
|
|
85
|
+
# stylesheet imports while retaining browser-helper filtering, route virtual CSS through
|
|
86
|
+
# the native inline compiler/registration path before the generic virtual-module shortcut,
|
|
87
|
+
# and relay Vite virtual stylesheet updates as tagged native CSS updates through the same
|
|
88
|
+
# adapted-text endpoint. Empty sheets clear previous rules; CSS compilation failures
|
|
89
|
+
# return HTTP 500 instead of raw CSS.
|
|
90
|
+
"@nativescript/vite@8.0.17": node_modules/.pnpm-config/@octane-xplat/patches/patches/@nativescript__vite@8.0.17.patch
|
|
91
|
+
# Retains the workspace's NativeScript driver fixes when upgrading to 0.2.5 for portal
|
|
92
|
+
# support: a prop dropped from an update's complete prop bag — or set to
|
|
93
|
+
# `undefined`/`null` — is a removal that restores the documented default
|
|
94
|
+
# (Property/CssProperty setters get `unsetValue`, plain fields the captured pre-prop
|
|
95
|
+
# value, `style` keys diff per key); a never-applied `undefined` still skips the write.
|
|
96
|
+
# Also advertises the implemented visibility capability, carries Octane callback ref
|
|
97
|
+
# types, and preserves authored Grid row/col/span metadata. Hosts custom-layout containers
|
|
98
|
+
# (e.g. MasonKit ViewBase) that own child lists via
|
|
99
|
+
# insertChild/removeChild/getChildrenCount without subclassing LayoutBase. Additionally,
|
|
100
|
+
# the masonry cutover: masonview resolves to MasonKit View and scrollview to MasonKit
|
|
101
|
+
# Scroll as the only layout containers, with zero layout props — every former prop name
|
|
102
|
+
# warns-and-drops and all CSS semantics arrive through the style bag; style keys on a
|
|
103
|
+
# mason child route to its leaf (stashed pre-attach, replayed at attach) via
|
|
104
|
+
# Style.fromView.
|
|
105
|
+
"@nativescript-community/octane@0.2.5": node_modules/.pnpm-config/@octane-xplat/patches/patches/@nativescript-community__octane@0.2.5.patch
|
|
106
|
+
# octaneServerStrategy's SCRIPT_FILE_RE only covers [jt]sx? — .tsrx edits never emit the
|
|
107
|
+
# deferred ns:hmr-delta broadcast, so component HMR silently never reaches the device.
|
|
108
|
+
# Also counts open /ns-hmr sockets so the update summary stops reporting recipients=0 for
|
|
109
|
+
# delivered updates.
|
|
110
|
+
"@nativescript-community/vite-octane@0.2.1": node_modules/.pnpm-config/@octane-xplat/patches/patches/@nativescript-community__vite-octane@0.2.1.patch
|
|
111
|
+
# Enables `resolveHiddenExtensions` on the language plugin's typescript facet so
|
|
112
|
+
# extensionless specifiers (e.g. './Text') probe `.tsrx` after moduleSuffixes — required
|
|
113
|
+
# for a single extensionless shared barrel to resolve per-platform leaves under tsrx-tsc
|
|
114
|
+
# and the IDE plugin.
|
|
115
|
+
"@tsrx/typescript-plugin@0.4.11": node_modules/.pnpm-config/@octane-xplat/patches/patches/@tsrx__typescript-plugin@0.4.11.patch
|
|
116
|
+
# linkifyjs declares /\p{L}/u and /\p{Emoji}/u as regex literals at module scope;
|
|
117
|
+
# NativeScript's embedded V8 builds without ICU, so the literals throw SyntaxError at
|
|
118
|
+
# parse time and poison the whole bundle (any bundled app importing @tiptap/extension-link
|
|
119
|
+
# dies on boot). Rewrites them as try/catch new RegExp builds with ASCII-range fallbacks —
|
|
120
|
+
# same behavior where ICU exists, graceful degradation where it doesn't.
|
|
121
|
+
linkifyjs@4.3.3: node_modules/.pnpm-config/@octane-xplat/patches/patches/linkifyjs@4.3.3.patch
|
|
122
|
+
# useEditor.ts reads bare process.env.NODE_ENV — no node global exists in the harness
|
|
123
|
+
# typecheck program. Adds a module-local process declaration for typecheck. (The
|
|
124
|
+
# './Context' self-import half of the 0.0.51 patch shipped upstream in 0.0.52 via
|
|
125
|
+
# octanejs/octane#1502.)
|
|
126
|
+
"@octanejs/tiptap@0.0.52": node_modules/.pnpm-config/@octane-xplat/patches/patches/@octanejs__tiptap@0.0.52.patch
|
|
127
|
+
# @lexical/link declares a /\p{L}\p{N}/u URL matcher as a regex literal at module scope;
|
|
128
|
+
# NativeScript's embedded V8 builds without ICU, so the literal throws SyntaxError at
|
|
129
|
+
# parse time and poisons the whole bundle (any bundled app importing @lexical/link dies on
|
|
130
|
+
# boot). Same fix as linkifyjs: try/catch new RegExp with an ASCII-range fallback — the
|
|
131
|
+
# matcher only feeds autolink, which native doesn't use.
|
|
132
|
+
"@lexical/link@0.51.0": node_modules/.pnpm-config/@octane-xplat/patches/patches/@lexical__link@0.51.0.patch
|
|
133
|
+
# ui-image 5.x regressed two iOS prefetch behaviors the 4.x line shipped (still present in
|
|
134
|
+
# 4.6.20): prefetchToCacheType ignores its cacheType argument — SDWebImagePrefetcher's
|
|
135
|
+
# default context stores to disk+memory either way, so a 'disk' prefetch also decodes and
|
|
136
|
+
# warms memory, diverging from Android's bytes-only Glide downloadOnly — and
|
|
137
|
+
# getContextFromOptions drops PrefetchOptions.headers because it never sets
|
|
138
|
+
# SDWebImageContextDownloadRequestModifier, so prefetch({headers}) silently sends no
|
|
139
|
+
# headers on iOS while Android's CustomGlideUrl forwards them. Ports upstream's own 4.x
|
|
140
|
+
# implementation — SDWebImageContextStoreCacheType on the prefetch context plus the
|
|
141
|
+
# request-modifier branch in getContextFromOptions — and additionally sets
|
|
142
|
+
# SDWebImageContextOriginalStoreCacheType so a memory prefetch does not spill original
|
|
143
|
+
# bytes to disk.
|
|
144
|
+
"@nativescript-community/ui-image@5.0.17": node_modules/.pnpm-config/@octane-xplat/patches/patches/@nativescript-community__ui-image@5.0.17.patch
|
|
145
|
+
# @nativescript/doctor's Android runtime check reads the app's raw devDependencies
|
|
146
|
+
# specifier and npm dist-tag resolves anything that isn't a semver range — a pnpm
|
|
147
|
+
# `catalog:` pin throws 'The determined Android runtime version ... is not valid' and
|
|
148
|
+
# kills `ns build android` before toolchain checks. Resolve catalog specifiers through the
|
|
149
|
+
# installed package's manifest, reusing the existing tgz-branch mechanism.
|
|
150
|
+
"@nativescript/doctor@2.0.18": node_modules/.pnpm-config/@octane-xplat/patches/patches/@nativescript__doctor@2.0.18.patch
|
|
151
|
+
# list/index.ios.js assigns `ObjCProtocols = [MasonListDelegate]` at module scope.
|
|
152
|
+
# MasonListDelegate is declared in Mason-Swift.h, which the app's metadata generation does
|
|
153
|
+
# not surface as a runtime global — importing masonkit on iOS throws `MasonListDelegate is
|
|
154
|
+
# not defined` during bundle eval and kills the app at boot. Guard the binding; actual
|
|
155
|
+
# List delegate conformance when the protocol is absent is a separate gap.
|
|
156
|
+
"@triniwiz/nativescript-masonkit@1.0.0-beta.110": node_modules/.pnpm-config/@octane-xplat/patches/patches/@triniwiz__nativescript-masonkit@1.0.0-beta.110.patch
|
|
@@ -67,6 +67,7 @@ try {
|
|
|
67
67
|
installDirectory(packed, sourceManifest.name, consumer)
|
|
68
68
|
for (const dependency of [
|
|
69
69
|
'octane',
|
|
70
|
+
'@octane-xplat/jsx',
|
|
70
71
|
'@octane-xplat/ui',
|
|
71
72
|
...(target === 'native'
|
|
72
73
|
? ['@nativescript-community/octane', '@nativescript/core', '@nativescript/types']
|
|
@@ -105,7 +106,7 @@ void element; void missing; void invalid
|
|
|
105
106
|
module: mode === 'bundler' ? 'esnext' : 'nodenext',
|
|
106
107
|
moduleResolution: mode,
|
|
107
108
|
jsx: 'react-jsx',
|
|
108
|
-
jsxImportSource: target === 'native' ? '@
|
|
109
|
+
jsxImportSource: target === 'native' ? '@octane-xplat/jsx' : '@octane-xplat/jsx/dom',
|
|
109
110
|
customConditions: [target],
|
|
110
111
|
moduleSuffixes: target === 'web' ? ['.web', ''] : ['.mobile', ''],
|
|
111
112
|
},
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "create-octane-xplat",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.16.0",
|
|
4
4
|
"description": "Scaffold Octane Xplat apps and cross-platform libraries",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"component-library",
|
|
@@ -21,22 +21,39 @@
|
|
|
21
21
|
"directory": "packages/create"
|
|
22
22
|
},
|
|
23
23
|
"bin": {
|
|
24
|
-
"create-octane-xplat": "./index.
|
|
24
|
+
"create-octane-xplat": "./dist/index.js"
|
|
25
25
|
},
|
|
26
26
|
"files": [
|
|
27
|
-
"index.
|
|
28
|
-
"scaffold.
|
|
27
|
+
"index.ts",
|
|
28
|
+
"scaffold.ts",
|
|
29
|
+
"compatibility.ts",
|
|
30
|
+
"compatibility.json",
|
|
31
|
+
"dist",
|
|
29
32
|
"template",
|
|
30
33
|
"library-template"
|
|
31
34
|
],
|
|
32
35
|
"type": "module",
|
|
33
36
|
"exports": {
|
|
34
|
-
"./scaffold": "./scaffold.
|
|
37
|
+
"./scaffold": "./dist/scaffold.js",
|
|
38
|
+
"./compatibility": {
|
|
39
|
+
"types": "./compatibility.ts",
|
|
40
|
+
"default": "./dist/compatibility.js"
|
|
41
|
+
},
|
|
42
|
+
"./compatibility.json": "./compatibility.json"
|
|
35
43
|
},
|
|
36
44
|
"publishConfig": {
|
|
37
45
|
"access": "public"
|
|
38
46
|
},
|
|
39
47
|
"dependencies": {
|
|
40
|
-
"@clack/prompts": "1.8.1"
|
|
48
|
+
"@clack/prompts": "1.8.1",
|
|
49
|
+
"yaml": "2.9.1"
|
|
50
|
+
},
|
|
51
|
+
"devDependencies": {
|
|
52
|
+
"@types/node": "24.10.1",
|
|
53
|
+
"vite": "8.3.0"
|
|
54
|
+
},
|
|
55
|
+
"scripts": {
|
|
56
|
+
"build": "vite build",
|
|
57
|
+
"typecheck": "tsc -p tsconfig.json"
|
|
41
58
|
}
|
|
42
59
|
}
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
// Scaffold manifest + compose/apply shared by create-octane-xplat and the
|
|
2
|
-
// CLI's `xplat add`.
|
|
3
|
-
//
|
|
4
|
-
// starter. The release bump script updates both manifests.
|
|
2
|
+
// CLI's `xplat add`. compatibility.json owns version pins; the generated
|
|
3
|
+
// templates own script bodies and the dependency set for each starter.
|
|
5
4
|
import {
|
|
6
5
|
cpSync,
|
|
7
6
|
existsSync,
|
|
@@ -14,29 +13,38 @@ import {
|
|
|
14
13
|
|
|
15
14
|
import { basename, dirname, join, resolve } from 'node:path'
|
|
16
15
|
import { fileURLToPath } from 'node:url'
|
|
16
|
+
import { parseDocument } from 'yaml'
|
|
17
|
+
import { ensureCompatibilityCatalog } from './compatibility.ts'
|
|
17
18
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
)
|
|
19
|
+
const moduleDir = dirname(fileURLToPath(import.meta.url))
|
|
20
|
+
// dist/ emit sits one level deeper than the root sources it mirrors.
|
|
21
|
+
const pkgRoot = basename(moduleDir) === 'dist' ? join(moduleDir, '..') : moduleDir
|
|
22
|
+
export const TEMPLATE_DIR = join(pkgRoot, 'template')
|
|
23
|
+
export const LIBRARY_TEMPLATE_DIR = join(pkgRoot, 'library-template')
|
|
23
24
|
|
|
24
25
|
/** Scaffoldable targets. `files` entries are template-relative paths —
|
|
25
26
|
* directories copy recursively. `deps`/`devDeps`/`scripts` name keys whose
|
|
26
27
|
* values come from template/package.json. `typecheck` contributes one clause
|
|
27
28
|
* to the generated typecheck script. */
|
|
28
|
-
export const targets = {
|
|
29
|
+
export const targets: Record<string, any> = {
|
|
29
30
|
web: {
|
|
30
31
|
label: 'Web (Vite)',
|
|
31
|
-
files: [
|
|
32
|
-
|
|
33
|
-
|
|
32
|
+
files: [
|
|
33
|
+
'index.html',
|
|
34
|
+
'vite.config.ts',
|
|
35
|
+
'src/main.web.tsrx',
|
|
36
|
+
'vitest.web.config.mts',
|
|
37
|
+
'src/app.web.test.tsx',
|
|
38
|
+
'src/test-setup.web.ts',
|
|
39
|
+
],
|
|
40
|
+
scripts: ['dev', 'build', 'test', 'test:web'],
|
|
41
|
+
devDeps: ['@octanejs/vite-plugin', 'jsdom'],
|
|
34
42
|
},
|
|
35
43
|
ios: {
|
|
36
44
|
label: 'iOS (NativeScript)',
|
|
37
45
|
requires: ['native-shared'],
|
|
38
46
|
files: ['App_Resources/iOS'],
|
|
39
|
-
scripts: ['dev:ios', '
|
|
47
|
+
scripts: ['dev:ios', 'build:ios', 'test:ios'],
|
|
40
48
|
devDeps: ['@nativescript/ios'],
|
|
41
49
|
note: 'needs macOS + Xcode — `pnpm xplat doctor` checks the toolchain',
|
|
42
50
|
},
|
|
@@ -44,7 +52,7 @@ export const targets = {
|
|
|
44
52
|
label: 'Android (NativeScript)',
|
|
45
53
|
requires: ['native-shared'],
|
|
46
54
|
files: ['App_Resources/Android'],
|
|
47
|
-
scripts: ['dev:android', '
|
|
55
|
+
scripts: ['dev:android', 'build:android', 'test:android'],
|
|
48
56
|
devDeps: ['@nativescript/android'],
|
|
49
57
|
note: 'needs Android SDK + a compatible JDK — `pnpm xplat doctor` checks the toolchain',
|
|
50
58
|
},
|
|
@@ -68,22 +76,15 @@ export const targets = {
|
|
|
68
76
|
'vite.config.native.mts',
|
|
69
77
|
'references.d.ts',
|
|
70
78
|
'src/main.ts',
|
|
71
|
-
|
|
72
|
-
// --env.unitTesting and runs every src/**/*.spec file on a
|
|
73
|
-
// simulator/emulator through the upstream runner.
|
|
79
|
+
'vitest.config.mts',
|
|
74
80
|
'src/test.ts',
|
|
75
81
|
'src/app.spec.tsrx',
|
|
76
82
|
'src/native-runtime.spec.ts',
|
|
77
83
|
'src/failure-reporting.spec.ts',
|
|
78
|
-
'vitest.config.mts',
|
|
79
84
|
],
|
|
80
85
|
deps: [
|
|
81
|
-
'@nativescript-community/gesturehandler',
|
|
82
86
|
'@nativescript-community/octane',
|
|
83
|
-
'@nativescript-community/ui-drawer',
|
|
84
87
|
'@nativescript/core',
|
|
85
|
-
'@octane-xplat/effects',
|
|
86
|
-
'@octane-xplat/image',
|
|
87
88
|
'@valor/nativescript-websockets',
|
|
88
89
|
],
|
|
89
90
|
devDeps: [
|
|
@@ -92,7 +93,6 @@ export const targets = {
|
|
|
92
93
|
'@nativescript/unit-test-runner',
|
|
93
94
|
'@nativescript/vite',
|
|
94
95
|
'nativescript',
|
|
95
|
-
'vitest',
|
|
96
96
|
],
|
|
97
97
|
typecheck: 'xplat typecheck',
|
|
98
98
|
},
|
|
@@ -111,7 +111,7 @@ const BASE_SCRIPTS = ['xplat', 'lint', 'lint:fix', 'format']
|
|
|
111
111
|
|
|
112
112
|
/** Expand `requires` edges, dependencies first. Throws on unknown ids. */
|
|
113
113
|
export function resolveTargets(ids) {
|
|
114
|
-
const resolved = []
|
|
114
|
+
const resolved: string[] = []
|
|
115
115
|
const visit = (id) => {
|
|
116
116
|
const t = targets[id]
|
|
117
117
|
if (!t) {
|
|
@@ -150,7 +150,7 @@ function ownerOf(path) {
|
|
|
150
150
|
}
|
|
151
151
|
|
|
152
152
|
function walk(dir, prefix = '') {
|
|
153
|
-
const out = []
|
|
153
|
+
const out: string[] = []
|
|
154
154
|
for (const name of readdirSync(dir)) {
|
|
155
155
|
if (name === 'node_modules') {
|
|
156
156
|
continue
|
|
@@ -168,7 +168,7 @@ function walk(dir, prefix = '') {
|
|
|
168
168
|
}
|
|
169
169
|
|
|
170
170
|
function pickKeys(source, names, label) {
|
|
171
|
-
const out = {}
|
|
171
|
+
const out: Record<string, string> = {}
|
|
172
172
|
for (const name of names) {
|
|
173
173
|
if (source[name] === undefined) {
|
|
174
174
|
throw new Error(`template/package.json has no ${label} entry "${name}"`)
|
|
@@ -187,11 +187,11 @@ function pickKeys(source, names, label) {
|
|
|
187
187
|
export function composeManifest(ids) {
|
|
188
188
|
const resolved = resolveTargets(ids)
|
|
189
189
|
const tpl = templateManifest()
|
|
190
|
-
const manifest = { ...tpl }
|
|
190
|
+
const manifest: Record<string, any> = { ...tpl }
|
|
191
191
|
|
|
192
192
|
for (const section of ['dependencies', 'devDependencies']) {
|
|
193
193
|
const key = section === 'dependencies' ? 'deps' : 'devDeps'
|
|
194
|
-
const wanted = new Set()
|
|
194
|
+
const wanted = new Set<string>()
|
|
195
195
|
for (const id of resolved) {
|
|
196
196
|
for (const name of targets[id][key] ?? []) {
|
|
197
197
|
wanted.add(name)
|
|
@@ -219,7 +219,7 @@ export function composeManifest(ids) {
|
|
|
219
219
|
}
|
|
220
220
|
|
|
221
221
|
const scripts = pickKeys(tpl.scripts ?? {}, [...wantedScripts].sort(), 'script')
|
|
222
|
-
const typecheck = [BASE_TYPECHECK]
|
|
222
|
+
const typecheck: string[] = [BASE_TYPECHECK]
|
|
223
223
|
for (const id of resolved) {
|
|
224
224
|
if (targets[id].typecheck) {
|
|
225
225
|
typecheck.push(targets[id].typecheck)
|
|
@@ -255,9 +255,9 @@ const patchPkg = (key) => key.replace(/@[^@]+$/, '')
|
|
|
255
255
|
// Split a pnpm-workspace.yaml into per-package patch blocks (leading comment
|
|
256
256
|
// lines included) so compose can drop entries and apply can transplant them.
|
|
257
257
|
function parsePatchEntries(lines) {
|
|
258
|
-
const entries = []
|
|
258
|
+
const entries: any[] = []
|
|
259
259
|
let inPatches = false
|
|
260
|
-
let pending = []
|
|
260
|
+
let pending: string[] = []
|
|
261
261
|
for (const line of lines) {
|
|
262
262
|
if (/^\S/.test(line)) {
|
|
263
263
|
inPatches = line.startsWith('patchedDependencies')
|
|
@@ -284,7 +284,7 @@ function parsePatchEntries(lines) {
|
|
|
284
284
|
/** The pnpm-workspace.yaml a given target set produces. */
|
|
285
285
|
export function composeWorkspaceYaml(ids) {
|
|
286
286
|
const resolved = new Set(resolveTargets(ids))
|
|
287
|
-
const drop = new Set()
|
|
287
|
+
const drop = new Set<string>()
|
|
288
288
|
for (const [id, t] of Object.entries(targets)) {
|
|
289
289
|
if (!resolved.has(id)) {
|
|
290
290
|
for (const name of t.patches ?? []) {
|
|
@@ -294,9 +294,9 @@ export function composeWorkspaceYaml(ids) {
|
|
|
294
294
|
}
|
|
295
295
|
|
|
296
296
|
const lines = readFileSync(join(TEMPLATE_DIR, 'pnpm-workspace.yaml'), 'utf8').split('\n')
|
|
297
|
-
const out = []
|
|
297
|
+
const out: string[] = []
|
|
298
298
|
let inPatches = false
|
|
299
|
-
let pending = []
|
|
299
|
+
let pending: string[] = []
|
|
300
300
|
for (const line of lines) {
|
|
301
301
|
if (/^\S/.test(line)) {
|
|
302
302
|
out.push(...pending, line)
|
|
@@ -326,7 +326,18 @@ export function composeWorkspaceYaml(ids) {
|
|
|
326
326
|
}
|
|
327
327
|
|
|
328
328
|
out.push(...pending)
|
|
329
|
-
|
|
329
|
+
const doc = parseDocument(out.join('\n'))
|
|
330
|
+
const manifest = composeManifest(ids)
|
|
331
|
+
const used = new Set([
|
|
332
|
+
...Object.keys(manifest.dependencies),
|
|
333
|
+
...Object.keys(manifest.devDependencies),
|
|
334
|
+
])
|
|
335
|
+
for (const name of Object.keys(doc.toJS().catalogs?.xplat ?? {})) {
|
|
336
|
+
if (!used.has(name)) {
|
|
337
|
+
doc.deleteIn(['catalogs', 'xplat', name])
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
return String(doc)
|
|
330
341
|
}
|
|
331
342
|
|
|
332
343
|
/**
|
|
@@ -407,7 +418,7 @@ export function composeTsconfig(ids) {
|
|
|
407
418
|
return JSON.stringify(config, null, '\t') + '\n'
|
|
408
419
|
}
|
|
409
420
|
|
|
410
|
-
/** Write a scaffold for `ids` into `dir` (must not exist or be empty). */
|
|
421
|
+
/** Write a scaffold with tests for `ids` into `dir` (must not exist or be empty). */
|
|
411
422
|
export function composeTargets(ids, dir) {
|
|
412
423
|
const resolved = new Set(resolveTargets(ids))
|
|
413
424
|
mkdirSync(dir, { recursive: true })
|
|
@@ -488,7 +499,7 @@ export function targetEnabled(appRoot, id) {
|
|
|
488
499
|
|
|
489
500
|
// ios/android: declared runtime devDep is the canonical signal — App_Resources
|
|
490
501
|
// alone doesn't reach the bundler config.
|
|
491
|
-
const manifest = readJson(join(appRoot, 'package.json')) ?? {}
|
|
502
|
+
const manifest: Record<string, any> = readJson(join(appRoot, 'package.json')) ?? {}
|
|
492
503
|
const declared = { ...manifest.dependencies, ...manifest.devDependencies }
|
|
493
504
|
const runtime = { ios: '@nativescript/ios', android: '@nativescript/android' }[id]
|
|
494
505
|
return runtime ? typeof declared[runtime] === 'string' : false
|
|
@@ -543,9 +554,9 @@ function enableNativeInTsconfig(appRoot) {
|
|
|
543
554
|
export function applyTarget(appRoot, id) {
|
|
544
555
|
const manifestPath = join(appRoot, 'package.json')
|
|
545
556
|
const raw = readFileSync(manifestPath, 'utf8')
|
|
546
|
-
const manifest = JSON.parse(raw)
|
|
557
|
+
const manifest: Record<string, any> = JSON.parse(raw)
|
|
547
558
|
const tpl = templateManifest()
|
|
548
|
-
const report = {
|
|
559
|
+
const report: Record<string, any> = {
|
|
549
560
|
enabled: [],
|
|
550
561
|
already: [],
|
|
551
562
|
copied: [],
|
|
@@ -620,12 +631,12 @@ export function applyTarget(appRoot, id) {
|
|
|
620
631
|
if (ensurePatchEntries(appRoot, [...report.enabled, ...report.already])) {
|
|
621
632
|
report.patchesAdded = true
|
|
622
633
|
}
|
|
634
|
+
ensureCompatibilityCatalog(appRoot, manifest)
|
|
623
635
|
|
|
624
636
|
if (touched) {
|
|
625
637
|
// Match the file's existing indent so diffs stay minimal.
|
|
626
638
|
const indent = raw.match(/\n(\s+)"/)?.[1] ?? '\t'
|
|
627
639
|
writeFileSync(manifestPath, JSON.stringify(manifest, null, indent) + '\n')
|
|
628
640
|
}
|
|
629
|
-
|
|
630
641
|
return report
|
|
631
642
|
}
|
|
@@ -119,16 +119,24 @@ export function NameField() {
|
|
|
119
119
|
```
|
|
120
120
|
|
|
121
121
|
- `.mobile.tsrx`/`.ios.tsrx`/`.android.tsrx` files and unsuffixed native-default files containing JSX must
|
|
122
|
-
start with `/** @jsxImportSource @
|
|
122
|
+
start with `/** @jsxImportSource @octane-xplat/jsx */` on
|
|
123
123
|
line 1 — nothing may precede it:
|
|
124
124
|
|
|
125
125
|
```tsrx
|
|
126
|
-
/** @jsxImportSource @
|
|
126
|
+
/** @jsxImportSource @octane-xplat/jsx */
|
|
127
127
|
import { Text } from '@octane-xplat/ui'
|
|
128
128
|
|
|
129
129
|
export function NativeGreeting() { return <Text>Hello</Text> }
|
|
130
130
|
```
|
|
131
131
|
|
|
132
|
+
- Browser JSX leaves select DOM types explicitly on line 1. These pragmas
|
|
133
|
+
select types; the build keeps the existing web and native renderers:
|
|
134
|
+
|
|
135
|
+
```tsx
|
|
136
|
+
/** @jsxImportSource @octane-xplat/jsx/dom */
|
|
137
|
+
export function BrowserGreeting() { return <p>Hello</p> }
|
|
138
|
+
```
|
|
139
|
+
|
|
132
140
|
## Verify
|
|
133
141
|
|
|
134
142
|
| Command | Catches |
|
|
@@ -141,7 +141,7 @@ Choose platform widgets when you want the OS's own controls. Import them only
|
|
|
141
141
|
in the matching platform file. This iOS example belongs in `Preference.ios.tsrx`:
|
|
142
142
|
|
|
143
143
|
```tsrx
|
|
144
|
-
/** @jsxImportSource @
|
|
144
|
+
/** @jsxImportSource @octane-xplat/jsx */
|
|
145
145
|
import { useState } from 'octane'
|
|
146
146
|
import { UISwitch } from '@octane-xplat/ui/ios'
|
|
147
147
|
|
|
@@ -154,7 +154,7 @@ export function Preference() {
|
|
|
154
154
|
The Android equivalent belongs in `Preference.android.tsrx`:
|
|
155
155
|
|
|
156
156
|
```tsrx
|
|
157
|
-
/** @jsxImportSource @
|
|
157
|
+
/** @jsxImportSource @octane-xplat/jsx */
|
|
158
158
|
import { useState } from 'octane'
|
|
159
159
|
import { MaterialSwitch } from '@octane-xplat/ui/android'
|
|
160
160
|
|
|
@@ -61,13 +61,13 @@ import { Scanner } from './parts/Scanner'
|
|
|
61
61
|
export function ScanScreen() { return <Scanner /> }
|
|
62
62
|
```
|
|
63
63
|
|
|
64
|
-
Native JSX files start with the
|
|
64
|
+
Native JSX files start with the shared JSX facade pragma on line 1. Import native
|
|
65
65
|
APIs from `@nativescript/core`, and read app lifecycle state inside the call
|
|
66
66
|
rather than during module initialization. This example belongs in
|
|
67
67
|
`Status.mobile.tsrx`:
|
|
68
68
|
|
|
69
69
|
```tsrx
|
|
70
|
-
/** @jsxImportSource @
|
|
70
|
+
/** @jsxImportSource @octane-xplat/jsx */
|
|
71
71
|
import { Application } from '@nativescript/core'
|
|
72
72
|
import { Pressable, Text } from '@octane-xplat/ui'
|
|
73
73
|
|
package/template/AGENTS.md
CHANGED
|
@@ -8,6 +8,53 @@ Before writing code, read `.agents/skills/xplat/SKILL.md` (or invoke the
|
|
|
8
8
|
`xplat` skill). Its invariants are enforced by `pnpm lint` — violations
|
|
9
9
|
usually still compile but silently break on one platform.
|
|
10
10
|
|
|
11
|
+
## Framework versions
|
|
12
|
+
|
|
13
|
+
Keep framework/compiler/native tool pins in the supplied `catalog:xplat`.
|
|
14
|
+
Upgrade the version set and patches together, then run the app's checks.
|
|
15
|
+
Within a multi-package workspace, upgrade covers all packages. Preserve
|
|
16
|
+
app-specific dependencies and reconcile custom-patch conflicts before retrying:
|
|
17
|
+
|
|
18
|
+
```sh
|
|
19
|
+
pnpm dlx @octane-xplat/cli@latest upgrade --check
|
|
20
|
+
pnpm dlx @octane-xplat/cli@latest upgrade
|
|
21
|
+
pnpm xplat doctor
|
|
22
|
+
pnpm typecheck
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## JSX types
|
|
26
|
+
|
|
27
|
+
The app's `tsconfig.json` selects shared native JSX types. Browser JSX leaves
|
|
28
|
+
select DOM types on their first line; keep runtime imports from `octane`:
|
|
29
|
+
|
|
30
|
+
```tsx
|
|
31
|
+
/** @jsxImportSource @octane-xplat/jsx/dom */
|
|
32
|
+
export function BrowserMessage() {
|
|
33
|
+
return <p>Ready</p>
|
|
34
|
+
}
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
Native leaves can explicitly select the same default with
|
|
38
|
+
`/** @jsxImportSource @octane-xplat/jsx */`. Do not replace it with a renderer
|
|
39
|
+
pragma. The facade supplies types; Vite still selects the platform renderer.
|
|
40
|
+
|
|
41
|
+
## App testing
|
|
42
|
+
|
|
43
|
+
The starter includes managed runners, sample tests, configuration, and scripts
|
|
44
|
+
for its enabled platforms. Adapt the samples to the app and run the relevant
|
|
45
|
+
commands:
|
|
46
|
+
|
|
47
|
+
```sh
|
|
48
|
+
pnpm test:web
|
|
49
|
+
pnpm test:ios -- --device DEVICE_UDID
|
|
50
|
+
pnpm test:android -- --device DEVICE_SERIAL
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
Only commands for enabled platforms are added. Web tests use jsdom; phone
|
|
54
|
+
tests need a selected simulator or device. Report targets actually run and
|
|
55
|
+
distinguish document checks from browser layout or OS input checks. See the
|
|
56
|
+
[testing guide](https://octane-xplat.goddardai.org/verify/testing#add-automated-tests).
|
|
57
|
+
|
|
11
58
|
## Stable automation targets
|
|
12
59
|
|
|
13
60
|
```tsx
|