@nan0web/ui 1.12.3 → 3.1.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/package.json +29 -20
- package/src/Component/index.js +1 -5
- package/src/Model/Element.js +183 -0
- package/src/Theme/AppTheme.js +19 -0
- package/src/Theme/CustomTheme.js +32 -0
- package/src/Theme/DarkLightTheme.js +34 -0
- package/src/Theme/Theme.js +25 -0
- package/src/Theme/atoms/Avatar.js +20 -0
- package/src/Theme/atoms/Badge.js +28 -0
- package/src/Theme/atoms/Button.js +88 -0
- package/src/Theme/atoms/Checkbox.js +26 -0
- package/src/Theme/atoms/Input.js +28 -0
- package/src/Theme/atoms/Radio.js +26 -0
- package/src/Theme/atoms/Select.js +16 -0
- package/src/Theme/atoms/TextArea.js +17 -0
- package/src/Theme/atoms/Typography.js +26 -0
- package/src/Theme/atoms/index.js +11 -0
- package/src/Theme/createTheme.js +22 -0
- package/src/Theme/index.js +20 -0
- package/src/Theme/molecules/Card.js +24 -0
- package/src/Theme/molecules/index.js +3 -0
- package/src/Theme/organisms/Modal.js +24 -0
- package/src/Theme/organisms/index.js +3 -0
- package/src/Theme/presets/HighContrastTheme.js +65 -0
- package/src/Theme/presets/NightTheme.js +66 -0
- package/src/Theme/presets/index.js +4 -0
- package/src/Theme/tokens.js +115 -0
- package/src/core/InputAdapter.js +1 -2
- package/src/core/Intent.js +22 -8
- package/src/core/Message/Message.js +3 -0
- package/src/core/OutputAdapter.js +9 -13
- package/src/core/index.js +7 -4
- package/src/domain/app/IntentAuditor.js +53 -0
- package/src/domain/app/JsIntentAuditor.js +145 -0
- package/src/domain/app/PyIntentAuditor.js +144 -0
- package/src/domain/app/SnapshotAuditor.js +8 -8
- package/src/domain/components/ShellModel.js +2 -2
- package/src/index.js +35 -9
- package/src/inspect.js +3 -0
- package/src/utils/format.js +21 -0
- package/src/utils/processI18n.js +27 -0
- package/src/utils/resolveContext.js +79 -0
- package/types/Component/index.d.ts +1 -5
- package/types/Model/Element.d.ts +87 -0
- package/types/Theme/AppTheme.d.ts +14 -0
- package/types/Theme/CustomTheme.d.ts +21 -0
- package/types/Theme/DarkLightTheme.d.ts +16 -0
- package/types/Theme/Theme.d.ts +18 -0
- package/types/Theme/atoms/Avatar.d.ts +14 -0
- package/types/Theme/atoms/Badge.d.ts +22 -0
- package/types/Theme/atoms/Button.d.ts +144 -0
- package/types/Theme/atoms/Checkbox.d.ts +20 -0
- package/types/Theme/atoms/Input.d.ts +22 -0
- package/types/Theme/atoms/Radio.d.ts +20 -0
- package/types/Theme/atoms/Select.d.ts +15 -0
- package/types/Theme/atoms/TextArea.d.ts +17 -0
- package/types/Theme/atoms/Typography.d.ts +47 -0
- package/types/Theme/atoms/index.d.ts +10 -0
- package/types/Theme/createTheme.d.ts +7 -0
- package/types/Theme/index.d.ts +10 -0
- package/types/Theme/molecules/Card.d.ts +18 -0
- package/types/Theme/molecules/index.d.ts +2 -0
- package/types/Theme/organisms/Modal.d.ts +18 -0
- package/types/Theme/organisms/index.d.ts +2 -0
- package/types/Theme/presets/HighContrastTheme.d.ts +2 -0
- package/types/Theme/presets/NightTheme.d.ts +2 -0
- package/types/Theme/presets/index.d.ts +3 -0
- package/types/Theme/tokens.d.ts +119 -0
- package/types/core/Intent.d.ts +10 -7
- package/types/core/Message/Message.d.ts +3 -0
- package/types/core/OutputAdapter.d.ts +2 -4
- package/types/core/index.d.ts +5 -2
- package/types/domain/Document.d.ts +2 -1
- package/types/domain/FooterModel.d.ts +2 -1
- package/types/domain/ModelAsApp.d.ts +1 -1
- package/types/domain/app/IntentAuditor.d.ts +23 -0
- package/types/domain/app/JsIntentAuditor.d.ts +22 -0
- package/types/domain/app/PyIntentAuditor.d.ts +22 -0
- package/types/domain/app/SnapshotAuditor.d.ts +5 -6
- package/types/domain/components/ShellModel.d.ts +1 -5
- package/types/index.d.ts +7 -9
- package/types/inspect.d.ts +3 -0
- package/types/utils/format.d.ts +5 -0
- package/types/utils/processI18n.d.ts +8 -0
- package/types/utils/resolveContext.d.ts +21 -0
- package/src/App/Command/DepsCommand.js +0 -24
- package/src/App/Core/CoreApp.js +0 -125
- package/src/App/Core/UI.js +0 -63
- package/src/App/Core/Widget.js +0 -61
- package/src/App/Core/index.js +0 -11
- package/src/App/Scenario.js +0 -45
- package/src/App/User/Command/Message.js +0 -3
- package/src/App/User/Command/index.js +0 -5
- package/src/App/User/UserApp.js +0 -85
- package/src/App/User/UserUI.js +0 -20
- package/src/App/User/index.js +0 -9
- package/src/App/index.js +0 -14
- package/src/Component/Process/Input.js +0 -63
- package/src/Component/Process/Process.js +0 -24
- package/src/Component/Process/index.js +0 -5
- package/src/Component/Welcome/Input.js +0 -48
- package/src/Component/Welcome/Welcome.js +0 -22
- package/src/Component/Welcome/index.js +0 -5
- package/src/Frame/Frame.js +0 -608
- package/src/Frame/Props.js +0 -96
- package/src/StdIn.js +0 -100
- package/src/StdOut.js +0 -95
- package/src/View/RenderOptions.js +0 -48
- package/src/View/View.js +0 -306
- package/src/core/Message/index.js +0 -6
- package/types/App/Command/DepsCommand.d.ts +0 -14
- package/types/App/Core/CoreApp.d.ts +0 -70
- package/types/App/Core/UI.d.ts +0 -38
- package/types/App/Core/Widget.d.ts +0 -39
- package/types/App/Core/index.d.ts +0 -10
- package/types/App/Scenario.d.ts +0 -26
- package/types/App/User/Command/Message.d.ts +0 -2
- package/types/App/User/Command/index.d.ts +0 -3
- package/types/App/User/UserApp.d.ts +0 -41
- package/types/App/User/UserUI.d.ts +0 -9
- package/types/App/User/index.d.ts +0 -8
- package/types/App/index.d.ts +0 -12
- package/types/Component/Process/Input.d.ts +0 -48
- package/types/Component/Process/Process.d.ts +0 -13
- package/types/Component/Process/index.d.ts +0 -4
- package/types/Component/Welcome/Input.d.ts +0 -34
- package/types/Component/Welcome/Welcome.d.ts +0 -13
- package/types/Component/Welcome/index.d.ts +0 -4
- package/types/Frame/Frame.d.ts +0 -186
- package/types/Frame/Props.d.ts +0 -77
- package/types/StdIn.d.ts +0 -62
- package/types/StdOut.d.ts +0 -52
- package/types/View/RenderOptions.d.ts +0 -29
- package/types/View/View.d.ts +0 -124
- package/types/core/Message/index.d.ts +0 -4
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
import { AuditorModel } from '@nan0web/inspect/domain/AuditorModel'
|
|
2
|
+
import { result, show, progress } from '../../core/Intent.js'
|
|
3
|
+
import { IntentAuditor } from './IntentAuditor.js'
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* JsIntentAuditor — Specialized auditor for JS/TS output hygiene.
|
|
7
|
+
*/
|
|
8
|
+
export class JsIntentAuditor extends AuditorModel {
|
|
9
|
+
/** @type {string[]} Directories to ignore during scanning */
|
|
10
|
+
static IGNORE_DIRS = ['node_modules', '.git', '.venv', '.datasets', 'dist', 'build', 'types', 'play', 'test']
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Checks if a directory or file should be ignored.
|
|
14
|
+
* @param {string} name
|
|
15
|
+
* @returns {boolean}
|
|
16
|
+
*/
|
|
17
|
+
static isIgnored(name) {
|
|
18
|
+
return name.startsWith('.') || JsIntentAuditor.IGNORE_DIRS.includes(name)
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Run the JS-specific intent audit.
|
|
23
|
+
* @returns {AsyncGenerator<import('@nan0web/ui').Intent, import('@nan0web/ui').ResultIntent, any>}
|
|
24
|
+
*/
|
|
25
|
+
async *run() {
|
|
26
|
+
const { t } = /** @type {any} */ (this)._
|
|
27
|
+
yield show(t(IntentAuditor.UI.starting, { dir: /** @type {any} */ (this).dir }))
|
|
28
|
+
|
|
29
|
+
const fsDb = /** @type {any} */ (this)._.db
|
|
30
|
+
if (!fsDb) {
|
|
31
|
+
yield show(t(IntentAuditor.UI.errorDb), 'error')
|
|
32
|
+
return result({ success: false })
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const files = []
|
|
36
|
+
const targetDir = fsDb.resolveSync(/** @type {any} */ (this).dir)
|
|
37
|
+
|
|
38
|
+
try {
|
|
39
|
+
for await (const entry of fsDb.browse(targetDir, { depth: Infinity })) {
|
|
40
|
+
if (JsIntentAuditor.isIgnored(entry.name)) continue
|
|
41
|
+
|
|
42
|
+
if (
|
|
43
|
+
entry.isFile &&
|
|
44
|
+
/\.(js|ts|jsx|tsx)$/.test(entry.name) &&
|
|
45
|
+
!entry.name.endsWith('.test.js') &&
|
|
46
|
+
!entry.name.endsWith('.story.js') &&
|
|
47
|
+
!entry.path.includes('/test/') &&
|
|
48
|
+
!entry.path.includes('/play/')
|
|
49
|
+
) {
|
|
50
|
+
files.push(entry.path)
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
} catch (e) {
|
|
54
|
+
// Directory might be missing
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
if (files.length === 0) {
|
|
58
|
+
yield show(t(IntentAuditor.UI.doneSuccess, {}), 'success')
|
|
59
|
+
return result({ success: true })
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
let hasErrors = false
|
|
63
|
+
const allErrors = []
|
|
64
|
+
|
|
65
|
+
for (const file of files) {
|
|
66
|
+
const displayFile = file.startsWith('@app/') ? file.slice(5) : file
|
|
67
|
+
const content = await fsDb.fetch(file)
|
|
68
|
+
const contentString = typeof content === 'string' ? content : JSON.stringify(content)
|
|
69
|
+
|
|
70
|
+
const fileErrors = JsIntentAuditor.inspectFileContent(contentString, t)
|
|
71
|
+
if (fileErrors.length > 0) {
|
|
72
|
+
const errorMessages = fileErrors.join('; ')
|
|
73
|
+
yield show(
|
|
74
|
+
t(IntentAuditor.UI.auditFailed, { file: displayFile, errors: errorMessages }),
|
|
75
|
+
'error',
|
|
76
|
+
)
|
|
77
|
+
allErrors.push(...fileErrors.map((e) => ({ file: displayFile, error: e })))
|
|
78
|
+
hasErrors = true
|
|
79
|
+
} else {
|
|
80
|
+
yield progress(t(IntentAuditor.UI.auditPassed, { file: displayFile }))
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
if (hasErrors) {
|
|
85
|
+
yield show(t(IntentAuditor.UI.doneErrors, {}), 'error')
|
|
86
|
+
return result({ success: false, errors: allErrors })
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
yield show(t(IntentAuditor.UI.doneSuccess, {}), 'success')
|
|
90
|
+
return result({ success: true })
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Inspects file content for console.* or process.* writes.
|
|
95
|
+
* @param {string} content Content of the file.
|
|
96
|
+
* @param {import('@nan0web/i18n').TFunction} t Translate function.
|
|
97
|
+
* @returns {string[]} List of error messages.
|
|
98
|
+
*/
|
|
99
|
+
static inspectFileContent(content, t) {
|
|
100
|
+
const errors = []
|
|
101
|
+
const lines = content.split('\n')
|
|
102
|
+
|
|
103
|
+
lines.forEach((lineText, index) => {
|
|
104
|
+
const lineNum = index + 1
|
|
105
|
+
const trimmed = lineText.trim()
|
|
106
|
+
|
|
107
|
+
// Skip comments
|
|
108
|
+
if (trimmed.startsWith('//') || trimmed.startsWith('*') || trimmed.startsWith('/*')) {
|
|
109
|
+
return
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// 1. Thorough console.* statement scan (including table, trace, etc.)
|
|
113
|
+
const consoleMatch = trimmed.match(/console\.(log|error|warn|info|debug|dir|table|trace|assert)\(/)
|
|
114
|
+
if (consoleMatch) {
|
|
115
|
+
const codeBeforeComment = trimmed.split('//')[0]
|
|
116
|
+
if (codeBeforeComment.includes(consoleMatch[0])) {
|
|
117
|
+
errors.push(
|
|
118
|
+
t(IntentAuditor.UI.errorConsoleLeak, {
|
|
119
|
+
line: lineNum,
|
|
120
|
+
match: consoleMatch[0] + '...',
|
|
121
|
+
}),
|
|
122
|
+
)
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// 2. process.stdout/stderr write scan
|
|
127
|
+
const processMatch = trimmed.match(/process\.(stdout|stderr)\.write\(/)
|
|
128
|
+
if (processMatch) {
|
|
129
|
+
const codeBeforeComment = trimmed.split('//')[0]
|
|
130
|
+
if (codeBeforeComment.includes(processMatch[0])) {
|
|
131
|
+
errors.push(
|
|
132
|
+
t(IntentAuditor.UI.errorProcessLeak, {
|
|
133
|
+
line: lineNum,
|
|
134
|
+
match: processMatch[0] + '...',
|
|
135
|
+
}),
|
|
136
|
+
)
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
})
|
|
140
|
+
|
|
141
|
+
return errors
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
export default JsIntentAuditor
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
import { AuditorModel } from '@nan0web/inspect/domain/AuditorModel'
|
|
2
|
+
import { result, show, progress } from '../../core/Intent.js'
|
|
3
|
+
import { IntentAuditor } from './IntentAuditor.js'
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* PyIntentAuditor — Specialized auditor for Python output hygiene.
|
|
7
|
+
*/
|
|
8
|
+
export class PyIntentAuditor extends AuditorModel {
|
|
9
|
+
/** @type {string[]} Directories to ignore during scanning */
|
|
10
|
+
static IGNORE_DIRS = ['node_modules', '.git', '.venv', '.datasets', 'dist', 'build', 'types', 'play', 'test', 'tests']
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Checks if a directory or file should be ignored.
|
|
14
|
+
* @param {string} name
|
|
15
|
+
* @returns {boolean}
|
|
16
|
+
*/
|
|
17
|
+
static isIgnored(name) {
|
|
18
|
+
return name.startsWith('.') || PyIntentAuditor.IGNORE_DIRS.includes(name)
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Run the Python-specific intent audit.
|
|
23
|
+
* @returns {AsyncGenerator<import('@nan0web/ui').Intent, import('@nan0web/ui').ResultIntent, any>}
|
|
24
|
+
*/
|
|
25
|
+
async *run() {
|
|
26
|
+
const { t } = /** @type {any} */ (this)._
|
|
27
|
+
yield show(t(IntentAuditor.UI.starting, { dir: /** @type {any} */ (this).dir }))
|
|
28
|
+
|
|
29
|
+
const fsDb = /** @type {any} */ (this)._.db
|
|
30
|
+
if (!fsDb) {
|
|
31
|
+
yield show(t(IntentAuditor.UI.errorDb), 'error')
|
|
32
|
+
return result({ success: false })
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const files = []
|
|
36
|
+
const targetDir = fsDb.resolveSync(/** @type {any} */ (this).dir)
|
|
37
|
+
|
|
38
|
+
try {
|
|
39
|
+
for await (const entry of fsDb.browse(targetDir, { depth: Infinity })) {
|
|
40
|
+
if (PyIntentAuditor.isIgnored(entry.name)) continue
|
|
41
|
+
|
|
42
|
+
if (
|
|
43
|
+
entry.isFile &&
|
|
44
|
+
/\.py$/.test(entry.name) &&
|
|
45
|
+
!entry.name.startsWith('test_') &&
|
|
46
|
+
!entry.name.endsWith('_test.py') &&
|
|
47
|
+
!entry.path.includes('/tests/')
|
|
48
|
+
) {
|
|
49
|
+
files.push(entry.path)
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
} catch (e) {
|
|
53
|
+
// Directory might be missing
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
if (files.length === 0) {
|
|
57
|
+
yield show(t(IntentAuditor.UI.doneSuccess, {}), 'success')
|
|
58
|
+
return result({ success: true })
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
let hasErrors = false
|
|
62
|
+
const allErrors = []
|
|
63
|
+
|
|
64
|
+
for (const file of files) {
|
|
65
|
+
const displayFile = file.startsWith('@app/') ? file.slice(5) : file
|
|
66
|
+
const content = await fsDb.fetch(file)
|
|
67
|
+
const contentString = typeof content === 'string' ? content : JSON.stringify(content)
|
|
68
|
+
|
|
69
|
+
const fileErrors = PyIntentAuditor.inspectFileContent(contentString, t)
|
|
70
|
+
if (fileErrors.length > 0) {
|
|
71
|
+
const errorMessages = fileErrors.join('; ')
|
|
72
|
+
yield show(
|
|
73
|
+
t(IntentAuditor.UI.auditFailed, { file: displayFile, errors: errorMessages }),
|
|
74
|
+
'error',
|
|
75
|
+
)
|
|
76
|
+
allErrors.push(...fileErrors.map((e) => ({ file: displayFile, error: e })))
|
|
77
|
+
hasErrors = true
|
|
78
|
+
} else {
|
|
79
|
+
yield progress(t(IntentAuditor.UI.auditPassed, { file: displayFile }))
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
if (hasErrors) {
|
|
84
|
+
yield show(t(IntentAuditor.UI.doneErrors, {}), 'error')
|
|
85
|
+
return result({ success: false, errors: allErrors })
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
yield show(t(IntentAuditor.UI.doneSuccess, {}), 'success')
|
|
89
|
+
return result({ success: true })
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Inspects Python file content for print or sys.stdout/stderr writes.
|
|
94
|
+
* @param {string} content Content of the file.
|
|
95
|
+
* @param {import('@nan0web/i18n').TFunction} t Translate function.
|
|
96
|
+
* @returns {string[]} List of error messages.
|
|
97
|
+
*/
|
|
98
|
+
static inspectFileContent(content, t) {
|
|
99
|
+
const errors = []
|
|
100
|
+
const lines = content.split('\n')
|
|
101
|
+
|
|
102
|
+
lines.forEach((lineText, index) => {
|
|
103
|
+
const lineNum = index + 1
|
|
104
|
+
const trimmed = lineText.trim()
|
|
105
|
+
|
|
106
|
+
// Python Comment skip
|
|
107
|
+
if (trimmed.startsWith('#')) {
|
|
108
|
+
return
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// 1. Python print statement check
|
|
112
|
+
const printMatch = trimmed.match(/\bprint\(/)
|
|
113
|
+
if (printMatch) {
|
|
114
|
+
const codeBeforeComment = trimmed.split('#')[0]
|
|
115
|
+
if (codeBeforeComment.includes(printMatch[0])) {
|
|
116
|
+
errors.push(
|
|
117
|
+
t(IntentAuditor.UI.errorPrintLeak, {
|
|
118
|
+
line: lineNum,
|
|
119
|
+
match: 'print(...)',
|
|
120
|
+
}),
|
|
121
|
+
)
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// 2. sys.stdout/stderr write check
|
|
126
|
+
const sysMatch = trimmed.match(/\bsys\.(stdout|stderr)\.write\(/)
|
|
127
|
+
if (sysMatch) {
|
|
128
|
+
const codeBeforeComment = trimmed.split('#')[0]
|
|
129
|
+
if (codeBeforeComment.includes(sysMatch[0])) {
|
|
130
|
+
errors.push(
|
|
131
|
+
t(IntentAuditor.UI.errorSysWriteLeak, {
|
|
132
|
+
line: lineNum,
|
|
133
|
+
match: sysMatch[0] + '...',
|
|
134
|
+
}),
|
|
135
|
+
)
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
})
|
|
139
|
+
|
|
140
|
+
return errors
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
export default PyIntentAuditor
|
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
import { NaN0 } from '@nan0web/types'
|
|
2
2
|
import { AuditorModel } from '@nan0web/inspect/domain/AuditorModel'
|
|
3
3
|
|
|
4
|
-
import { result, show } from '../../core/Intent.js'
|
|
4
|
+
import { result, show, progress } from '../../core/Intent.js'
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
7
|
* SnapshotAuditor — Zero-Hallucination Snapshot Validation (Model-as-Schema v2).
|
|
8
8
|
* Parses snapshots without evaluating the app logic and detects artifacts.
|
|
9
9
|
*/
|
|
10
10
|
export class SnapshotAuditor extends AuditorModel {
|
|
11
|
-
static alias = '
|
|
11
|
+
static alias = 'snapshots'
|
|
12
12
|
|
|
13
13
|
static dir = {
|
|
14
14
|
type: 'string',
|
|
@@ -122,7 +122,7 @@ export class SnapshotAuditor extends AuditorModel {
|
|
|
122
122
|
|
|
123
123
|
/**
|
|
124
124
|
* Scans data directories to build a word set for each language.
|
|
125
|
-
* @param {
|
|
125
|
+
* @param {any} fsDb FileSystem DB.
|
|
126
126
|
* @param {string} data
|
|
127
127
|
* @returns {Promise<Record<string, Set<string>>>}
|
|
128
128
|
*/
|
|
@@ -170,11 +170,11 @@ export class SnapshotAuditor extends AuditorModel {
|
|
|
170
170
|
* @returns {AsyncGenerator<import('@nan0web/ui').Intent, import('@nan0web/ui').ResultIntent, any>}
|
|
171
171
|
*/
|
|
172
172
|
async *run() {
|
|
173
|
-
const { t } = this._
|
|
173
|
+
const { t } = /** @type {any} */ (this)._
|
|
174
174
|
|
|
175
|
-
yield show(t(SnapshotAuditor.UI.starting, { dir: this.dir }))
|
|
175
|
+
yield show(t(SnapshotAuditor.UI.starting, { dir: /** @type {any} */ (this).dir }))
|
|
176
176
|
|
|
177
|
-
const fsDb = this._.db
|
|
177
|
+
const fsDb = /** @type {any} */ (this)._.db
|
|
178
178
|
|
|
179
179
|
if (!fsDb) {
|
|
180
180
|
yield show(t(SnapshotAuditor.UI.errorDb), 'error')
|
|
@@ -182,7 +182,7 @@ export class SnapshotAuditor extends AuditorModel {
|
|
|
182
182
|
}
|
|
183
183
|
|
|
184
184
|
const files = []
|
|
185
|
-
const snapshotsDir =
|
|
185
|
+
const snapshotsDir = fsDb.resolveSync(/** @type {any} */ (this).dir)
|
|
186
186
|
|
|
187
187
|
// Use robust DB.browse for recursive snapshot detection
|
|
188
188
|
try {
|
|
@@ -234,7 +234,7 @@ export class SnapshotAuditor extends AuditorModel {
|
|
|
234
234
|
allErrors.push(...audit.errors.map((e) => ({ file: displayFile, error: e })))
|
|
235
235
|
hasErrors = true
|
|
236
236
|
} else {
|
|
237
|
-
yield
|
|
237
|
+
yield progress(t(SnapshotAuditor.UI.auditPassed, { file: displayFile }))
|
|
238
238
|
}
|
|
239
239
|
}
|
|
240
240
|
|
|
@@ -37,7 +37,7 @@ export class ShellModel extends Model {
|
|
|
37
37
|
],
|
|
38
38
|
required: true,
|
|
39
39
|
}
|
|
40
|
-
|
|
40
|
+
|
|
41
41
|
static data = {
|
|
42
42
|
help: 'Data source (DSN)',
|
|
43
43
|
type: 'string',
|
|
@@ -207,7 +207,7 @@ export class ShellModel extends Model {
|
|
|
207
207
|
if (!spawn) return yield log('error', 'Spawn missing')
|
|
208
208
|
|
|
209
209
|
const { existsSync } = await import('node:fs')
|
|
210
|
-
const viteConfig = existsSync('vite.docs.js') ? 'vite.docs.js' :
|
|
210
|
+
const viteConfig = existsSync('vite.docs.js') ? 'vite.docs.js' :
|
|
211
211
|
existsSync('vite.config.js') ? 'vite.config.js' : null
|
|
212
212
|
|
|
213
213
|
if (viteConfig) {
|
package/src/index.js
CHANGED
|
@@ -1,16 +1,20 @@
|
|
|
1
|
-
import Frame from './Frame/Frame.js'
|
|
2
1
|
import Locale from './Locale.js'
|
|
3
|
-
import StdIn from './StdIn.js'
|
|
4
|
-
import StdOut from './StdOut.js'
|
|
5
|
-
import View from './View/View.js'
|
|
6
|
-
import RenderOptions from './View/RenderOptions.js'
|
|
7
|
-
import FrameProps from './Frame/Props.js'
|
|
8
2
|
import { Model } from '@nan0web/types'
|
|
9
3
|
import Models from './Model/index.js'
|
|
10
4
|
import Component from './Component/index.js'
|
|
11
|
-
import App from './App/index.js'
|
|
12
5
|
|
|
13
|
-
export {
|
|
6
|
+
export { Locale, Model, Models, Component }
|
|
7
|
+
export { default as Element } from './Model/Element.js'
|
|
8
|
+
export {
|
|
9
|
+
default as Theme,
|
|
10
|
+
getUserTheme,
|
|
11
|
+
CustomTheme,
|
|
12
|
+
DarkLightTheme,
|
|
13
|
+
NightTheme,
|
|
14
|
+
createTheme,
|
|
15
|
+
} from './Theme/index.js'
|
|
16
|
+
export { resolveContext } from './utils/resolveContext.js'
|
|
17
|
+
export { processI18n } from './utils/processI18n.js'
|
|
14
18
|
export { format } from './format.js'
|
|
15
19
|
export { default as Navigation } from './domain/Navigation.js'
|
|
16
20
|
|
|
@@ -22,10 +26,10 @@ export { default as OutputAdapter } from './core/OutputAdapter.js'
|
|
|
22
26
|
export { default as OutputMessage } from './core/Message/OutputMessage.js'
|
|
23
27
|
export { default as UiForm } from './core/Form/Form.js'
|
|
24
28
|
export { default as UiMessage } from './core/Message/Message.js'
|
|
25
|
-
export { default as UiStream } from './core/Stream.js'
|
|
26
29
|
export { default as Error, CancelError } from './core/Error/index.js'
|
|
27
30
|
export { default as UiAdapter } from './core/UiAdapter.js'
|
|
28
31
|
export { resolvePositionalArgs } from './core/resolvePositionalArgs.js'
|
|
32
|
+
export { tokens } from './Theme/tokens.js'
|
|
29
33
|
|
|
30
34
|
// OLMUI Generator Engine
|
|
31
35
|
/** @typedef {import('./core/Intent.js').LogLevel} LogLevel */
|
|
@@ -51,5 +55,27 @@ export { IntentErrorModel } from './core/IntentErrorModel.js'
|
|
|
51
55
|
export { runGenerator } from './core/GeneratorRunner.js'
|
|
52
56
|
export { buildNan0SpecFromTrace } from './testing/CrashReporter.js'
|
|
53
57
|
|
|
58
|
+
// Flow components
|
|
59
|
+
export {
|
|
60
|
+
runFlow,
|
|
61
|
+
flow,
|
|
62
|
+
Prompt,
|
|
63
|
+
Stream,
|
|
64
|
+
Alert,
|
|
65
|
+
Toast,
|
|
66
|
+
Badge,
|
|
67
|
+
Text,
|
|
68
|
+
Table,
|
|
69
|
+
Input,
|
|
70
|
+
Select,
|
|
71
|
+
Confirm,
|
|
72
|
+
Multiselect,
|
|
73
|
+
Mask,
|
|
74
|
+
Password,
|
|
75
|
+
Spinner,
|
|
76
|
+
Progress,
|
|
77
|
+
} from './core/Flow.js'
|
|
78
|
+
export { default as Flow } from './core/Flow.js'
|
|
79
|
+
|
|
54
80
|
/** @typedef {import('./domain/index.js').ModelAsAppOptions} ModelAsAppOptions */
|
|
55
81
|
export * from './domain/index.js'
|
package/src/inspect.js
CHANGED
|
@@ -1,3 +1,6 @@
|
|
|
1
1
|
export * from './testing/SnapshotRunner.js'
|
|
2
2
|
export * from './testing/verifySnapshot.js'
|
|
3
3
|
export { SnapshotAuditor } from './domain/app/SnapshotAuditor.js'
|
|
4
|
+
export { IntentAuditor } from './domain/app/IntentAuditor.js'
|
|
5
|
+
export { JsIntentAuditor } from './domain/app/JsIntentAuditor.js'
|
|
6
|
+
export { PyIntentAuditor } from './domain/app/PyIntentAuditor.js'
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
export const format = {
|
|
2
|
+
currency: (value, currency = 'UAH', locale = 'uk-UA') => {
|
|
3
|
+
return new Intl.NumberFormat(locale, {
|
|
4
|
+
style: 'currency',
|
|
5
|
+
currency,
|
|
6
|
+
maximumFractionDigits: 0
|
|
7
|
+
}).format(value).replace(/,/g, ' ')
|
|
8
|
+
},
|
|
9
|
+
rate: (value, locale = 'uk-UA') => {
|
|
10
|
+
const rate = value < 1 ? value * 100 : value
|
|
11
|
+
return new Intl.NumberFormat(locale, {
|
|
12
|
+
style: 'percent',
|
|
13
|
+
maximumFractionDigits: 2
|
|
14
|
+
}).format(rate / 100)
|
|
15
|
+
},
|
|
16
|
+
number: (value, locale = 'uk-UA') => {
|
|
17
|
+
return new Intl.NumberFormat(locale, {
|
|
18
|
+
maximumFractionDigits: 2
|
|
19
|
+
}).format(value).replace(/,/g, ' ')
|
|
20
|
+
}
|
|
21
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Обробляє i18n та замінює змінні в тексті.
|
|
3
|
+
* @param {any} input - Вміст для обробки.
|
|
4
|
+
* @param {Function} [t] - Функція перекладу.
|
|
5
|
+
* @param {Object} [data] - Дані для підстановки.
|
|
6
|
+
* @returns {any}
|
|
7
|
+
*/
|
|
8
|
+
export function processI18n(input, t, data = {}) {
|
|
9
|
+
// If input is an object with $t property
|
|
10
|
+
if (input && typeof input === 'object' && input.$t) {
|
|
11
|
+
if (t) input = t(input.$t)
|
|
12
|
+
else input = input.$t
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
// Process string content
|
|
16
|
+
if (typeof input === 'string') {
|
|
17
|
+
return input.replace(/\{\{(\w+)\}\}/g, (_, key) => data[key] || '—')
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
// Process arrays recursively
|
|
21
|
+
if (Array.isArray(input)) {
|
|
22
|
+
return input.map((item) => processI18n(item, t, data))
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// Return input as is for other types
|
|
26
|
+
return input
|
|
27
|
+
}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { Data as BaseData } from '@nan0web/db'
|
|
2
|
+
|
|
3
|
+
class Data extends BaseData {
|
|
4
|
+
static OBJECT_DIVIDER = '.'
|
|
5
|
+
}
|
|
6
|
+
/**
|
|
7
|
+
* Resolves context-aware values in strings and objects.
|
|
8
|
+
* Supports:
|
|
9
|
+
* - References: `data:currencies`, `action:save`, `fn:calculate,100,USD,UAH`
|
|
10
|
+
* - Templates: `{{user.name}}`, `{{currencies[0]}}`
|
|
11
|
+
* - Scalars: numbers, booleans, etc. (passthrough)
|
|
12
|
+
*
|
|
13
|
+
* @param {Object} context - Application context (data, actions, functions)
|
|
14
|
+
* @param {*} value - Raw value to resolve
|
|
15
|
+
* @returns {*} Resolved value
|
|
16
|
+
*/
|
|
17
|
+
export function resolveContext(context, value) {
|
|
18
|
+
if (typeof value !== 'string') return value
|
|
19
|
+
|
|
20
|
+
// Handle data:* reference
|
|
21
|
+
if (value.startsWith('data:')) {
|
|
22
|
+
const path = value.slice(5)
|
|
23
|
+
return Data.find(path, context.data) ?? []
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// Handle action:* reference
|
|
27
|
+
if (value.startsWith('action:')) {
|
|
28
|
+
const name = value.slice(7)
|
|
29
|
+
return context.actions?.[name]
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// Handle fn:* reference with arguments
|
|
33
|
+
if (value.startsWith('fn:')) {
|
|
34
|
+
const [fnName, ...rawArgs] = value.slice(3).split(',')
|
|
35
|
+
const fn = context.functions?.[fnName]
|
|
36
|
+
if (typeof fn !== 'function') return undefined
|
|
37
|
+
|
|
38
|
+
// Parse arguments (numbers, booleans, strings)
|
|
39
|
+
const args = rawArgs.map((arg) => {
|
|
40
|
+
try {
|
|
41
|
+
return JSON.parse(arg)
|
|
42
|
+
} catch {
|
|
43
|
+
return arg // Return as-is if parsing fails
|
|
44
|
+
}
|
|
45
|
+
})
|
|
46
|
+
|
|
47
|
+
return () => fn(...args)
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// Handle template interpolation {{key}} using Data.find
|
|
51
|
+
return value.replace(/{{(.*?)}}/g, (_, path) => {
|
|
52
|
+
const result = Data.find(path, context.data)
|
|
53
|
+
return result !== undefined ? result : `{{${path}}}`
|
|
54
|
+
})
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Resolves a context value or returns undefined if resolution fails.
|
|
59
|
+
* Used internally by renderers to safely resolve props.
|
|
60
|
+
*
|
|
61
|
+
* @param {Object} ctx - Context with data, actions and functions
|
|
62
|
+
* @param {string} value - String value to resolve
|
|
63
|
+
* @returns {*} Resolved value or undefined
|
|
64
|
+
*/
|
|
65
|
+
export function resolveContextValue(ctx, value) {
|
|
66
|
+
if (typeof value !== 'string') return undefined
|
|
67
|
+
if (value.startsWith('data:')) {
|
|
68
|
+
return Data.find(value.slice(5), ctx.data) ?? []
|
|
69
|
+
}
|
|
70
|
+
if (value.startsWith('action:')) {
|
|
71
|
+
return ctx.actions?.[value.slice(7)]
|
|
72
|
+
}
|
|
73
|
+
if (value.startsWith('fn:')) {
|
|
74
|
+
const [fnName, ...args] = value.slice(3).split(',')
|
|
75
|
+
const fn = ctx.functions?.[fnName]
|
|
76
|
+
return typeof fn === 'function' ? fn : undefined
|
|
77
|
+
}
|
|
78
|
+
return undefined
|
|
79
|
+
}
|
|
@@ -1,10 +1,6 @@
|
|
|
1
|
+
export { SortableList };
|
|
1
2
|
declare namespace _default {
|
|
2
|
-
export { Welcome };
|
|
3
|
-
export { Process };
|
|
4
3
|
export { SortableList };
|
|
5
4
|
}
|
|
6
5
|
export default _default;
|
|
7
|
-
import Welcome from './Welcome/index.js';
|
|
8
|
-
import Process from './Process/index.js';
|
|
9
6
|
import SortableList from './SortableList/index.js';
|
|
10
|
-
export { Welcome, Process, SortableList };
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file Element — the model of a declarative UI block.
|
|
3
|
+
* Format: { Button: [...], $variant: 'info', $onClick: fn }
|
|
4
|
+
* Framework-agnostic.
|
|
5
|
+
*/
|
|
6
|
+
export default class Element {
|
|
7
|
+
/** @type {Record<string, string | ((val: any, key: string) => Record<string, any>)>} */
|
|
8
|
+
static PROP_ALIASES: Record<string, string | ((val: any, key: string) => Record<string, any>)>;
|
|
9
|
+
/**
|
|
10
|
+
* Factory method to create or return an existing `Element`.
|
|
11
|
+
* @param {Object} input
|
|
12
|
+
* @returns {Element}
|
|
13
|
+
*/
|
|
14
|
+
static from(input: any): Element;
|
|
15
|
+
/**
|
|
16
|
+
* Parses $-props.
|
|
17
|
+
* Converts for example:
|
|
18
|
+
* - $style="color:red;font-size:14px" → { style: { color: 'red', 'font-size': '14px' } }
|
|
19
|
+
* - $onClick=fn → { onClick: fn }
|
|
20
|
+
* - $ariaHidden=true → { 'aria-hidden': true }
|
|
21
|
+
*
|
|
22
|
+
* @param {string} key - The name of the prop
|
|
23
|
+
* @param {any} value - The value of the prop
|
|
24
|
+
* @returns {Object} - An object { propName: newVal }
|
|
25
|
+
*/
|
|
26
|
+
static parseProp(key: string, value: any): any;
|
|
27
|
+
/**
|
|
28
|
+
* Parses an inline CSS style string.
|
|
29
|
+
* @param {string} styleStr
|
|
30
|
+
* @returns {Object}
|
|
31
|
+
*/
|
|
32
|
+
static parseInlineStyle(styleStr: string): any;
|
|
33
|
+
/**
|
|
34
|
+
* Extracts $-props from the block.
|
|
35
|
+
* @param {Object} block
|
|
36
|
+
* @param {boolean} keep$ - Whether to keep the prefix `$`.
|
|
37
|
+
* @param {Function} [flatMap] - Function that transforms a key-value pair into a new key-value pair.
|
|
38
|
+
* @returns {Object}
|
|
39
|
+
*/
|
|
40
|
+
static extractProps(block: any, keep$?: boolean, flatMap?: Function): any;
|
|
41
|
+
/**
|
|
42
|
+
* Extracts tags (non-prefixed keys) from the block.
|
|
43
|
+
* @param {Object} block
|
|
44
|
+
* @returns {Array<[string, any]>}
|
|
45
|
+
*/
|
|
46
|
+
static extractTags(block: any): Array<[string, any]>;
|
|
47
|
+
/**
|
|
48
|
+
* Creates an `Element` from a declarative block.
|
|
49
|
+
* @param {Object} input - For example, { Button: ["Click"], $variant: "primary" }
|
|
50
|
+
*/
|
|
51
|
+
constructor(input?: any);
|
|
52
|
+
/**
|
|
53
|
+
* The component type or HTML tag (e.g., "Button", "div")
|
|
54
|
+
* @type {string}
|
|
55
|
+
*/
|
|
56
|
+
type: string;
|
|
57
|
+
/**
|
|
58
|
+
* The content of the element.
|
|
59
|
+
* Can be:
|
|
60
|
+
* - string → text
|
|
61
|
+
* - true → empty
|
|
62
|
+
* - array → nested blocks { Icon } or text nodes
|
|
63
|
+
* @type {string | true | any[]}
|
|
64
|
+
*/
|
|
65
|
+
content: string | true | any[];
|
|
66
|
+
/**
|
|
67
|
+
* Props extracted from $-keys.
|
|
68
|
+
* For example, { $onClick: fn, $variant: 'info' } → { onClick: fn, variant: 'info' }
|
|
69
|
+
* @type {Object}
|
|
70
|
+
*/
|
|
71
|
+
props: any;
|
|
72
|
+
/**
|
|
73
|
+
* Checks if the element contains nested elements (not only text).
|
|
74
|
+
* @returns {boolean}
|
|
75
|
+
*/
|
|
76
|
+
hasChildren(): boolean;
|
|
77
|
+
/**
|
|
78
|
+
* Checks if the content contains text fragments.
|
|
79
|
+
* @returns {boolean}
|
|
80
|
+
*/
|
|
81
|
+
hasText(): boolean;
|
|
82
|
+
/**
|
|
83
|
+
* Returns an array of child elements (Element instances).
|
|
84
|
+
* @returns {Element[]}
|
|
85
|
+
*/
|
|
86
|
+
getChildElements(): Element[];
|
|
87
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
declare const _default: {
|
|
2
|
+
fontFamily: string;
|
|
3
|
+
atoms: Partial<typeof import("./atoms/index.js")>;
|
|
4
|
+
molecules: typeof import("./molecules/index.js");
|
|
5
|
+
organisms: typeof import("./organisms/index.js");
|
|
6
|
+
};
|
|
7
|
+
export default _default;
|
|
8
|
+
/**
|
|
9
|
+
* Base application theme.
|
|
10
|
+
* Should be extended by app-specific themes.
|
|
11
|
+
*/
|
|
12
|
+
export type AppThemeConfig = import("./Theme.js").ThemeConfig & {
|
|
13
|
+
fontFamily?: string;
|
|
14
|
+
};
|