create-octane-xplat 0.13.0 → 0.15.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 +21 -1
- package/index.mjs +109 -39
- package/library-template/.oxlintrc.json +24 -0
- package/library-template/AGENTS.md +16 -0
- package/{LICENSE → library-template/LICENSE} +6 -6
- package/library-template/README.md +64 -0
- package/library-template/gitignore +5 -0
- package/library-template/package.json +89 -0
- package/library-template/pnpm-workspace.yaml +17 -0
- package/library-template/src/Badge.tsx +21 -0
- package/library-template/src/Badge.web.tsx +21 -0
- package/library-template/src/index.ts +3 -0
- package/library-template/src/index.web.ts +3 -0
- package/library-template/src/normalizeBadgeLabel.ts +12 -0
- package/library-template/src/props.ts +14 -0
- package/library-template/tests/normalizeBadgeLabel.test.ts +6 -0
- package/library-template/tests/packed-consumer.mjs +155 -0
- package/library-template/tsconfig.envs.json +14 -0
- package/library-template/tsconfig.json +27 -0
- package/library-template/tsconfig.types.json +11 -0
- package/library-template/tsrx-typegen.json +16 -0
- package/package.json +5 -3
- package/scaffold.mjs +105 -14
- package/template/.agents/skills/xplat/SKILL.md +1 -1
- package/template/.agents/skills/xplat/references/navigation.md +9 -5
- package/template/.oxlintrc.json +0 -1
- package/template/package.json +19 -18
- package/template/pnpm-workspace.yaml +34 -7
- package/template/src/app.spec.tsrx +80 -2
- package/template/src/failure-reporting.spec.ts +11 -0
- package/template/src/native-runtime.spec.ts +18 -0
- package/template/tsconfig.json +31 -7
- package/template/vite.config.ts +2 -2
- package/template/tsconfig.native.json +0 -18
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
import assert from 'node:assert/strict'
|
|
2
|
+
import { spawnSync } from 'node:child_process'
|
|
3
|
+
import {
|
|
4
|
+
cpSync,
|
|
5
|
+
mkdirSync,
|
|
6
|
+
mkdtempSync,
|
|
7
|
+
readdirSync,
|
|
8
|
+
readFileSync,
|
|
9
|
+
rmSync,
|
|
10
|
+
symlinkSync,
|
|
11
|
+
writeFileSync,
|
|
12
|
+
} from 'node:fs'
|
|
13
|
+
|
|
14
|
+
import { tmpdir } from 'node:os'
|
|
15
|
+
import { dirname, join, resolve } from 'node:path'
|
|
16
|
+
import { fileURLToPath } from 'node:url'
|
|
17
|
+
|
|
18
|
+
const packageRoot = resolve(dirname(fileURLToPath(import.meta.url)), '..')
|
|
19
|
+
const sourceManifest = JSON.parse(readFileSync(join(packageRoot, 'package.json'), 'utf8'))
|
|
20
|
+
const temporary = mkdtempSync(join(tmpdir(), 'octane-library-consumer-'))
|
|
21
|
+
|
|
22
|
+
function run(command, args, cwd) {
|
|
23
|
+
const result = spawnSync(command, args, { cwd, encoding: 'utf8', maxBuffer: 16 * 1024 * 1024 })
|
|
24
|
+
if (result.stdout) {
|
|
25
|
+
process.stdout.write(result.stdout)
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
if (result.stderr) {
|
|
29
|
+
process.stderr.write(result.stderr)
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
if (result.error) {
|
|
33
|
+
throw result.error
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
assert.equal(result.status, 0, `${command} ${args.join(' ')} failed`)
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function installDirectory(root, name, destination) {
|
|
40
|
+
const segments = name.split('/')
|
|
41
|
+
const path = join(destination, 'node_modules', ...segments)
|
|
42
|
+
mkdirSync(dirname(path), { recursive: true })
|
|
43
|
+
cpSync(root, path, { recursive: true })
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
try {
|
|
47
|
+
const pack = join(temporary, 'pack')
|
|
48
|
+
const extracted = join(temporary, 'extracted')
|
|
49
|
+
mkdirSync(pack)
|
|
50
|
+
mkdirSync(extracted)
|
|
51
|
+
run('pnpm', ['pack', '--pack-destination', pack], packageRoot)
|
|
52
|
+
const tarball = readdirSync(pack).find((name) => name.endsWith('.tgz'))
|
|
53
|
+
assert.ok(tarball)
|
|
54
|
+
run('tar', ['-xzf', join(pack, tarball), '-C', extracted], temporary)
|
|
55
|
+
const packed = join(extracted, 'package')
|
|
56
|
+
const manifest = JSON.parse(readFileSync(join(packed, 'package.json'), 'utf8'))
|
|
57
|
+
assert.ok(manifest.exports['.'].web.default.startsWith('./src/'))
|
|
58
|
+
assert.ok(manifest.exports['.'].native.default.startsWith('./src/'))
|
|
59
|
+
assert.ok(manifest.exports['.'].web.types)
|
|
60
|
+
assert.ok(manifest.exports['.'].native.types)
|
|
61
|
+
|
|
62
|
+
for (const target of ['web', 'native']) {
|
|
63
|
+
for (const mode of ['bundler', 'nodenext']) {
|
|
64
|
+
const consumer = join(temporary, `${target}-${mode}`)
|
|
65
|
+
mkdirSync(consumer)
|
|
66
|
+
mkdirSync(join(consumer, 'node_modules'), { recursive: true })
|
|
67
|
+
installDirectory(packed, sourceManifest.name, consumer)
|
|
68
|
+
for (const dependency of [
|
|
69
|
+
'octane',
|
|
70
|
+
'@octane-xplat/ui',
|
|
71
|
+
...(target === 'native'
|
|
72
|
+
? ['@nativescript-community/octane', '@nativescript/core', '@nativescript/types']
|
|
73
|
+
: []),
|
|
74
|
+
]) {
|
|
75
|
+
const source = join(packageRoot, 'node_modules', ...dependency.split('/'))
|
|
76
|
+
const destination = join(consumer, 'node_modules', ...dependency.split('/'))
|
|
77
|
+
mkdirSync(dirname(destination), { recursive: true })
|
|
78
|
+
symlinkSync(source, destination, 'dir')
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
writeFileSync(join(consumer, 'package.json'), '{"type":"module"}')
|
|
82
|
+
writeFileSync(
|
|
83
|
+
join(consumer, 'consumer.tsx'),
|
|
84
|
+
`import { Badge, type BadgeProps } from '${sourceManifest.name}'
|
|
85
|
+
const props: BadgeProps = { label: 'Ready' }
|
|
86
|
+
const element = <Badge {...props} />
|
|
87
|
+
// @ts-expect-error label is required
|
|
88
|
+
const missing = <Badge />
|
|
89
|
+
// @ts-expect-error label must be a string
|
|
90
|
+
const invalid = <Badge label={1} />
|
|
91
|
+
void element; void missing; void invalid
|
|
92
|
+
`,
|
|
93
|
+
)
|
|
94
|
+
|
|
95
|
+
writeFileSync(
|
|
96
|
+
join(consumer, 'tsconfig.json'),
|
|
97
|
+
JSON.stringify(
|
|
98
|
+
{
|
|
99
|
+
compilerOptions: {
|
|
100
|
+
strict: true,
|
|
101
|
+
noEmit: true,
|
|
102
|
+
skipLibCheck: true,
|
|
103
|
+
target: 'esnext',
|
|
104
|
+
types: [],
|
|
105
|
+
module: mode === 'bundler' ? 'esnext' : 'nodenext',
|
|
106
|
+
moduleResolution: mode,
|
|
107
|
+
jsx: 'react-jsx',
|
|
108
|
+
jsxImportSource: target === 'native' ? '@nativescript-community/octane' : 'octane',
|
|
109
|
+
customConditions: [target],
|
|
110
|
+
moduleSuffixes: target === 'web' ? ['.web', ''] : ['.mobile', ''],
|
|
111
|
+
},
|
|
112
|
+
files: ['consumer.tsx'],
|
|
113
|
+
},
|
|
114
|
+
null,
|
|
115
|
+
'\t',
|
|
116
|
+
),
|
|
117
|
+
)
|
|
118
|
+
|
|
119
|
+
run(
|
|
120
|
+
process.execPath,
|
|
121
|
+
[join(packageRoot, 'node_modules/typescript/bin/tsc'), '-p', 'tsconfig.json'],
|
|
122
|
+
consumer,
|
|
123
|
+
)
|
|
124
|
+
|
|
125
|
+
const listed = spawnSync(
|
|
126
|
+
process.execPath,
|
|
127
|
+
[
|
|
128
|
+
join(packageRoot, 'node_modules/typescript/bin/tsc'),
|
|
129
|
+
'-p',
|
|
130
|
+
'tsconfig.json',
|
|
131
|
+
'--listFilesOnly',
|
|
132
|
+
],
|
|
133
|
+
{ cwd: consumer, encoding: 'utf8' },
|
|
134
|
+
)
|
|
135
|
+
|
|
136
|
+
assert.equal(listed.status, 0, listed.stderr)
|
|
137
|
+
const implementation = listed.stdout
|
|
138
|
+
.split('\n')
|
|
139
|
+
.filter(
|
|
140
|
+
(file) =>
|
|
141
|
+
file.includes(`/node_modules/${sourceManifest.name}/src/`) && !file.endsWith('.d.ts'),
|
|
142
|
+
)
|
|
143
|
+
|
|
144
|
+
assert.deepEqual(
|
|
145
|
+
implementation,
|
|
146
|
+
[],
|
|
147
|
+
'consumer types use declarations without package implementation',
|
|
148
|
+
)
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
console.log('packed consumer: web/native declarations pass Bundler and NodeNext')
|
|
153
|
+
} finally {
|
|
154
|
+
rmSync(temporary, { recursive: true, force: true })
|
|
155
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"strict": true,
|
|
4
|
+
"target": "es2022",
|
|
5
|
+
"module": "esnext",
|
|
6
|
+
"moduleResolution": "bundler",
|
|
7
|
+
"jsx": "react-jsx",
|
|
8
|
+
"jsxImportSource": "octane",
|
|
9
|
+
"moduleSuffixes": [
|
|
10
|
+
"",
|
|
11
|
+
".web",
|
|
12
|
+
".ios",
|
|
13
|
+
".android",
|
|
14
|
+
".mobile",
|
|
15
|
+
".native",
|
|
16
|
+
".macos",
|
|
17
|
+
".linux",
|
|
18
|
+
".windows"
|
|
19
|
+
],
|
|
20
|
+
"lib": ["ESNext", "DOM", "DOM.Iterable"],
|
|
21
|
+
"types": ["@nativescript/types"],
|
|
22
|
+
"noEmit": true,
|
|
23
|
+
"skipLibCheck": true
|
|
24
|
+
},
|
|
25
|
+
"include": ["src/**/*.ts", "src/**/*.tsx", "src/**/*.tsrx"],
|
|
26
|
+
"exclude": ["dist", "types", "node_modules"]
|
|
27
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
{
|
|
2
|
+
"targets": {
|
|
3
|
+
"octane": {
|
|
4
|
+
"renderer": "octane",
|
|
5
|
+
"project": "tsconfig.types.json",
|
|
6
|
+
"outDir": "types",
|
|
7
|
+
"entrypoints": {
|
|
8
|
+
".": {
|
|
9
|
+
"source": "src/index.ts",
|
|
10
|
+
"runtime": ["./src/index.ts", "./src/index.web.ts"],
|
|
11
|
+
"types": ["./types/index.d.ts", "./types/index.web.d.ts"]
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
}
|
package/package.json
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "create-octane-xplat",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "Scaffold
|
|
3
|
+
"version": "0.15.0",
|
|
4
|
+
"description": "Scaffold Octane Xplat apps and cross-platform libraries",
|
|
5
5
|
"keywords": [
|
|
6
|
+
"component-library",
|
|
6
7
|
"create",
|
|
7
8
|
"cross-platform",
|
|
8
9
|
"init",
|
|
@@ -25,7 +26,8 @@
|
|
|
25
26
|
"files": [
|
|
26
27
|
"index.mjs",
|
|
27
28
|
"scaffold.mjs",
|
|
28
|
-
"template"
|
|
29
|
+
"template",
|
|
30
|
+
"library-template"
|
|
29
31
|
],
|
|
30
32
|
"type": "module",
|
|
31
33
|
"exports": {
|
package/scaffold.mjs
CHANGED
|
@@ -1,9 +1,7 @@
|
|
|
1
1
|
// Scaffold manifest + compose/apply shared by create-octane-xplat and the
|
|
2
|
-
// CLI's `xplat add`. template/package.json is the canonical store for dep
|
|
3
|
-
// versions and script bodies
|
|
4
|
-
//
|
|
5
|
-
// template/package.json means scripts/bump-versions.mjs keeps working
|
|
6
|
-
// unchanged at release time.
|
|
2
|
+
// CLI's `xplat add`. template/package.json is the canonical store for app dep
|
|
3
|
+
// versions and script bodies; library-template/package.json owns the library
|
|
4
|
+
// starter. The release bump script updates both manifests.
|
|
7
5
|
import {
|
|
8
6
|
cpSync,
|
|
9
7
|
existsSync,
|
|
@@ -14,10 +12,14 @@ import {
|
|
|
14
12
|
writeFileSync,
|
|
15
13
|
} from 'node:fs'
|
|
16
14
|
|
|
17
|
-
import { dirname, join } from 'node:path'
|
|
15
|
+
import { basename, dirname, join, resolve } from 'node:path'
|
|
18
16
|
import { fileURLToPath } from 'node:url'
|
|
19
17
|
|
|
20
18
|
export const TEMPLATE_DIR = join(dirname(fileURLToPath(import.meta.url)), 'template')
|
|
19
|
+
export const LIBRARY_TEMPLATE_DIR = join(
|
|
20
|
+
dirname(fileURLToPath(import.meta.url)),
|
|
21
|
+
'library-template',
|
|
22
|
+
)
|
|
21
23
|
|
|
22
24
|
/** Scaffoldable targets. `files` entries are template-relative paths —
|
|
23
25
|
* directories copy recursively. `deps`/`devDeps`/`scripts` name keys whose
|
|
@@ -64,7 +66,6 @@ export const targets = {
|
|
|
64
66
|
files: [
|
|
65
67
|
'nativescript.config.ts',
|
|
66
68
|
'vite.config.native.mts',
|
|
67
|
-
'tsconfig.native.json',
|
|
68
69
|
'references.d.ts',
|
|
69
70
|
'src/main.ts',
|
|
70
71
|
// On-device Vitest lane — `ns test` builds src/test.ts under
|
|
@@ -72,6 +73,8 @@ export const targets = {
|
|
|
72
73
|
// simulator/emulator through the upstream runner.
|
|
73
74
|
'src/test.ts',
|
|
74
75
|
'src/app.spec.tsrx',
|
|
76
|
+
'src/native-runtime.spec.ts',
|
|
77
|
+
'src/failure-reporting.spec.ts',
|
|
75
78
|
'vitest.config.mts',
|
|
76
79
|
],
|
|
77
80
|
deps: [
|
|
@@ -91,7 +94,7 @@ export const targets = {
|
|
|
91
94
|
'nativescript',
|
|
92
95
|
'vitest',
|
|
93
96
|
],
|
|
94
|
-
typecheck: '
|
|
97
|
+
typecheck: 'xplat typecheck',
|
|
95
98
|
},
|
|
96
99
|
}
|
|
97
100
|
|
|
@@ -100,11 +103,11 @@ export const selectableTargets = Object.keys(targets).filter((id) => !targets[id
|
|
|
100
103
|
/** `pnpm create` without --targets and `xplat add`'s prompt default. */
|
|
101
104
|
export const defaultTargets = ['web', 'ios', 'android']
|
|
102
105
|
|
|
103
|
-
const BASE_TYPECHECK = '
|
|
106
|
+
const BASE_TYPECHECK = 'xplat typecheck'
|
|
104
107
|
|
|
105
108
|
// package.json keys every scaffolded app keeps regardless of target set —
|
|
106
109
|
// the targets' `scripts` lists own the rest.
|
|
107
|
-
const BASE_SCRIPTS = ['xplat', 'lint', 'lint:fix']
|
|
110
|
+
const BASE_SCRIPTS = ['xplat', 'lint', 'lint:fix', 'format']
|
|
108
111
|
|
|
109
112
|
/** Expand `requires` edges, dependencies first. Throws on unknown ids. */
|
|
110
113
|
export function resolveTargets(ids) {
|
|
@@ -223,7 +226,7 @@ export function composeManifest(ids) {
|
|
|
223
226
|
}
|
|
224
227
|
}
|
|
225
228
|
|
|
226
|
-
scripts.typecheck = typecheck.join(' && ')
|
|
229
|
+
scripts.typecheck = [...new Set(typecheck)].join(' && ')
|
|
227
230
|
// Keep the template's key order: scripts sit where they were.
|
|
228
231
|
manifest.scripts = {}
|
|
229
232
|
for (const name of Object.keys(tpl.scripts ?? {})) {
|
|
@@ -384,13 +387,33 @@ function ensurePatchEntries(appRoot, enabledIds) {
|
|
|
384
387
|
return true
|
|
385
388
|
}
|
|
386
389
|
|
|
390
|
+
/**
|
|
391
|
+
* The tsconfig.json a given target set produces. One program covers every
|
|
392
|
+
* platform suffix (moduleSuffixes is the fixed precedence order); a web-only
|
|
393
|
+
* scaffold just drops the native ambient root and entrypoint.
|
|
394
|
+
*/
|
|
395
|
+
export function composeTsconfig(ids) {
|
|
396
|
+
const resolved = new Set(resolveTargets(ids))
|
|
397
|
+
const config = JSON.parse(readFileSync(join(TEMPLATE_DIR, 'tsconfig.json'), 'utf8'))
|
|
398
|
+
if (!resolved.has('native-shared')) {
|
|
399
|
+
delete config.files
|
|
400
|
+
config.compilerOptions.types = (config.compilerOptions.types ?? []).filter(
|
|
401
|
+
(t) => t !== '@nativescript/types',
|
|
402
|
+
)
|
|
403
|
+
|
|
404
|
+
config.exclude = [...new Set([...(config.exclude ?? []), 'src/main.ts'])]
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
return JSON.stringify(config, null, '\t') + '\n'
|
|
408
|
+
}
|
|
409
|
+
|
|
387
410
|
/** Write a scaffold for `ids` into `dir` (must not exist or be empty). */
|
|
388
411
|
export function composeTargets(ids, dir) {
|
|
389
412
|
const resolved = new Set(resolveTargets(ids))
|
|
390
413
|
mkdirSync(dir, { recursive: true })
|
|
391
414
|
|
|
392
415
|
for (const rel of walk(TEMPLATE_DIR)) {
|
|
393
|
-
if (rel === 'package.json' || rel === 'pnpm-workspace.yaml') {
|
|
416
|
+
if (rel === 'package.json' || rel === 'pnpm-workspace.yaml' || rel === 'tsconfig.json') {
|
|
394
417
|
continue // generated below
|
|
395
418
|
}
|
|
396
419
|
|
|
@@ -406,9 +429,43 @@ export function composeTargets(ids, dir) {
|
|
|
406
429
|
|
|
407
430
|
writeFileSync(join(dir, 'package.json'), JSON.stringify(composeManifest(ids), null, '\t') + '\n')
|
|
408
431
|
writeFileSync(join(dir, 'pnpm-workspace.yaml'), composeWorkspaceYaml(ids))
|
|
432
|
+
writeFileSync(join(dir, 'tsconfig.json'), composeTsconfig(ids))
|
|
409
433
|
return [...resolved]
|
|
410
434
|
}
|
|
411
435
|
|
|
436
|
+
/** Write the cross-platform library starter into `dir`. */
|
|
437
|
+
export function composeLibrary(dir, packageName = basename(resolve(dir))) {
|
|
438
|
+
if (
|
|
439
|
+
packageName.length > 214 ||
|
|
440
|
+
!/^(?:@[a-z0-9][a-z0-9._-]*\/)?[a-z0-9][a-z0-9._-]*$/.test(packageName)
|
|
441
|
+
) {
|
|
442
|
+
throw new Error(
|
|
443
|
+
`invalid npm package name "${packageName}" — use lowercase letters, numbers, dots, underscores, or hyphens`,
|
|
444
|
+
)
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
mkdirSync(dir, { recursive: true })
|
|
448
|
+
for (const rel of walk(LIBRARY_TEMPLATE_DIR)) {
|
|
449
|
+
if (rel === 'package.json') {
|
|
450
|
+
continue
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
const dest = join(dir, rel === 'gitignore' ? '.gitignore' : rel)
|
|
454
|
+
mkdirSync(dirname(dest), { recursive: true })
|
|
455
|
+
const source = join(LIBRARY_TEMPLATE_DIR, rel)
|
|
456
|
+
if (/\.(?:md|ts|tsx|mjs|json)$/.test(rel)) {
|
|
457
|
+
const content = readFileSync(source, 'utf8').replaceAll('octane-xplat-library', packageName)
|
|
458
|
+
writeFileSync(dest, content)
|
|
459
|
+
} else {
|
|
460
|
+
cpSync(source, dest, { recursive: true })
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
const manifest = JSON.parse(readFileSync(join(LIBRARY_TEMPLATE_DIR, 'package.json'), 'utf8'))
|
|
465
|
+
manifest.name = packageName
|
|
466
|
+
writeFileSync(join(dir, 'package.json'), JSON.stringify(manifest, null, '\t') + '\n')
|
|
467
|
+
}
|
|
468
|
+
|
|
412
469
|
const readJson = (file) => {
|
|
413
470
|
try {
|
|
414
471
|
return JSON.parse(readFileSync(file, 'utf8'))
|
|
@@ -451,6 +508,33 @@ function copyMissing(rel, appRoot, report) {
|
|
|
451
508
|
report.copied.push(rel)
|
|
452
509
|
}
|
|
453
510
|
|
|
511
|
+
/**
|
|
512
|
+
* Fold a native-capable app's sources into the one tsconfig program: the
|
|
513
|
+
* manifest (tsconfig.envs.json) is gone — one program covers every platform
|
|
514
|
+
* suffix — so enabling native means wiring the ambient declaration file and
|
|
515
|
+
* the entrypoint into the app's own config.
|
|
516
|
+
*/
|
|
517
|
+
function enableNativeInTsconfig(appRoot) {
|
|
518
|
+
const path = join(appRoot, 'tsconfig.json')
|
|
519
|
+
const text = existsSync(path) ? readFileSync(path, 'utf8') : ''
|
|
520
|
+
const config = readJson(path)
|
|
521
|
+
if (!config || typeof config !== 'object') {
|
|
522
|
+
return false
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
const indent = text.match(/\n(\s+)"/)?.[1] ?? '\t'
|
|
526
|
+
config.files = [...new Set(['references.d.ts', ...(config.files ?? [])])]
|
|
527
|
+
config.exclude = (config.exclude ?? []).filter((e) => e !== 'src/main.ts')
|
|
528
|
+
|
|
529
|
+
const types = config.compilerOptions?.types
|
|
530
|
+
if (config.compilerOptions && Array.isArray(types) && !types.includes('@nativescript/types')) {
|
|
531
|
+
config.compilerOptions.types = [...types, '@nativescript/types']
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
writeFileSync(path, JSON.stringify(config, null, indent) + '\n')
|
|
535
|
+
return true
|
|
536
|
+
}
|
|
537
|
+
|
|
454
538
|
/**
|
|
455
539
|
* Enable `id` (plus unmet `requires`) inside an existing app at `appRoot`.
|
|
456
540
|
* Additive only: existing files, scripts, and dep entries are skipped, never
|
|
@@ -515,8 +599,15 @@ export function applyTarget(appRoot, id) {
|
|
|
515
599
|
}
|
|
516
600
|
}
|
|
517
601
|
|
|
518
|
-
if (tid === 'native-shared'
|
|
519
|
-
manifest.main
|
|
602
|
+
if (tid === 'native-shared') {
|
|
603
|
+
if (manifest.main === undefined) {
|
|
604
|
+
manifest.main = tpl.main
|
|
605
|
+
}
|
|
606
|
+
|
|
607
|
+
// Native sources join the app's single typecheck program — pull the
|
|
608
|
+
// ambient declaration entry into the roots and un-exclude the
|
|
609
|
+
// NativeScript entry that a web-only scaffold skips.
|
|
610
|
+
report.tsconfigUpdated = enableNativeInTsconfig(appRoot)
|
|
520
611
|
}
|
|
521
612
|
|
|
522
613
|
touched = true
|
|
@@ -134,7 +134,7 @@ export function NativeGreeting() { return <Text>Hello</Text> }
|
|
|
134
134
|
| Command | Catches |
|
|
135
135
|
| ----------------------------------- | ------------------------------------------------------------------------ |
|
|
136
136
|
| `pnpm lint` | vocabulary / DOM-global / style violations — its output is authoritative |
|
|
137
|
-
| `pnpm typecheck` |
|
|
137
|
+
| `pnpm typecheck` | `tsconfig.json` + every environment declared in `tsconfig.envs.json` |
|
|
138
138
|
| `pnpm dev` | web behavior |
|
|
139
139
|
| `pnpm dev:ios` / `pnpm dev:android` | the platform where behavior actually differs |
|
|
140
140
|
|
|
@@ -5,7 +5,8 @@ web turns it into a URL, while native presents the screen in the app's navigatio
|
|
|
5
5
|
Put components in `.tsrx` files.
|
|
6
6
|
|
|
7
7
|
```tsrx
|
|
8
|
-
import {
|
|
8
|
+
import { Text } from '@octane-xplat/ui'
|
|
9
|
+
import { defineRoutes, registerRoutes } from '@octane-xplat/ui/routing'
|
|
9
10
|
|
|
10
11
|
function Home() { return <Text>Home</Text> }
|
|
11
12
|
function Settings() { return <Text>Settings</Text> }
|
|
@@ -20,7 +21,8 @@ After registering `settings`, use `NavLink` for a screen link or `pushRoute`
|
|
|
20
21
|
in an event handler. `Link` accepts a URL, including an external website.
|
|
21
22
|
|
|
22
23
|
```tsrx
|
|
23
|
-
import { HStack, Link, NavLink, Pressable, Text
|
|
24
|
+
import { HStack, Link, NavLink, Pressable, Text } from '@octane-xplat/ui'
|
|
25
|
+
import { pushRoute } from '@octane-xplat/ui/routing'
|
|
24
26
|
|
|
25
27
|
export function SettingsLinks() {
|
|
26
28
|
const settings = { stack: 'root', name: 'settings', params: {} }
|
|
@@ -36,7 +38,8 @@ Use `useRoute` to read the current screen and `useCanGoBack` to decide whether
|
|
|
36
38
|
to offer a back button. Pass the same stack name to `popRoute`.
|
|
37
39
|
|
|
38
40
|
```tsrx
|
|
39
|
-
import { Pressable, Text
|
|
41
|
+
import { Pressable, Text } from '@octane-xplat/ui'
|
|
42
|
+
import { useRoute, useCanGoBack, popRoute } from '@octane-xplat/ui/routing'
|
|
40
43
|
|
|
41
44
|
export function NavigationStatus() {
|
|
42
45
|
const route = useRoute('root')
|
|
@@ -56,7 +59,7 @@ on web with a warning; do not rely on it to carry application state. With a
|
|
|
56
59
|
registered `/projects/:id` screen, navigate using the project ID:
|
|
57
60
|
|
|
58
61
|
```ts
|
|
59
|
-
import { pushRoute } from '@octane-xplat/ui'
|
|
62
|
+
import { pushRoute } from '@octane-xplat/ui/routing'
|
|
60
63
|
|
|
61
64
|
pushRoute({ stack: 'root', name: 'projects/:id', params: { id: '42' } })
|
|
62
65
|
```
|
|
@@ -66,7 +69,8 @@ initial content; pushing a route into that stack opens it inside the tab pane.
|
|
|
66
69
|
The screen below assumes `settings` is already registered.
|
|
67
70
|
|
|
68
71
|
```tsrx
|
|
69
|
-
import { Tabs, Text, Pressable
|
|
72
|
+
import { Tabs, Text, Pressable } from '@octane-xplat/ui'
|
|
73
|
+
import { pushRoute } from '@octane-xplat/ui/routing'
|
|
70
74
|
|
|
71
75
|
export function AppTabs() {
|
|
72
76
|
return <Tabs tabs={[
|
package/template/.oxlintrc.json
CHANGED
package/template/package.json
CHANGED
|
@@ -15,37 +15,38 @@
|
|
|
15
15
|
"build:android": "ns build android",
|
|
16
16
|
"lint": "xplat-lint",
|
|
17
17
|
"lint:fix": "xplat-lint --fix",
|
|
18
|
-
"typecheck": "
|
|
18
|
+
"typecheck": "xplat typecheck",
|
|
19
|
+
"format": "xplat fmt"
|
|
19
20
|
},
|
|
20
21
|
"dependencies": {
|
|
21
22
|
"@nativescript-community/gesturehandler": "2.0.45",
|
|
22
|
-
"@nativescript-community/octane": "
|
|
23
|
+
"@nativescript-community/octane": "catalog:",
|
|
23
24
|
"@nativescript-community/ui-drawer": "0.1.32",
|
|
24
|
-
"@nativescript/core": "
|
|
25
|
-
"@octane-xplat/effects": "^0.
|
|
26
|
-
"@octane-xplat/image": "^0.
|
|
27
|
-
"@octane-xplat/platform": "^0.
|
|
28
|
-
"@octane-xplat/ui": "^0.
|
|
25
|
+
"@nativescript/core": "catalog:",
|
|
26
|
+
"@octane-xplat/effects": "^0.15.0",
|
|
27
|
+
"@octane-xplat/image": "^0.15.0",
|
|
28
|
+
"@octane-xplat/platform": "^0.15.0",
|
|
29
|
+
"@octane-xplat/ui": "^0.15.0",
|
|
29
30
|
"@valor/nativescript-websockets": "2.0.3",
|
|
30
|
-
"octane": "
|
|
31
|
+
"octane": "catalog:"
|
|
31
32
|
},
|
|
32
33
|
"devDependencies": {
|
|
33
|
-
"@nativescript-community/vite-octane": "
|
|
34
|
+
"@nativescript-community/vite-octane": "catalog:",
|
|
34
35
|
"@nativescript/android": "9.1.1",
|
|
35
36
|
"@nativescript/ios": "9.1.0",
|
|
36
|
-
"@nativescript/types": "
|
|
37
|
-
"@nativescript/unit-test-runner": "
|
|
37
|
+
"@nativescript/types": "catalog:",
|
|
38
|
+
"@nativescript/unit-test-runner": "catalog:",
|
|
38
39
|
"@nativescript/vite": "8.0.17",
|
|
39
|
-
"@octane-xplat/cli": "^0.
|
|
40
|
-
"@octane-xplat/lint": "^0.
|
|
41
|
-
"@octanejs/vite-plugin": "
|
|
40
|
+
"@octane-xplat/cli": "^0.15.0",
|
|
41
|
+
"@octane-xplat/lint": "^0.15.0",
|
|
42
|
+
"@octanejs/vite-plugin": "catalog:",
|
|
42
43
|
"@tsrx/oxc": "0.20.0",
|
|
43
|
-
"@tsrx/typescript-plugin": "
|
|
44
|
+
"@tsrx/typescript-plugin": "catalog:",
|
|
44
45
|
"nativescript": "9.1.1",
|
|
45
46
|
"oxlint": "^1.74.0",
|
|
46
|
-
"typescript": "
|
|
47
|
-
"vite": "
|
|
48
|
-
"vitest": "
|
|
47
|
+
"typescript": "catalog:",
|
|
48
|
+
"vite": "catalog:",
|
|
49
|
+
"vitest": "catalog:"
|
|
49
50
|
},
|
|
50
51
|
"packageManager": "pnpm@11.24.0"
|
|
51
52
|
}
|
|
@@ -14,6 +14,19 @@ allowBuilds:
|
|
|
14
14
|
'@parcel/watcher': true
|
|
15
15
|
nativescript: true
|
|
16
16
|
'@nativescript/core': false
|
|
17
|
+
# Shared version pins — bump these here, every dependency field reads them.
|
|
18
|
+
catalog:
|
|
19
|
+
octane: 0.11.0
|
|
20
|
+
'@nativescript-community/octane': 0.2.4
|
|
21
|
+
'@nativescript-community/vite-octane': 0.2.1
|
|
22
|
+
'@nativescript/core': 9.1.3
|
|
23
|
+
'@nativescript/types': 9.1.4
|
|
24
|
+
'@nativescript/unit-test-runner': 5.0.0
|
|
25
|
+
typescript: 5.9.3
|
|
26
|
+
vite: 8.3.0
|
|
27
|
+
'@octanejs/vite-plugin': 0.1.61
|
|
28
|
+
'@tsrx/typescript-plugin': 0.4.11
|
|
29
|
+
vitest: 4.1.11
|
|
17
30
|
# The patch set covers optional leaf deps (tiptap, linkifyjs, …) the starter
|
|
18
31
|
# doesn't install — tolerate entries that apply to nothing.
|
|
19
32
|
allowUnusedPatches: true
|
|
@@ -29,7 +42,8 @@ patchedDependencies:
|
|
|
29
42
|
# complete universal imperative-ref lifecycle, the Descriptor declaring-owner reader
|
|
30
43
|
# fallback, and the signal-owner/enclosingOwner chain so child reads reuse instance
|
|
31
44
|
# declarations. Verified against the registry 0.11.0 tarball; all hunks re-anchored to the
|
|
32
|
-
# rewritten compile.js and document-owner call sites.
|
|
45
|
+
# rewritten compile.js and document-owner call sites. Disposes DOM provider descendants
|
|
46
|
+
# when children become null or undefined, releasing their refs and effects.
|
|
33
47
|
octane@0.11.0: node_modules/.pnpm-config/@octane-xplat/patches/patches/octane@0.11.0.patch
|
|
34
48
|
# Published 9.1.3 ships the #11446 nested-tab-frame lifecycle fix and iOS explicit-size
|
|
35
49
|
# flex alignment, so those hunks are removed. Retains the unshipped parent-padding
|
|
@@ -66,12 +80,25 @@ patchedDependencies:
|
|
|
66
80
|
# device verbatim and the HTTP loader throws `Cannot find module`, deadlocking dev boot
|
|
67
81
|
# before any boot recording can bundle the dep.
|
|
68
82
|
"@nativescript/vite@8.0.17": node_modules/.pnpm-config/@octane-xplat/patches/patches/@nativescript__vite@8.0.17.patch
|
|
69
|
-
# Driver
|
|
70
|
-
#
|
|
71
|
-
#
|
|
72
|
-
#
|
|
73
|
-
#
|
|
83
|
+
# Driver treats a prop dropped from an update's complete prop bag — or set to
|
|
84
|
+
# `undefined`/`null` — as a removal and restores the documented default:
|
|
85
|
+
# Property/CssProperty setters get `unsetValue`, plain fields restore the captured
|
|
86
|
+
# pre-prop value, and applied `style` keys diff per key. A never-applied `undefined` still
|
|
87
|
+
# skips the write since NativeScript native setters coerce it (e.g. editable →
|
|
88
|
+
# ios.userInteractionEnabled = NO). Aligns intrinsic ref types with Octane callback
|
|
89
|
+
# cleanup and nested arrays with optional entries. Advertises the implemented visibility
|
|
90
|
+
# capability so committed suspense bodies can be retained while pending. Preserves
|
|
91
|
+
# authored Grid row/col/span metadata independently of NativeScript defaults and computed
|
|
92
|
+
# auto-placement, notifying Grid when placement props change.
|
|
74
93
|
"@nativescript-community/octane@0.2.4": node_modules/.pnpm-config/@octane-xplat/patches/patches/@nativescript-community__octane@0.2.4.patch
|
|
94
|
+
# Retains the workspace's NativeScript driver fixes when upgrading to 0.2.5 for portal
|
|
95
|
+
# support: a prop dropped from an update's complete prop bag — or set to
|
|
96
|
+
# `undefined`/`null` — is a removal that restores the documented default
|
|
97
|
+
# (Property/CssProperty setters get `unsetValue`, plain fields the captured pre-prop
|
|
98
|
+
# value, `style` keys diff per key); a never-applied `undefined` still skips the write.
|
|
99
|
+
# Also advertises the implemented visibility capability, carries Octane callback ref
|
|
100
|
+
# types, and preserves authored Grid row/col/span metadata.
|
|
101
|
+
"@nativescript-community/octane@0.2.5": node_modules/.pnpm-config/@octane-xplat/patches/patches/@nativescript-community__octane@0.2.5.patch
|
|
75
102
|
# octaneServerStrategy's SCRIPT_FILE_RE only covers [jt]sx? — .tsrx edits never emit the
|
|
76
103
|
# deferred ns:hmr-delta broadcast, so component HMR silently never reaches the device.
|
|
77
104
|
# Also counts open /ns-hmr sockets so the update summary stops reporting recipients=0 for
|
|
@@ -112,4 +139,4 @@ patchedDependencies:
|
|
|
112
139
|
# bytes to disk.
|
|
113
140
|
"@nativescript-community/ui-image@5.0.17": node_modules/.pnpm-config/@octane-xplat/patches/patches/@nativescript-community__ui-image@5.0.17.patch
|
|
114
141
|
configDependencies:
|
|
115
|
-
"@octane-xplat/patches": 0.
|
|
142
|
+
"@octane-xplat/patches": 0.15.0
|