create-octane-xplat 0.15.0 → 0.16.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +57 -0
- package/compatibility.json +199 -0
- package/compatibility.ts +109 -0
- package/dist/compatibility.js +66 -0
- package/dist/index.js +132 -0
- package/dist/scaffold.js +439 -0
- package/{index.mjs → index.ts} +3 -4
- package/library-template/AGENTS.md +3 -1
- package/library-template/README.md +20 -0
- package/library-template/package.json +22 -19
- package/library-template/pnpm-workspace.yaml +150 -11
- package/library-template/src/Badge.tsx +1 -1
- package/library-template/src/Badge.web.tsx +1 -1
- package/library-template/tests/packed-consumer.mjs +2 -1
- package/library-template/tsconfig.envs.json +3 -0
- package/library-template/tsconfig.json +1 -1
- package/library-template/tsrx-typegen.json +1 -1
- package/package.json +23 -6
- package/{scaffold.mjs → scaffold.ts} +51 -40
- package/template/.agents/skills/xplat/SKILL.md +10 -2
- package/template/.agents/skills/xplat/references/components.md +2 -2
- package/template/.agents/skills/xplat/references/platform.md +2 -2
- package/template/AGENTS.md +47 -0
- package/template/App_Resources/Android/app.gradle +1 -1
- package/template/package.json +33 -31
- package/template/pnpm-workspace.yaml +64 -29
- package/template/src/app.web.test.tsx +20 -0
- package/template/src/main.web.tsrx +1 -0
- package/template/src/test-setup.web.ts +17 -0
- package/template/tsconfig.json +1 -1
- package/template/vite.config.ts +2 -2
- package/template/vitest.web.config.mts +13 -0
- package/library-template/.oxlintrc.json +0 -24
- package/template/.oxlintrc.json +0 -24
package/README.md
CHANGED
|
@@ -16,6 +16,20 @@ With Node.js and pnpm 11.24.0 installed:
|
|
|
16
16
|
pnpm create octane-xplat my-app
|
|
17
17
|
```
|
|
18
18
|
|
|
19
|
+
The starter's `tsconfig.json` selects the shared native JSX types from its
|
|
20
|
+
direct `@octane-xplat/jsx` dependency. Browser JSX leaves select DOM types on
|
|
21
|
+
their first line, as `src/main.web.tsrx` does:
|
|
22
|
+
|
|
23
|
+
```tsx
|
|
24
|
+
/** @jsxImportSource @octane-xplat/jsx/dom */
|
|
25
|
+
export function BrowserMessage() {
|
|
26
|
+
return <p>Ready</p>
|
|
27
|
+
}
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
See [platform files and JSX types](https://octane-xplat.goddardai.org/platform/module-resolution#typescript)
|
|
31
|
+
when adding a native or browser implementation.
|
|
32
|
+
|
|
19
33
|
## Start a library package
|
|
20
34
|
|
|
21
35
|
To start a reusable package instead of an app, select the library template:
|
|
@@ -63,6 +77,49 @@ The generated app pins pnpm 11.24.0. Its `pnpm-workspace.yaml` loads
|
|
|
63
77
|
`@octane-xplat/patches` as a `configDependencies` package, making the
|
|
64
78
|
framework patches available during the first install.
|
|
65
79
|
|
|
80
|
+
Apps start with the runtime, build packages, and tests their selected platforms
|
|
81
|
+
need. Feature packages are added when you use them.
|
|
82
|
+
|
|
83
|
+
## Run app tests
|
|
84
|
+
|
|
85
|
+
New apps include Vitest, configuration, sample tests, and scripts. From your
|
|
86
|
+
app's folder, run the command for a platform you enabled:
|
|
87
|
+
|
|
88
|
+
```sh
|
|
89
|
+
pnpm test:web # when web is enabled
|
|
90
|
+
pnpm test:ios -- --device DEVICE_UDID # when iOS is enabled
|
|
91
|
+
pnpm test:android -- --device DEVICE_SERIAL # when Android is enabled
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
Web tests use jsdom, which simulates a browser's
|
|
95
|
+
document on your computer; phone tests use NativeScript's test runner on a
|
|
96
|
+
selected simulator or device. The runners are explicit development dependencies
|
|
97
|
+
with framework-managed versions in `catalog:xplat`. Adapt the sample tests to
|
|
98
|
+
your app as you replace the starter screen.
|
|
99
|
+
|
|
100
|
+
Platforms added later also receive their test setup. The library template
|
|
101
|
+
includes its component tests and publishing checks.
|
|
102
|
+
See [checking an app](../../docs/verify/testing.md#add-automated-tests) for the
|
|
103
|
+
commands, file conventions, and checks that still need a browser or phone.
|
|
104
|
+
|
|
105
|
+
## Keep compatible versions
|
|
106
|
+
|
|
107
|
+
App and library starters share the framework's compatible compiler, runtime,
|
|
108
|
+
and tool versions through `catalog:xplat`. To update an existing project,
|
|
109
|
+
preview and apply a published CLI release's set, then check the installed
|
|
110
|
+
packages:
|
|
111
|
+
|
|
112
|
+
```sh
|
|
113
|
+
pnpm dlx @octane-xplat/cli@latest upgrade --check
|
|
114
|
+
pnpm dlx @octane-xplat/cli@latest upgrade
|
|
115
|
+
pnpm xplat doctor
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
An upgrade covers the workspace's managed dependencies and patches. Your
|
|
119
|
+
app-specific packages and library consumer peer ranges remain yours. See
|
|
120
|
+
[compatible versions](../cli/README.md#compatible-versions) for local links,
|
|
121
|
+
custom patches, deferred installs, and failure recovery.
|
|
122
|
+
|
|
66
123
|
Leave the server running while you edit. After stopping it, restart from the
|
|
67
124
|
app directory:
|
|
68
125
|
|
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$comment": "Release compatibility set, shared by create-octane-xplat and xplat doctor/upgrade. Edit external pins here, then run pnpm sync:compatibility. Framework package names are generated from the lockstep release set. Optional packages are managed only when declared; library consumer peer ranges remain app-owned.",
|
|
3
|
+
"schemaVersion": 1,
|
|
4
|
+
"frameworkVersion": "0.16.1",
|
|
5
|
+
"tools": {
|
|
6
|
+
"nodeMinimum": "22.18.0",
|
|
7
|
+
"pnpm": "11.24.0"
|
|
8
|
+
},
|
|
9
|
+
"frameworkPackages": [
|
|
10
|
+
"@octane-xplat/audio",
|
|
11
|
+
"@octane-xplat/auth",
|
|
12
|
+
"@octane-xplat/bamboo",
|
|
13
|
+
"@octane-xplat/biometrics",
|
|
14
|
+
"@octane-xplat/camera",
|
|
15
|
+
"@octane-xplat/canvas",
|
|
16
|
+
"@octane-xplat/charts",
|
|
17
|
+
"@octane-xplat/cli",
|
|
18
|
+
"@octane-xplat/context-menu",
|
|
19
|
+
"@octane-xplat/date-picker",
|
|
20
|
+
"@octane-xplat/desktop-webview",
|
|
21
|
+
"@octane-xplat/dnd-kit",
|
|
22
|
+
"@octane-xplat/effects",
|
|
23
|
+
"@octane-xplat/files",
|
|
24
|
+
"@octane-xplat/geolocation",
|
|
25
|
+
"@octane-xplat/gif",
|
|
26
|
+
"@octane-xplat/haptics",
|
|
27
|
+
"@octane-xplat/icons",
|
|
28
|
+
"@octane-xplat/image",
|
|
29
|
+
"@octane-xplat/image-crop",
|
|
30
|
+
"@octane-xplat/intersection-observer",
|
|
31
|
+
"@octane-xplat/jsx",
|
|
32
|
+
"@octane-xplat/lexical",
|
|
33
|
+
"@octane-xplat/lib",
|
|
34
|
+
"@octane-xplat/lingui",
|
|
35
|
+
"@octane-xplat/lint",
|
|
36
|
+
"@octane-xplat/lottie",
|
|
37
|
+
"@octane-xplat/macos-renderer",
|
|
38
|
+
"@octane-xplat/mcp",
|
|
39
|
+
"@octane-xplat/media",
|
|
40
|
+
"@octane-xplat/motion",
|
|
41
|
+
"@octane-xplat/nano-icons",
|
|
42
|
+
"@octane-xplat/notifications",
|
|
43
|
+
"@octane-xplat/pager",
|
|
44
|
+
"@octane-xplat/patches",
|
|
45
|
+
"@octane-xplat/picker",
|
|
46
|
+
"@octane-xplat/platform",
|
|
47
|
+
"@octane-xplat/push",
|
|
48
|
+
"@octane-xplat/recorder",
|
|
49
|
+
"@octane-xplat/richtext",
|
|
50
|
+
"@octane-xplat/rolling-text",
|
|
51
|
+
"@octane-xplat/secure-storage",
|
|
52
|
+
"@octane-xplat/shaders",
|
|
53
|
+
"@octane-xplat/share",
|
|
54
|
+
"@octane-xplat/sheet",
|
|
55
|
+
"@octane-xplat/smooth-corners",
|
|
56
|
+
"@octane-xplat/sounds",
|
|
57
|
+
"@octane-xplat/sqlite",
|
|
58
|
+
"@octane-xplat/table",
|
|
59
|
+
"@octane-xplat/tiptap",
|
|
60
|
+
"@octane-xplat/ui",
|
|
61
|
+
"@octane-xplat/updates",
|
|
62
|
+
"@octane-xplat/video",
|
|
63
|
+
"@octane-xplat/workshop",
|
|
64
|
+
"create-octane-xplat"
|
|
65
|
+
],
|
|
66
|
+
"packages": {
|
|
67
|
+
"octane": {
|
|
68
|
+
"version": "0.11.0",
|
|
69
|
+
"group": "runtime",
|
|
70
|
+
"singleton": true
|
|
71
|
+
},
|
|
72
|
+
"@nativescript-community/octane": {
|
|
73
|
+
"version": "0.2.5",
|
|
74
|
+
"group": "runtime",
|
|
75
|
+
"singleton": true
|
|
76
|
+
},
|
|
77
|
+
"@nativescript/core": {
|
|
78
|
+
"version": "9.1.3",
|
|
79
|
+
"group": "runtime",
|
|
80
|
+
"singleton": true
|
|
81
|
+
},
|
|
82
|
+
"typescript": {
|
|
83
|
+
"version": "5.9.3",
|
|
84
|
+
"group": "compiler"
|
|
85
|
+
},
|
|
86
|
+
"@tsrx/oxc": {
|
|
87
|
+
"version": "0.20.0",
|
|
88
|
+
"group": "compiler"
|
|
89
|
+
},
|
|
90
|
+
"@tsrx/typescript-plugin": {
|
|
91
|
+
"version": "0.4.11",
|
|
92
|
+
"group": "compiler"
|
|
93
|
+
},
|
|
94
|
+
"tsrx-typegen": {
|
|
95
|
+
"version": "0.2.2",
|
|
96
|
+
"group": "compiler"
|
|
97
|
+
},
|
|
98
|
+
"vite": {
|
|
99
|
+
"version": "8.3.0",
|
|
100
|
+
"group": "bundler"
|
|
101
|
+
},
|
|
102
|
+
"@octanejs/vite-plugin": {
|
|
103
|
+
"version": "0.1.61",
|
|
104
|
+
"group": "bundler"
|
|
105
|
+
},
|
|
106
|
+
"@nativescript-community/vite-octane": {
|
|
107
|
+
"version": "0.2.1",
|
|
108
|
+
"group": "bundler"
|
|
109
|
+
},
|
|
110
|
+
"@nativescript/vite": {
|
|
111
|
+
"version": "8.0.17",
|
|
112
|
+
"group": "bundler"
|
|
113
|
+
},
|
|
114
|
+
"nativescript": {
|
|
115
|
+
"version": "9.1.1",
|
|
116
|
+
"group": "native-toolchain"
|
|
117
|
+
},
|
|
118
|
+
"@nativescript/ios": {
|
|
119
|
+
"version": "9.1.0",
|
|
120
|
+
"group": "native-toolchain"
|
|
121
|
+
},
|
|
122
|
+
"@nativescript/android": {
|
|
123
|
+
"version": "9.1.1",
|
|
124
|
+
"group": "native-toolchain"
|
|
125
|
+
},
|
|
126
|
+
"@nativescript/types": {
|
|
127
|
+
"version": "9.1.4",
|
|
128
|
+
"group": "native-toolchain"
|
|
129
|
+
},
|
|
130
|
+
"@nativescript/types-ios": {
|
|
131
|
+
"version": "9.1.3",
|
|
132
|
+
"group": "native-toolchain"
|
|
133
|
+
},
|
|
134
|
+
"@nativescript/types-android": {
|
|
135
|
+
"version": "9.1.1",
|
|
136
|
+
"group": "native-toolchain"
|
|
137
|
+
},
|
|
138
|
+
"@nativescript/macos-node-api": {
|
|
139
|
+
"version": "0.4.4-next.2026-08-09-31292056208",
|
|
140
|
+
"group": "native-toolchain"
|
|
141
|
+
},
|
|
142
|
+
"@nativescript/windows": {
|
|
143
|
+
"version": "0.1.0-alpha.144",
|
|
144
|
+
"group": "native-toolchain"
|
|
145
|
+
},
|
|
146
|
+
"vitest": {
|
|
147
|
+
"version": "4.1.11",
|
|
148
|
+
"group": "testing"
|
|
149
|
+
},
|
|
150
|
+
"@nativescript/unit-test-runner": {
|
|
151
|
+
"version": "5.0.0",
|
|
152
|
+
"group": "testing"
|
|
153
|
+
},
|
|
154
|
+
"@nativescript-community/gesturehandler": {
|
|
155
|
+
"version": "2.0.45",
|
|
156
|
+
"group": "plugin"
|
|
157
|
+
},
|
|
158
|
+
"@nativescript-community/ui-drawer": {
|
|
159
|
+
"version": "0.1.32",
|
|
160
|
+
"group": "plugin"
|
|
161
|
+
},
|
|
162
|
+
"@nativescript-community/ui-image": {
|
|
163
|
+
"version": "5.0.17",
|
|
164
|
+
"group": "plugin"
|
|
165
|
+
},
|
|
166
|
+
"@valor/nativescript-websockets": {
|
|
167
|
+
"version": "2.0.3",
|
|
168
|
+
"group": "plugin"
|
|
169
|
+
},
|
|
170
|
+
"@triniwiz/nativescript-masonkit": {
|
|
171
|
+
"version": "1.0.0-beta.110",
|
|
172
|
+
"group": "runtime"
|
|
173
|
+
},
|
|
174
|
+
"jsdom": {
|
|
175
|
+
"version": "30.1.1",
|
|
176
|
+
"group": "testing"
|
|
177
|
+
}
|
|
178
|
+
},
|
|
179
|
+
"profiles": {
|
|
180
|
+
"windows": {
|
|
181
|
+
"@nativescript/core": {
|
|
182
|
+
"version": "9.1.3-next.2",
|
|
183
|
+
"specifier": "https://pkg.pr.new/@nativescript/core@7d0adce"
|
|
184
|
+
},
|
|
185
|
+
"@nativescript/vite": {
|
|
186
|
+
"version": "8.0.13",
|
|
187
|
+
"specifier": "https://pkg.pr.new/@nativescript/vite@7d0adce"
|
|
188
|
+
},
|
|
189
|
+
"nativescript": {
|
|
190
|
+
"version": "9.1.2-dev.2026-09-24-36031892256"
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
},
|
|
194
|
+
"overrides": {
|
|
195
|
+
"@nativescript/vite>esbuild": "0.27.7",
|
|
196
|
+
"@nativescript-community/vite-octane>esbuild": "0.27.7",
|
|
197
|
+
"vite>esbuild": "0.27.7"
|
|
198
|
+
}
|
|
199
|
+
}
|
package/compatibility.ts
ADDED
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import { existsSync, readFileSync, writeFileSync } from 'node:fs'
|
|
2
|
+
import { createRequire } from 'node:module'
|
|
3
|
+
import { basename, dirname, join, resolve } from 'node:path'
|
|
4
|
+
import { fileURLToPath } from 'node:url'
|
|
5
|
+
import { parseDocument } from 'yaml'
|
|
6
|
+
|
|
7
|
+
/** One supported external version, optionally using a preview tarball specifier. */
|
|
8
|
+
export type ManagedPackage = {
|
|
9
|
+
version: string
|
|
10
|
+
specifier?: string
|
|
11
|
+
group: string
|
|
12
|
+
singleton?: boolean
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/** The release-owned pins used by creator, doctor, and upgrade. */
|
|
16
|
+
export type Compatibility = {
|
|
17
|
+
schemaVersion: number
|
|
18
|
+
frameworkVersion: string
|
|
19
|
+
tools: { nodeMinimum: string; pnpm: string }
|
|
20
|
+
frameworkPackages: string[]
|
|
21
|
+
packages: Record<string, ManagedPackage>
|
|
22
|
+
profiles: Record<string, Record<string, Partial<ManagedPackage>>>
|
|
23
|
+
overrides: Record<string, string>
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const dir = dirname(fileURLToPath(import.meta.url))
|
|
27
|
+
const root = basename(dir) === 'dist' ? dirname(dir) : dir
|
|
28
|
+
// Keep JSON on disk: Vite's asset URL transform produces a data URL here.
|
|
29
|
+
export const compatibility: Compatibility = createRequire(import.meta.url)(
|
|
30
|
+
join(root, 'compatibility.json'),
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
/** Look up a known framework package or a profile's external dependency pin. */
|
|
34
|
+
export function managedPackage(name: string, profile = 'default'): ManagedPackage | undefined {
|
|
35
|
+
if (compatibility.frameworkPackages.includes(name)) {
|
|
36
|
+
return {
|
|
37
|
+
version: compatibility.frameworkVersion,
|
|
38
|
+
group: 'framework',
|
|
39
|
+
singleton: name === '@octane-xplat/ui',
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const base = compatibility.packages[name]
|
|
44
|
+
return base ? { ...base, ...compatibility.profiles[profile]?.[name] } : undefined
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/** Return the pnpm specifier, keeping preview URLs separate from package versions. */
|
|
48
|
+
export function managedSpecifier(name: string, profile = 'default') {
|
|
49
|
+
const entry = managedPackage(name, profile)
|
|
50
|
+
return entry?.specifier ?? entry?.version
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/** Named pnpm catalog reserved for this compatibility profile. */
|
|
54
|
+
export const managedCatalog = (profile = 'default') =>
|
|
55
|
+
profile === 'default' ? 'xplat' : `xplat-${profile}`
|
|
56
|
+
|
|
57
|
+
/** Installed dependency fields; library consumer peers intentionally keep their ranges. */
|
|
58
|
+
export const dependencySections = [
|
|
59
|
+
'dependencies',
|
|
60
|
+
'devDependencies',
|
|
61
|
+
'optionalDependencies',
|
|
62
|
+
] as const
|
|
63
|
+
|
|
64
|
+
/** Select Windows preview pins, rejecting a package that also needs mobile pins. */
|
|
65
|
+
export function projectProfile(pkg: Record<string, any>): string {
|
|
66
|
+
const deps = Object.assign({}, ...dependencySections.map((section) => pkg[section]))
|
|
67
|
+
const windows = deps['@nativescript/windows'] || pkg.xplat?.targets?.windows
|
|
68
|
+
if (windows && (deps['@nativescript/ios'] || deps['@nativescript/android'])) {
|
|
69
|
+
throw new Error(
|
|
70
|
+
'Windows preview and mobile need separate workspace packages: their NativeScript pins differ',
|
|
71
|
+
)
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
return windows ? 'windows' : 'default'
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/** Add missing pins when enabling a target in an older scaffold. Existing pins stay intact. */
|
|
78
|
+
export function ensureCompatibilityCatalog(appRoot: string, pkg: Record<string, any>) {
|
|
79
|
+
let at = resolve(appRoot)
|
|
80
|
+
while (!existsSync(join(at, 'pnpm-workspace.yaml')) && dirname(at) !== at) {
|
|
81
|
+
at = dirname(at)
|
|
82
|
+
}
|
|
83
|
+
const workspace = existsSync(join(at, 'pnpm-workspace.yaml')) ? at : appRoot
|
|
84
|
+
const path = join(workspace, 'pnpm-workspace.yaml')
|
|
85
|
+
const before = existsSync(path) ? readFileSync(path, 'utf8') : ''
|
|
86
|
+
const doc = parseDocument(before || '{}\n')
|
|
87
|
+
if (doc.errors.length) {
|
|
88
|
+
throw new Error(`${path}: ${doc.errors[0].message}`)
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const profile = projectProfile(pkg)
|
|
92
|
+
for (const section of dependencySections) {
|
|
93
|
+
for (const [name, spec] of Object.entries(pkg[section] ?? {})) {
|
|
94
|
+
if (spec !== `catalog:${managedCatalog(profile)}`) {
|
|
95
|
+
continue
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
const key = ['catalogs', managedCatalog(profile), name]
|
|
99
|
+
if (!doc.hasIn(key)) {
|
|
100
|
+
doc.setIn(key, managedSpecifier(name, profile))
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
const after = String(doc)
|
|
106
|
+
if (after !== before) {
|
|
107
|
+
writeFileSync(path, after)
|
|
108
|
+
}
|
|
109
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { existsSync, readFileSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { basename, dirname, join, resolve } from "node:path";
|
|
3
|
+
import { fileURLToPath } from "node:url";
|
|
4
|
+
import { parseDocument } from "yaml";
|
|
5
|
+
import { createRequire } from "node:module";
|
|
6
|
+
//#region compatibility.ts
|
|
7
|
+
var dir = dirname(fileURLToPath(import.meta.url));
|
|
8
|
+
var root = basename(dir) === "dist" ? dirname(dir) : dir;
|
|
9
|
+
var compatibility = createRequire(import.meta.url)(join(root, "compatibility.json"));
|
|
10
|
+
/** Look up a known framework package or a profile's external dependency pin. */
|
|
11
|
+
function managedPackage(name, profile = "default") {
|
|
12
|
+
if (compatibility.frameworkPackages.includes(name)) return {
|
|
13
|
+
version: compatibility.frameworkVersion,
|
|
14
|
+
group: "framework",
|
|
15
|
+
singleton: name === "@octane-xplat/ui"
|
|
16
|
+
};
|
|
17
|
+
const base = compatibility.packages[name];
|
|
18
|
+
return base ? {
|
|
19
|
+
...base,
|
|
20
|
+
...compatibility.profiles[profile]?.[name]
|
|
21
|
+
} : void 0;
|
|
22
|
+
}
|
|
23
|
+
/** Return the pnpm specifier, keeping preview URLs separate from package versions. */
|
|
24
|
+
function managedSpecifier(name, profile = "default") {
|
|
25
|
+
const entry = managedPackage(name, profile);
|
|
26
|
+
return entry?.specifier ?? entry?.version;
|
|
27
|
+
}
|
|
28
|
+
/** Named pnpm catalog reserved for this compatibility profile. */
|
|
29
|
+
var managedCatalog = (profile = "default") => profile === "default" ? "xplat" : `xplat-${profile}`;
|
|
30
|
+
/** Installed dependency fields; library consumer peers intentionally keep their ranges. */
|
|
31
|
+
var dependencySections = [
|
|
32
|
+
"dependencies",
|
|
33
|
+
"devDependencies",
|
|
34
|
+
"optionalDependencies"
|
|
35
|
+
];
|
|
36
|
+
/** Select Windows preview pins, rejecting a package that also needs mobile pins. */
|
|
37
|
+
function projectProfile(pkg) {
|
|
38
|
+
const deps = Object.assign({}, ...dependencySections.map((section) => pkg[section]));
|
|
39
|
+
const windows = deps["@nativescript/windows"] || pkg.xplat?.targets?.windows;
|
|
40
|
+
if (windows && (deps["@nativescript/ios"] || deps["@nativescript/android"])) throw new Error("Windows preview and mobile need separate workspace packages: their NativeScript pins differ");
|
|
41
|
+
return windows ? "windows" : "default";
|
|
42
|
+
}
|
|
43
|
+
/** Add missing pins when enabling a target in an older scaffold. Existing pins stay intact. */
|
|
44
|
+
function ensureCompatibilityCatalog(appRoot, pkg) {
|
|
45
|
+
let at = resolve(appRoot);
|
|
46
|
+
while (!existsSync(join(at, "pnpm-workspace.yaml")) && dirname(at) !== at) at = dirname(at);
|
|
47
|
+
const workspace = existsSync(join(at, "pnpm-workspace.yaml")) ? at : appRoot;
|
|
48
|
+
const path = join(workspace, "pnpm-workspace.yaml");
|
|
49
|
+
const before = existsSync(path) ? readFileSync(path, "utf8") : "";
|
|
50
|
+
const doc = parseDocument(before || "{}\n");
|
|
51
|
+
if (doc.errors.length) throw new Error(`${path}: ${doc.errors[0].message}`);
|
|
52
|
+
const profile = projectProfile(pkg);
|
|
53
|
+
for (const section of dependencySections) for (const [name, spec] of Object.entries(pkg[section] ?? {})) {
|
|
54
|
+
if (spec !== `catalog:${managedCatalog(profile)}`) continue;
|
|
55
|
+
const key = [
|
|
56
|
+
"catalogs",
|
|
57
|
+
managedCatalog(profile),
|
|
58
|
+
name
|
|
59
|
+
];
|
|
60
|
+
if (!doc.hasIn(key)) doc.setIn(key, managedSpecifier(name, profile));
|
|
61
|
+
}
|
|
62
|
+
const after = String(doc);
|
|
63
|
+
if (after !== before) writeFileSync(path, after);
|
|
64
|
+
}
|
|
65
|
+
//#endregion
|
|
66
|
+
export { compatibility, dependencySections, ensureCompatibilityCatalog, managedCatalog, managedPackage, managedSpecifier, projectProfile };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { composeLibrary, composeTargets, defaultTargets, resolveTargets, selectableTargets, targets } from "./scaffold.js";
|
|
3
|
+
import { existsSync, readdirSync } from "node:fs";
|
|
4
|
+
import { basename, resolve } from "node:path";
|
|
5
|
+
import { spawnSync } from "node:child_process";
|
|
6
|
+
//#region index.ts
|
|
7
|
+
var argv = process.argv.slice(2);
|
|
8
|
+
var noInstall = argv.includes("--no-install");
|
|
9
|
+
var templateIndex = argv.findIndex((arg) => arg === "--template" || arg.startsWith("--template="));
|
|
10
|
+
var templateValueIndex = templateIndex !== -1 && argv[templateIndex] === "--template" ? templateIndex + 1 : -1;
|
|
11
|
+
var templateArg = templateIndex === -1 ? "app" : templateValueIndex !== -1 ? argv[templateValueIndex] : argv[templateIndex].slice(11);
|
|
12
|
+
var nameIndex = argv.findIndex((arg) => arg === "--name" || arg.startsWith("--name="));
|
|
13
|
+
var nameValueIndex = nameIndex !== -1 && argv[nameIndex] === "--name" ? nameIndex + 1 : -1;
|
|
14
|
+
var nameArg = nameIndex === -1 ? void 0 : nameValueIndex !== -1 ? argv[nameValueIndex] : argv[nameIndex].slice(7);
|
|
15
|
+
if (templateIndex !== -1 && (!templateArg || templateArg.startsWith("-"))) {
|
|
16
|
+
console.error("✗ --template needs a value: app or library");
|
|
17
|
+
process.exit(1);
|
|
18
|
+
}
|
|
19
|
+
if (!["app", "library"].includes(templateArg)) {
|
|
20
|
+
console.error(`✗ unknown template "${templateArg}" — expected app or library`);
|
|
21
|
+
process.exit(1);
|
|
22
|
+
}
|
|
23
|
+
if (nameIndex !== -1 && (!nameArg || nameArg.startsWith("-"))) {
|
|
24
|
+
console.error("✗ --name needs an npm package name");
|
|
25
|
+
process.exit(1);
|
|
26
|
+
}
|
|
27
|
+
var targetsIndex = argv.findIndex((arg) => arg === "--targets" || arg.startsWith("--targets="));
|
|
28
|
+
var targetsValueIndex = targetsIndex !== -1 && argv[targetsIndex] === "--targets" ? targetsIndex + 1 : -1;
|
|
29
|
+
var targetsArg = targetsIndex === -1 ? void 0 : targetsValueIndex !== -1 ? argv[targetsValueIndex] : argv[targetsIndex].slice(10);
|
|
30
|
+
if (targetsIndex !== -1 && (targetsArg === void 0 || targetsArg.startsWith("-"))) {
|
|
31
|
+
console.error(`✗ --targets needs a comma list: ${selectableTargets.join(", ")}`);
|
|
32
|
+
process.exit(1);
|
|
33
|
+
}
|
|
34
|
+
var optionValues = new Set([
|
|
35
|
+
targetsValueIndex,
|
|
36
|
+
templateValueIndex,
|
|
37
|
+
nameValueIndex
|
|
38
|
+
].filter((index) => index !== -1));
|
|
39
|
+
var positional = argv.filter((arg, i) => !arg.startsWith("-") && !optionValues.has(i));
|
|
40
|
+
var defaultDir = templateArg === "library" ? "octane-xplat-library" : "octane-xplat-app";
|
|
41
|
+
var dir = resolve(positional[0] ?? defaultDir);
|
|
42
|
+
if (templateArg === "library" && targetsIndex !== -1) {
|
|
43
|
+
console.error("✗ --targets applies to the app template; libraries start with web and native builds");
|
|
44
|
+
process.exit(1);
|
|
45
|
+
}
|
|
46
|
+
if (templateArg === "app" && nameIndex !== -1) {
|
|
47
|
+
console.error("✗ --name applies only to the library template");
|
|
48
|
+
process.exit(1);
|
|
49
|
+
}
|
|
50
|
+
if (existsSync(dir) && readdirSync(dir).length > 0) {
|
|
51
|
+
console.error(`✗ ${dir} exists and is not empty`);
|
|
52
|
+
process.exit(1);
|
|
53
|
+
}
|
|
54
|
+
var parseTargets = (value) => {
|
|
55
|
+
const ids = value.split(",").map((s) => s.trim()).filter(Boolean);
|
|
56
|
+
if (ids.length === 0) throw new Error(`--targets needs a comma list: ${selectableTargets.join(", ")}`);
|
|
57
|
+
for (const id of ids) if (!selectableTargets.includes(id)) throw new Error(`unknown target "${id}" — expected a comma list of: ${selectableTargets.join(", ")}`);
|
|
58
|
+
return resolveTargets(ids);
|
|
59
|
+
};
|
|
60
|
+
var ids = [];
|
|
61
|
+
if (templateArg === "library") {
|
|
62
|
+
const packageName = nameArg ?? basename(dir);
|
|
63
|
+
try {
|
|
64
|
+
composeLibrary(dir, packageName);
|
|
65
|
+
} catch (error) {
|
|
66
|
+
console.error(`✗ ${error.message}`);
|
|
67
|
+
process.exit(1);
|
|
68
|
+
}
|
|
69
|
+
console.log(`\n✓ scaffolded ${dir} (library: ${packageName}; web + iOS + Android)`);
|
|
70
|
+
} else {
|
|
71
|
+
if (targetsArg !== void 0) try {
|
|
72
|
+
ids = parseTargets(targetsArg);
|
|
73
|
+
} catch (error) {
|
|
74
|
+
console.error(`✗ ${error.message}`);
|
|
75
|
+
process.exit(1);
|
|
76
|
+
}
|
|
77
|
+
else if (process.stdout.isTTY) {
|
|
78
|
+
const p = await import("@clack/prompts");
|
|
79
|
+
p.intro("create-octane-xplat");
|
|
80
|
+
const picked = await p.multiselect({
|
|
81
|
+
message: "Target platforms",
|
|
82
|
+
options: selectableTargets.map((id) => ({
|
|
83
|
+
value: id,
|
|
84
|
+
label: targets[id].label,
|
|
85
|
+
hint: targets[id].note
|
|
86
|
+
})),
|
|
87
|
+
initialValues: [...defaultTargets],
|
|
88
|
+
required: true
|
|
89
|
+
});
|
|
90
|
+
if (p.isCancel(picked)) {
|
|
91
|
+
p.cancel("Cancelled");
|
|
92
|
+
process.exit(0);
|
|
93
|
+
}
|
|
94
|
+
ids = picked;
|
|
95
|
+
} else ids = defaultTargets;
|
|
96
|
+
composeTargets(ids, dir);
|
|
97
|
+
console.log(`\n✓ scaffolded ${dir} (${[...new Set(resolveTargets(ids))].filter((id) => !targets[id].hidden).join(", ")})`);
|
|
98
|
+
}
|
|
99
|
+
if (noInstall) {
|
|
100
|
+
console.log("\nSkipped install (--no-install). To run manually:");
|
|
101
|
+
printNextSteps(dir, ids, templateArg);
|
|
102
|
+
process.exit(0);
|
|
103
|
+
}
|
|
104
|
+
var install = spawnSync("pnpm", ["install"], {
|
|
105
|
+
cwd: dir,
|
|
106
|
+
stdio: "inherit"
|
|
107
|
+
});
|
|
108
|
+
if (install.status !== 0) {
|
|
109
|
+
console.log(`\nInstall didn't finish (pnpm missing?). To run manually:`);
|
|
110
|
+
printNextSteps(dir, ids, templateArg);
|
|
111
|
+
process.exit(install.status ?? 1);
|
|
112
|
+
}
|
|
113
|
+
if (templateArg === "app" && ids.includes("web")) {
|
|
114
|
+
console.log("\n✓ installed — starting dev server (Ctrl+C to stop)\n");
|
|
115
|
+
spawnSync("pnpm", ["dev"], {
|
|
116
|
+
cwd: dir,
|
|
117
|
+
stdio: "inherit"
|
|
118
|
+
});
|
|
119
|
+
} else {
|
|
120
|
+
console.log("\n✓ installed. Next steps:");
|
|
121
|
+
printNextSteps(dir, ids, templateArg);
|
|
122
|
+
}
|
|
123
|
+
function printNextSteps(dir, ids, template) {
|
|
124
|
+
console.log(` cd ${dir}\n pnpm install`);
|
|
125
|
+
if (template === "library") {
|
|
126
|
+
console.log(" pnpm typecheck\n pnpm lint\n pnpm typegen\n pnpm test\n pnpm test:packed");
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
129
|
+
if (ids.includes("web")) console.log(" pnpm dev");
|
|
130
|
+
else console.log(" pnpm xplat dev");
|
|
131
|
+
}
|
|
132
|
+
//#endregion
|