@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.
- package/LICENSE +21 -0
- package/MOBILE.md +143 -0
- package/README.md +305 -1
- package/dist/node/index.js +50 -0
- package/package.json +118 -5
- package/src/catalog.ts +58 -0
- package/src/contract.ts +257 -0
- package/src/controls.ts +159 -0
- package/src/discover.mjs +584 -0
- package/src/errors.ts +47 -0
- package/src/fixture.ts +158 -0
- package/src/index.ts +6 -0
- package/src/macos/controller.ts +170 -0
- package/src/macos/index.macos.ts +2 -0
- package/src/macos/shell.macos.mjs +673 -0
- package/src/macos/stage.macos.ts +182 -0
- package/src/macos-node.mjs +95 -0
- package/src/macos-vite.mjs +47 -0
- package/src/mobile/controller.ts +134 -0
- package/src/mobile/entry.mobile.ts +607 -0
- package/src/mobile/index.mobile.ts +3 -0
- package/src/mobile/mount.mobile.ts +60 -0
- package/src/mobile/tree.mobile.tsrx +17 -0
- package/src/mobile-node.mjs +126 -0
- package/src/mobile-regex.mjs +60 -0
- package/src/node.d.ts +65 -0
- package/src/node.mjs +536 -0
- package/src/session.ts +520 -0
- package/src/vite-mobile.d.ts +21 -0
- package/src/vite-mobile.mjs +101 -0
- package/src/vite.d.ts +11 -0
- package/src/vite.mjs +315 -0
- package/src/web/entry.web.ts +270 -0
- package/src/web/index.web.ts +7 -0
- package/src/web/mount.web.ts +86 -0
- package/src/web/shell.css +449 -0
- package/src/web/shell.web.tsrx +759 -0
- package/src/windows/preflight.mjs +113 -0
- package/types/generated/.tsrx-typegen-manifest.json +24 -0
- package/types/generated/catalog.d.ts +30 -0
- package/types/generated/contract.d.ts +145 -0
- package/types/generated/controls.d.ts +28 -0
- package/types/generated/errors.d.ts +13 -0
- package/types/generated/fixture.d.ts +53 -0
- package/types/generated/index.d.ts +6 -0
- package/types/generated/macos/controller.d.ts +52 -0
- package/types/generated/macos/index.macos.d.ts +2 -0
- package/types/generated/macos/stage.macos.d.ts +10 -0
- package/types/generated/mobile/controller.d.ts +31 -0
- package/types/generated/mobile/entry.mobile.d.ts +22 -0
- package/types/generated/mobile/index.mobile.d.ts +3 -0
- package/types/generated/mobile/mount.mobile.d.ts +4 -0
- package/types/generated/mobile/tree.mobile.d.ts +4 -0
- package/types/generated/session.d.ts +84 -0
- package/types/generated/web/entry.web.d.ts +53 -0
- package/types/generated/web/index.web.d.ts +7 -0
- package/types/generated/web/mount.web.d.ts +2 -0
- package/types/generated/web/shell.web.d.ts +4 -0
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
/** Read-only Windows setup evidence. Prerequisites never qualify a host. */
|
|
2
|
+
import { execFileSync } from 'node:child_process'
|
|
3
|
+
import { existsSync, readFileSync } from 'node:fs'
|
|
4
|
+
import { createRequire } from 'node:module'
|
|
5
|
+
import { join } from 'node:path'
|
|
6
|
+
|
|
7
|
+
function commandOutput(command, args) {
|
|
8
|
+
try {
|
|
9
|
+
return execFileSync(command, args, {
|
|
10
|
+
encoding: 'utf8',
|
|
11
|
+
timeout: 5000,
|
|
12
|
+
maxBuffer: 65536,
|
|
13
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
14
|
+
windowsHide: true,
|
|
15
|
+
}).trim()
|
|
16
|
+
} catch {
|
|
17
|
+
return null
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function windowsPreflight(root, { platform = process.platform, run = commandOutput } = {}) {
|
|
22
|
+
const manifest = JSON.parse(readFileSync(join(root, 'package.json'), 'utf8'))
|
|
23
|
+
const require = createRequire(join(root, 'package.json'))
|
|
24
|
+
const installed = (name) => {
|
|
25
|
+
for (const specifier of [`${name}/package.json`, name]) {
|
|
26
|
+
try {
|
|
27
|
+
require.resolve(specifier)
|
|
28
|
+
return true
|
|
29
|
+
} catch {
|
|
30
|
+
/* Try the public entry. */
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
return false
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const runtime =
|
|
38
|
+
manifest.devDependencies?.['@nativescript/windows'] ??
|
|
39
|
+
manifest.dependencies?.['@nativescript/windows']
|
|
40
|
+
|
|
41
|
+
const requirements = {
|
|
42
|
+
'Windows host': platform === 'win32',
|
|
43
|
+
'nativescript.config': ['ts', 'mts', 'js', 'mjs'].some((ext) =>
|
|
44
|
+
existsSync(join(root, `nativescript.config.${ext}`)),
|
|
45
|
+
),
|
|
46
|
+
'App_Resources/Windows/Package.appxmanifest': existsSync(
|
|
47
|
+
join(root, 'App_Resources/Windows/Package.appxmanifest'),
|
|
48
|
+
),
|
|
49
|
+
'@nativescript/windows exact version':
|
|
50
|
+
typeof runtime === 'string' && /^\d+\.\d+\.\d+(?:-[\w.-]+)?(?:\+[\w.-]+)?$/.test(runtime),
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
for (const name of [
|
|
54
|
+
'@nativescript/windows',
|
|
55
|
+
'@nativescript/core',
|
|
56
|
+
'@nativescript-community/octane',
|
|
57
|
+
'@nativescript/vite',
|
|
58
|
+
'nativescript',
|
|
59
|
+
'vite',
|
|
60
|
+
]) {
|
|
61
|
+
requirements[name] = installed(name)
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// Never run Windows commands on another host, boot a device, or change settings.
|
|
65
|
+
if (platform === 'win32') {
|
|
66
|
+
const sdk = run('dotnet', ['--version'])
|
|
67
|
+
requirements['.NET SDK >= 10'] =
|
|
68
|
+
typeof sdk === 'string' && /^\d+\.\d+\.\d+/.test(sdk) && Number(sdk.split('.')[0]) >= 10
|
|
69
|
+
|
|
70
|
+
const devMode = run('powershell', [
|
|
71
|
+
'-NoProfile',
|
|
72
|
+
'-NonInteractive',
|
|
73
|
+
'-Command',
|
|
74
|
+
'(Get-ItemProperty "HKLM:\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\AppModelUnlock" -Name AllowDevelopmentWithoutDevLicense -ErrorAction SilentlyContinue).AllowDevelopmentWithoutDevLicense',
|
|
75
|
+
])
|
|
76
|
+
|
|
77
|
+
requirements['Developer Mode (development launch)'] = devMode?.trim() === '1'
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
return {
|
|
81
|
+
backend: 'experimental nativescript/winui',
|
|
82
|
+
executionHost: { required: 'win32', current: platform, reachable: platform === 'win32' },
|
|
83
|
+
qualification: 'unverified',
|
|
84
|
+
requirements,
|
|
85
|
+
status:
|
|
86
|
+
platform !== 'win32'
|
|
87
|
+
? 'remote-host-required'
|
|
88
|
+
: Object.values(requirements).every(Boolean)
|
|
89
|
+
? 'unqualified-backend'
|
|
90
|
+
: 'missing-prerequisites',
|
|
91
|
+
note:
|
|
92
|
+
'Windows workshop mounting, shell and reload are not implemented or host-qualified. ' +
|
|
93
|
+
(platform !== 'win32'
|
|
94
|
+
? 'Run these commands in a Windows checkout; SDK and Developer Mode checks were not run. '
|
|
95
|
+
: '') +
|
|
96
|
+
'Prerequisites do not prove renderer compatibility, input, accessibility or lifecycle support. No web fallback is used.',
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
export function windowsLaunchBlocker(report) {
|
|
101
|
+
if (report.status === 'remote-host-required') {
|
|
102
|
+
return 'Windows workshop requires a real Windows host. Run `xplat workshop doctor` in that host’s checkout. No web fallback is used.'
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
const missing = Object.entries(report.requirements)
|
|
106
|
+
.filter(([, ok]) => !ok)
|
|
107
|
+
.map(([name]) => name)
|
|
108
|
+
|
|
109
|
+
return (
|
|
110
|
+
(missing.length ? `Windows prerequisites missing: ${missing.join(', ')}. ` : '') +
|
|
111
|
+
'Windows workshop mounting, shell and reload remain unimplemented and unqualified; launch is blocked. Run `xplat workshop doctor` for setup evidence.'
|
|
112
|
+
)
|
|
113
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 1,
|
|
3
|
+
"files": [
|
|
4
|
+
"catalog.d.ts",
|
|
5
|
+
"contract.d.ts",
|
|
6
|
+
"controls.d.ts",
|
|
7
|
+
"errors.d.ts",
|
|
8
|
+
"fixture.d.ts",
|
|
9
|
+
"index.d.ts",
|
|
10
|
+
"macos/controller.d.ts",
|
|
11
|
+
"macos/index.macos.d.ts",
|
|
12
|
+
"macos/stage.macos.d.ts",
|
|
13
|
+
"mobile/controller.d.ts",
|
|
14
|
+
"mobile/entry.mobile.d.ts",
|
|
15
|
+
"mobile/index.mobile.d.ts",
|
|
16
|
+
"mobile/mount.mobile.d.ts",
|
|
17
|
+
"mobile/tree.mobile.d.ts",
|
|
18
|
+
"session.d.ts",
|
|
19
|
+
"web/entry.web.d.ts",
|
|
20
|
+
"web/index.web.d.ts",
|
|
21
|
+
"web/mount.web.d.ts",
|
|
22
|
+
"web/shell.web.d.ts"
|
|
23
|
+
]
|
|
24
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Catalog model shared by Node-side discovery, the catalog virtual module,
|
|
3
|
+
* and the shell. Metadata is literal and statically readable — discovery
|
|
4
|
+
* never evaluates story modules.
|
|
5
|
+
*/
|
|
6
|
+
import type { WorkshopTarget } from './contract.js';
|
|
7
|
+
export interface StoryMeta {
|
|
8
|
+
id: string;
|
|
9
|
+
title: string;
|
|
10
|
+
/** Effective targets after declared targets ∩ file-suffix applicability. */
|
|
11
|
+
targets: readonly WorkshopTarget[];
|
|
12
|
+
capabilities: readonly string[];
|
|
13
|
+
/** Absolute file path of the story module. */
|
|
14
|
+
file: string;
|
|
15
|
+
/** Export binding the definition is attached to. */
|
|
16
|
+
exportName: string;
|
|
17
|
+
/** Whether this story can mount on the current build target. */
|
|
18
|
+
applicable: boolean;
|
|
19
|
+
/** Why the story cannot mount here (unsupported-by-story / leaf mismatch). */
|
|
20
|
+
reason?: string;
|
|
21
|
+
}
|
|
22
|
+
export interface CatalogEntry extends StoryMeta {
|
|
23
|
+
/** Present only when applicable: loads the story definition. */
|
|
24
|
+
load?: (stamp?: string | number) => Promise<unknown>;
|
|
25
|
+
}
|
|
26
|
+
/** Platform suffix on a story file narrows applicability to those targets. */
|
|
27
|
+
export declare const SUFFIX_TARGETS: Record<string, readonly WorkshopTarget[]>;
|
|
28
|
+
export declare const PLATFORM_SUFFIX_RE: RegExp;
|
|
29
|
+
export declare function storyFileTag(file: string): string | null;
|
|
30
|
+
export declare function applicableTargets(declared: readonly WorkshopTarget[], file: string): readonly WorkshopTarget[];
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @octane-xplat/workshop — typed authoring contract.
|
|
3
|
+
*
|
|
4
|
+
* One `defineStory` declaration describes an isolated development run of an
|
|
5
|
+
* Octane component: stable identity, target applicability, default args,
|
|
6
|
+
* explicit controls, fresh state, a cancellable async fixture, and ordered
|
|
7
|
+
* provider wrappers. The story file compiles under the consumer's normal
|
|
8
|
+
* renderer rules; this module carries no renderer or host globals itself.
|
|
9
|
+
*/
|
|
10
|
+
import type { Store } from '@octane-xplat/ui/data';
|
|
11
|
+
/** The six product targets. Only `web` is qualified by the current release. */
|
|
12
|
+
export type WorkshopTarget = 'web' | 'ios' | 'android' | 'macos' | 'windows' | 'linux';
|
|
13
|
+
export declare const WORKSHOP_TARGETS: readonly WorkshopTarget[];
|
|
14
|
+
/** Version of the authoring contract — bumped on breaking changes. */
|
|
15
|
+
export declare const WORKSHOP_CONTRACT_VERSION = 1;
|
|
16
|
+
export type StoryTargets = 'all' | readonly WorkshopTarget[];
|
|
17
|
+
/**
|
|
18
|
+
* Portable cancellation contract for fixture work. The host adapter supplies
|
|
19
|
+
* an implementation with equivalent observable semantics — do not rely on
|
|
20
|
+
* `AbortController`/`signal.throwIfAborted` being the underlying mechanism.
|
|
21
|
+
*/
|
|
22
|
+
export interface CancellationSignal {
|
|
23
|
+
readonly aborted: boolean;
|
|
24
|
+
addEventListener(type: 'abort', listener: () => void): void;
|
|
25
|
+
removeEventListener(type: 'abort', listener: () => void): void;
|
|
26
|
+
}
|
|
27
|
+
export type StoryCleanup = () => void | Promise<void>;
|
|
28
|
+
/** Context handed to the fixture factory for each story generation. */
|
|
29
|
+
export interface FixtureContext {
|
|
30
|
+
/** The real target this run mounts on. */
|
|
31
|
+
readonly target: WorkshopTarget;
|
|
32
|
+
/** Capabilities declared by the story. */
|
|
33
|
+
readonly capabilities: readonly string[];
|
|
34
|
+
/** Observable cancellation; aborted when the generation retires. */
|
|
35
|
+
readonly signal: CancellationSignal;
|
|
36
|
+
/**
|
|
37
|
+
* Register an owned resource for disposal. Disposal runs exactly once —
|
|
38
|
+
* including for resources registered after cancellation, which are
|
|
39
|
+
* released as soon as the fixture settles.
|
|
40
|
+
*/
|
|
41
|
+
onCleanup(cleanup: StoryCleanup): void;
|
|
42
|
+
}
|
|
43
|
+
export type FixtureFactory<F> = (context: FixtureContext) => F | Promise<F>;
|
|
44
|
+
/** A compiler-produced Octane component. Kept opaque to transport/tooling. */
|
|
45
|
+
export type StoryComponent<P> = (props: P) => unknown;
|
|
46
|
+
/**
|
|
47
|
+
* An ordered provider wrapper. Consumer presets wrap story providers;
|
|
48
|
+
* each receives the typed fixture result and `children`.
|
|
49
|
+
*/
|
|
50
|
+
export type StoryProvider<F> = (props: {
|
|
51
|
+
fixture: F;
|
|
52
|
+
children?: unknown;
|
|
53
|
+
}) => unknown;
|
|
54
|
+
export interface BooleanControl {
|
|
55
|
+
type: 'boolean';
|
|
56
|
+
}
|
|
57
|
+
export interface StringControl {
|
|
58
|
+
type: 'string';
|
|
59
|
+
placeholder?: string;
|
|
60
|
+
}
|
|
61
|
+
export interface NumberControl {
|
|
62
|
+
type: 'number';
|
|
63
|
+
min?: number;
|
|
64
|
+
max?: number;
|
|
65
|
+
step?: number;
|
|
66
|
+
}
|
|
67
|
+
export interface EnumControl<T> {
|
|
68
|
+
type: 'enum';
|
|
69
|
+
options: readonly T[];
|
|
70
|
+
}
|
|
71
|
+
type ControlValue<T> = 0 extends 1 & T ? BooleanControl | NumberControl | StringControl | EnumControl<T> : [T] extends [boolean] ? BooleanControl : [T] extends [number] ? NumberControl | EnumControl<T> : [T] extends [string] ? StringControl | EnumControl<T> : never;
|
|
72
|
+
/**
|
|
73
|
+
* Controls may be declared only for explicitly declared, portable-valued
|
|
74
|
+
* props. `allowUnset` is legal only on optional props — Unset removes the
|
|
75
|
+
* key from the next props bag rather than merging a stale value.
|
|
76
|
+
*/
|
|
77
|
+
export type StoryControl<T, Optional extends boolean> = ControlValue<T> & (Optional extends true ? {
|
|
78
|
+
allowUnset?: boolean;
|
|
79
|
+
} : {
|
|
80
|
+
allowUnset?: false;
|
|
81
|
+
});
|
|
82
|
+
export type StoryControls<P> = {
|
|
83
|
+
[K in keyof P]?: StoryControl<NonNullable<P[K]>, undefined extends P[K] ? true : false>;
|
|
84
|
+
};
|
|
85
|
+
/** Props the runner injects beyond `args`: fresh state store + fixture. */
|
|
86
|
+
export type InjectedStoryProps<S, F> = (S extends void ? unknown : {
|
|
87
|
+
state: Store<S>;
|
|
88
|
+
}) & (F extends void ? unknown : {
|
|
89
|
+
fixture: F;
|
|
90
|
+
});
|
|
91
|
+
type ReservedStoryKeys = 'state' | 'fixture';
|
|
92
|
+
/** Reserved injected prop names cannot collide with declared props. */
|
|
93
|
+
type PropsWithoutReserved<P> = Extract<keyof P, ReservedStoryKeys> extends never ? P : never;
|
|
94
|
+
/**
|
|
95
|
+
* A complete story declaration. `id`, `title`, `targets`, and `capabilities`
|
|
96
|
+
* must be literal values — discovery reads them statically without
|
|
97
|
+
* evaluating the module.
|
|
98
|
+
*/
|
|
99
|
+
export type StoryDefinition<P = Record<string, never>, S = void, F = void> = {
|
|
100
|
+
/** Stable, unique story id — e.g. 'account/profile-card'. */
|
|
101
|
+
id: string;
|
|
102
|
+
/** Display title; '/' segments group the catalog. */
|
|
103
|
+
title: string;
|
|
104
|
+
/** Targets this story can mount on; 'all' covers the six product targets. */
|
|
105
|
+
targets: StoryTargets;
|
|
106
|
+
/** Host capabilities the story requires (e.g. 'camera', 'network-write'). */
|
|
107
|
+
capabilities?: readonly string[];
|
|
108
|
+
/** The component under development. */
|
|
109
|
+
component: StoryComponent<PropsWithoutReserved<P> & InjectedStoryProps<S, F>>;
|
|
110
|
+
/** Immutable default props for each run; cloned fresh per generation. */
|
|
111
|
+
args: PropsWithoutReserved<P>;
|
|
112
|
+
/** Explicit editable inputs for declared props. */
|
|
113
|
+
controls?: StoryControls<P>;
|
|
114
|
+
/** Fixture preparation budget in milliseconds; defaults to 10 000. */
|
|
115
|
+
fixtureTimeout?: number;
|
|
116
|
+
/** Ordered wrappers applied around the component for each generation. */
|
|
117
|
+
providers?: readonly StoryProvider<F extends void ? undefined : F>[];
|
|
118
|
+
} & (S extends void ? {
|
|
119
|
+
state?: never;
|
|
120
|
+
} : {
|
|
121
|
+
state: () => S;
|
|
122
|
+
}) & (F extends void ? {
|
|
123
|
+
fixture?: never;
|
|
124
|
+
} : {
|
|
125
|
+
fixture: FixtureFactory<F>;
|
|
126
|
+
});
|
|
127
|
+
/** Declare a story. Validates cheap authoring rules eagerly. */
|
|
128
|
+
export declare function defineStory<P = Record<string, never>, S = void, F = void>(definition: StoryDefinition<P, S, F>): StoryDefinition<P, S, F>;
|
|
129
|
+
/** Consumer workshop configuration, loaded by the dev tooling. */
|
|
130
|
+
export interface WorkshopConfig {
|
|
131
|
+
stories?: {
|
|
132
|
+
/** Glob(s) matching story modules. Default: '**/*.stories.{ts,tsx,tsrx}' plus platform-suffixed leaves. */
|
|
133
|
+
include?: string | readonly string[];
|
|
134
|
+
exclude?: string | readonly string[];
|
|
135
|
+
};
|
|
136
|
+
/**
|
|
137
|
+
* Module specifiers (relative to the config file) whose `providers` or
|
|
138
|
+
* `default` export is an array of provider components wrapping every story.
|
|
139
|
+
*/
|
|
140
|
+
providerModules?: readonly string[];
|
|
141
|
+
/** Extra static asset roots served under /@workshop-assets/<name>/. */
|
|
142
|
+
assets?: Record<string, string>;
|
|
143
|
+
}
|
|
144
|
+
export declare function defineWorkshopConfig(config: WorkshopConfig): WorkshopConfig;
|
|
145
|
+
export {};
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Control application helpers — host-agnostic. The next props bag is always
|
|
3
|
+
* complete: Unset removes the key entirely; removed keys reach the renderer
|
|
4
|
+
* as explicit `undefined` so nothing stale merges forward.
|
|
5
|
+
*/
|
|
6
|
+
import type { StoryDefinition } from './contract.js';
|
|
7
|
+
export declare const UNSET: unique symbol;
|
|
8
|
+
export type ControlValueInput = boolean | string | number | typeof UNSET;
|
|
9
|
+
export interface FieldError {
|
|
10
|
+
message: string;
|
|
11
|
+
}
|
|
12
|
+
export declare function isUnsettable(definition: StoryDefinition<any, any, any>, key: string): boolean;
|
|
13
|
+
/** Clone default args per generation — nested data must not leak across runs. */
|
|
14
|
+
export declare function freshArgs(args: Record<string, unknown>): Record<string, unknown>;
|
|
15
|
+
/**
|
|
16
|
+
* Validate and fold a control input into the next props bag. Returns either
|
|
17
|
+
* the next complete bag or a per-field error; invalid input never mutates.
|
|
18
|
+
*/
|
|
19
|
+
export declare function applyControlInput(definition: StoryDefinition<any, any, any>, currentProps: Record<string, unknown>, key: string, raw: string | boolean | number | typeof UNSET): {
|
|
20
|
+
props?: Record<string, unknown>;
|
|
21
|
+
error?: FieldError;
|
|
22
|
+
};
|
|
23
|
+
/**
|
|
24
|
+
* Produce the props object handed to the renderer: next bag plus explicit
|
|
25
|
+
* `undefined` for every key present before and absent now — removal is
|
|
26
|
+
* visible to hosts that reconcile keys rather than whole objects.
|
|
27
|
+
*/
|
|
28
|
+
export declare function reconcileProps(previous: Record<string, unknown>, next: Record<string, unknown>): Record<string, unknown>;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/** Failure phases surfaced by the workshop shell and CLI. */
|
|
2
|
+
export type WorkshopErrorPhase = 'discovery' | 'compile' | 'fixture' | 'mount' | 'interaction' | 'cleanup' | 'launch' | 'connection';
|
|
3
|
+
export declare class WorkshopError extends Error {
|
|
4
|
+
readonly phase: WorkshopErrorPhase;
|
|
5
|
+
readonly storyId?: string;
|
|
6
|
+
readonly sourceLocation?: string;
|
|
7
|
+
constructor(phase: WorkshopErrorPhase, message: string, options?: {
|
|
8
|
+
storyId?: string;
|
|
9
|
+
cause?: unknown;
|
|
10
|
+
sourceLocation?: string;
|
|
11
|
+
});
|
|
12
|
+
}
|
|
13
|
+
export declare function describeCause(cause: unknown): string;
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Owned-resource and cancellation machinery for one story generation.
|
|
3
|
+
* Host-agnostic: no DOM globals, no AbortController — the portable
|
|
4
|
+
* CancellationSignal contract only.
|
|
5
|
+
*/
|
|
6
|
+
import type { CancellationSignal, FixtureContext, StoryCleanup, WorkshopTarget } from './contract.js';
|
|
7
|
+
/** The default fixture preparation budget (per-story override: fixtureTimeout). */
|
|
8
|
+
export declare const DEFAULT_FIXTURE_TIMEOUT = 10000;
|
|
9
|
+
/** Bound on owned cleanup before declaring a restart-required state. */
|
|
10
|
+
export declare const DEFAULT_DISPOSAL_TIMEOUT = 10000;
|
|
11
|
+
/** Rejection used to interrupt awaits when a generation is cancelled. */
|
|
12
|
+
export declare class CancelledError extends Error {
|
|
13
|
+
constructor();
|
|
14
|
+
}
|
|
15
|
+
export declare class Cancellation implements CancellationSignal {
|
|
16
|
+
aborted: boolean;
|
|
17
|
+
private readonly listeners;
|
|
18
|
+
private rejectPromise;
|
|
19
|
+
/** Rejects as soon as this generation is cancelled — race it against
|
|
20
|
+
* fixture work so a superseded selection never waits for a dead run. */
|
|
21
|
+
readonly promise: Promise<never>;
|
|
22
|
+
constructor();
|
|
23
|
+
cancel(): void;
|
|
24
|
+
addEventListener(_type: 'abort', listener: () => void): void;
|
|
25
|
+
removeEventListener(_type: 'abort', listener: () => void): void;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Resources a fixture or provider registered for cleanup. Disposal runs each
|
|
29
|
+
* cleanup exactly once, attempts every entry even after a failure, and —
|
|
30
|
+
* after settle() — immediately disposes anything registered late (a fixture
|
|
31
|
+
* that ignored cancellation still has its resources released).
|
|
32
|
+
*/
|
|
33
|
+
export declare class OwnedResources {
|
|
34
|
+
private readonly onLateFailure?;
|
|
35
|
+
private readonly lateTimeout;
|
|
36
|
+
private readonly cleanups;
|
|
37
|
+
private settled;
|
|
38
|
+
private pending;
|
|
39
|
+
constructor(onLateFailure?: ((error: unknown) => void) | undefined, lateTimeout?: number);
|
|
40
|
+
readonly failures: {
|
|
41
|
+
cleanup: StoryCleanup;
|
|
42
|
+
error: unknown;
|
|
43
|
+
}[];
|
|
44
|
+
register(cleanup: StoryCleanup): void;
|
|
45
|
+
private runOne;
|
|
46
|
+
/** Unwind in reverse registration order; every entry is attempted. */
|
|
47
|
+
dispose(): Promise<{
|
|
48
|
+
errors: unknown[];
|
|
49
|
+
}>;
|
|
50
|
+
}
|
|
51
|
+
export declare function createFixtureContext(target: WorkshopTarget, capabilities: readonly string[], cancellation: Cancellation, resources: OwnedResources): FixtureContext;
|
|
52
|
+
/** Race a value/promise against a budget; calls onTimeout before rejecting. */
|
|
53
|
+
export declare function withTimeout<T>(value: T | Promise<T>, ms: number, onTimeout: () => void): Promise<T>;
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { WorkshopSession } from '../session.js';
|
|
2
|
+
import type { CatalogEntry } from '../catalog.js';
|
|
3
|
+
import type { StageHost } from '../session.js';
|
|
4
|
+
/** Selection and draft ownership are independent of the native chrome's layout. */
|
|
5
|
+
export declare class MacOSWorkshopController {
|
|
6
|
+
readonly session: WorkshopSession;
|
|
7
|
+
catalog: readonly CatalogEntry[];
|
|
8
|
+
selectedId: string | null;
|
|
9
|
+
query: string;
|
|
10
|
+
panel: 'stories' | 'controls' | null;
|
|
11
|
+
stageOnly: boolean;
|
|
12
|
+
mode: 'wide' | 'compact' | 'phone';
|
|
13
|
+
drafts: Record<string, string>;
|
|
14
|
+
errors: Record<string, string>;
|
|
15
|
+
catalogError: string;
|
|
16
|
+
private loadGeneration;
|
|
17
|
+
private readonly listeners;
|
|
18
|
+
constructor(host: StageHost, providers?: readonly unknown[]);
|
|
19
|
+
subscribe(listener: () => void): () => boolean;
|
|
20
|
+
notify(): void;
|
|
21
|
+
resize(width: number, height: number): void;
|
|
22
|
+
select(id: string): Promise<void>;
|
|
23
|
+
updateCatalog(catalog: readonly CatalogEntry[], diagnostics?: readonly {
|
|
24
|
+
message: string;
|
|
25
|
+
}[]): Promise<void>;
|
|
26
|
+
control(key: string, input: unknown): void;
|
|
27
|
+
reset(): Promise<void>;
|
|
28
|
+
dispose(): Promise<void>;
|
|
29
|
+
/** Observable state for host automation and qualification evidence. */
|
|
30
|
+
inspect(): {
|
|
31
|
+
phase: import("../session.js").SessionPhase;
|
|
32
|
+
storyId: string | null;
|
|
33
|
+
selectedId: string | null;
|
|
34
|
+
revision: number;
|
|
35
|
+
mountedGeneration: number;
|
|
36
|
+
stale: boolean;
|
|
37
|
+
failure: string | null;
|
|
38
|
+
catalogError: string | null;
|
|
39
|
+
props: Record<string, unknown> | null;
|
|
40
|
+
store: {} | null;
|
|
41
|
+
fixture: {} | null;
|
|
42
|
+
mode: "wide" | "compact" | "phone";
|
|
43
|
+
panel: "controls" | "stories" | null;
|
|
44
|
+
stageOnly: boolean;
|
|
45
|
+
drafts: {
|
|
46
|
+
[x: string]: string;
|
|
47
|
+
};
|
|
48
|
+
errors: {
|
|
49
|
+
[x: string]: string;
|
|
50
|
+
};
|
|
51
|
+
};
|
|
52
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { StageHost } from '../session.js';
|
|
2
|
+
export interface MacOSStageOptions {
|
|
3
|
+
/** An isolated native stage view; shell layout may move/resize it without remounting. */
|
|
4
|
+
view: any;
|
|
5
|
+
/** The shell's window — story-owned windows and sheets anchor to it. */
|
|
6
|
+
parentWindow: any;
|
|
7
|
+
fontFamily?: string;
|
|
8
|
+
}
|
|
9
|
+
/** Session-owned AppKit root. Full bags remove omitted props, including Unset. */
|
|
10
|
+
export declare function createMacOSStageHost(options: MacOSStageOptions): StageHost;
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import type { CatalogEntry } from '../catalog.js';
|
|
2
|
+
import { WorkshopSession } from '../session.js';
|
|
3
|
+
import { type ControlValueInput } from '../controls.js';
|
|
4
|
+
/** Window geometry changes shell presentation only, never the story root. */
|
|
5
|
+
export declare function mobileShellMode(width: number, height: number): 'wide' | 'compact' | 'phone';
|
|
6
|
+
export declare class MobileController {
|
|
7
|
+
readonly session: WorkshopSession;
|
|
8
|
+
readonly changed: () => void;
|
|
9
|
+
selectedId: string | null;
|
|
10
|
+
catalog: readonly CatalogEntry[];
|
|
11
|
+
query: string;
|
|
12
|
+
panel: 'stories' | 'controls' | null;
|
|
13
|
+
stageOnly: boolean;
|
|
14
|
+
missingId: string | null;
|
|
15
|
+
private generation;
|
|
16
|
+
private disposed;
|
|
17
|
+
constructor(session: WorkshopSession, catalog: readonly CatalogEntry[], changed: () => void);
|
|
18
|
+
select(id: string, revision?: string): Promise<void>;
|
|
19
|
+
updateCatalog(catalog: readonly CatalogEntry[], diagnostics?: readonly {
|
|
20
|
+
message: string;
|
|
21
|
+
}[]): void;
|
|
22
|
+
/**
|
|
23
|
+
* One Back/Escape step: dismiss shell chrome before the host treats the key
|
|
24
|
+
* as navigation. The active panel closes first; a stage-only session exits
|
|
25
|
+
* next; nothing to dismiss returns null so the key keeps its host meaning.
|
|
26
|
+
* Story content is untouched — panel/flag changes never mount or dispose.
|
|
27
|
+
*/
|
|
28
|
+
dismissOverlay(): 'panel' | 'stage-only' | null;
|
|
29
|
+
control(key: string, value: ControlValueInput): string | undefined;
|
|
30
|
+
dispose(): Promise<void>;
|
|
31
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { Page } from '@nativescript/core';
|
|
2
|
+
import type { CatalogEntry } from '../catalog.js';
|
|
3
|
+
import { MobileController } from './controller.js';
|
|
4
|
+
export interface MobileWorkshopOptions {
|
|
5
|
+
catalog: readonly CatalogEntry[];
|
|
6
|
+
diagnostics?: readonly {
|
|
7
|
+
message: string;
|
|
8
|
+
}[];
|
|
9
|
+
target: 'ios' | 'android';
|
|
10
|
+
initialStoryId?: string | null;
|
|
11
|
+
globalProviders?: readonly unknown[];
|
|
12
|
+
}
|
|
13
|
+
export interface MobileWorkshopRuntime {
|
|
14
|
+
controller: MobileController;
|
|
15
|
+
page: Page;
|
|
16
|
+
dispose(): Promise<void>;
|
|
17
|
+
}
|
|
18
|
+
export declare function catalogDidUpdate(catalog: readonly CatalogEntry[], diagnostics?: readonly {
|
|
19
|
+
message: string;
|
|
20
|
+
}[]): void;
|
|
21
|
+
/** A host-native shell. Its stage ContentView is attached once for its lifetime. */
|
|
22
|
+
export declare function startWorkshop(options: MobileWorkshopOptions): MobileWorkshopRuntime;
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import type { CancellationSignal, StoryDefinition, WorkshopTarget } from './contract.js';
|
|
2
|
+
import { WorkshopError } from './errors.js';
|
|
3
|
+
export type SessionPhase = 'discovered' | 'preparing' | 'mounting' | 'ready' | 'rebuilding' | 'failed' | 'disposing' | 'disconnected' | 'restart-required';
|
|
4
|
+
export interface SessionSnapshot {
|
|
5
|
+
phase: SessionPhase;
|
|
6
|
+
/** The story currently selected (may differ from the mounted one while preparing). */
|
|
7
|
+
storyId: string | null;
|
|
8
|
+
/** The generation that produced the visible stage. */
|
|
9
|
+
mountedGeneration: number;
|
|
10
|
+
/** True when the visible stage is a previous successful revision. */
|
|
11
|
+
stale: boolean;
|
|
12
|
+
/** Present when phase is 'failed' or 'restart-required'. */
|
|
13
|
+
failure: WorkshopError | undefined;
|
|
14
|
+
/** Number of successful mounts so far. */
|
|
15
|
+
revision: number;
|
|
16
|
+
}
|
|
17
|
+
export interface MountRequest {
|
|
18
|
+
story: StoryDefinition<any, any, any>;
|
|
19
|
+
component: (props: any) => unknown;
|
|
20
|
+
props: Record<string, unknown>;
|
|
21
|
+
providers: readonly unknown[];
|
|
22
|
+
fixture: unknown;
|
|
23
|
+
signal: CancellationSignal;
|
|
24
|
+
onRenderError: (error: unknown) => void;
|
|
25
|
+
}
|
|
26
|
+
export interface MountedStory {
|
|
27
|
+
update(props: Record<string, unknown>): void;
|
|
28
|
+
unmount(): void | Promise<void>;
|
|
29
|
+
}
|
|
30
|
+
/** Host adapter seam — the web adapter supplies createRoot mounting. */
|
|
31
|
+
export interface StageHost {
|
|
32
|
+
mount(request: MountRequest): MountedStory | Promise<MountedStory>;
|
|
33
|
+
}
|
|
34
|
+
interface SessionOptions {
|
|
35
|
+
host: StageHost;
|
|
36
|
+
target: WorkshopTarget;
|
|
37
|
+
globalProviders?: readonly unknown[];
|
|
38
|
+
onChange?: (snapshot: SessionSnapshot) => void;
|
|
39
|
+
disposalTimeout?: number;
|
|
40
|
+
}
|
|
41
|
+
export declare class WorkshopSession {
|
|
42
|
+
private readonly options;
|
|
43
|
+
private generation;
|
|
44
|
+
private blockedFailure;
|
|
45
|
+
private current;
|
|
46
|
+
private snapshot;
|
|
47
|
+
private readonly listeners;
|
|
48
|
+
constructor(options: SessionOptions);
|
|
49
|
+
get state(): SessionSnapshot;
|
|
50
|
+
get target(): WorkshopTarget;
|
|
51
|
+
/** The story definition backing the current generation, if any. */
|
|
52
|
+
get currentStory(): StoryDefinition<any, any, any> | undefined;
|
|
53
|
+
/** The current generation's complete props bag (args + applied controls). */
|
|
54
|
+
get currentProps(): Record<string, unknown> | undefined;
|
|
55
|
+
subscribe(listener: (snapshot: SessionSnapshot) => void): () => void;
|
|
56
|
+
private emit;
|
|
57
|
+
private fail;
|
|
58
|
+
/** Begin a fresh run of `story`; any prior generation is retired first. */
|
|
59
|
+
select(story: StoryDefinition<any, any, any>, mode?: 'select' | 'reset' | 'rebuild'): Promise<void>;
|
|
60
|
+
private injectedProps;
|
|
61
|
+
/**
|
|
62
|
+
* Apply the complete next props bag — controls preserve the current
|
|
63
|
+
* fixture, providers, store and component-local state. Removed keys are
|
|
64
|
+
* forwarded as explicit `undefined`.
|
|
65
|
+
*/
|
|
66
|
+
applyProps(next: Record<string, unknown>): void;
|
|
67
|
+
/** A full fresh run: fresh args, fresh store, fresh fixture, fresh root. */
|
|
68
|
+
reset(): Promise<void>;
|
|
69
|
+
/** A successful source edit remounts the selected story revision. */
|
|
70
|
+
reload(next: StoryDefinition<any, any, any>): Promise<void>;
|
|
71
|
+
/** A source edit is in flight; the next success remounts. */
|
|
72
|
+
markRebuilding(): void;
|
|
73
|
+
/** A failed source edit keeps the last successful stage, visibly stale. */
|
|
74
|
+
markCompileError(error: unknown): void;
|
|
75
|
+
markDisconnected(): void;
|
|
76
|
+
/** Back to live after a disconnect notice. */
|
|
77
|
+
markConnected(): void;
|
|
78
|
+
/** Retire a generation: cancel, unmount, run owned cleanups (all of them). */
|
|
79
|
+
private retire;
|
|
80
|
+
private disposeGeneration;
|
|
81
|
+
/** Dispose the session entirely (server shutdown / target restart). */
|
|
82
|
+
dispose(): Promise<void>;
|
|
83
|
+
}
|
|
84
|
+
export {};
|