@nan0web/release 3.2.0 → 3.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (69) hide show
  1. package/bin/nan0release.js +33 -2
  2. package/bin/serve.js +73 -0
  3. package/package.json +45 -17
  4. package/src/Release/Document.js +1 -0
  5. package/src/Release/Scanner.js +3 -2
  6. package/src/Release.js +7 -7
  7. package/src/domain/App.js +31 -2
  8. package/src/domain/BumpCommand.js +10 -3
  9. package/src/domain/CheckCommand.js +504 -146
  10. package/src/domain/CheckCommand.story.js +92 -0
  11. package/src/domain/CloseCommand.js +13 -9
  12. package/src/domain/Dashboard/Dashboard.story.js +155 -0
  13. package/src/domain/Dashboard/DashboardModel.js +308 -0
  14. package/src/domain/Dashboard/ReleaseDiscovery.js +132 -0
  15. package/src/domain/Dashboard/ReleaseParser.js +226 -0
  16. package/src/domain/Dashboard/index.js +19 -0
  17. package/src/domain/DepsCommand.js +5 -2
  18. package/src/domain/InitCommand.js +18 -16
  19. package/src/domain/ListCommand.js +45 -0
  20. package/src/domain/PreviewCommand.js +39 -0
  21. package/src/domain/PublishCommand.js +7 -3
  22. package/src/domain/Registry/Registry.story.js +82 -0
  23. package/src/domain/Registry/RegistryModel.js +124 -0
  24. package/src/domain/ServeCommand.js +165 -0
  25. package/src/domain/ServeCommand.story.js +88 -0
  26. package/src/domain/SpecCommand.js +3 -1
  27. package/src/domain/StatusCommand.js +249 -3
  28. package/src/domain/ViewCommand.js +99 -0
  29. package/src/domain/WebCommand.js +140 -0
  30. package/src/domain/inspect/ReleaseAuditor.js +508 -0
  31. package/src/domain/inspect/ReleaseAuditor.story.js +136 -0
  32. package/src/domain/templates/release.html +34 -0
  33. package/src/domain/templates/release.html.template.js +34 -0
  34. package/src/index.js +6 -0
  35. package/src/inspectors/inspect-js.js +32 -0
  36. package/src/releases/1/0/v1.0.3/task.test.js +1 -1
  37. package/src/utils/nodejs/GitTelemetry.js +154 -0
  38. package/types/Release/Document.d.ts +4 -0
  39. package/types/Release/Scanner.d.ts +2 -2
  40. package/types/domain/App.d.ts +19 -3
  41. package/types/domain/BumpCommand.d.ts +9 -3
  42. package/types/domain/CheckCommand.d.ts +127 -15
  43. package/types/domain/CheckCommand.story.d.ts +1 -0
  44. package/types/domain/CloseCommand.d.ts +2 -1
  45. package/types/domain/Dashboard/Dashboard.story.d.ts +1 -0
  46. package/types/domain/Dashboard/DashboardModel.d.ts +273 -0
  47. package/types/domain/Dashboard/GitTelemetry.d.ts +23 -0
  48. package/types/domain/Dashboard/ReleaseDiscovery.d.ts +47 -0
  49. package/types/domain/Dashboard/ReleaseParser.d.ts +79 -0
  50. package/types/domain/Dashboard/index.d.ts +3 -0
  51. package/types/domain/DepsCommand.d.ts +4 -2
  52. package/types/domain/InitCommand.d.ts +2 -1
  53. package/types/domain/ListCommand.d.ts +18 -0
  54. package/types/domain/PreviewCommand.d.ts +6 -0
  55. package/types/domain/PublishCommand.d.ts +6 -3
  56. package/types/domain/Registry/Registry.story.d.ts +1 -0
  57. package/types/domain/Registry/RegistryModel.d.ts +61 -0
  58. package/types/domain/ServeCommand.d.ts +59 -0
  59. package/types/domain/ServeCommand.story.d.ts +1 -0
  60. package/types/domain/SpecCommand.d.ts +2 -1
  61. package/types/domain/StatusCommand.d.ts +39 -0
  62. package/types/domain/ViewCommand.d.ts +27 -0
  63. package/types/domain/WebCommand.d.ts +47 -0
  64. package/types/domain/inspect/ReleaseAuditor.d.ts +134 -0
  65. package/types/domain/inspect/ReleaseAuditor.story.d.ts +1 -0
  66. package/types/domain/templates/release.html.template.d.ts +2 -0
  67. package/types/index.d.ts +5 -0
  68. package/types/inspectors/inspect-js.d.ts +2 -0
  69. package/types/utils/nodejs/GitTelemetry.d.ts +23 -0
@@ -0,0 +1,34 @@
1
+ export default `<!DOCTYPE html>
2
+ <html lang="uk" data-bs-theme="dark">
3
+ <head>
4
+ <meta charset="utf-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1">
6
+ <title>Releases Catalog — NaN•Web</title>
7
+ <style>
8
+ :root {
9
+ --bg: #0f172a;
10
+ --surface: #1e293b;
11
+ --accent: #38bdf8;
12
+ --text: #f8fafc;
13
+ --text-dim: #94a3b8;
14
+ --border: #334155;
15
+ }
16
+ body { font-family: system-ui, -apple-system, sans-serif; background: var(--bg); color: var(--text); margin: 0; padding: 2.5rem 1.5rem; display: flex; justify-content: center; line-height: 1.6; }
17
+ .container { max-width: 880px; width: 100%; }
18
+ h1, h2, h3 { color: #fff; font-weight: 700; margin-top: 1.5rem; }
19
+ h1 { font-size: 2rem; border-bottom: 1px solid var(--border); padding-bottom: 0.8rem; }
20
+ a { color: var(--accent); text-decoration: none; }
21
+ a:hover { text-decoration: underline; }
22
+ table { width: 100%; border-collapse: collapse; margin: 1.5rem 0; background: var(--surface); border-radius: 8px; overflow: hidden; }
23
+ th, td { padding: 0.85rem 1rem; text-align: left; border-bottom: 1px solid var(--border); }
24
+ th { background: rgba(0,0,0,0.2); font-weight: 600; color: var(--accent); }
25
+ pre { background: var(--surface); padding: 1rem; border-radius: 8px; border: 1px solid var(--border); overflow-x: auto; color: #a5b4fc; font-family: monospace; }
26
+ blockquote { border-left: 4px solid var(--accent); margin: 1rem 0; padding: 0.5rem 1rem; background: var(--surface); border-radius: 0 8px 8px 0; }
27
+ </style>
28
+ </head>
29
+ <body>
30
+ <div class="container">
31
+ {{CONTENT}}
32
+ </div>
33
+ </body>
34
+ </html>`
package/src/index.js CHANGED
@@ -30,3 +30,9 @@ export { default as DepsCommand } from './domain/DepsCommand.js'
30
30
  export { default as PublishCommand } from './domain/PublishCommand.js'
31
31
  export { default as SpecCommand } from './domain/SpecCommand.js'
32
32
  export { default as StatusCommand } from './domain/StatusCommand.js'
33
+ export { default as WebCommand } from './domain/WebCommand.js'
34
+ export { default as PreviewCommand } from './domain/PreviewCommand.js'
35
+ export { RegistryModel } from './domain/Registry/RegistryModel.js'
36
+ export { DashboardModel } from './domain/Dashboard/DashboardModel.js'
37
+ export { ReleaseAuditor } from './domain/inspect/ReleaseAuditor.js'
38
+
@@ -0,0 +1,32 @@
1
+ #!/usr/bin/env node
2
+ // Inspector: checks JavaScript/TypeScript files for syntax errors using `node --check`.
3
+ // Uses git to obtain list of changed files in the current HEAD.
4
+ const { execSync } = require('child_process');
5
+
6
+ function getChangedFiles() {
7
+ try {
8
+ const output = execSync('git diff --name-only --diff-filter=ACM HEAD', { encoding: 'utf8' });
9
+ return output.split(/\n/).filter(f => f.match(/\.(js|ts)$/));
10
+ } catch (e) {
11
+ console.error('Failed to obtain changed files:', e.message);
12
+ process.exit(1);
13
+ }
14
+ }
15
+
16
+ function checkFile(file) {
17
+ console.log(`🔎 Checking ${file}`);
18
+ try {
19
+ execSync(`node --check ${file}`, { stdio: 'inherit' });
20
+ } catch (e) {
21
+ console.error(`❌ Syntax error in ${file}`);
22
+ process.exit(1);
23
+ }
24
+ }
25
+
26
+ const files = getChangedFiles();
27
+ if (files.length === 0) {
28
+ console.log('✅ No JavaScript/TypeScript files changed.');
29
+ process.exit(0);
30
+ }
31
+ files.forEach(checkFile);
32
+ process.exit(0);
@@ -9,7 +9,7 @@ describe('Release v3.0.0 - Dependency Alignment', () => {
9
9
  const pkgPath = join(process.cwd(), 'package.json')
10
10
  const pkg = JSON.parse(await readFile(pkgPath, 'utf8'))
11
11
 
12
- assert.ok(/^3\.1\.\d+$/.test(pkg.version), 'Version should start with 3.1')
12
+ assert.ok(/^3\.\d+\.\d+$/.test(pkg.version), 'Version should start with 3.x')
13
13
  assert.ok(/^(\^1\.8\.0|workspace:\*)$/.test(pkg.dependencies['@nan0web/ui']), '@nan0web/ui should be ^1.8.0 or workspace:*')
14
14
  assert.ok(/^(\^2\.9\.0|workspace:\*)$/.test(pkg.dependencies['@nan0web/ui-cli']), '@nan0web/ui-cli should be ^2.9.0 or workspace:*')
15
15
  })
@@ -0,0 +1,154 @@
1
+ import fs from 'node:fs'
2
+ import { execSync } from 'node:child_process'
3
+
4
+ let rootGitStatusCache = null
5
+ let rootGitBranchCache = null
6
+ const gitInfoCache = new Map()
7
+ const gitTelemetryCache = new Map()
8
+
9
+ /**
10
+ * Reset internal Git caches (useful for testing or cache invalidation)
11
+ */
12
+ export function resetGitCache() {
13
+ rootGitStatusCache = null
14
+ rootGitBranchCache = null
15
+ gitInfoCache.clear()
16
+ gitTelemetryCache.clear()
17
+ }
18
+
19
+ /**
20
+ * Retrieve git branch and workspace status (clean / dirty)
21
+ * @param {string} [projectDir]
22
+ * @returns {{ branch: string, gitStatus: 'clean' | 'dirty' }}
23
+ */
24
+ export function getGitInfo(projectDir) {
25
+ const key = projectDir || process.cwd()
26
+ const isMonorepoInternal =
27
+ !projectDir ||
28
+ projectDir === '.' ||
29
+ (!projectDir.startsWith('../') && !projectDir.startsWith('/'))
30
+
31
+ if (isMonorepoInternal) {
32
+ if (rootGitStatusCache === null) {
33
+ try {
34
+ rootGitBranchCache =
35
+ execSync('git rev-parse --abbrev-ref HEAD', {
36
+ cwd: process.cwd(),
37
+ stdio: ['ignore', 'pipe', 'ignore'],
38
+ encoding: 'utf8',
39
+ }).trim() || 'main'
40
+ rootGitStatusCache = execSync('git status --porcelain', {
41
+ cwd: process.cwd(),
42
+ stdio: ['ignore', 'pipe', 'ignore'],
43
+ encoding: 'utf8',
44
+ }).trim()
45
+ } catch {
46
+ rootGitBranchCache = 'main'
47
+ rootGitStatusCache = ''
48
+ }
49
+ }
50
+ const normPrefix =
51
+ projectDir && projectDir !== '.'
52
+ ? `${projectDir.replace(/\/+$/, '')}/`
53
+ : ''
54
+ const isDirty = normPrefix
55
+ ? rootGitStatusCache
56
+ .split('\n')
57
+ .some((line) => line.slice(3).trim().startsWith(normPrefix))
58
+ : rootGitStatusCache.length > 0
59
+
60
+ return {
61
+ branch: rootGitBranchCache,
62
+ gitStatus: isDirty ? 'dirty' : 'clean',
63
+ }
64
+ }
65
+
66
+ if (gitInfoCache.has(key)) return gitInfoCache.get(key)
67
+ if (fs.existsSync && !fs.existsSync(key)) {
68
+ const res = { branch: 'main', gitStatus: 'clean' }
69
+ gitInfoCache.set(key, res)
70
+ return res
71
+ }
72
+
73
+ try {
74
+ const branch = execSync('git rev-parse --abbrev-ref HEAD', {
75
+ cwd: key,
76
+ stdio: ['ignore', 'pipe', 'ignore'],
77
+ encoding: 'utf8',
78
+ }).trim()
79
+ const statusOutput = execSync('git status --porcelain', {
80
+ cwd: key,
81
+ stdio: ['ignore', 'pipe', 'ignore'],
82
+ encoding: 'utf8',
83
+ }).trim()
84
+ const res = {
85
+ branch: branch || 'main',
86
+ gitStatus: statusOutput.length > 0 ? 'dirty' : 'clean',
87
+ }
88
+ gitInfoCache.set(key, res)
89
+ return res
90
+ } catch {
91
+ const res = { branch: 'main', gitStatus: 'clean' }
92
+ gitInfoCache.set(key, res)
93
+ return res
94
+ }
95
+ }
96
+
97
+ /**
98
+ * Retrieve development hours and iterations from git commit log
99
+ * @param {string} [projectDir]
100
+ * @param {string} [releasePath]
101
+ * @returns {{ hours: number, iterations: number }}
102
+ */
103
+ export function getGitCommitTelemetry(projectDir, releasePath) {
104
+ const targetDir = releasePath || projectDir
105
+ if (!targetDir) return { hours: 0, iterations: 0 }
106
+ if (gitTelemetryCache.has(targetDir)) return gitTelemetryCache.get(targetDir)
107
+ if (fs.existsSync && !fs.existsSync(targetDir)) {
108
+ const res = { hours: 0, iterations: 0 }
109
+ gitTelemetryCache.set(targetDir, res)
110
+ return res
111
+ }
112
+
113
+ try {
114
+ const logOutput = execSync(
115
+ `git log --format="%at" -n 50 -- "${targetDir}"`,
116
+ {
117
+ cwd: process.cwd(),
118
+ stdio: ['ignore', 'pipe', 'ignore'],
119
+ encoding: 'utf8',
120
+ }
121
+ ).trim()
122
+
123
+ if (!logOutput) {
124
+ const res = { hours: 0, iterations: 0 }
125
+ gitTelemetryCache.set(targetDir, res)
126
+ return res
127
+ }
128
+
129
+ const timestamps = logOutput
130
+ .split('\n')
131
+ .map((t) => parseInt(t.trim(), 10))
132
+ .filter((t) => !isNaN(t))
133
+
134
+ if (timestamps.length < 2) {
135
+ const res = { hours: 0.5, iterations: timestamps.length }
136
+ gitTelemetryCache.set(targetDir, res)
137
+ return res
138
+ }
139
+
140
+ const minT = Math.min(...timestamps)
141
+ const maxT = Math.max(...timestamps)
142
+ const diffHours = Math.round(((maxT - minT) / 3600) * 10) / 10
143
+ const hours = diffHours > 0 ? diffHours : 0.5
144
+ const iterations = timestamps.length
145
+
146
+ const res = { hours, iterations }
147
+ gitTelemetryCache.set(targetDir, res)
148
+ return res
149
+ } catch {
150
+ const res = { hours: 0, iterations: 0 }
151
+ gitTelemetryCache.set(targetDir, res)
152
+ return res
153
+ }
154
+ }
@@ -26,6 +26,10 @@ declare class ReleaseDocument extends Markdown {
26
26
  version: string;
27
27
  /** @type {Date | undefined} */
28
28
  date: Date | undefined;
29
+ sections: {
30
+ title: string;
31
+ tasks: any[];
32
+ }[] | undefined;
29
33
  }
30
34
  import Markdown from '@nan0web/markdown';
31
35
  import Person from './Person.js';
@@ -19,9 +19,9 @@
19
19
  */
20
20
  export default class Scanner {
21
21
  /**
22
- * @param {string} root - Root directory of the project
22
+ * @param {string} [root] - Root directory of the project
23
23
  */
24
- constructor(root: string);
24
+ constructor(root?: string);
25
25
  /** @type {string} */
26
26
  root: string;
27
27
  /**
@@ -1,17 +1,33 @@
1
1
  export class ReleaseApp extends ModelAsApp {
2
+ static alias: string;
3
+ static UI: {
4
+ title: string;
5
+ };
2
6
  static command: {
3
7
  help: string;
4
- options: (typeof CheckCommand | typeof StatusCommand)[];
8
+ options: (typeof CheckCommand | typeof CloseCommand | typeof DepsCommand | typeof PublishCommand | typeof StatusCommand | typeof WebCommand | typeof ListCommand | typeof ServeCommand)[];
5
9
  positional: boolean;
6
- default: null;
10
+ default: typeof ViewCommand;
7
11
  };
8
12
  /**
9
13
  * @param {Partial<ReleaseApp>} [data]
10
14
  * @param {Partial<import('@nan0web/ui').ModelAsAppOptions>} [options]
11
15
  */
12
16
  constructor(data?: Partial<ReleaseApp>, options?: Partial<import("@nan0web/ui").ModelAsAppOptions>);
13
- /** @type {ModelAsApp} Subcommand to run */ command: ModelAsApp;
17
+ /**
18
+ * Run the main controller logic.
19
+ * @returns {AsyncGenerator<import('@nan0web/ui').Intent, any, any>}
20
+ */
21
+ run(): AsyncGenerator<import("@nan0web/ui").Intent, any, any>;
14
22
  }
23
+ export default ReleaseApp;
15
24
  import { ModelAsApp } from "@nan0web/ui";
16
25
  import CheckCommand from './CheckCommand.js';
26
+ import CloseCommand from './CloseCommand.js';
27
+ import DepsCommand from './DepsCommand.js';
28
+ import PublishCommand from './PublishCommand.js';
17
29
  import StatusCommand from './StatusCommand.js';
30
+ import WebCommand from './WebCommand.js';
31
+ import ListCommand from './ListCommand.js';
32
+ import ServeCommand from './ServeCommand.js';
33
+ import ViewCommand from './ViewCommand.js';
@@ -1,5 +1,8 @@
1
1
  /**
2
2
  * Command to bump the version of changed packages in the monorepo.
3
+ * @property {string} version Target version to set
4
+ * @property {string} since Git reference to diff against
5
+ * @property {boolean} dryRun Run the command without making any changes
3
6
  */
4
7
  export default class BumpCommand extends ModelAsApp {
5
8
  static version: {
@@ -32,8 +35,11 @@ export default class BumpCommand extends ModelAsApp {
32
35
  * @param {Partial<import('@nan0web/ui').ModelAsAppOptions>} [options]
33
36
  */
34
37
  constructor(data?: Partial<BumpCommand>, options?: Partial<import("@nan0web/ui").ModelAsAppOptions>);
35
- /** @type {string} Target version */ version: string;
36
- /** @type {string} Since reference */ since: string;
37
- /** @type {boolean} Dry run */ dryRun: boolean;
38
+ /** @type {string} Target version */
39
+ version: string;
40
+ /** @type {string} Git reference to diff against */
41
+ since: string;
42
+ /** @type {boolean} Run without making changes */
43
+ dryRun: boolean;
38
44
  }
39
45
  import { ModelAsApp } from '@nan0web/ui';
@@ -1,24 +1,136 @@
1
- export default class CheckCommand extends ModelAsApp {
1
+ /**
2
+ * @typedef {Object} DiagnosticItem
3
+ * @property {'error' | 'warning' | 'info'} level
4
+ * @property {string} code
5
+ * @property {string} project
6
+ * @property {string} message
7
+ * @property {string} file
8
+ * @property {string} fix
9
+ */
10
+ /**
11
+ * CheckCommand - PM-as-Code Release Structure & Compliance Validator.
12
+ */
13
+ export class CheckCommand extends ModelAsApp {
14
+ static alias: string;
2
15
  static UI: {
16
+ errorDbRequired: string;
3
17
  title: string;
4
- icon: string;
5
- description: string;
18
+ header: string;
19
+ scanningCount: string;
20
+ projectHeader: string;
21
+ missingPackageJson: string;
22
+ packageJsonValid: string;
23
+ noReleasesDir: string;
24
+ releasesDirFound: string;
25
+ releaseHeader: string;
26
+ releaseDocCompleted: string;
27
+ missingReleaseDoc: string;
28
+ missingUserDoc: string;
29
+ incompleteUserMetrics: string;
30
+ userMetricsValid: string;
31
+ ambiguousContracts: string;
32
+ missingContract: string;
33
+ activeContractWip: string;
34
+ sealedRegressionClosed: string;
35
+ orphanFile: string;
36
+ recommendationsHeader: string;
37
+ recommendationItem: string;
38
+ summary: string;
39
+ Diagnostics: {
40
+ MISSING_PACKAGE_JSON: {
41
+ level: string;
42
+ code: string;
43
+ message: string;
44
+ fix: string;
45
+ };
46
+ MISSING_RELEASES_DIR: {
47
+ level: string;
48
+ code: string;
49
+ message: string;
50
+ fix: string;
51
+ };
52
+ MISSING_RELEASE_MD: {
53
+ level: string;
54
+ code: string;
55
+ message: string;
56
+ fix: string;
57
+ };
58
+ MISSING_USER_MD: {
59
+ level: string;
60
+ code: string;
61
+ message: string;
62
+ fix: string;
63
+ };
64
+ INCOMPLETE_USER_METRICS: {
65
+ level: string;
66
+ code: string;
67
+ message: string;
68
+ fix: string;
69
+ };
70
+ AMBIGUOUS_CONTRACTS: {
71
+ level: string;
72
+ code: string;
73
+ message: string;
74
+ fix: string;
75
+ };
76
+ MISSING_CONTRACT: {
77
+ level: string;
78
+ code: string;
79
+ message: string;
80
+ fix: string;
81
+ };
82
+ ORPHAN_RELEASE_FILE: {
83
+ level: string;
84
+ code: string;
85
+ message: string;
86
+ fix: string;
87
+ };
88
+ };
89
+ };
90
+ static target: {
91
+ help: string;
92
+ default: string;
93
+ type: string;
94
+ positional: boolean;
95
+ };
96
+ static registryFile: {
97
+ help: string;
98
+ default: string;
99
+ type: string;
100
+ alias: string;
101
+ };
102
+ static all: {
103
+ help: string;
104
+ default: boolean;
105
+ type: string;
106
+ alias: string;
6
107
  };
7
- static name: string;
8
- /**
9
- * @returns {AsyncGenerator<any, any, any>}
10
- */
11
- run(): AsyncGenerator<any, any, any>;
12
108
  /**
13
- * @param {string} absDir
14
- * @returns {Promise<string[]>}
109
+ * @param {Partial<CheckCommand>} [data]
110
+ * @param {Partial<import('@nan0web/ui').ModelAsAppOptions>} [options]
15
111
  */
16
- _scanLocal(absDir: string): Promise<string[]>;
112
+ constructor(data?: Partial<CheckCommand>, options?: Partial<import("@nan0web/ui").ModelAsAppOptions>);
113
+ /** @type {string} */ target: string;
114
+ /** @type {string} */ registryFile: string;
115
+ /** @type {boolean} */ all: boolean;
17
116
  /**
18
- * @param {string} base
19
- * @param {string} [dir]
20
- * @returns {Promise<string[]>}
117
+ * Execute release structure check
118
+ * @returns {AsyncGenerator<import('@nan0web/ui').Intent, { success: boolean, diagnostics: DiagnosticItem[], checkedProjects: any[] }, any>}
21
119
  */
22
- _scanDirRecursive(base: string, dir?: string): Promise<string[]>;
120
+ run(): AsyncGenerator<import("@nan0web/ui").Intent, {
121
+ success: boolean;
122
+ diagnostics: DiagnosticItem[];
123
+ checkedProjects: any[];
124
+ }, any>;
125
+ #private;
23
126
  }
127
+ export default CheckCommand;
128
+ export type DiagnosticItem = {
129
+ level: "error" | "warning" | "info";
130
+ code: string;
131
+ project: string;
132
+ message: string;
133
+ file: string;
134
+ fix: string;
135
+ };
24
136
  import { ModelAsApp } from '@nan0web/ui';
@@ -0,0 +1 @@
1
+ export {};
@@ -12,6 +12,7 @@ export default class CloseCommand extends ModelAsApp {
12
12
  * @param {Partial<import('@nan0web/ui').ModelAsAppOptions>} [options]
13
13
  */
14
14
  constructor(data?: Partial<CloseCommand>, options?: Partial<import("@nan0web/ui").ModelAsAppOptions>);
15
- /** @type {string} Release version */ version: string;
15
+ /** @type {string} Release version */
16
+ version: string;
16
17
  }
17
18
  import { ModelAsApp } from '@nan0web/ui';
@@ -0,0 +1 @@
1
+ export {};