@octane-xplat/cli 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 +35 -0
- package/package.json +11 -5
- package/src/cli.mjs +10 -9
- package/src/commands/build.mjs +47 -25
- package/src/commands/clean.mjs +22 -12
- package/src/commands/dev.mjs +47 -22
- package/src/commands/doctor.mjs +265 -33
- package/src/commands/routes.mjs +203 -0
- package/src/commands/typecheck.mjs +22 -11
- package/src/procs.mjs +22 -16
- package/src/targets.mjs +62 -30
- package/src/vite.d.ts +20 -0
- package/src/vite.mjs +286 -0
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
import { command, option, optional, string } from '@alloc/cmd-ts'
|
|
2
|
+
import { existsSync, readdirSync, statSync, writeFileSync } from 'node:fs'
|
|
3
|
+
import { join, relative, sep } from 'node:path'
|
|
4
|
+
import * as p from '@clack/prompts'
|
|
5
|
+
|
|
6
|
+
// Mirror of packages/ui/src/route-table.ts conventions — the CLI walks the
|
|
7
|
+
// route dir off disk (no vite glob), so the derivation is re-implemented
|
|
8
|
+
// here. Keep the two in sync: same suffix strip order, same index/_layout
|
|
9
|
+
// rules. All platform variants count — the union is cross-platform.
|
|
10
|
+
const EXT = /\.(tsrx|tsx|ts|mts|cts|js|mjs|cjs|jsx)$/
|
|
11
|
+
const SUFFIX = /\.(web|native|ios|android)$/
|
|
12
|
+
const PARAM = /^\[(.+)\]$/
|
|
13
|
+
const PRESENT = /\+(modal|fade|push)$/
|
|
14
|
+
|
|
15
|
+
function walk(dir, out = []) {
|
|
16
|
+
for (const name of readdirSync(dir).sort()) {
|
|
17
|
+
const full = join(dir, name)
|
|
18
|
+
if (statSync(full).isDirectory()) {
|
|
19
|
+
walk(full, out)
|
|
20
|
+
} else if (EXT.test(name)) {
|
|
21
|
+
out.push(full)
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
return out
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function routeFor(rel) {
|
|
29
|
+
rel = rel.split(sep).join('/').replace(EXT, '')
|
|
30
|
+
const parts = rel.split('/')
|
|
31
|
+
let base = parts[parts.length - 1]
|
|
32
|
+
const sm = SUFFIX.exec(base)
|
|
33
|
+
if (sm) {
|
|
34
|
+
base = base.slice(0, base.length - sm[0].length)
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const pm = PRESENT.exec(base)
|
|
38
|
+
const presentation = pm ? pm[1] : undefined
|
|
39
|
+
if (pm) {
|
|
40
|
+
base = base.slice(0, base.length - pm[0].length)
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
if (base === '_layout') {
|
|
44
|
+
return null
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const segs = parts.slice(0, -1).concat(base)
|
|
48
|
+
if (segs[segs.length - 1] === 'index') {
|
|
49
|
+
segs.pop()
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const segments = segs.map((s) => {
|
|
53
|
+
const m = PARAM.exec(s)
|
|
54
|
+
return m ? ':' + m[1] : s
|
|
55
|
+
})
|
|
56
|
+
|
|
57
|
+
return {
|
|
58
|
+
name: segments.join('/') || 'index',
|
|
59
|
+
params: segments.filter((s) => s.startsWith(':')).map((s) => s.slice(1)),
|
|
60
|
+
presentation,
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/** Generates routes.gen.types.ts + platform manifest glue for a route dir.
|
|
65
|
+
* Returns true when a dir was found and files written; false when no route
|
|
66
|
+
* dir exists (callers that run this implicitly — dev/build — stay quiet). */
|
|
67
|
+
export function generateRoutes(cwd, dir, out) {
|
|
68
|
+
if (!dir) {
|
|
69
|
+
return false
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// `out` is the module basename — three files are emitted:
|
|
73
|
+
// <out>.types.ts shared types (RouteName/Params/Presentations)
|
|
74
|
+
// <out>.web.ts web glob + registerRoutes
|
|
75
|
+
// <out>.native.ts native glob + registerRoutes (Device.os prefer)
|
|
76
|
+
// Importing '<out>' resolves the platform leaf automatically.
|
|
77
|
+
const base = (out ?? join(dir, '..', 'routes.gen')).replace(/\.ts$/, '')
|
|
78
|
+
|
|
79
|
+
// name → {params, presentation}; platform variants of one route
|
|
80
|
+
// collapse to a single entry (union across platforms).
|
|
81
|
+
const seen = new Map()
|
|
82
|
+
for (const file of walk(join(cwd, dir))) {
|
|
83
|
+
const r = routeFor(relative(join(cwd, dir), file))
|
|
84
|
+
if (!r) {
|
|
85
|
+
continue
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
if (!seen.has(r.name)) {
|
|
89
|
+
seen.set(r.name, r)
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
const list = [...seen.values()].sort((a, b) => a.name.localeCompare(b.name))
|
|
94
|
+
|
|
95
|
+
const union = list.length ? list.map((r) => `\n\t| '${r.name}'`).join('') : 'never'
|
|
96
|
+
|
|
97
|
+
const params = list
|
|
98
|
+
.map((r) => `\t'${r.name}': { ${r.params.map((k) => `${k}: string`).join('; ')} }`)
|
|
99
|
+
|
|
100
|
+
const presents = list
|
|
101
|
+
.filter((r) => r.presentation)
|
|
102
|
+
.map((r) => `\t'${r.name}': '${r.presentation}'`)
|
|
103
|
+
|
|
104
|
+
const src = `// Generated by \`xplat routes\` — do not edit. Re-run after
|
|
105
|
+
// touching the route dir (add/remove/rename route files).
|
|
106
|
+
export type RouteName =${union}
|
|
107
|
+
|
|
108
|
+
export interface RouteParams {
|
|
109
|
+
${params.length ? params.join('\n') : '\t// no param routes'}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
export interface RoutePresentations {
|
|
113
|
+
${presents.length ? presents.join('\n') : '\t// no +modal/+fade routes'}
|
|
114
|
+
}
|
|
115
|
+
`
|
|
116
|
+
|
|
117
|
+
writeFileSync(join(cwd, base + '.types.ts'), src)
|
|
118
|
+
|
|
119
|
+
// Platform-suffixed twins carry the actual manifest derivation +
|
|
120
|
+
// registration — routes.gen.web.ts / routes.gen.native.ts resolve
|
|
121
|
+
// through the platform extension chain, so importing './routes.gen'
|
|
122
|
+
// gets the right glob for the platform with no app-side leaf file.
|
|
123
|
+
// Types live in '.types.ts' — './routes.gen' inside a .web.ts sibling
|
|
124
|
+
// would self-resolve.
|
|
125
|
+
const outDir = join(cwd, base, '..')
|
|
126
|
+
const rel = relative(outDir, join(cwd, dir)).split(sep).join('/')
|
|
127
|
+
const globDir = rel.startsWith('.') ? rel : './' + rel
|
|
128
|
+
const baseName = base.split('/').pop()
|
|
129
|
+
const typesRef = `export type { RouteName, RouteParams, RoutePresentations } from './${baseName}.types'`
|
|
130
|
+
|
|
131
|
+
const shared = (prelude, globs, prefer) => `// Generated by \`xplat routes\` — do not edit.
|
|
132
|
+
${prelude}import { deriveRouteManifest, registerRoutes } from '@octane-xplat/ui'
|
|
133
|
+
${typesRef}
|
|
134
|
+
|
|
135
|
+
const files = import.meta.glob(
|
|
136
|
+
[
|
|
137
|
+
'${globDir}/**/*.{tsrx,tsx}',
|
|
138
|
+
${globs}
|
|
139
|
+
],
|
|
140
|
+
{ eager: true },
|
|
141
|
+
)
|
|
142
|
+
|
|
143
|
+
export const routes = deriveRouteManifest(files, ${prefer})
|
|
144
|
+
registerRoutes(routes)
|
|
145
|
+
export const screens = routes.screens
|
|
146
|
+
`
|
|
147
|
+
|
|
148
|
+
writeFileSync(
|
|
149
|
+
join(cwd, base + '.web.ts'),
|
|
150
|
+
shared(
|
|
151
|
+
'',
|
|
152
|
+
[
|
|
153
|
+
`\t\t'!${globDir}/**/*.native.{tsrx,tsx}'`,
|
|
154
|
+
`\t\t'!${globDir}/**/*.ios.{tsrx,tsx}'`,
|
|
155
|
+
`\t\t'!${globDir}/**/*.android.{tsrx,tsx}'`,
|
|
156
|
+
].join(',\n'),
|
|
157
|
+
`['web']`,
|
|
158
|
+
),
|
|
159
|
+
)
|
|
160
|
+
|
|
161
|
+
writeFileSync(
|
|
162
|
+
join(cwd, base + '.native.ts'),
|
|
163
|
+
shared(
|
|
164
|
+
`import { Device } from '@nativescript/core'\n`,
|
|
165
|
+
`\t\t'!${globDir}/**/*.web.{tsrx,tsx}'`,
|
|
166
|
+
`Device.os === 'Android' ? ['android', 'native'] : ['ios', 'native']`,
|
|
167
|
+
),
|
|
168
|
+
)
|
|
169
|
+
|
|
170
|
+
return list.length
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
export const routes = command({
|
|
174
|
+
name: 'routes',
|
|
175
|
+
description: 'Generate routes.gen.ts (typed RouteName/params) from the route dir',
|
|
176
|
+
args: {
|
|
177
|
+
dir: option({
|
|
178
|
+
long: 'dir',
|
|
179
|
+
short: 'd',
|
|
180
|
+
type: optional(string),
|
|
181
|
+
description: 'Route dir — default: first of ./app, ./src/app',
|
|
182
|
+
}),
|
|
183
|
+
out: option({
|
|
184
|
+
long: 'out',
|
|
185
|
+
short: 'o',
|
|
186
|
+
type: optional(string),
|
|
187
|
+
description: 'Output file — default: <route dir>/../routes.gen.ts',
|
|
188
|
+
}),
|
|
189
|
+
},
|
|
190
|
+
handler: async (args) => {
|
|
191
|
+
const cwd = process.cwd()
|
|
192
|
+
const dir = args.dir ?? ['app', 'src/app'].find((d) => existsSync(join(cwd, d)))
|
|
193
|
+
if (!dir) {
|
|
194
|
+
p.log.error('No route dir found — expected ./app or ./src/app (or pass --dir).')
|
|
195
|
+
process.exit(1)
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
const count = generateRoutes(cwd, dir, args.out)
|
|
199
|
+
p.log.success(
|
|
200
|
+
`Wrote routes.gen.{types,web,native}.ts — ${count} route${count === 1 ? '' : 's'}`,
|
|
201
|
+
)
|
|
202
|
+
},
|
|
203
|
+
})
|
|
@@ -1,23 +1,34 @@
|
|
|
1
|
-
import { command } from '@alloc/cmd-ts'
|
|
2
|
-
import { existsSync } from 'node:fs'
|
|
3
|
-
import
|
|
4
|
-
import
|
|
1
|
+
import { command } from '@alloc/cmd-ts'
|
|
2
|
+
import { existsSync } from 'node:fs'
|
|
3
|
+
import { join } from 'node:path'
|
|
4
|
+
import * as p from '@clack/prompts'
|
|
5
|
+
import { runTagged } from '../procs.mjs'
|
|
6
|
+
import { generateRoutes } from './routes.mjs'
|
|
5
7
|
|
|
6
8
|
export const typecheck = command({
|
|
7
9
|
name: 'typecheck',
|
|
8
10
|
description: 'tsrx-tsc --noEmit for every tsconfig present',
|
|
9
11
|
args: {},
|
|
10
12
|
handler: async () => {
|
|
11
|
-
const cwd = process.cwd()
|
|
12
|
-
const
|
|
13
|
+
const cwd = process.cwd()
|
|
14
|
+
const routeDir = ['app', 'src/app'].find((d) => existsSync(join(cwd, d)))
|
|
15
|
+
if (routeDir) {
|
|
16
|
+
generateRoutes(cwd, routeDir)
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const configs = ['tsconfig.json', 'tsconfig.native.json'].filter((f) =>
|
|
20
|
+
existsSync(`${cwd}/${f}`),
|
|
21
|
+
)
|
|
22
|
+
|
|
13
23
|
for (const c of configs) {
|
|
14
24
|
try {
|
|
15
|
-
await runTagged('tsc', 'pnpm', ['exec', 'tsrx-tsc', '--noEmit', '-p', c], cwd)
|
|
25
|
+
await runTagged('tsc', 'pnpm', ['exec', 'tsrx-tsc', '--noEmit', '-p', c], cwd)
|
|
16
26
|
} catch (e) {
|
|
17
|
-
p.log.error(String(e))
|
|
18
|
-
process.exit(1)
|
|
27
|
+
p.log.error(String(e))
|
|
28
|
+
process.exit(1)
|
|
19
29
|
}
|
|
20
30
|
}
|
|
21
|
-
|
|
31
|
+
|
|
32
|
+
p.log.success('Typecheck clean')
|
|
22
33
|
},
|
|
23
|
-
})
|
|
34
|
+
})
|
package/src/procs.mjs
CHANGED
|
@@ -1,33 +1,39 @@
|
|
|
1
1
|
// Prefixed process plumbing — dev runs several tools side by side, so each
|
|
2
2
|
// gets a short tag on its output lines. SIGINT fans out to every child.
|
|
3
|
-
import { spawn } from 'node:child_process'
|
|
3
|
+
import { spawn } from 'node:child_process'
|
|
4
4
|
|
|
5
|
-
const children = new Set()
|
|
5
|
+
const children = new Set()
|
|
6
6
|
|
|
7
7
|
process.on('SIGINT', () => {
|
|
8
|
-
for (const p of children)
|
|
9
|
-
|
|
10
|
-
}
|
|
8
|
+
for (const p of children) {
|
|
9
|
+
p.kill('SIGINT')
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
process.exit(130)
|
|
13
|
+
})
|
|
11
14
|
|
|
12
15
|
/** Spawn long-running, output prefixed `[tag]`. */
|
|
13
16
|
export function spawnTagged(tag, cmd, args, cwd) {
|
|
14
|
-
const p = spawn(cmd, args, { cwd, env: process.env })
|
|
15
|
-
children.add(p)
|
|
17
|
+
const p = spawn(cmd, args, { cwd, env: process.env })
|
|
18
|
+
children.add(p)
|
|
16
19
|
const prefix = (chunk) => {
|
|
17
20
|
for (const line of chunk.toString().split('\n')) {
|
|
18
|
-
if (line.trim())
|
|
21
|
+
if (line.trim()) {
|
|
22
|
+
process.stdout.write(`[${tag}] ${line}\n`)
|
|
23
|
+
}
|
|
19
24
|
}
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
p.
|
|
23
|
-
p.on('
|
|
24
|
-
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
p.stdout.on('data', prefix)
|
|
28
|
+
p.stderr.on('data', prefix)
|
|
29
|
+
p.on('exit', () => children.delete(p))
|
|
30
|
+
return p
|
|
25
31
|
}
|
|
26
32
|
|
|
27
33
|
/** Spawn to completion, output prefixed `[tag]`. Resolves on exit 0. */
|
|
28
34
|
export function runTagged(tag, cmd, args, cwd) {
|
|
29
35
|
return new Promise((resolve, reject) => {
|
|
30
|
-
const p = spawnTagged(tag, cmd, args, cwd)
|
|
31
|
-
p.on('exit', (code) => (code === 0 ? resolve() : reject(new Error(`${tag} exited ${code}`))))
|
|
32
|
-
})
|
|
36
|
+
const p = spawnTagged(tag, cmd, args, cwd)
|
|
37
|
+
p.on('exit', (code) => (code === 0 ? resolve() : reject(new Error(`${tag} exited ${code}`))))
|
|
38
|
+
})
|
|
33
39
|
}
|
package/src/targets.mjs
CHANGED
|
@@ -1,78 +1,110 @@
|
|
|
1
1
|
// Target + device discovery. Everything degrades quietly — a missing
|
|
2
2
|
// toolchain means the target is absent from prompts, not an error.
|
|
3
|
-
import { existsSync } from 'node:fs'
|
|
4
|
-
import { execFileSync } from 'node:child_process'
|
|
3
|
+
import { existsSync } from 'node:fs'
|
|
4
|
+
import { execFileSync } from 'node:child_process'
|
|
5
5
|
|
|
6
6
|
const run = (cmd, args) => {
|
|
7
7
|
try {
|
|
8
|
-
return execFileSync(cmd, args, {
|
|
8
|
+
return execFileSync(cmd, args, {
|
|
9
|
+
encoding: 'utf8',
|
|
10
|
+
timeout: 15000,
|
|
11
|
+
stdio: ['ignore', 'pipe', 'ignore'],
|
|
12
|
+
})
|
|
9
13
|
} catch {
|
|
10
|
-
return null
|
|
14
|
+
return null
|
|
11
15
|
}
|
|
12
|
-
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export const hasWeb = (cwd) =>
|
|
19
|
+
existsSync(`${cwd}/vite.config.ts`) || existsSync(`${cwd}/vite.config.mts`)
|
|
13
20
|
|
|
14
|
-
export const
|
|
15
|
-
export const hasNative = (cwd) => existsSync(`${cwd}/nativescript.config.ts`);
|
|
21
|
+
export const hasNative = (cwd) => existsSync(`${cwd}/nativescript.config.ts`)
|
|
16
22
|
|
|
17
23
|
/** iOS targets: booted sims first, then other available sims, then physical devices. */
|
|
18
24
|
export function iosTargets() {
|
|
19
|
-
const out = run('xcrun', ['simctl', 'list', 'devices', 'available', '-j'])
|
|
20
|
-
if (!out)
|
|
25
|
+
const out = run('xcrun', ['simctl', 'list', 'devices', 'available', '-j'])
|
|
26
|
+
if (!out) {
|
|
27
|
+
return []
|
|
28
|
+
}
|
|
29
|
+
|
|
21
30
|
try {
|
|
22
|
-
const j = JSON.parse(out)
|
|
23
|
-
const sims = []
|
|
31
|
+
const j = JSON.parse(out)
|
|
32
|
+
const sims = []
|
|
24
33
|
for (const list of Object.values(j.devices ?? {})) {
|
|
25
34
|
for (const d of list) {
|
|
26
|
-
if (!d.isAvailable)
|
|
35
|
+
if (!d.isAvailable) {
|
|
36
|
+
continue
|
|
37
|
+
}
|
|
38
|
+
|
|
27
39
|
sims.push({
|
|
28
40
|
kind: 'ios',
|
|
29
41
|
id: d.udid,
|
|
30
42
|
name: d.name + (d.state === 'Booted' ? ' (booted)' : ''),
|
|
31
43
|
device: d.udid,
|
|
32
44
|
booted: d.state === 'Booted',
|
|
33
|
-
})
|
|
45
|
+
})
|
|
34
46
|
}
|
|
35
47
|
}
|
|
48
|
+
|
|
36
49
|
// Booted first — that's almost always the one you mean.
|
|
37
|
-
return sims.sort((a, b) => (b.booted ? 1 : 0) - (a.booted ? 1 : 0))
|
|
50
|
+
return sims.sort((a, b) => (b.booted ? 1 : 0) - (a.booted ? 1 : 0))
|
|
38
51
|
} catch {
|
|
39
|
-
return []
|
|
52
|
+
return []
|
|
40
53
|
}
|
|
41
54
|
}
|
|
42
55
|
|
|
43
56
|
/** Android targets: emulators + physical devices from adb. */
|
|
44
57
|
export function androidTargets() {
|
|
45
|
-
const out = run('adb', ['devices'])
|
|
46
|
-
if (!out)
|
|
58
|
+
const out = run('adb', ['devices'])
|
|
59
|
+
if (!out) {
|
|
60
|
+
return []
|
|
61
|
+
}
|
|
62
|
+
|
|
47
63
|
return out
|
|
48
64
|
.split('\n')
|
|
49
65
|
.slice(1)
|
|
50
66
|
.map((l) => l.trim())
|
|
51
67
|
.filter((l) => l.endsWith('\tdevice'))
|
|
52
68
|
.map((l) => {
|
|
53
|
-
const serial = l.split('\t')[0]
|
|
54
|
-
const emu = serial.startsWith('emulator-')
|
|
55
|
-
return {
|
|
56
|
-
|
|
69
|
+
const serial = l.split('\t')[0]
|
|
70
|
+
const emu = serial.startsWith('emulator-')
|
|
71
|
+
return {
|
|
72
|
+
kind: 'android',
|
|
73
|
+
id: serial,
|
|
74
|
+
name: emu ? `${serial} (emulator)` : `${serial} (device)`,
|
|
75
|
+
device: serial,
|
|
76
|
+
}
|
|
77
|
+
})
|
|
57
78
|
}
|
|
58
79
|
|
|
59
80
|
/** Every launchable target for this project + machine. */
|
|
60
81
|
export function discoverTargets(cwd) {
|
|
61
|
-
const targets = []
|
|
62
|
-
if (hasWeb(cwd))
|
|
82
|
+
const targets = []
|
|
83
|
+
if (hasWeb(cwd)) {
|
|
84
|
+
targets.push({ kind: 'web', id: 'web', name: 'Web (vite :5200)' })
|
|
85
|
+
}
|
|
86
|
+
|
|
63
87
|
if (hasNative(cwd)) {
|
|
64
|
-
targets.push(...iosTargets(), ...androidTargets())
|
|
88
|
+
targets.push(...iosTargets(), ...androidTargets())
|
|
65
89
|
}
|
|
66
|
-
|
|
90
|
+
|
|
91
|
+
return targets
|
|
67
92
|
}
|
|
68
93
|
|
|
69
94
|
/** Distinct platform buckets for `build` — one entry per platform, no device picks. */
|
|
70
95
|
export function buildTargets(cwd) {
|
|
71
|
-
const t = []
|
|
72
|
-
if (hasWeb(cwd))
|
|
96
|
+
const t = []
|
|
97
|
+
if (hasWeb(cwd)) {
|
|
98
|
+
t.push({ kind: 'web', id: 'web', name: 'Web (vite build)' })
|
|
99
|
+
}
|
|
100
|
+
|
|
73
101
|
if (hasNative(cwd)) {
|
|
74
|
-
if (iosTargets().length || run('xcrun', ['--version']))
|
|
75
|
-
|
|
102
|
+
if (iosTargets().length || run('xcrun', ['--version'])) {
|
|
103
|
+
t.push({ kind: 'ios', id: 'ios', name: 'iOS (ns build ios)' })
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
t.push({ kind: 'android', id: 'android', name: 'Android (ns build android)' })
|
|
76
107
|
}
|
|
77
|
-
|
|
108
|
+
|
|
109
|
+
return t
|
|
78
110
|
}
|
package/src/vite.d.ts
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { UserConfig } from 'vite'
|
|
2
|
+
|
|
3
|
+
export const nativeExtensions: string[]
|
|
4
|
+
|
|
5
|
+
export interface XplatNativeOptions {
|
|
6
|
+
/** Extra optimizeDeps.exclude entries — app-shipped @nativescript plugins. */
|
|
7
|
+
deps?: string[]
|
|
8
|
+
/** Renderer rules override — defaults cover src/ + linked package source. */
|
|
9
|
+
rules?: unknown[]
|
|
10
|
+
/** App-specific config merged in last (plugins, server, …). */
|
|
11
|
+
extra?: UserConfig
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/** Full native (iOS/Android) Vite config — the shared preset. Resolves the
|
|
15
|
+
* app's own vite/vite-octane/octane toolchain (async because it loads
|
|
16
|
+
* through the app's node_modules). */
|
|
17
|
+
export function xplatNative(
|
|
18
|
+
env: { mode: string } | string,
|
|
19
|
+
opts?: XplatNativeOptions,
|
|
20
|
+
): Promise<UserConfig>
|