create-octane-xplat 0.3.0 → 0.5.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 +25 -0
- package/index.mjs +20 -17
- package/package.json +2 -2
- package/template/.agents/skills/xplat/SKILL.md +77 -0
- package/template/.agents/skills/xplat/references/components.md +49 -0
- package/template/.agents/skills/xplat/references/navigation.md +30 -0
- package/template/.agents/skills/xplat/references/platform.md +45 -0
- package/template/.agents/skills/xplat/references/styling.md +42 -0
- package/template/.oxlintrc.json +25 -0
- package/template/AGENTS.md +8 -0
- package/template/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/Contents.json +121 -121
- package/template/App_Resources/iOS/Assets.xcassets/Contents.json +5 -5
- package/template/App_Resources/iOS/Assets.xcassets/LaunchScreen.AspectFill.imageset/Contents.json +22 -22
- package/template/App_Resources/iOS/Assets.xcassets/LaunchScreen.Center.imageset/Contents.json +22 -22
- package/template/App_Resources/iOS/Assets.xcassets/octane.imageset/Contents.json +6 -6
- package/template/index.html +9 -9
- package/template/nativescript.config.ts +18 -11
- package/template/package.json +42 -35
- package/template/src/{native.ts → index.native.ts} +14 -14
- package/template/src/style.css +4 -2
- package/template/tsconfig.json +1 -1
- package/template/vite.config.native.mts +7 -84
- package/template/vite.config.ts +14 -7
package/README.md
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# create-octane-xplat
|
|
2
|
+
|
|
3
|
+
> Scaffolds an Octane xplat app — one codebase targeting web, iOS, and
|
|
4
|
+
> Android via NativeScript.
|
|
5
|
+
>
|
|
6
|
+
> Status: `0.x` — the API surface is still moving. iOS/Android targets need
|
|
7
|
+
> the NativeScript toolchain (Xcode/JDK + `ns`).
|
|
8
|
+
|
|
9
|
+
```sh
|
|
10
|
+
pnpm create octane-xplat my-app
|
|
11
|
+
# or: npm create octane-xplat my-app
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
Copies the starter template (vite + nativescript configs, shared `src/`,
|
|
15
|
+
`App_Resources`, pinned workspace deps), installs dependencies with pnpm,
|
|
16
|
+
and starts the web dev server. From there:
|
|
17
|
+
|
|
18
|
+
```sh
|
|
19
|
+
pnpm dev # web
|
|
20
|
+
pnpm dev:ios # iOS simulator — needs the NativeScript toolchain
|
|
21
|
+
pnpm dev:android # Android — needs the NativeScript toolchain
|
|
22
|
+
pnpm typecheck # web + native tsconfigs
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
Docs: [Running and checking an app](https://octane-xplat.goddardai.org/toolchain)
|
package/index.mjs
CHANGED
|
@@ -1,28 +1,31 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { existsSync, readdirSync, cpSync, renameSync } from 'node:fs'
|
|
3
|
-
import { dirname, join, resolve } from 'node:path'
|
|
4
|
-
import { fileURLToPath } from 'node:url'
|
|
5
|
-
import { spawnSync } from 'node:child_process'
|
|
2
|
+
import { existsSync, readdirSync, cpSync, renameSync } from 'node:fs'
|
|
3
|
+
import { dirname, join, resolve } from 'node:path'
|
|
4
|
+
import { fileURLToPath } from 'node:url'
|
|
5
|
+
import { spawnSync } from 'node:child_process'
|
|
6
6
|
|
|
7
|
-
const dir = resolve(process.argv[2] ?? 'octane-xplat-app')
|
|
7
|
+
const dir = resolve(process.argv[2] ?? 'octane-xplat-app')
|
|
8
8
|
|
|
9
9
|
if (existsSync(dir) && readdirSync(dir).length > 0) {
|
|
10
|
-
console.error(`✗ ${dir} exists and is not empty`)
|
|
11
|
-
process.exit(1)
|
|
10
|
+
console.error(`✗ ${dir} exists and is not empty`)
|
|
11
|
+
process.exit(1)
|
|
12
12
|
}
|
|
13
13
|
|
|
14
|
-
const template = join(dirname(fileURLToPath(import.meta.url)), 'template')
|
|
15
|
-
cpSync(template, dir, { recursive: true })
|
|
14
|
+
const template = join(dirname(fileURLToPath(import.meta.url)), 'template')
|
|
15
|
+
cpSync(template, dir, { recursive: true })
|
|
16
16
|
// npm packs .gitignore as a renamed file or drops it — ship it undotted.
|
|
17
|
-
try {
|
|
18
|
-
|
|
17
|
+
try {
|
|
18
|
+
renameSync(join(dir, 'gitignore'), join(dir, '.gitignore'))
|
|
19
|
+
} catch {}
|
|
19
20
|
|
|
20
|
-
|
|
21
|
+
console.log(`\n✓ scaffolded ${dir}`)
|
|
22
|
+
|
|
23
|
+
const install = spawnSync('pnpm', ['install'], { cwd: dir, stdio: 'inherit' })
|
|
21
24
|
if (install.status !== 0) {
|
|
22
|
-
console.log(`\nInstall didn't finish (pnpm missing?). To run manually:`)
|
|
23
|
-
console.log(` cd ${dir}\n pnpm install\n pnpm dev`)
|
|
24
|
-
process.exit(install.status ?? 1)
|
|
25
|
+
console.log(`\nInstall didn't finish (pnpm missing?). To run manually:`)
|
|
26
|
+
console.log(` cd ${dir}\n pnpm install\n pnpm dev`)
|
|
27
|
+
process.exit(install.status ?? 1)
|
|
25
28
|
}
|
|
26
29
|
|
|
27
|
-
console.log('\n✓ installed — starting dev server (Ctrl+C to stop)\n')
|
|
28
|
-
spawnSync('pnpm', ['dev'], { cwd: dir, stdio: 'inherit' })
|
|
30
|
+
console.log('\n✓ installed — starting dev server (Ctrl+C to stop)\n')
|
|
31
|
+
spawnSync('pnpm', ['dev'], { cwd: dir, stdio: 'inherit' })
|
package/package.json
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "create-octane-xplat",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"type": "module",
|
|
3
|
+
"version": "0.5.0",
|
|
5
4
|
"description": "Scaffold a minimal @octane-xplat/ui app — one Octane codebase for web + iOS + Android",
|
|
6
5
|
"bin": {
|
|
7
6
|
"create-octane-xplat": "./index.mjs"
|
|
@@ -10,6 +9,7 @@
|
|
|
10
9
|
"index.mjs",
|
|
11
10
|
"template"
|
|
12
11
|
],
|
|
12
|
+
"type": "module",
|
|
13
13
|
"publishConfig": {
|
|
14
14
|
"access": "public"
|
|
15
15
|
}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: xplat
|
|
3
|
+
description: Build and review code in an Octane xplat app — one TypeScript codebase targeting web (DOM) and iOS/Android (NativeScript). Use when writing components, styles, routes, platform leaves, or device-service calls, and when a feature works on one target but not another.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Octane xplat app
|
|
7
|
+
|
|
8
|
+
Every `src/*.tsrx` file compiles twice: once to the DOM renderer, once to
|
|
9
|
+
NativeScript views. The common failure mode is **silent divergence** — code
|
|
10
|
+
that compiles and works on web but no-ops or crashes on native. The rules
|
|
11
|
+
below prevent that; `pnpm lint` enforces most of them.
|
|
12
|
+
|
|
13
|
+
## Non-negotiables
|
|
14
|
+
|
|
15
|
+
1. **One element vocabulary per file.** Shared code renders only
|
|
16
|
+
`@octane-xplat/ui` components. Platform divergence lives in whole files
|
|
17
|
+
— `Foo.web.tsrx`, `Foo.native.tsrx`, `Foo.ios.tsrx`, `Foo.android.tsrx` —
|
|
18
|
+
chosen by the bundler when something imports `./Foo`. Never branch on
|
|
19
|
+
platform inside JSX (`Platform.OS`, conditional imports, `typeof
|
|
20
|
+
document` checks).
|
|
21
|
+
2. **No DOM globals in shared code.** `document`, `window`, `localStorage`,
|
|
22
|
+
DOM events — all web-only. Device capabilities (clipboard, storage,
|
|
23
|
+
permissions, connectivity, geolocation, …) come from
|
|
24
|
+
`@octane-xplat/platform`.
|
|
25
|
+
3. **Styles:** `className` for anything static; `style` objects only for
|
|
26
|
+
values that change while the app runs. Shared CSS must use the portable
|
|
27
|
+
subset — the native build warns on declarations it drops
|
|
28
|
+
(`position: fixed`, `margin: auto`, `box-shadow`, …).
|
|
29
|
+
4. **Hooks and JSX live in `.tsrx` (or `.tsx`).** Plain `.ts` is for
|
|
30
|
+
non-component code and may not import `.tsrx`. Import `.tsrx` files with
|
|
31
|
+
the explicit extension: `import { App } from './App.tsrx'`.
|
|
32
|
+
5. **State = `octane/signals`.** `signal$()` for module-level shared state,
|
|
33
|
+
`useSignal$()` for component-local, `derived$()` for computed, `query$()`
|
|
34
|
+
for async data. Name signal variables with a `$` suffix (`count$`,
|
|
35
|
+
`user$`) — the compiler uses the suffix to preserve reactive reads on
|
|
36
|
+
native. Every module that reads or writes signals needs a runtime
|
|
37
|
+
`import 'octane/signals'` (or `octane/signals/client`).
|
|
38
|
+
6. **One `octane` per app.** Don't add a second renderer or duplicate the
|
|
39
|
+
package — two copies break the reconciler without a helpful error.
|
|
40
|
+
|
|
41
|
+
## Compiler rules that surprise
|
|
42
|
+
|
|
43
|
+
- `.tsrx` files can't declare `async` top-level functions — the compiler
|
|
44
|
+
treats them as async components and the native build fails.
|
|
45
|
+
- Literal `@{` in JSX text parses as a code block. Write `{'@'}{expr}` to
|
|
46
|
+
render a literal `@`.
|
|
47
|
+
- Effect dependencies are inferred from closure reads. An effect that only
|
|
48
|
+
writes (refs, DOM) and never reads its driving prop won't re-run —
|
|
49
|
+
declare deps explicitly: `useLayoutEffect(fn, [props.x])`.
|
|
50
|
+
- Input handlers must be idempotent — `onInput`/`onChange` can dispatch
|
|
51
|
+
more than once per user action on web.
|
|
52
|
+
- `.native.tsrx`/`.ios.tsrx`/`.android.tsrx` files containing JSX must
|
|
53
|
+
start with `/** @jsxImportSource @nativescript-community/octane */` on
|
|
54
|
+
line 1 — nothing may precede it.
|
|
55
|
+
|
|
56
|
+
## Verify
|
|
57
|
+
|
|
58
|
+
| Command | Catches |
|
|
59
|
+
| --- | --- |
|
|
60
|
+
| `pnpm lint` | vocabulary / DOM-global / style violations — its output is authoritative |
|
|
61
|
+
| `pnpm typecheck` | both web and native TS configs |
|
|
62
|
+
| `pnpm dev` | web behavior |
|
|
63
|
+
| `pnpm dev:ios` / `pnpm dev:android` | the platform where behavior actually differs |
|
|
64
|
+
|
|
65
|
+
## Read next (only when needed)
|
|
66
|
+
|
|
67
|
+
- `references/components.md` — the component vocabulary plus per-platform
|
|
68
|
+
divergences and name traps.
|
|
69
|
+
- `references/styling.md` — tokens, the portable CSS subset, dark mode.
|
|
70
|
+
- `references/navigation.md` — route table, links, params, stacks.
|
|
71
|
+
- `references/platform.md` — device services and writing your own leaf.
|
|
72
|
+
- Current limits and platform boundaries:
|
|
73
|
+
https://octane-xplat.goddardai.org/known-limits
|
|
74
|
+
- Full framework docs, one file:
|
|
75
|
+
https://octane-xplat.goddardai.org/llms-full.txt (index:
|
|
76
|
+
https://octane-xplat.goddardai.org/llms.txt). The `/notes/*` design
|
|
77
|
+
record explains *why* a constraint exists — rarely needed for app work.
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
# Components — the shared vocabulary
|
|
2
|
+
|
|
3
|
+
Import everything from `@octane-xplat/ui`. Each component has a web leaf
|
|
4
|
+
(DOM) and a native leaf (NativeScript view) — the JSX is identical on both.
|
|
5
|
+
|
|
6
|
+
| Need | Component(s) |
|
|
7
|
+
| --- | --- |
|
|
8
|
+
| Layout | `View` (aliased `Column`), `Row`, `Grid`, `Stack`, `Absolute`, `Spacer`, `SafeArea`, `KeyboardAvoiding` |
|
|
9
|
+
| Text | `Text`, `Heading`, `RichText` + `RichTextSpan` |
|
|
10
|
+
| Tap/hover | `Pressable`, `Link`, `NavLink`, `Hoverable` |
|
|
11
|
+
| Input | `TextInput`, `TextArea`, `Switch`, `Slider` |
|
|
12
|
+
| Collections | `List` |
|
|
13
|
+
| Scrolling | `ScrollView`, `ScrollBox` |
|
|
14
|
+
| Media | `Image`, `Icon` (`registerIcon`/`registerIcons` for glyphs) |
|
|
15
|
+
| Feedback | `ActivityIndicator`, `Meter`, `showToast` |
|
|
16
|
+
| Overlays | `Modal`/`openModal`, `Sheet`/`openSheet`/`closeSheet`, `Overlay`, `Popover`, `Drawer`, `openWindow` |
|
|
17
|
+
| Shells | `Screen`, `Tabs` (+ `TabSpec`) |
|
|
18
|
+
| Animation | `useAnimation` → `AnimatedValue` |
|
|
19
|
+
| Theme/measure | `useColorScheme`/`getColorScheme`, `useSafeAreaInsets`, `useMeasure`, `styled()`, `createStore` |
|
|
20
|
+
|
|
21
|
+
## Divergences that matter
|
|
22
|
+
|
|
23
|
+
- `ScrollBox` is a **name trap**: on native it's a plain, non-scrolling
|
|
24
|
+
container (a `List` inside it owns scrolling). Use `ScrollView` when you
|
|
25
|
+
actually want a scroller.
|
|
26
|
+
- `List` is not virtualized on web. On native it must not sit inside a
|
|
27
|
+
`ScrollView` — two scrollers fight and the framework throws.
|
|
28
|
+
- `TextArea` `onSubmit`: web fires on Cmd/Ctrl+Enter; native fires only
|
|
29
|
+
when `returnKeyType` is `"done"` or `"send"` — otherwise every newline
|
|
30
|
+
reports as a submit.
|
|
31
|
+
- `Modal` on Android: a non-fullscreen modal shows as a centered dialog,
|
|
32
|
+
not a bottom sheet. Context and theme don't cross overlay roots — read
|
|
33
|
+
them inside the overlay or pass values down.
|
|
34
|
+
- `Sheet`/`openSheet` is a bottom-anchored sheet on both platforms; on web
|
|
35
|
+
it renders an in-window portal layer over the app.
|
|
36
|
+
- `Drawer` has an edge-swipe gesture on native only — give web users a
|
|
37
|
+
visible toggle.
|
|
38
|
+
- `RichTextSpan` children carry their own `className`/`style`/`onPress`;
|
|
39
|
+
native maps spans to `FormattedString` runs.
|
|
40
|
+
- Accessibility props (`accessible`, label, hint, role, state, live
|
|
41
|
+
region) share names across targets; native translates roles such as
|
|
42
|
+
`heading` → `header`.
|
|
43
|
+
|
|
44
|
+
## Things that look right but aren't
|
|
45
|
+
|
|
46
|
+
- `PlatformBadge` is an internal probe, not app vocabulary — don't use it.
|
|
47
|
+
- `keyFor` on `List` is web-only.
|
|
48
|
+
- Route `params` must be scalar (see navigation reference) — objects work
|
|
49
|
+
on native but are silently dropped on web.
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# Navigation
|
|
2
|
+
|
|
3
|
+
One route table serves both targets. Declare named screens and stacks;
|
|
4
|
+
navigate with `<Link>`/`<NavLink>` in JSX or `pushRoute`/`popRoute`/
|
|
5
|
+
`useRoute`/`useCanGoBack` imperatively. Web maps routes to URLs and
|
|
6
|
+
history; native maps them to Frame pages and the back stack — the same
|
|
7
|
+
call sites drive both.
|
|
8
|
+
|
|
9
|
+
## Rules
|
|
10
|
+
|
|
11
|
+
- **Params are scalar.** They serialize as query strings on web — pass
|
|
12
|
+
strings and numbers. Objects survive on native and are silently dropped
|
|
13
|
+
on web.
|
|
14
|
+
- **Tabs and named stacks** (`Tabs` + `TabSpec`, `registerStack`) exist on
|
|
15
|
+
both targets. Use a named stack for overlay-style flows (sheets,
|
|
16
|
+
sidebars) that shouldn't replace the root page.
|
|
17
|
+
- **Deep links:** web is the URL itself; native parses incoming URLs into
|
|
18
|
+
the same route names — keep names stable and shareable.
|
|
19
|
+
- **Back** — browser back, hardware back (Android), and swipe-back (iOS)
|
|
20
|
+
all map to `popRoute` on the current stack.
|
|
21
|
+
|
|
22
|
+
## Known gap
|
|
23
|
+
|
|
24
|
+
Pushing into a *named* stack is iOS-only until
|
|
25
|
+
[NativeScript#11444](https://github.com/NativeScript/NativeScript/issues/11444)
|
|
26
|
+
lands — `pushRoute` there warns loudly on Android instead of dropping
|
|
27
|
+
silently. Root-stack navigation works on both platforms.
|
|
28
|
+
|
|
29
|
+
For loaders, route config, and generated route types, see the navigation
|
|
30
|
+
guide: https://octane-xplat.goddardai.org/navigation
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# Platform services + writing leaves
|
|
2
|
+
|
|
3
|
+
Two seams cover everything shared code can't reach:
|
|
4
|
+
|
|
5
|
+
## `@octane-xplat/platform` — device services
|
|
6
|
+
|
|
7
|
+
One import per capability; each service resolves internally to a `.web` or
|
|
8
|
+
`.native` implementation. Surface: `device`, `appInfo`, `locale`,
|
|
9
|
+
`connectivity`, `useWindowSize`, `useAppState`, `useSafeAreaInsets`, deep
|
|
10
|
+
links, clipboard, `secureStorage`, haptics, notifications, `announce`,
|
|
11
|
+
`systemBars`, geolocation, permissions, media, files, share, `openUrl`,
|
|
12
|
+
`openSettings`, biometrics.
|
|
13
|
+
|
|
14
|
+
Services report honestly — `biometrics` returns `unsupported` on web
|
|
15
|
+
rather than faking a result. Handle the tier your app can fall back to
|
|
16
|
+
instead of assuming success.
|
|
17
|
+
|
|
18
|
+
## Leaf files — your own divergence
|
|
19
|
+
|
|
20
|
+
When a service doesn't exist yet or a visual behavior truly differs,
|
|
21
|
+
split the file, not the JSX:
|
|
22
|
+
|
|
23
|
+
```text
|
|
24
|
+
src/parts/Scanner.tsrx shared part — imports './Scanner.impl'
|
|
25
|
+
src/parts/Scanner.impl.web.ts DOM implementation (camera via getUserMedia)
|
|
26
|
+
src/parts/Scanner.impl.native.ts NativeScript implementation
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
Import the bare specifier (`./Scanner.impl`); the bundler picks the leaf
|
|
30
|
+
via the suffix chain `.ios` → `.android` → `.native` → shared / `.web` →
|
|
31
|
+
shared.
|
|
32
|
+
|
|
33
|
+
Leaf rules:
|
|
34
|
+
|
|
35
|
+
- Inside a leaf, platform APIs are *expected*: DOM globals in `.web.*`,
|
|
36
|
+
`@nativescript/*` imports in `.native.*`. The lint rules key off the
|
|
37
|
+
suffix — don't fight them with suppressions.
|
|
38
|
+
- Never read app-level NativeScript globals (`Application.android.*`)
|
|
39
|
+
at module top — read them inside the call. Top-level reads crash cold
|
|
40
|
+
start before the app exists.
|
|
41
|
+
- `.native.tsrx` files with JSX start with
|
|
42
|
+
`/** @jsxImportSource @nativescript-community/octane */` on line 1.
|
|
43
|
+
- Deep imports under `@nativescript/core/ui/*` bundle as a second module
|
|
44
|
+
instance — import from `@nativescript/core` only.
|
|
45
|
+
- `console.debug` doesn't exist on device — use `console.log`.
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# Styling
|
|
2
|
+
|
|
3
|
+
Two channels, chosen by *when the value is known*:
|
|
4
|
+
|
|
5
|
+
- `className` → CSS classes and design tokens. Use for anything static.
|
|
6
|
+
- `style={{ ... }}` → inline object for runtime values: animated offsets,
|
|
7
|
+
measured sizes, state-driven opacity. Don't put static styles here.
|
|
8
|
+
|
|
9
|
+
## The portable subset
|
|
10
|
+
|
|
11
|
+
Shared CSS compiles for both targets. The native build rewrites `px` →
|
|
12
|
+
`dip` and warns on declarations it can't honor — `position: fixed`/`sticky`,
|
|
13
|
+
`margin-*: auto`, `float`, `box-shadow`, `white-space: pre-wrap`. A warning
|
|
14
|
+
means the property does nothing on native: restructure (flex alignment,
|
|
15
|
+
`Absolute`, transforms) instead of suppressing it.
|
|
16
|
+
|
|
17
|
+
NativeScript limits worth knowing before you design around them:
|
|
18
|
+
|
|
19
|
+
- A scroll container can't clip children to a parent's rounded corners —
|
|
20
|
+
inset separators/padding instead.
|
|
21
|
+
- CSS transforms reach native views only through the `transform:`
|
|
22
|
+
shorthand — `transform: translateX(16) scaleX(0.95)`. For motion, prefer
|
|
23
|
+
`useAnimation`, which composes this per platform.
|
|
24
|
+
|
|
25
|
+
## Tokens and dark mode
|
|
26
|
+
|
|
27
|
+
`import '@octane-xplat/ui/theme/tokens.css'` (already in `main.web.tsrx`)
|
|
28
|
+
provides color/spacing tokens and `--font-sans`/`--font-mono` fallbacks.
|
|
29
|
+
The shipped utility classes (`flex-1`, `items-center`, `gap-*`, `rounded`,
|
|
30
|
+
`text-*`, …) mirror Tailwind v4 names and scales — write them as you would
|
|
31
|
+
Tailwind utilities.
|
|
32
|
+
|
|
33
|
+
Dark mode: `useColorScheme()` reads the system; the app's override pattern
|
|
34
|
+
is in `src/App.tsrx` — hold `boolean | null` state, apply a `dark`/`light`
|
|
35
|
+
class on the root, and let tokens switch underneath.
|
|
36
|
+
|
|
37
|
+
## Fonts
|
|
38
|
+
|
|
39
|
+
Family names differ per target (web face name, iOS PostScript name,
|
|
40
|
+
Android filename). Ship the font file in the app, register per platform,
|
|
41
|
+
and set the `--font-*` tokens in `style.css`. See the styling guide at
|
|
42
|
+
https://octane-xplat.goddardai.org/styling for the per-target table.
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
{
|
|
2
|
+
"jsPlugins": ["@octane-xplat/lint/oxlint-plugin"],
|
|
3
|
+
"rules": {
|
|
4
|
+
"curly": "error",
|
|
5
|
+
"xplat/spacing": "error",
|
|
6
|
+
"xplat/no-dom-globals": "error",
|
|
7
|
+
"xplat/no-web-only-api": "error",
|
|
8
|
+
"xplat/element-vocabulary": "error",
|
|
9
|
+
"xplat/no-nativescript-import": "error",
|
|
10
|
+
"xplat/no-ns-deep-import": "error",
|
|
11
|
+
"xplat/no-native-dead-style": "error",
|
|
12
|
+
"xplat/no-line-height-unitless": "error",
|
|
13
|
+
"xplat/no-style-string": "error",
|
|
14
|
+
"xplat/no-unknown-class": "warn",
|
|
15
|
+
"xplat/no-ts-imports-tsrx": "error",
|
|
16
|
+
"xplat/signal-dollar-suffix": "error",
|
|
17
|
+
"xplat/require-signals-runtime": "warn",
|
|
18
|
+
"xplat/no-view-onpress": "error",
|
|
19
|
+
"xplat/no-console-debug": "error",
|
|
20
|
+
"xplat/hooks-in-plain-ts": "warn",
|
|
21
|
+
"xplat/native-pragma-first-line": "error",
|
|
22
|
+
"xplat/no-jsx-text-breakers": "error",
|
|
23
|
+
"xplat/no-root-logical-render": "error"
|
|
24
|
+
}
|
|
25
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
# AGENTS.md
|
|
2
|
+
|
|
3
|
+
Octane xplat app — one TypeScript codebase for web + iOS + Android (Octane
|
|
4
|
+
+ NativeScript).
|
|
5
|
+
|
|
6
|
+
Before writing code, read `.agents/skills/xplat/SKILL.md` (or invoke the
|
|
7
|
+
`xplat` skill). Its invariants are enforced by `pnpm lint` — violations
|
|
8
|
+
usually still compile but silently break on one platform.
|
|
@@ -1,122 +1,122 @@
|
|
|
1
1
|
{
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
}
|
|
2
|
+
"images": [
|
|
3
|
+
{
|
|
4
|
+
"size": "20x20",
|
|
5
|
+
"idiom": "iphone",
|
|
6
|
+
"filename": "icon-20@2x.png",
|
|
7
|
+
"scale": "2x"
|
|
8
|
+
},
|
|
9
|
+
{
|
|
10
|
+
"size": "20x20",
|
|
11
|
+
"idiom": "iphone",
|
|
12
|
+
"filename": "icon-20@3x.png",
|
|
13
|
+
"scale": "3x"
|
|
14
|
+
},
|
|
15
|
+
{
|
|
16
|
+
"size": "29x29",
|
|
17
|
+
"idiom": "iphone",
|
|
18
|
+
"filename": "icon-29.png",
|
|
19
|
+
"scale": "1x"
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
"size": "29x29",
|
|
23
|
+
"idiom": "iphone",
|
|
24
|
+
"filename": "icon-29@2x.png",
|
|
25
|
+
"scale": "2x"
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
"size": "29x29",
|
|
29
|
+
"idiom": "iphone",
|
|
30
|
+
"filename": "icon-29@3x.png",
|
|
31
|
+
"scale": "3x"
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
"size": "40x40",
|
|
35
|
+
"idiom": "iphone",
|
|
36
|
+
"filename": "icon-40@2x.png",
|
|
37
|
+
"scale": "2x"
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
"size": "40x40",
|
|
41
|
+
"idiom": "iphone",
|
|
42
|
+
"filename": "icon-40@3x.png",
|
|
43
|
+
"scale": "3x"
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
"size": "60x60",
|
|
47
|
+
"idiom": "iphone",
|
|
48
|
+
"filename": "icon-60@2x.png",
|
|
49
|
+
"scale": "2x"
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
"size": "60x60",
|
|
53
|
+
"idiom": "iphone",
|
|
54
|
+
"filename": "icon-60@3x.png",
|
|
55
|
+
"scale": "3x"
|
|
56
|
+
},
|
|
57
|
+
{
|
|
58
|
+
"size": "20x20",
|
|
59
|
+
"idiom": "ipad",
|
|
60
|
+
"filename": "icon-20.png",
|
|
61
|
+
"scale": "1x"
|
|
62
|
+
},
|
|
63
|
+
{
|
|
64
|
+
"size": "20x20",
|
|
65
|
+
"idiom": "ipad",
|
|
66
|
+
"filename": "icon-20@2x.png",
|
|
67
|
+
"scale": "2x"
|
|
68
|
+
},
|
|
69
|
+
{
|
|
70
|
+
"size": "29x29",
|
|
71
|
+
"idiom": "ipad",
|
|
72
|
+
"filename": "icon-29.png",
|
|
73
|
+
"scale": "1x"
|
|
74
|
+
},
|
|
75
|
+
{
|
|
76
|
+
"size": "29x29",
|
|
77
|
+
"idiom": "ipad",
|
|
78
|
+
"filename": "icon-29@2x.png",
|
|
79
|
+
"scale": "2x"
|
|
80
|
+
},
|
|
81
|
+
{
|
|
82
|
+
"size": "40x40",
|
|
83
|
+
"idiom": "ipad",
|
|
84
|
+
"filename": "icon-40.png",
|
|
85
|
+
"scale": "1x"
|
|
86
|
+
},
|
|
87
|
+
{
|
|
88
|
+
"size": "40x40",
|
|
89
|
+
"idiom": "ipad",
|
|
90
|
+
"filename": "icon-40@2x.png",
|
|
91
|
+
"scale": "2x"
|
|
92
|
+
},
|
|
93
|
+
{
|
|
94
|
+
"size": "76x76",
|
|
95
|
+
"idiom": "ipad",
|
|
96
|
+
"filename": "icon-76.png",
|
|
97
|
+
"scale": "1x"
|
|
98
|
+
},
|
|
99
|
+
{
|
|
100
|
+
"size": "76x76",
|
|
101
|
+
"idiom": "ipad",
|
|
102
|
+
"filename": "icon-76@2x.png",
|
|
103
|
+
"scale": "2x"
|
|
104
|
+
},
|
|
105
|
+
{
|
|
106
|
+
"size": "83.5x83.5",
|
|
107
|
+
"idiom": "ipad",
|
|
108
|
+
"filename": "icon-83.5@2x.png",
|
|
109
|
+
"scale": "2x"
|
|
110
|
+
},
|
|
111
|
+
{
|
|
112
|
+
"size": "1024x1024",
|
|
113
|
+
"idiom": "ios-marketing",
|
|
114
|
+
"filename": "icon-1024.png",
|
|
115
|
+
"scale": "1x"
|
|
116
|
+
}
|
|
117
|
+
],
|
|
118
|
+
"info": {
|
|
119
|
+
"version": 1,
|
|
120
|
+
"author": "xcode"
|
|
121
|
+
}
|
|
122
|
+
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
}
|
|
2
|
+
"info": {
|
|
3
|
+
"version": 1,
|
|
4
|
+
"author": "xcode"
|
|
5
|
+
}
|
|
6
|
+
}
|
package/template/App_Resources/iOS/Assets.xcassets/LaunchScreen.AspectFill.imageset/Contents.json
CHANGED
|
@@ -1,23 +1,23 @@
|
|
|
1
1
|
{
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
}
|
|
2
|
+
"images": [
|
|
3
|
+
{
|
|
4
|
+
"idiom": "universal",
|
|
5
|
+
"filename": "LaunchScreen-AspectFill.png",
|
|
6
|
+
"scale": "1x"
|
|
7
|
+
},
|
|
8
|
+
{
|
|
9
|
+
"idiom": "universal",
|
|
10
|
+
"filename": "LaunchScreen-AspectFill@2x.png",
|
|
11
|
+
"scale": "2x"
|
|
12
|
+
},
|
|
13
|
+
{
|
|
14
|
+
"idiom": "universal",
|
|
15
|
+
"filename": "LaunchScreen-AspectFill@3x.png",
|
|
16
|
+
"scale": "3x"
|
|
17
|
+
}
|
|
18
|
+
],
|
|
19
|
+
"info": {
|
|
20
|
+
"version": 1,
|
|
21
|
+
"author": "xcode"
|
|
22
|
+
}
|
|
23
|
+
}
|
package/template/App_Resources/iOS/Assets.xcassets/LaunchScreen.Center.imageset/Contents.json
CHANGED
|
@@ -1,23 +1,23 @@
|
|
|
1
1
|
{
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
}
|
|
2
|
+
"images": [
|
|
3
|
+
{
|
|
4
|
+
"idiom": "universal",
|
|
5
|
+
"filename": "LaunchScreen-Center.png",
|
|
6
|
+
"scale": "1x"
|
|
7
|
+
},
|
|
8
|
+
{
|
|
9
|
+
"idiom": "universal",
|
|
10
|
+
"filename": "LaunchScreen-Center@2x.png",
|
|
11
|
+
"scale": "2x"
|
|
12
|
+
},
|
|
13
|
+
{
|
|
14
|
+
"idiom": "universal",
|
|
15
|
+
"filename": "LaunchScreen-Center@3x.png",
|
|
16
|
+
"scale": "3x"
|
|
17
|
+
}
|
|
18
|
+
],
|
|
19
|
+
"info": {
|
|
20
|
+
"version": 1,
|
|
21
|
+
"author": "xcode"
|
|
22
|
+
}
|
|
23
|
+
}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
2
|
+
"images": [
|
|
3
|
+
{ "filename": "octane.png", "idiom": "universal", "scale": "1x" },
|
|
4
|
+
{ "filename": "octane@2x.png", "idiom": "universal", "scale": "2x" },
|
|
5
|
+
{ "filename": "octane@3x.png", "idiom": "universal", "scale": "3x" }
|
|
6
|
+
],
|
|
7
|
+
"info": { "author": "xcode", "version": 1 }
|
|
8
8
|
}
|
package/template/index.html
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
<!doctype html>
|
|
2
2
|
<html lang="en">
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="utf-8" />
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
6
|
+
<title>octane-xplat app</title>
|
|
7
|
+
</head>
|
|
8
|
+
<body>
|
|
9
|
+
<div id="root"></div>
|
|
10
|
+
<script type="module" src="/src/main.web.tsrx"></script>
|
|
11
|
+
</body>
|
|
12
12
|
</html>
|
|
@@ -1,13 +1,20 @@
|
|
|
1
|
-
import { NativeScriptConfig } from '@nativescript/core'
|
|
1
|
+
import { NativeScriptConfig } from '@nativescript/core'
|
|
2
2
|
|
|
3
3
|
export default {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
}
|
|
4
|
+
id: 'org.nativescript.octanexplat',
|
|
5
|
+
appPath: 'src',
|
|
6
|
+
appResourcesPath: 'App_Resources',
|
|
7
|
+
// The workspace is pnpm-managed — without this the CLI defaults to npm
|
|
8
|
+
// and spawns the bundler with --preserve-symlinks, which breaks isolated
|
|
9
|
+
// node_modules resolution (a package's transitive deps never realpath
|
|
10
|
+
// into the .pnpm store → ERR_MODULE_NOT_FOUND).
|
|
11
|
+
cli: {
|
|
12
|
+
packageManager: 'pnpm',
|
|
13
|
+
},
|
|
14
|
+
android: {
|
|
15
|
+
v8Flags: '--expose_gc',
|
|
16
|
+
markingMode: 'none',
|
|
17
|
+
},
|
|
18
|
+
bundler: 'vite',
|
|
19
|
+
bundlerConfigPath: 'vite.config.native.mts',
|
|
20
|
+
} as NativeScriptConfig
|
package/template/package.json
CHANGED
|
@@ -1,37 +1,44 @@
|
|
|
1
1
|
{
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
2
|
+
"name": "octane-xplat-app",
|
|
3
|
+
"private": true,
|
|
4
|
+
"type": "module",
|
|
5
|
+
"main": "src/index.native.ts",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"xplat": "xplat",
|
|
8
|
+
"dev": "vite",
|
|
9
|
+
"dev:ios": "ns run ios",
|
|
10
|
+
"dev:android": "ns run android",
|
|
11
|
+
"build": "vite build",
|
|
12
|
+
"build:ios": "ns build ios",
|
|
13
|
+
"build:android": "ns build android",
|
|
14
|
+
"lint": "xplat-lint",
|
|
15
|
+
"lint:fix": "xplat-lint --fix",
|
|
16
|
+
"typecheck": "tsrx-tsc --noEmit && tsrx-tsc --noEmit -p tsconfig.native.json"
|
|
17
|
+
},
|
|
18
|
+
"dependencies": {
|
|
19
|
+
"@nativescript-community/gesturehandler": "2.0.45",
|
|
20
|
+
"@nativescript-community/octane": "0.2.1",
|
|
21
|
+
"@nativescript-community/ui-canvas": "4.6.52",
|
|
22
|
+
"@nativescript-community/ui-drawer": "0.1.32",
|
|
23
|
+
"@nativescript-community/ui-svg": "0.2.24",
|
|
24
|
+
"@nativescript/core": "9.1.2",
|
|
25
|
+
"@octane-xplat/ui": "^0.5.0",
|
|
26
|
+
"@valor/nativescript-websockets": "2.0.3",
|
|
27
|
+
"octane": "0.4.0"
|
|
28
|
+
},
|
|
29
|
+
"devDependencies": {
|
|
30
|
+
"@nativescript-community/vite-octane": "0.2.1",
|
|
31
|
+
"@nativescript/android": "9.1.1",
|
|
32
|
+
"@nativescript/ios": "9.1.0",
|
|
33
|
+
"@nativescript/types": "9.0.0",
|
|
34
|
+
"@nativescript/vite": "8.0.11",
|
|
35
|
+
"@octane-xplat/cli": "^0.5.0",
|
|
36
|
+
"@octane-xplat/lint": "^0.1.0",
|
|
37
|
+
"@octanejs/vite-plugin": "0.1.58",
|
|
38
|
+
"@tsrx/typescript-plugin": "0.4.7",
|
|
39
|
+
"oxlint": "^1.74.0",
|
|
40
|
+
"nativescript": "9.1.1",
|
|
41
|
+
"typescript": "5.9.3",
|
|
42
|
+
"vite": "8.3.0"
|
|
43
|
+
}
|
|
37
44
|
}
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
// Native entry — the iOS/Android twin of main.web.tsrx. Same App.tsrx,
|
|
2
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'
|
|
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
8
|
|
|
9
9
|
const entry = {
|
|
10
10
|
create: () => {
|
|
@@ -12,14 +12,14 @@ const entry = {
|
|
|
12
12
|
// window root doubles as the 'root' stack with no registration;
|
|
13
13
|
// named stacks come from TabSpec.stack or registerStack. Route
|
|
14
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
|
|
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
21
|
},
|
|
22
|
-
}
|
|
22
|
+
}
|
|
23
23
|
|
|
24
24
|
// Under the vite dev session the placeholder bootstrap has already run
|
|
25
25
|
// Application.run(), and a module-graph reload re-evaluates this entry.
|
|
@@ -27,7 +27,7 @@ const entry = {
|
|
|
27
27
|
// (iOS core handles the placeholder handoff internally); resetRootView is
|
|
28
28
|
// the documented way to swap the root of a running app.
|
|
29
29
|
if (Application.started) {
|
|
30
|
-
Application.resetRootView(entry)
|
|
30
|
+
Application.resetRootView(entry)
|
|
31
31
|
} else {
|
|
32
|
-
Application.run(entry)
|
|
32
|
+
Application.run(entry)
|
|
33
33
|
}
|
package/template/src/style.css
CHANGED
|
@@ -65,17 +65,19 @@ body {
|
|
|
65
65
|
|
|
66
66
|
.btn {
|
|
67
67
|
background-color: var(--color-primary);
|
|
68
|
-
color: var(--color-
|
|
68
|
+
color: var(--color-onprimary);
|
|
69
69
|
padding: 10px 20px;
|
|
70
70
|
border-radius: 10px;
|
|
71
|
+
corner-shape: squircle;
|
|
71
72
|
font-size: 20px;
|
|
72
73
|
}
|
|
73
74
|
|
|
74
75
|
.chip {
|
|
75
76
|
background-color: var(--color-primary);
|
|
76
|
-
color: var(--color-
|
|
77
|
+
color: var(--color-onprimary);
|
|
77
78
|
padding: 6px 14px;
|
|
78
79
|
border-radius: 14px;
|
|
80
|
+
corner-shape: squircle;
|
|
79
81
|
font-size: 14px;
|
|
80
82
|
}
|
|
81
83
|
|
package/template/tsconfig.json
CHANGED
|
@@ -1,85 +1,8 @@
|
|
|
1
|
-
import { defineConfig
|
|
2
|
-
import {
|
|
3
|
-
import { nativeScriptRenderer } from '@nativescript-community/octane/config';
|
|
1
|
+
import { defineConfig } from 'vite'
|
|
2
|
+
import { xplatNative } from '@octane-xplat/cli/vite'
|
|
4
3
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
* and the device silently stays stale. Warn once when a dev session was
|
|
11
|
-
* fetched but no client ever attached.
|
|
12
|
-
*/
|
|
13
|
-
function nsHmrClientWatchdog(): Plugin {
|
|
14
|
-
return {
|
|
15
|
-
name: 'ns-hmr-client-watchdog',
|
|
16
|
-
configureServer(server) {
|
|
17
|
-
let everConnected = false;
|
|
18
|
-
let timer: ReturnType<typeof setTimeout> | undefined;
|
|
19
|
-
// Hook the raw 'request' event — middlewares.use() appends after the
|
|
20
|
-
// ns plugin's session handler, which ends the response without next(),
|
|
21
|
-
// so a connect middleware never observes /__ns_dev__/session at all.
|
|
22
|
-
server.httpServer?.on('request', (req) => {
|
|
23
|
-
if (!everConnected && req.url?.startsWith('/__ns_dev__/session')) {
|
|
24
|
-
clearTimeout(timer);
|
|
25
|
-
timer = setTimeout(() => {
|
|
26
|
-
if (!everConnected) {
|
|
27
|
-
console.warn(
|
|
28
|
-
'[ns-hmr-client-watchdog] the app fetched its dev session ' +
|
|
29
|
-
'but no /ns-hmr websocket client connected — edits will not ' +
|
|
30
|
-
'reach the device. Check that @valor/nativescript-websockets ' +
|
|
31
|
-
'is installed, `adb reverse tcp:<port>` covers this vite port ' +
|
|
32
|
-
'(physical Android), and the device log for hmr-client errors.',
|
|
33
|
-
);
|
|
34
|
-
}
|
|
35
|
-
}, 15_000);
|
|
36
|
-
}
|
|
37
|
-
});
|
|
38
|
-
server.httpServer?.on('upgrade', (req) => {
|
|
39
|
-
if (req.url?.startsWith('/ns-hmr')) {
|
|
40
|
-
everConnected = true;
|
|
41
|
-
clearTimeout(timer);
|
|
42
|
-
}
|
|
43
|
-
});
|
|
44
|
-
},
|
|
45
|
-
};
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
// The native build (iOS/Android). @octane-xplat/ui resolves through the
|
|
49
|
-
// package's `native` export condition → compiled dist/native — no .tsrx
|
|
50
|
-
// leaves needed from node_modules, so the renderer rule covers src/ only.
|
|
51
|
-
export default defineConfig(({ mode }) =>
|
|
52
|
-
mergeConfig(
|
|
53
|
-
octaneConfig(
|
|
54
|
-
{ mode },
|
|
55
|
-
{
|
|
56
|
-
octane: {
|
|
57
|
-
renderers: {
|
|
58
|
-
registry: { nativescript: nativeScriptRenderer },
|
|
59
|
-
rules: [
|
|
60
|
-
{ include: 'src/**/*.{ts,tsx,tsrx}', renderer: 'nativescript' },
|
|
61
|
-
],
|
|
62
|
-
},
|
|
63
|
-
},
|
|
64
|
-
},
|
|
65
|
-
),
|
|
66
|
-
{
|
|
67
|
-
plugins: [nsHmrClientWatchdog()],
|
|
68
|
-
resolve: {
|
|
69
|
-
conditions: ['native'],
|
|
70
|
-
// The deps-bundle scanner sees source-level 'octane' imports
|
|
71
|
-
// before the compiler retargets hooks — without this it vendors
|
|
72
|
-
// the DOM runtime. Exact match only.
|
|
73
|
-
alias: [{ find: /^octane$/, replacement: 'octane/universal/native' }],
|
|
74
|
-
preserveSymlinks: false,
|
|
75
|
-
// Suffix chain (first match wins): .ios/.android → .native → shared.
|
|
76
|
-
extensions: [
|
|
77
|
-
'.ios.tsrx', '.android.tsrx', '.native.tsrx', '.tsrx',
|
|
78
|
-
'.ios.tsx', '.android.tsx', '.native.tsx', '.tsx',
|
|
79
|
-
'.ios.ts', '.android.ts', '.native.ts', '.mjs', '.mts', '.ts',
|
|
80
|
-
'.jsx', '.js', '.json',
|
|
81
|
-
],
|
|
82
|
-
},
|
|
83
|
-
},
|
|
84
|
-
),
|
|
85
|
-
);
|
|
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))
|
package/template/vite.config.ts
CHANGED
|
@@ -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',
|
|
16
|
-
'.
|
|
17
|
-
'.web.
|
|
18
|
-
'.
|
|
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
|
+
})
|