@octane-xplat/cli 0.4.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/package.json +1 -1
- package/src/commands/doctor.mjs +186 -2
- package/src/vite.mjs +15 -0
package/package.json
CHANGED
package/src/commands/doctor.mjs
CHANGED
|
@@ -1,7 +1,178 @@
|
|
|
1
1
|
import { command } from '@alloc/cmd-ts'
|
|
2
2
|
import { execFileSync } from 'node:child_process'
|
|
3
|
+
import { existsSync, readdirSync, readFileSync } from 'node:fs'
|
|
4
|
+
import { dirname, join, parse, relative, resolve } from 'node:path'
|
|
3
5
|
import * as p from '@clack/prompts'
|
|
4
6
|
|
|
7
|
+
const frameworkFallbacks = {
|
|
8
|
+
'@octane-xplat/ui': [
|
|
9
|
+
'@nativescript-community/gesturehandler',
|
|
10
|
+
'@nativescript-community/ui-canvas',
|
|
11
|
+
'@nativescript-community/ui-drawer',
|
|
12
|
+
'@nativescript-community/ui-svg',
|
|
13
|
+
],
|
|
14
|
+
'@octane-xplat/platform': [
|
|
15
|
+
'@nativescript-community/ui-document-picker',
|
|
16
|
+
'@nativescript/biometrics',
|
|
17
|
+
'@nativescript/geolocation',
|
|
18
|
+
'@nativescript/haptics',
|
|
19
|
+
'@nativescript/imagepicker',
|
|
20
|
+
'@nativescript/local-notifications',
|
|
21
|
+
'@nativescript/secure-storage',
|
|
22
|
+
'@nativescript/social-share',
|
|
23
|
+
'nativescript-clipboard',
|
|
24
|
+
],
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const nativePlugin = (name) =>
|
|
28
|
+
(name.startsWith('@nativescript/') ||
|
|
29
|
+
name.startsWith('@nativescript-community/') ||
|
|
30
|
+
name.startsWith('nativescript-')) &&
|
|
31
|
+
!name.endsWith('/octane') &&
|
|
32
|
+
!name.endsWith('/core')
|
|
33
|
+
|
|
34
|
+
const readJson = (file) => {
|
|
35
|
+
try {
|
|
36
|
+
return JSON.parse(readFileSync(file, 'utf8'))
|
|
37
|
+
} catch {
|
|
38
|
+
return null
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const packageName = (specifier) => {
|
|
43
|
+
if (!specifier.startsWith('@') && !specifier.includes('/')) {return specifier}
|
|
44
|
+
const parts = specifier.split('/')
|
|
45
|
+
return specifier.startsWith('@') ? parts.slice(0, 2).join('/') : parts[0]
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const importSpecifiers = (source) => {
|
|
49
|
+
const specs = new Set()
|
|
50
|
+
for (const pattern of [
|
|
51
|
+
/\bfrom\s*['"]([^'"]+)['"]/g,
|
|
52
|
+
/\bimport\s*['"]([^'"]+)['"]/g,
|
|
53
|
+
/\bimport\s*\(\s*['"]([^'"]+)['"]\s*\)/g,
|
|
54
|
+
]) {
|
|
55
|
+
for (const match of source.matchAll(pattern)) {specs.add(match[1])}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
return specs
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const sourceFiles = (root) => {
|
|
62
|
+
if (!existsSync(root)) {return []}
|
|
63
|
+
const out = []
|
|
64
|
+
const visit = (dir) => {
|
|
65
|
+
for (const entry of readdirSync(dir, { withFileTypes: true })) {
|
|
66
|
+
if (entry.name === 'node_modules' || entry.name.startsWith('.')) {continue}
|
|
67
|
+
const file = join(dir, entry.name)
|
|
68
|
+
if (entry.isDirectory()) {visit(file)}
|
|
69
|
+
else if (/\.(?:m?[jt]sx?|tsrx)$/.test(entry.name)) {out.push(file)}
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
visit(root)
|
|
74
|
+
return out
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const workspacePackages = (cwd) => {
|
|
78
|
+
const found = new Map()
|
|
79
|
+
let dir = resolve(cwd)
|
|
80
|
+
while (true) {
|
|
81
|
+
for (const group of ['apps', 'packages']) {
|
|
82
|
+
const parent = join(dir, group)
|
|
83
|
+
if (!existsSync(parent)) {continue}
|
|
84
|
+
for (const name of readdirSync(parent)) {
|
|
85
|
+
const root = join(parent, name)
|
|
86
|
+
const manifest = readJson(join(root, 'package.json'))
|
|
87
|
+
if (manifest?.name) {found.set(manifest.name, root)}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const next = dirname(dir)
|
|
92
|
+
if (next === dir) {break}
|
|
93
|
+
dir = next
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
return found
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
const packageRoot = (cwd, name, workspaces) => {
|
|
100
|
+
const direct = join(cwd, 'node_modules', ...name.split('/'))
|
|
101
|
+
if (existsSync(join(direct, 'package.json'))) {return direct}
|
|
102
|
+
return workspaces.get(name)
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
const frameworkPlugins = (cwd, name, workspaces) => {
|
|
106
|
+
const root = packageRoot(cwd, name, workspaces)
|
|
107
|
+
const manifest = root && readJson(join(root, 'package.json'))
|
|
108
|
+
const declared = [
|
|
109
|
+
...Object.keys(manifest?.dependencies ?? {}),
|
|
110
|
+
...Object.keys(manifest?.peerDependencies ?? {}),
|
|
111
|
+
].filter(nativePlugin)
|
|
112
|
+
|
|
113
|
+
return declared.length ? declared : frameworkFallbacks[name] ?? []
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Find native plugins required by the framework packages reachable from an app
|
|
118
|
+
* source tree, then compare them with the app's own package.json. This is a
|
|
119
|
+
* warning because the web target does not need native plugin declarations and
|
|
120
|
+
* a package may intentionally keep an optional native capability unused.
|
|
121
|
+
*/
|
|
122
|
+
export function findMissingPluginDeclarations(cwd) {
|
|
123
|
+
const manifest = readJson(join(cwd, 'package.json')) ?? {}
|
|
124
|
+
const owned = new Set([
|
|
125
|
+
...Object.keys(manifest.dependencies ?? {}),
|
|
126
|
+
...Object.keys(manifest.devDependencies ?? {}),
|
|
127
|
+
...Object.keys(manifest.optionalDependencies ?? {}),
|
|
128
|
+
...Object.keys(manifest.peerDependencies ?? {}),
|
|
129
|
+
])
|
|
130
|
+
|
|
131
|
+
const workspaces = workspacePackages(cwd)
|
|
132
|
+
const pending = sourceFiles(join(cwd, 'src'))
|
|
133
|
+
for (const dir of ['app', 'src/app']) {pending.push(...sourceFiles(join(cwd, dir)))}
|
|
134
|
+
const visited = new Set()
|
|
135
|
+
const frameworks = new Map()
|
|
136
|
+
|
|
137
|
+
while (pending.length) {
|
|
138
|
+
const file = pending.pop()
|
|
139
|
+
if (visited.has(file)) {continue}
|
|
140
|
+
visited.add(file)
|
|
141
|
+
let source
|
|
142
|
+
try {
|
|
143
|
+
source = readFileSync(file, 'utf8')
|
|
144
|
+
} catch {
|
|
145
|
+
continue
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
for (const specifier of importSpecifiers(source)) {
|
|
149
|
+
const name = packageName(specifier)
|
|
150
|
+
if (frameworkFallbacks[name]) {
|
|
151
|
+
if (!frameworks.has(name)) {frameworks.set(name, new Set())}
|
|
152
|
+
frameworks.get(name).add(relative(cwd, file) || parse(file).base)
|
|
153
|
+
continue
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
const root = packageRoot(cwd, name, workspaces)
|
|
157
|
+
if (root && workspaces.has(name)) {pending.push(...sourceFiles(root))}
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
const missing = new Map()
|
|
162
|
+
for (const [framework] of frameworks) {
|
|
163
|
+
for (const plugin of frameworkPlugins(cwd, framework, workspaces)) {
|
|
164
|
+
if (!owned.has(plugin)) {
|
|
165
|
+
if (!missing.has(plugin)) {missing.set(plugin, new Set())}
|
|
166
|
+
missing.get(plugin).add(framework)
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
return [...missing.entries()]
|
|
172
|
+
.sort(([a], [b]) => a.localeCompare(b))
|
|
173
|
+
.map(([plugin, frameworks]) => ({ plugin, frameworks: [...frameworks].sort() }))
|
|
174
|
+
}
|
|
175
|
+
|
|
5
176
|
const check = (cmd, args) => {
|
|
6
177
|
try {
|
|
7
178
|
return {
|
|
@@ -25,6 +196,7 @@ export const doctor = command({
|
|
|
25
196
|
description: 'Check the toolchain for web + native builds',
|
|
26
197
|
args: {},
|
|
27
198
|
handler: async () => {
|
|
199
|
+
const cwd = process.cwd()
|
|
28
200
|
p.intro('xplat doctor')
|
|
29
201
|
const rows = []
|
|
30
202
|
const row = (name, ok, detail, hint) => rows.push({ name, ok, detail, hint })
|
|
@@ -77,6 +249,13 @@ export const doctor = command({
|
|
|
77
249
|
'JDK 17 (JDK 25 breaks the Android toolchain)',
|
|
78
250
|
)
|
|
79
251
|
|
|
252
|
+
const pluginWarnings = findMissingPluginDeclarations(cwd)
|
|
253
|
+
for (const { plugin, frameworks } of pluginWarnings) {
|
|
254
|
+
p.log.warn(
|
|
255
|
+
`native plugin declaration — ${plugin} is required by ${frameworks.join(', ')}; add it to this app's package.json`,
|
|
256
|
+
)
|
|
257
|
+
}
|
|
258
|
+
|
|
80
259
|
let bad = 0
|
|
81
260
|
for (const r of rows) {
|
|
82
261
|
if (r.ok) {
|
|
@@ -87,10 +266,15 @@ export const doctor = command({
|
|
|
87
266
|
}
|
|
88
267
|
}
|
|
89
268
|
|
|
90
|
-
|
|
269
|
+
const summary =
|
|
91
270
|
bad === 0
|
|
92
271
|
? 'All checks pass'
|
|
93
|
-
: `${bad} missing — web still works, native targets need the above
|
|
272
|
+
: `${bad} missing — web still works, native targets need the above`
|
|
273
|
+
|
|
274
|
+
p.outro(
|
|
275
|
+
pluginWarnings.length
|
|
276
|
+
? `${summary}; ${pluginWarnings.length} native plugin declaration warning(s)`
|
|
277
|
+
: summary,
|
|
94
278
|
)
|
|
95
279
|
},
|
|
96
280
|
})
|
package/src/vite.mjs
CHANGED
|
@@ -233,7 +233,22 @@ export async function xplatNative(env, opts = {}) {
|
|
|
233
233
|
),
|
|
234
234
|
{
|
|
235
235
|
plugins: [pxToDip(), nsHmrClientWatchdog()],
|
|
236
|
+
build: {
|
|
237
|
+
rolldownOptions: {
|
|
238
|
+
// Dev/HMR universal emit retains JSX in expression props
|
|
239
|
+
// (e.g. `renderItem={(item) => <gridlayout>…}`) for the file's
|
|
240
|
+
// own @jsxImportSource pragma to lower. Rolldown only parses
|
|
241
|
+
// JSX in script-lang modules, so mark .tsrx transform
|
|
242
|
+
// output tsx.
|
|
243
|
+
moduleTypes: { '.tsrx': 'tsx' },
|
|
244
|
+
},
|
|
245
|
+
},
|
|
236
246
|
optimizeDeps: {
|
|
247
|
+
// vite-octane excludes `octane` from the deps bundle, so the
|
|
248
|
+
// optimizeDeps graph walk never descends into it — alien-signals
|
|
249
|
+
// (the signal impl imported by octane/universal/native) is missed
|
|
250
|
+
// and the device fetch 504s. Seed it directly.
|
|
251
|
+
include: ['alien-signals', 'alien-signals/system'],
|
|
237
252
|
// Flattened optimizeDeps chunks get mangled by the /ns/m device
|
|
238
253
|
// transform (`import import "/ns/core/utils"`) and miss the vendor
|
|
239
254
|
// manifest — serve @nativescript plugins per-module instead.
|