@nan0web/ui 1.10.0 → 1.12.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 +69 -3
- package/package.json +65 -29
- package/src/App/Command/DepsCommand.js +3 -4
- package/src/Frame/Props.js +12 -18
- package/src/InterfaceTemplate/InterfaceTemplate.js +9 -7
- package/src/Model/index.js +61 -6
- package/src/StdIn.js +2 -6
- package/src/cli.js +1 -0
- package/src/core/GeneratorRunner.js +67 -7
- package/src/core/InputAdapter.js +22 -5
- package/src/core/Intent.js +230 -18
- package/src/core/Message/Message.js +4 -7
- package/src/core/Message/OutputMessage.js +4 -9
- package/src/core/StreamEntry.js +20 -28
- package/src/core/index.js +4 -0
- package/src/domain/Content.js +198 -0
- package/src/domain/Document.js +25 -0
- package/src/domain/FooterModel.js +37 -19
- package/src/domain/HeaderModel.js +47 -21
- package/src/domain/HeroModel.js +24 -22
- package/src/domain/LayoutModel.js +43 -0
- package/src/domain/ModelAsApp.js +46 -0
- package/src/domain/SandboxModel.js +19 -16
- package/src/domain/app/GalleryCommand.js +53 -0
- package/src/domain/app/GalleryRenderIntent.js +77 -0
- package/src/domain/app/SnapshotAuditor.js +399 -0
- package/src/domain/app/SnapshotRunner.js +264 -0
- package/src/domain/app/UIApp.js +78 -0
- package/src/domain/components/BreadcrumbModel.js +10 -6
- package/src/domain/components/FeatureGridModel.js +62 -0
- package/src/domain/components/MarkdownModel.js +24 -0
- package/src/domain/components/ShellModel.js +243 -0
- package/src/domain/components/TableModel.js +10 -6
- package/src/domain/components/ToastModel.js +10 -6
- package/src/domain/components/index.js +3 -1
- package/src/domain/index.js +14 -4
- package/src/index.js +23 -2
- package/src/inspect.js +2 -0
- package/src/test/ScenarioAdapter.js +59 -0
- package/src/test/ScenarioTest.js +51 -0
- package/src/test/ScenarioTest.story.js +56 -0
- package/src/testing/CrashReporter.js +56 -0
- package/src/testing/GalleryGenerator.js +15 -71
- package/src/testing/LogicInspector.js +4 -4
- package/src/testing/SnapshotRunner.js +22 -0
- package/src/testing/SpecAdapter.js +114 -0
- package/src/testing/SpecRunner.js +121 -0
- package/src/testing/VisualAdapter.js +24 -19
- package/src/testing/index.js +5 -1
- package/src/testing/verifySnapshot.js +17 -0
- package/types/App/Command/DepsCommand.d.ts +0 -2
- package/types/Model/index.d.ts +56 -62
- package/types/StdIn.d.ts +3 -3
- package/types/cli.d.ts +1 -0
- package/types/core/GeneratorRunner.d.ts +14 -1
- package/types/core/InputAdapter.d.ts +50 -6
- package/types/core/Intent.d.ts +280 -32
- package/types/core/Message/Message.d.ts +2 -2
- package/types/core/Message/OutputMessage.d.ts +0 -2
- package/types/core/index.d.ts +4 -0
- package/types/domain/Content.d.ts +344 -0
- package/types/domain/Document.d.ts +40 -0
- package/types/domain/FooterModel.d.ts +22 -12
- package/types/domain/HeaderModel.d.ts +36 -13
- package/types/domain/HeroModel.d.ts +19 -17
- package/types/domain/LayoutModel.d.ts +34 -0
- package/types/domain/ModelAsApp.d.ts +23 -0
- package/types/domain/SandboxModel.d.ts +10 -0
- package/types/domain/app/GalleryCommand.d.ts +55 -0
- package/types/domain/app/GalleryRenderIntent.d.ts +31 -0
- package/types/domain/app/SnapshotAuditor.d.ts +99 -0
- package/types/domain/app/SnapshotRunner.d.ts +45 -0
- package/types/domain/app/UIApp.d.ts +60 -0
- package/types/domain/components/BreadcrumbModel.d.ts +6 -8
- package/types/domain/components/FeatureGridModel.d.ts +50 -0
- package/types/domain/components/MarkdownModel.d.ts +19 -0
- package/types/domain/components/ShellModel.d.ts +56 -0
- package/types/domain/components/TableModel.d.ts +4 -0
- package/types/domain/components/ToastModel.d.ts +4 -0
- package/types/domain/components/index.d.ts +3 -0
- package/types/domain/index.d.ts +10 -4
- package/types/index.d.ts +21 -1
- package/types/inspect.d.ts +2 -0
- package/types/test/ScenarioAdapter.d.ts +43 -0
- package/types/test/ScenarioTest.d.ts +24 -0
- package/types/test/ScenarioTest.story.d.ts +1 -0
- package/types/testing/CrashReporter.d.ts +13 -0
- package/types/testing/SnapshotRunner.d.ts +7 -0
- package/types/testing/SpecAdapter.d.ts +58 -0
- package/types/testing/SpecRunner.d.ts +41 -0
- package/types/testing/VisualAdapter.d.ts +0 -6
- package/types/testing/index.d.ts +5 -1
- package/types/testing/verifySnapshot.d.ts +14 -0
- package/src/testing/SnapshotInspector.js +0 -84
- package/types/App/Command/Options.d.ts +0 -43
- package/types/App/Command/index.d.ts +0 -8
- package/types/App/User/Command/Options.d.ts +0 -34
- package/types/core/Message/InputMessage.d.ts +0 -71
- package/types/domain/components/HeroModel.d.ts +0 -24
- package/types/domain/components/ShowcaseAppModel.d.ts +0 -32
- package/types/testing/SnapshotInspector.d.ts +0 -17
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { ModelAsApp } from '../ModelAsApp.js'
|
|
2
|
+
import { resolvePositionalArgs } from '@nan0web/ui-cli'
|
|
3
|
+
import SnapshotAuditor from './SnapshotAuditor.js'
|
|
4
|
+
import GalleryCommand from './GalleryCommand.js'
|
|
5
|
+
import { show, result } from '../../core/Intent.js'
|
|
6
|
+
|
|
7
|
+
export class UIApp extends ModelAsApp {
|
|
8
|
+
static command = {
|
|
9
|
+
type: 'string',
|
|
10
|
+
help: 'Command to run (e.g. gallery)',
|
|
11
|
+
options: [GalleryCommand, SnapshotAuditor],
|
|
12
|
+
default: GalleryCommand.alias || GalleryCommand.name,
|
|
13
|
+
positional: true,
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
static UI = {
|
|
17
|
+
helpText: [
|
|
18
|
+
'# ✨ NaN•Web UI CLI',
|
|
19
|
+
'',
|
|
20
|
+
'Zero-Hallucination Tools for the NaN•Web ecosystem.',
|
|
21
|
+
'',
|
|
22
|
+
'## Usage',
|
|
23
|
+
'',
|
|
24
|
+
'```bash',
|
|
25
|
+
'',
|
|
26
|
+
'nan0ui gallery audit',
|
|
27
|
+
'',
|
|
28
|
+
'nan0ui gallery render',
|
|
29
|
+
'',
|
|
30
|
+
'```',
|
|
31
|
+
].join('\n'),
|
|
32
|
+
unknownCommand: 'Unknown command: {command}',
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
static help = {
|
|
36
|
+
help: 'Show help message',
|
|
37
|
+
default: false,
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* @param {Partial<UIApp> | Record<string, any>} [data={}]
|
|
42
|
+
* @param {import('@nan0web/types').ModelOptions} [options={}]
|
|
43
|
+
*/
|
|
44
|
+
constructor(data = {}, options = {}) {
|
|
45
|
+
super(data, options)
|
|
46
|
+
/** @type {string[]} */ this._positionals = []
|
|
47
|
+
/** @type {string} Type of command to run */ this.command
|
|
48
|
+
/** @type {boolean} Show help message */ this.help
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
async *run() {
|
|
52
|
+
const t = this._.t
|
|
53
|
+
if (this.help || this.command === 'help') {
|
|
54
|
+
yield show(t(UIApp.UI.helpText, undefined))
|
|
55
|
+
return result({})
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const TargetCommand = UIApp.command.options.find((opt) =>
|
|
59
|
+
[opt.alias, opt.name].includes(this.command),
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
if (!TargetCommand) {
|
|
63
|
+
yield show(t(UIApp.UI.unknownCommand, { command: this.command }), 'error')
|
|
64
|
+
return result({ status: 'error' })
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// Pass remaining positionals down to the target action
|
|
68
|
+
const nextData = resolvePositionalArgs(
|
|
69
|
+
/** @type {any} */ (TargetCommand),
|
|
70
|
+
this._positionals || [],
|
|
71
|
+
this
|
|
72
|
+
)
|
|
73
|
+
const intent = new TargetCommand(nextData, this._)
|
|
74
|
+
return yield* intent.run()
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export default UIApp
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { Model } from '@nan0web/types'
|
|
2
|
+
import { show, result } from '../../core/Intent.js'
|
|
2
3
|
|
|
3
4
|
/**
|
|
4
5
|
* @typedef {Object} BreadcrumbItem
|
|
@@ -28,6 +29,12 @@ import { Model } from '@nan0web/types'
|
|
|
28
29
|
* Ctrl+C = always exit (adapter responsibility).
|
|
29
30
|
*/
|
|
30
31
|
export class BreadcrumbModel extends Model {
|
|
32
|
+
static $id = '@nan0web/ui/BreadcrumbModel'
|
|
33
|
+
|
|
34
|
+
static UI = {
|
|
35
|
+
navigated: 'Navigated to Context',
|
|
36
|
+
}
|
|
37
|
+
|
|
31
38
|
// ==========================================
|
|
32
39
|
// 1. MODEL AS SCHEMA (Static Definition)
|
|
33
40
|
// ==========================================
|
|
@@ -243,17 +250,14 @@ export class BreadcrumbModel extends Model {
|
|
|
243
250
|
// ==========================================
|
|
244
251
|
|
|
245
252
|
/**
|
|
246
|
-
* Yields a
|
|
253
|
+
* Yields a show intent with the current breadcrumb path.
|
|
247
254
|
* This is a "display-only" run — it shows the navigation state.
|
|
248
255
|
*/
|
|
249
256
|
async *run() {
|
|
250
|
-
yield {
|
|
251
|
-
type: 'log',
|
|
252
|
-
level: 'info',
|
|
253
|
-
message: this.toString(),
|
|
257
|
+
yield show(this._.t(BreadcrumbModel.UI.navigated, {}), 'info', {
|
|
254
258
|
component: 'Breadcrumbs',
|
|
255
259
|
model: /** @type {BreadcrumbModel} */ (this),
|
|
256
|
-
}
|
|
260
|
+
})
|
|
257
261
|
|
|
258
262
|
return {
|
|
259
263
|
type: 'result',
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { Model } from '@nan0web/types'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* FeatureItemModel — OLMUI Component Model
|
|
5
|
+
* Represents a single features block entry with an icon, title, and description.
|
|
6
|
+
*/
|
|
7
|
+
export class FeatureItemModel extends Model {
|
|
8
|
+
static $id = '@nan0web/ui/FeatureItemModel'
|
|
9
|
+
|
|
10
|
+
static icon = {
|
|
11
|
+
help: 'Icon name from @nan0web/icons or SVG path',
|
|
12
|
+
placeholder: 'stars',
|
|
13
|
+
default: '',
|
|
14
|
+
}
|
|
15
|
+
static title = {
|
|
16
|
+
help: 'Feature heading',
|
|
17
|
+
placeholder: 'High Performance',
|
|
18
|
+
default: '',
|
|
19
|
+
required: true,
|
|
20
|
+
}
|
|
21
|
+
static description = {
|
|
22
|
+
help: 'Detailed feature description',
|
|
23
|
+
placeholder: 'Built with the latest technologies...',
|
|
24
|
+
default: '',
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* @param {Partial<FeatureItemModel>} data
|
|
29
|
+
*/
|
|
30
|
+
constructor(data = {}) {
|
|
31
|
+
super(data)
|
|
32
|
+
/** @type {string} Icon name */ this.icon
|
|
33
|
+
/** @type {string} Feature heading */ this.title
|
|
34
|
+
/** @type {string} Feature description */ this.description
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* FeatureGridModel — OLMUI Component Model
|
|
40
|
+
* Grid of features with icons and descriptions.
|
|
41
|
+
*/
|
|
42
|
+
export class FeatureGridModel extends Model {
|
|
43
|
+
static $id = '@nan0web/ui/FeatureGridModel'
|
|
44
|
+
|
|
45
|
+
static items = {
|
|
46
|
+
help: 'Array of features to display in a grid',
|
|
47
|
+
type: 'FeatureItemModel[]',
|
|
48
|
+
hint: FeatureItemModel,
|
|
49
|
+
default: [],
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* @param {Partial<FeatureGridModel>} data
|
|
54
|
+
*/
|
|
55
|
+
constructor(data = {}) {
|
|
56
|
+
super(data)
|
|
57
|
+
/** @type {FeatureItemModel[]} List of features */
|
|
58
|
+
this.items = Array.isArray(this.items)
|
|
59
|
+
? this.items.map((i) => (i instanceof FeatureItemModel ? i : new FeatureItemModel(i)))
|
|
60
|
+
: []
|
|
61
|
+
}
|
|
62
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { Model } from '@nan0web/types'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* MarkdownModel — OLMUI Model-as-Schema
|
|
5
|
+
* Represents a rich text block powered by Markdown.
|
|
6
|
+
*/
|
|
7
|
+
export class MarkdownModel extends Model {
|
|
8
|
+
static $id = '@nan0web/ui/MarkdownModel'
|
|
9
|
+
|
|
10
|
+
static content = {
|
|
11
|
+
help: 'Markdown content string',
|
|
12
|
+
type: 'textarea',
|
|
13
|
+
default: '',
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* @param {Partial<MarkdownModel> | Record<string, any>} data Model input data.
|
|
18
|
+
* @param {object} [options] Extended options
|
|
19
|
+
*/
|
|
20
|
+
constructor(data = {}, options = {}) {
|
|
21
|
+
super(data, options)
|
|
22
|
+
/** @type {string} Markdown content string */ this.content
|
|
23
|
+
}
|
|
24
|
+
}
|
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
import { Model } from '@nan0web/types'
|
|
2
|
+
import { ask, log } from '../../core/Intent.js'
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* ShellModel — OLMUI Component Model for CLI Orchestration
|
|
6
|
+
* Canonical CLI entry that describes available operations as a schema.
|
|
7
|
+
*/
|
|
8
|
+
export class ShellModel extends Model {
|
|
9
|
+
static $id = '@nan0web/ui/ShellModel'
|
|
10
|
+
|
|
11
|
+
static command = {
|
|
12
|
+
help: 'What do you want to do?',
|
|
13
|
+
type: 'select',
|
|
14
|
+
default: null,
|
|
15
|
+
positional: true,
|
|
16
|
+
// options: [
|
|
17
|
+
// BootEngine,
|
|
18
|
+
// InteractiveCLI,
|
|
19
|
+
// DevMode,
|
|
20
|
+
// BuildProject,
|
|
21
|
+
// TestSSG,
|
|
22
|
+
// SSGGallery,
|
|
23
|
+
// TestWeb,
|
|
24
|
+
// WebGallery,
|
|
25
|
+
// ConfigWizard,
|
|
26
|
+
// ],
|
|
27
|
+
options: [
|
|
28
|
+
{ label: '📡 Boot Engine (Run OS)', value: 'run' },
|
|
29
|
+
{ label: '🖥️ Interactive CLI', value: 'cli' },
|
|
30
|
+
{ label: '🧬 Dev Mode (Hot-Reload)', value: 'dev' },
|
|
31
|
+
{ label: '📦 Build Project (Data & UI)', value: 'build' },
|
|
32
|
+
{ label: '🧪 Test SSG', value: 'test:ssg' },
|
|
33
|
+
{ label: '🔭 SSG Gallery', value: 'ssg:gallery' },
|
|
34
|
+
{ label: '🧪 Test Web', value: 'test:web' },
|
|
35
|
+
{ label: '🔭 Web Gallery', value: 'web:gallery' },
|
|
36
|
+
{ label: '🔧 Config Wizard', value: 'config' },
|
|
37
|
+
],
|
|
38
|
+
required: true,
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
static data = {
|
|
42
|
+
help: 'Data source (DSN)',
|
|
43
|
+
type: 'string',
|
|
44
|
+
default: 'data/',
|
|
45
|
+
alias: 'dsn',
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
static index = {
|
|
49
|
+
help: 'Directory index file name (e.g. README or index)',
|
|
50
|
+
type: 'string',
|
|
51
|
+
default: 'index',
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
static locale = {
|
|
55
|
+
help: 'Application locale',
|
|
56
|
+
type: 'string',
|
|
57
|
+
default: 'en',
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
static port = {
|
|
61
|
+
help: 'Server port',
|
|
62
|
+
type: 'string',
|
|
63
|
+
default: '3000',
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
#options = {}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* @param {object} data
|
|
71
|
+
* @param {object} [options] External dependencies (AppRunner, SSRServer, etc.)
|
|
72
|
+
*/
|
|
73
|
+
constructor(data = {}, options = {}) {
|
|
74
|
+
super(data)
|
|
75
|
+
this.#options = options
|
|
76
|
+
/** @type {string|null} */ this.command
|
|
77
|
+
/** @type {string} */ this.data
|
|
78
|
+
/** @type {string} */ this.index
|
|
79
|
+
/** @type {string} */ this.locale
|
|
80
|
+
/** @type {string} */ this.port
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
async *run() {
|
|
84
|
+
yield log('info', '📡 NaN0Web Engine OLMUI Shell Ready')
|
|
85
|
+
|
|
86
|
+
if (!this.command || this.command === 'help') {
|
|
87
|
+
const res = yield ask('Shell', ShellModel)
|
|
88
|
+
if (res.cancelled) return
|
|
89
|
+
Object.assign(this, res.value)
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
if (this.command !== 'cli' && this.command !== 'help') {
|
|
93
|
+
yield log('info', `📡 Executing command: ${this.command}...`)
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
switch (this.command) {
|
|
97
|
+
case 'run':
|
|
98
|
+
return yield* this.#runEngine()
|
|
99
|
+
case 'cli':
|
|
100
|
+
return yield* this.#runCli()
|
|
101
|
+
case 'config':
|
|
102
|
+
return yield* this.#runConfig()
|
|
103
|
+
case 'build':
|
|
104
|
+
return yield* this.#runBuild()
|
|
105
|
+
case 'dev':
|
|
106
|
+
return yield* this.#runDev()
|
|
107
|
+
case 'test:ssg':
|
|
108
|
+
case 'ssg:gallery':
|
|
109
|
+
case 'test:web':
|
|
110
|
+
case 'web:gallery':
|
|
111
|
+
return yield* this.#runNpmScript(this.command)
|
|
112
|
+
default:
|
|
113
|
+
yield log('error', `Unknown command: ${this.command}`)
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
this.command = null
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
async *#runCli() {
|
|
120
|
+
const { spawn, locale, dsn } = this.#options
|
|
121
|
+
if (!spawn) {
|
|
122
|
+
yield log('error', 'Spawn utility missing. CLI mode requires Node environment.')
|
|
123
|
+
return
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
const bankFrame = ' ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ '
|
|
127
|
+
yield log(
|
|
128
|
+
'success',
|
|
129
|
+
`\x1b[1m\n${bankFrame}\n📡 Launching Sub-App CLI...\n${bankFrame}\n\x1b[22m`,
|
|
130
|
+
)
|
|
131
|
+
|
|
132
|
+
const absPath = (await import('node:path')).resolve('src/ui/cli/index.js')
|
|
133
|
+
const { existsSync } = await import('node:fs')
|
|
134
|
+
if (!existsSync(absPath)) {
|
|
135
|
+
yield log(
|
|
136
|
+
'error',
|
|
137
|
+
`No CLI runner found at ${absPath}. Ensure you are in the application root.`,
|
|
138
|
+
)
|
|
139
|
+
return
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
const args = [absPath]
|
|
143
|
+
if (this.locale) args.push('--locale', this.locale)
|
|
144
|
+
if (this.data) args.push('--data', this.data)
|
|
145
|
+
|
|
146
|
+
const extra = (process.argv || []).slice(3)
|
|
147
|
+
args.push(...extra)
|
|
148
|
+
|
|
149
|
+
try {
|
|
150
|
+
const code = await spawn('node', args, { stdio: 'inherit' })
|
|
151
|
+
if (code !== 0) yield log('error', `CLI exited with code ${code}`)
|
|
152
|
+
} catch (/** @type {any} */ e) {
|
|
153
|
+
yield log('error', `Failed to spawn CLI: ${e.message}`)
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
async *#runEngine() {
|
|
158
|
+
const { AppRunner, SSRServer } = this.#options
|
|
159
|
+
if (!AppRunner) return yield log('error', 'AppRunner dependency missing')
|
|
160
|
+
|
|
161
|
+
const runner = new AppRunner({
|
|
162
|
+
dsn: this.data,
|
|
163
|
+
port: this.port,
|
|
164
|
+
locale: this.locale,
|
|
165
|
+
directoryIndex: this.index,
|
|
166
|
+
})
|
|
167
|
+
for await (const msg of runner.run()) {
|
|
168
|
+
yield log('info', msg)
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
const server = new SSRServer(runner)
|
|
172
|
+
const port = runner.config?.port || 3000
|
|
173
|
+
const { protocol } = await server.listen(port)
|
|
174
|
+
|
|
175
|
+
yield log('success', `\n🌐 Server running on ${protocol}://localhost:${port}`)
|
|
176
|
+
|
|
177
|
+
// Keep alive in CLI mode
|
|
178
|
+
if (typeof process !== 'undefined') {
|
|
179
|
+
while (true) {
|
|
180
|
+
await new Promise((r) => setTimeout(r, 60000))
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
async *#runConfig() {
|
|
186
|
+
const { NaN0WebConfig, DBwithFSDriver } = this.#options
|
|
187
|
+
const res = yield ask('config', NaN0WebConfig)
|
|
188
|
+
if (res.cancelled) return
|
|
189
|
+
|
|
190
|
+
const data = res.value
|
|
191
|
+
if (typeof process !== 'undefined' && DBwithFSDriver) {
|
|
192
|
+
const db = new DBwithFSDriver({ cwd: process.cwd() })
|
|
193
|
+
await db.connect()
|
|
194
|
+
await db.saveDocument('nan0web.config.yaml', {
|
|
195
|
+
name: data.name,
|
|
196
|
+
dsn: data.data || data.dsn,
|
|
197
|
+
locale: data.locale,
|
|
198
|
+
port: data.port,
|
|
199
|
+
directoryIndex: data.index,
|
|
200
|
+
})
|
|
201
|
+
yield log('success', '\n✅ Config saved to nan0web.config.yaml')
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
async *#runBuild() {
|
|
206
|
+
const { spawn, AppRunner, SSRServer } = this.#options
|
|
207
|
+
if (!spawn) return yield log('error', 'Spawn missing')
|
|
208
|
+
|
|
209
|
+
const { existsSync } = await import('node:fs')
|
|
210
|
+
const viteConfig = existsSync('vite.docs.js') ? 'vite.docs.js' :
|
|
211
|
+
existsSync('vite.config.js') ? 'vite.config.js' : null
|
|
212
|
+
|
|
213
|
+
if (viteConfig) {
|
|
214
|
+
yield log('info', `🛠 Building UI (Vite using ${viteConfig})...`)
|
|
215
|
+
const exitCode = await spawn('npx', ['vite', 'build', '-c', viteConfig])
|
|
216
|
+
if (exitCode !== 0) yield log('error', '⚠️ Vite build failed.')
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
const runner = new AppRunner({
|
|
220
|
+
dsn: this.data,
|
|
221
|
+
locale: this.locale,
|
|
222
|
+
directoryIndex: this.index,
|
|
223
|
+
})
|
|
224
|
+
for await (const msg of runner.run()) yield log('info', msg)
|
|
225
|
+
const server = new SSRServer(runner)
|
|
226
|
+
const stats = await server.exportStatic('dist')
|
|
227
|
+
yield log('success', `✅ Built ${stats.count}/${stats.total} pages into /dist`)
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
async *#runDev() {
|
|
231
|
+
const { spawn } = this.#options
|
|
232
|
+
if (!spawn) return yield log('error', 'Dev mode requires spawn')
|
|
233
|
+
yield log('info', '🧬 Starting VITE Dev Server...')
|
|
234
|
+
await spawn('npx', ['vite'], { stdio: 'inherit' })
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
async *#runNpmScript(script) {
|
|
238
|
+
const { spawn } = this.#options
|
|
239
|
+
if (!spawn) return
|
|
240
|
+
yield log('info', `🔭 Running npm run ${script}...`)
|
|
241
|
+
await spawn('npm', ['run', script], { stdio: 'inherit' })
|
|
242
|
+
}
|
|
243
|
+
}
|
|
@@ -1,10 +1,17 @@
|
|
|
1
1
|
import { Model } from '@nan0web/types'
|
|
2
|
+
import { show, result } from '../../core/Intent.js'
|
|
2
3
|
|
|
3
4
|
/**
|
|
4
5
|
* Model-as-Schema for Table Data component.
|
|
5
6
|
* Displays tabular string data in rows and columns.
|
|
6
7
|
*/
|
|
7
8
|
export class TableModel extends Model {
|
|
9
|
+
static $id = '@nan0web/ui/TableModel'
|
|
10
|
+
|
|
11
|
+
static UI = {
|
|
12
|
+
displayingTable: 'Displaying table with {count} rows',
|
|
13
|
+
}
|
|
14
|
+
|
|
8
15
|
static columns = {
|
|
9
16
|
help: 'Array of column headers',
|
|
10
17
|
type: 'string[]',
|
|
@@ -34,14 +41,11 @@ export class TableModel extends Model {
|
|
|
34
41
|
* @returns {AsyncGenerator<any, any, any>}
|
|
35
42
|
*/
|
|
36
43
|
async *run() {
|
|
37
|
-
yield {
|
|
38
|
-
type: 'log',
|
|
39
|
-
level: 'info',
|
|
40
|
-
message: `Displaying table with ${this.rows?.length || 0} rows`,
|
|
44
|
+
yield show(this._.t(TableModel.UI.displayingTable, { count: this.rows?.length || 0 }), 'info', {
|
|
41
45
|
component: 'Table',
|
|
42
46
|
model: this,
|
|
43
|
-
}
|
|
47
|
+
})
|
|
44
48
|
|
|
45
|
-
return
|
|
49
|
+
return result({ rowsCount: this.rows?.length || 0 })
|
|
46
50
|
}
|
|
47
51
|
}
|
|
@@ -1,9 +1,16 @@
|
|
|
1
1
|
import { Model } from '@nan0web/types'
|
|
2
|
+
import { show, result } from '../../core/Intent.js'
|
|
2
3
|
|
|
3
4
|
/**
|
|
4
5
|
* Model-as-Schema for Toast notification.
|
|
5
6
|
*/
|
|
6
7
|
export class ToastModel extends Model {
|
|
8
|
+
static $id = '@nan0web/ui/ToastModel'
|
|
9
|
+
|
|
10
|
+
static UI = {
|
|
11
|
+
toastLog: '{message}',
|
|
12
|
+
}
|
|
13
|
+
|
|
7
14
|
static variant = {
|
|
8
15
|
help: 'Notification color scheme',
|
|
9
16
|
default: 'info',
|
|
@@ -37,14 +44,11 @@ export class ToastModel extends Model {
|
|
|
37
44
|
* @returns {AsyncGenerator<any, any, any>}
|
|
38
45
|
*/
|
|
39
46
|
async *run() {
|
|
40
|
-
yield {
|
|
41
|
-
type: 'log',
|
|
42
|
-
level: this.variant === 'error' ? 'error' : 'info',
|
|
43
|
-
message: this.message,
|
|
47
|
+
yield show(this._.t(ToastModel.UI.toastLog, { message: this.message }), this.variant === 'error' ? 'error' : 'info', {
|
|
44
48
|
component: 'Toast',
|
|
45
49
|
model: this,
|
|
46
|
-
}
|
|
50
|
+
})
|
|
47
51
|
|
|
48
|
-
return
|
|
52
|
+
return result({ dismissed: true })
|
|
49
53
|
}
|
|
50
54
|
}
|
|
@@ -20,6 +20,7 @@ export { GalleryModel } from './GalleryModel.js'
|
|
|
20
20
|
export { PriceModel } from './PriceModel.js'
|
|
21
21
|
export { PricingModel } from './PricingModel.js'
|
|
22
22
|
export { PricingSectionModel } from './PricingSectionModel.js'
|
|
23
|
+
export { FeatureGridModel, FeatureItemModel } from './FeatureGridModel.js'
|
|
23
24
|
|
|
24
25
|
// Hero
|
|
25
26
|
|
|
@@ -39,7 +40,8 @@ export { HeaderConfigModel } from './HeaderConfigModel.js'
|
|
|
39
40
|
export { FooterVisibilityModel } from './FooterVisibilityModel.js'
|
|
40
41
|
export { FooterConfigModel } from './FooterConfigModel.js'
|
|
41
42
|
|
|
42
|
-
// Business Critical
|
|
43
43
|
export { EmptyStateModel } from './EmptyStateModel.js'
|
|
44
44
|
export { BannerModel } from './BannerModel.js'
|
|
45
45
|
export { ProfileDropdownModel } from './ProfileDropdownModel.js'
|
|
46
|
+
export { MarkdownModel } from './MarkdownModel.js'
|
|
47
|
+
export { ShellModel } from './ShellModel.js'
|
package/src/domain/index.js
CHANGED
|
@@ -1,13 +1,19 @@
|
|
|
1
|
+
// Model runner (Model as App)
|
|
2
|
+
/** @typedef {import('./ModelAsApp.js').ModelAsAppOptions} ModelAsAppOptions */
|
|
3
|
+
export { ModelAsApp } from './ModelAsApp.js'
|
|
4
|
+
|
|
1
5
|
// Domain Models — OLMUI Model-as-Schema
|
|
2
6
|
export { SandboxModel } from './SandboxModel.js'
|
|
7
|
+
export { Content } from './Content.js'
|
|
8
|
+
export { Document } from './Document.js'
|
|
3
9
|
export { ShowcaseAppModel } from './ShowcaseAppModel.js'
|
|
4
10
|
export { default as Navigation } from './Navigation.js'
|
|
5
|
-
export { Language } from '@nan0web/i18n/src/domain/Language.js'
|
|
6
11
|
|
|
7
12
|
// Layout Models (Phase 1)
|
|
8
|
-
export {
|
|
9
|
-
export {
|
|
10
|
-
export {
|
|
13
|
+
export { LayoutModel } from './LayoutModel.js'
|
|
14
|
+
export { HeaderModel } from './HeaderModel.js'
|
|
15
|
+
export { FooterModel } from './FooterModel.js'
|
|
16
|
+
export { HeroModel } from './HeroModel.js'
|
|
11
17
|
|
|
12
18
|
// Component Models
|
|
13
19
|
export {
|
|
@@ -24,6 +30,8 @@ export {
|
|
|
24
30
|
AccordionModel,
|
|
25
31
|
GalleryModel,
|
|
26
32
|
PriceModel,
|
|
33
|
+
FeatureGridModel,
|
|
34
|
+
FeatureItemModel,
|
|
27
35
|
PricingModel,
|
|
28
36
|
CommentModel,
|
|
29
37
|
TestimonialModel,
|
|
@@ -39,3 +47,5 @@ export {
|
|
|
39
47
|
BannerModel,
|
|
40
48
|
ProfileDropdownModel,
|
|
41
49
|
} from './components/index.js'
|
|
50
|
+
|
|
51
|
+
export { ShellModel } from './components/ShellModel.js'
|
package/src/index.js
CHANGED
|
@@ -12,7 +12,6 @@ import App from './App/index.js'
|
|
|
12
12
|
export { Frame, FrameProps, Locale, StdIn, StdOut, View, RenderOptions, Model, Component, App }
|
|
13
13
|
export { format } from './format.js'
|
|
14
14
|
export { default as Navigation } from './domain/Navigation.js'
|
|
15
|
-
export { Language } from '@nan0web/i18n/src/domain/Language.js'
|
|
16
15
|
|
|
17
16
|
// export default App
|
|
18
17
|
export { default as FormMessage } from './core/Form/Message.js'
|
|
@@ -27,6 +26,28 @@ export { default as Error, CancelError } from './core/Error/index.js'
|
|
|
27
26
|
export { default as UiAdapter } from './core/UiAdapter.js'
|
|
28
27
|
|
|
29
28
|
// OLMUI Generator Engine
|
|
30
|
-
|
|
29
|
+
/** @typedef {import('./core/Intent.js').LogLevel} LogLevel */
|
|
30
|
+
/** @typedef {import('./core/Intent.js').ShowLevel} ShowLevel */
|
|
31
|
+
/** @typedef {import('./core/Intent.js').FieldSchema} FieldSchema */
|
|
32
|
+
/** @typedef {import('./core/Intent.js').Intent} Intent */
|
|
33
|
+
/** @typedef {import('./core/Intent.js').IntentResponse} IntentResponse */
|
|
34
|
+
/** @typedef {import('./core/Intent.js').AskIntent} AskIntent */
|
|
35
|
+
/** @typedef {import('./core/Intent.js').ProgressIntent} ProgressIntent */
|
|
36
|
+
/** @typedef {import('./core/Intent.js').ProgressOptions} ProgressOptions */
|
|
37
|
+
/** @typedef {import('./core/Intent.js').LogIntent} LogIntent */
|
|
38
|
+
/** @typedef {import('./core/Intent.js').ShowIntent} ShowIntent */
|
|
39
|
+
/** @typedef {import('./core/Intent.js').RenderIntent} RenderIntent */
|
|
40
|
+
/** @typedef {import('./core/Intent.js').ResultIntent} ResultIntent */
|
|
41
|
+
/** @typedef {import('./core/Intent.js').IntentType} IntentType */
|
|
42
|
+
/** @typedef {import('./core/Intent.js').AskResponse} AskResponse */
|
|
43
|
+
/** @typedef {import('./core/Intent.js').AbortResponse} AbortResponse */
|
|
44
|
+
/** @typedef {import('./core/Intent.js').ShowData} ShowData */
|
|
45
|
+
/** @typedef {import('./core/InputAdapter.js').AskOptions} AskOptions */
|
|
46
|
+
export * from './core/Intent.js'
|
|
47
|
+
|
|
31
48
|
export { IntentErrorModel } from './core/IntentErrorModel.js'
|
|
32
49
|
export { runGenerator } from './core/GeneratorRunner.js'
|
|
50
|
+
export { buildNan0SpecFromTrace } from './testing/CrashReporter.js'
|
|
51
|
+
|
|
52
|
+
/** @typedef {import('./domain/index.js').ModelAsAppOptions} ModelAsAppOptions */
|
|
53
|
+
export * from './domain/index.js'
|
package/src/inspect.js
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import InputAdapter from '../core/InputAdapter.js'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Deterministic Scenario Adapter for OLMUI Testing.
|
|
5
|
+
*
|
|
6
|
+
* Drives the Model generator through a predefined script of responses,
|
|
7
|
+
* enabling millisecond-fast verification of complex business logic.
|
|
8
|
+
*/
|
|
9
|
+
export default class ScenarioAdapter extends InputAdapter {
|
|
10
|
+
/**
|
|
11
|
+
* @param {Array<{field: string, value: any, cancelled?: boolean}>} [scenario=[]]
|
|
12
|
+
*/
|
|
13
|
+
constructor(scenario = []) {
|
|
14
|
+
super()
|
|
15
|
+
this.scenario = scenario
|
|
16
|
+
this.intents = [] // Recorded intents for verification
|
|
17
|
+
this.console = {
|
|
18
|
+
info: () => {},
|
|
19
|
+
warn: () => {},
|
|
20
|
+
error: () => {},
|
|
21
|
+
debug: () => {},
|
|
22
|
+
log: () => {},
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* @param {import('../core/Intent.js').AskIntent} intent
|
|
28
|
+
* @returns {Promise<import('../core/Intent.js').AskResponse>}
|
|
29
|
+
*/
|
|
30
|
+
async askIntent(intent) {
|
|
31
|
+
this.intents.push(intent)
|
|
32
|
+
const match = this.scenario.find((s) => s.field === intent.field)
|
|
33
|
+
if (match) {
|
|
34
|
+
return { value: match.value, cancelled: !!match.cancelled }
|
|
35
|
+
}
|
|
36
|
+
// If no specific match, try to use the first available answer or default
|
|
37
|
+
return { value: null, cancelled: false }
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/** @param {import('../core/Intent.js').ProgressIntent} intent */
|
|
41
|
+
async progressIntent(intent) {
|
|
42
|
+
this.intents.push(intent)
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/** @param {import('../core/Intent.js').ShowIntent} intent */
|
|
46
|
+
async showIntent(intent) {
|
|
47
|
+
this.intents.push(intent)
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/** @param {import('../core/Intent.js').RenderIntent} intent */
|
|
51
|
+
async renderIntent(intent) {
|
|
52
|
+
this.intents.push(intent)
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/** @param {import('../core/Intent.js').ResultIntent} intent */
|
|
56
|
+
async resultIntent(intent) {
|
|
57
|
+
this.intents.push(intent)
|
|
58
|
+
}
|
|
59
|
+
}
|