@octane-xplat/workshop 0.1.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.
Files changed (58) hide show
  1. package/LICENSE +21 -0
  2. package/MOBILE.md +143 -0
  3. package/README.md +305 -1
  4. package/dist/node/index.js +50 -0
  5. package/package.json +118 -5
  6. package/src/catalog.ts +58 -0
  7. package/src/contract.ts +257 -0
  8. package/src/controls.ts +159 -0
  9. package/src/discover.mjs +584 -0
  10. package/src/errors.ts +47 -0
  11. package/src/fixture.ts +158 -0
  12. package/src/index.ts +6 -0
  13. package/src/macos/controller.ts +170 -0
  14. package/src/macos/index.macos.ts +2 -0
  15. package/src/macos/shell.macos.mjs +673 -0
  16. package/src/macos/stage.macos.ts +182 -0
  17. package/src/macos-node.mjs +95 -0
  18. package/src/macos-vite.mjs +47 -0
  19. package/src/mobile/controller.ts +134 -0
  20. package/src/mobile/entry.mobile.ts +607 -0
  21. package/src/mobile/index.mobile.ts +3 -0
  22. package/src/mobile/mount.mobile.ts +60 -0
  23. package/src/mobile/tree.mobile.tsrx +17 -0
  24. package/src/mobile-node.mjs +126 -0
  25. package/src/mobile-regex.mjs +60 -0
  26. package/src/node.d.ts +65 -0
  27. package/src/node.mjs +536 -0
  28. package/src/session.ts +520 -0
  29. package/src/vite-mobile.d.ts +21 -0
  30. package/src/vite-mobile.mjs +101 -0
  31. package/src/vite.d.ts +11 -0
  32. package/src/vite.mjs +315 -0
  33. package/src/web/entry.web.ts +270 -0
  34. package/src/web/index.web.ts +7 -0
  35. package/src/web/mount.web.ts +86 -0
  36. package/src/web/shell.css +449 -0
  37. package/src/web/shell.web.tsrx +759 -0
  38. package/src/windows/preflight.mjs +113 -0
  39. package/types/generated/.tsrx-typegen-manifest.json +24 -0
  40. package/types/generated/catalog.d.ts +30 -0
  41. package/types/generated/contract.d.ts +145 -0
  42. package/types/generated/controls.d.ts +28 -0
  43. package/types/generated/errors.d.ts +13 -0
  44. package/types/generated/fixture.d.ts +53 -0
  45. package/types/generated/index.d.ts +6 -0
  46. package/types/generated/macos/controller.d.ts +52 -0
  47. package/types/generated/macos/index.macos.d.ts +2 -0
  48. package/types/generated/macos/stage.macos.d.ts +10 -0
  49. package/types/generated/mobile/controller.d.ts +31 -0
  50. package/types/generated/mobile/entry.mobile.d.ts +22 -0
  51. package/types/generated/mobile/index.mobile.d.ts +3 -0
  52. package/types/generated/mobile/mount.mobile.d.ts +4 -0
  53. package/types/generated/mobile/tree.mobile.d.ts +4 -0
  54. package/types/generated/session.d.ts +84 -0
  55. package/types/generated/web/entry.web.d.ts +53 -0
  56. package/types/generated/web/index.web.d.ts +7 -0
  57. package/types/generated/web/mount.web.d.ts +2 -0
  58. package/types/generated/web/shell.web.d.ts +4 -0
@@ -0,0 +1,126 @@
1
+ import { createRequire } from 'node:module'
2
+ import { existsSync, readFileSync } from 'node:fs'
3
+ import { dirname, join, resolve } from 'node:path'
4
+ import { spawn } from 'node:child_process'
5
+
6
+ const CONFIGS = [
7
+ 'vite.config.native.mts',
8
+ 'vite.config.native.ts',
9
+ 'vite.config.mts',
10
+ 'vite.config.ts',
11
+ 'vite.config.mjs',
12
+ ]
13
+
14
+ export function mobilePrerequisites(root, target, host = process.platform, env = process.env) {
15
+ const req = createRequire(join(root, 'package.json'))
16
+ const installed = (name) => {
17
+ try {
18
+ req.resolve(name)
19
+ return true
20
+ } catch {
21
+ return false
22
+ }
23
+ }
24
+
25
+ const configFile = CONFIGS.map((name) => join(root, name)).find(existsSync)
26
+ const requirements = {
27
+ 'nativescript.config.ts': existsSync(join(root, 'nativescript.config.ts')),
28
+ 'native workshop Vite setup': Boolean(
29
+ configFile && /withWorkshopMobile/.test(readFileSync(configFile, 'utf8')),
30
+ ),
31
+ '@nativescript/core': installed('@nativescript/core'),
32
+ '@nativescript-community/octane': installed('@nativescript-community/octane'),
33
+ '@nativescript-community/vite-octane': installed('@nativescript-community/vite-octane'),
34
+ '@valor/nativescript-websockets': installed('@valor/nativescript-websockets'),
35
+ 'nativescript CLI': installed('nativescript'),
36
+ }
37
+
38
+ if (target === 'ios') {
39
+ requirements['macOS / Xcode host'] = host === 'darwin' && existsSync('/Applications/Xcode.app')
40
+ } else {
41
+ requirements['Android SDK directory'] = Boolean(
42
+ (env.ANDROID_HOME || env.ANDROID_SDK_ROOT) &&
43
+ existsSync(env.ANDROID_HOME || env.ANDROID_SDK_ROOT),
44
+ )
45
+
46
+ requirements['JAVA_HOME directory'] = Boolean(env.JAVA_HOME && existsSync(env.JAVA_HOME))
47
+ }
48
+
49
+ return {
50
+ backend: 'nativescript (Octane universal renderer)',
51
+ requirements,
52
+ status:
53
+ target === 'ios' && host !== 'darwin'
54
+ ? 'remote-host-required'
55
+ : Object.values(requirements).every(Boolean)
56
+ ? 'unqualified-backend'
57
+ : 'missing-prerequisites',
58
+ note: 'Mobile adapter installed; actual-host lifecycle, touch, keyboard, screen-reader and IME qualification remain open. Doctor does not launch or select a device.',
59
+ }
60
+ }
61
+
62
+ export function mobileLaunchSpec({ root, target, device, config, story }) {
63
+ if (!['ios', 'android'].includes(target)) {
64
+ throw new Error('Expected ios or android')
65
+ }
66
+
67
+ if (!device || typeof device !== 'string' || device.startsWith('-')) {
68
+ throw new Error(
69
+ 'Mobile workshop requires --device <device-id>; it never chooses or boots a device implicitly',
70
+ )
71
+ }
72
+
73
+ const req = createRequire(join(root, 'package.json'))
74
+ const packageFile = req.resolve('nativescript/package.json')
75
+ const manifest = JSON.parse(readFileSync(packageFile, 'utf8'))
76
+ const binary =
77
+ typeof manifest.bin === 'string' ? manifest.bin : (manifest.bin.ns ?? manifest.bin.nativescript)
78
+
79
+ if (!binary) {
80
+ throw new Error('Installed NativeScript CLI has no ns binary')
81
+ }
82
+
83
+ return {
84
+ command: process.execPath,
85
+ args: [resolve(dirname(packageFile), binary), 'run', target, '--device', device],
86
+ options: {
87
+ cwd: root,
88
+ stdio: 'inherit',
89
+ shell: false,
90
+ env: {
91
+ ...process.env,
92
+ XPLAT_WORKSHOP_TARGET: target,
93
+ XPLAT_WORKSHOP_CONFIG: JSON.stringify(config),
94
+ XPLAT_WORKSHOP_STORY: story ?? '',
95
+ },
96
+ },
97
+ }
98
+ }
99
+
100
+ export async function launchMobile(options) {
101
+ const spec = mobileLaunchSpec(options)
102
+ const child = spawn(spec.command, spec.args, spec.options)
103
+ const interrupt = () => child.kill('SIGINT')
104
+ const terminate = () => child.kill('SIGTERM')
105
+ process.on('SIGINT', interrupt)
106
+ process.on('SIGTERM', terminate)
107
+ try {
108
+ await new Promise((resolve, reject) => {
109
+ child.once('error', reject)
110
+ child.once('exit', (code, signal) =>
111
+ code === 0
112
+ ? resolve()
113
+ : reject(
114
+ new Error(
115
+ `NativeScript workshop ${options.target} exited ${signal ?? code}; check launch logs and restart the target`,
116
+ ),
117
+ ),
118
+ )
119
+ })
120
+ } finally {
121
+ process.off('SIGINT', interrupt)
122
+ process.off('SIGTERM', terminate)
123
+ }
124
+
125
+ return { target: options.target, exited: true }
126
+ }
@@ -0,0 +1,60 @@
1
+ import { transformAsync, types } from '@babel/core'
2
+ import unicodeProperties from '@babel/plugin-transform-unicode-property-regex'
3
+
4
+ // Compatibility approach shared with apps/mobile/visual-regex.mjs; no testing-host dependency.
5
+ /** Compile Unicode properties for the pinned iOS runtime without changing globals. */
6
+ export function transformMobileRegex(code, filename = 'workshop-host.mjs') {
7
+ return transformAsync(code, {
8
+ filename,
9
+ configFile: false,
10
+ babelrc: false,
11
+ sourceMaps: true,
12
+ plugins: [
13
+ // Storybook also embeds constant RegExp constructors. Turn only unshadowed
14
+ // built-in constructors into literals for Babel's standard regex transform.
15
+ () => ({
16
+ visitor: {
17
+ NewExpression(path) {
18
+ const node = path.node
19
+ if (
20
+ !types.isIdentifier(node.callee, { name: 'RegExp' }) ||
21
+ path.scope.getBinding('RegExp') ||
22
+ node.arguments.length !== 2
23
+ ) {
24
+ return
25
+ }
26
+
27
+ const [pattern, flags] = node.arguments
28
+ if (
29
+ types.isStringLiteral(pattern) &&
30
+ types.isStringLiteral(flags) &&
31
+ flags.value.includes('u') &&
32
+ /\\[pP]\{/.test(pattern.value)
33
+ ) {
34
+ path.replaceWith(types.regExpLiteral(pattern.value, flags.value))
35
+ }
36
+ },
37
+ },
38
+ }),
39
+ unicodeProperties.default ?? unicodeProperties,
40
+ ],
41
+ })
42
+ }
43
+
44
+ export function mobileRegexPlugin() {
45
+ return {
46
+ name: 'xplat-workshop-mobile-unicode-regex',
47
+ async transform(code, id) {
48
+ if (!/\\[pP]\{/.test(code) || !/\.[cm]?js(?:\?|$)/.test(id)) {
49
+ return null
50
+ }
51
+
52
+ const result = await transformMobileRegex(code, id.split('?')[0])
53
+ return result ? { code: result.code, map: result.map } : null
54
+ },
55
+ async renderChunk(code, chunk) {
56
+ const result = await transformMobileRegex(code, chunk.fileName)
57
+ return result ? { code: result.code, map: result.map } : null
58
+ },
59
+ }
60
+ }
package/src/node.d.ts ADDED
@@ -0,0 +1,65 @@
1
+ export interface WorkshopListResult {
2
+ entries: unknown[]
3
+ diagnostics: { file: string; exportName?: string; message: string }[]
4
+ }
5
+
6
+ export interface WorkshopDoctorReport {
7
+ root: string
8
+ targets: Record<
9
+ string,
10
+ {
11
+ backend: string
12
+ requirements: Record<string, boolean>
13
+ status: string
14
+ note?: string
15
+ executionHost?: { required: string; current: string; reachable: boolean }
16
+ qualification?: 'unverified'
17
+ }
18
+ >
19
+ }
20
+
21
+ export class WorkshopUsageError extends Error {}
22
+ export declare function workshopList(options?: {
23
+ cwd?: string
24
+ target?: string
25
+ json?: boolean
26
+ }): Promise<WorkshopListResult>
27
+
28
+ export declare function workshopDoctor(options?: {
29
+ cwd?: string
30
+ json?: boolean
31
+ }): Promise<WorkshopDoctorReport>
32
+
33
+ export declare function workshopInit(options?: {
34
+ cwd?: string
35
+ scripts?: boolean
36
+ }): Promise<{ created: string[]; skipped: string[] }>
37
+
38
+ export declare function workshopDev(options?: {
39
+ cwd?: string
40
+ target?: string
41
+ story?: string
42
+ port?: number
43
+ device?: string
44
+ }): Promise<unknown>
45
+
46
+ /**
47
+ * Shared dev-server seam for DOM targets — the caller owns process
48
+ * lifecycle. Exported so qualification harnesses can boot the exact
49
+ * production wiring.
50
+ */
51
+ export declare function startWorkshopServer(options?: {
52
+ root?: string
53
+ config?: object
54
+ target?: string
55
+ story?: string
56
+ port?: number
57
+ }): Promise<{ server: unknown; url: string }>
58
+
59
+ /** Linux host prerequisites as a missing-item list (empty when satisfied). */
60
+ export declare function linuxMissingPrerequisites(): string[]
61
+
62
+ export declare function findConfigFile(root: string): string | null
63
+ export declare function loadWorkshopConfig(
64
+ root: string,
65
+ ): Promise<{ config: object; file: string | null }>