dazscript-framework 1.0.39 → 1.0.41

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 CHANGED
@@ -480,6 +480,20 @@ action({ text: 'My Dialog Script' }, () => {
480
480
  });
481
481
  ```
482
482
 
483
+ #### 4. Prepare without opening modally
484
+
485
+ `prepare()` builds the dialog and returns its native `DzBasicDialog` without entering the modal event loop. Use it when code must inspect or configure the completed native dialog before showing it.
486
+
487
+ ```typescript
488
+ const dialog = new MyDialog(new MyModel());
489
+ const nativeDialog = dialog.prepare();
490
+
491
+ nativeDialog.minWidth = 600;
492
+ const accepted = nativeDialog.exec();
493
+ ```
494
+
495
+ Each call builds the dialog. After `prepare()`, execute the returned native dialog directly instead of calling `run()` or `ok()`, which would build it again.
496
+
483
497
  ---
484
498
 
485
499
  ### Observables
@@ -629,37 +643,7 @@ The `test/unit/` files are generated only when you run `dazscript init --unit-te
629
643
 
630
644
  ### Development & Publishing
631
645
 
632
- This package uses **semantic-release** for automatic versioning and npm publishing.
633
-
634
- #### Commit message conventions
635
-
636
- | Prefix | Effect |
637
- |---|---|
638
- | `fix: ...` | Patch bump (`1.0.0` → `1.0.1`) |
639
- | `feat: ...` | Minor bump (`1.0.0` → `1.1.0`) |
640
- | `BREAKING CHANGE: ...` in commit body | Major bump (`1.0.0` → `2.0.0`) |
641
- | No prefix | No version bump |
642
-
643
- Examples:
644
- ```
645
- fix: resolve layout overflow in group builder
646
- feat: add tree view builder
647
- feat: refactor action entrypoint
648
-
649
- BREAKING CHANGE: action() now requires an explicit menuPath
650
- ```
651
-
652
- #### Publishing
653
-
654
- Every push to `master` automatically:
655
-
656
- 1. Analyzes commit messages since the last release
657
- 2. Updates the version in `package.json`
658
- 3. Builds the project
659
- 4. Creates a GitHub release with changelog
660
- 5. Publishes to npm
661
-
662
- No manual steps required.
646
+ Every eligible push to `main` runs the npm publishing workflow. It installs from the lockfile, builds the package, increments the patch version, commits the version update with an annotated tag, and publishes the package to npm. Bot commits and commits containing `[skip ci]` do not start another release.
663
647
 
664
648
  ---
665
649
 
package/babel.config.js CHANGED
@@ -18,7 +18,6 @@ module.exports = {
18
18
  [require.resolve('@babel/plugin-proposal-decorators'), { version: 'legacy' }],
19
19
  [require.resolve('@babel/plugin-transform-arrow-functions')],
20
20
  require.resolve('@babel/plugin-transform-block-scoping'),
21
- require.resolve('@babel/plugin-proposal-class-properties'),
22
21
  require.resolve('@babel/plugin-transform-private-property-in-object'),
23
22
  require.resolve('@babel/plugin-transform-private-methods'),
24
23
  ],
@@ -2,7 +2,7 @@
2
2
 
3
3
  const fs = require('fs');
4
4
  const path = require('path');
5
- const glob = require('glob');
5
+ const { globSync } = require('glob');
6
6
 
7
7
  function copyFile(sourcePath, targetPath) {
8
8
  fs.mkdirSync(path.dirname(targetPath), { recursive: true });
@@ -14,7 +14,7 @@ function copyIcons(workdir, options) {
14
14
  const sourceRoot = path.resolve(workdir, 'src');
15
15
  const outDir = path.resolve(workdir, options.outDir || './out');
16
16
  const pattern = path.join(sourceRoot, '**/*.png').replace(/\\/g, '/');
17
- const files = glob.sync(pattern);
17
+ const files = globSync(pattern);
18
18
 
19
19
  files.forEach((filePath) => {
20
20
  const relativePath = path.relative(sourceRoot, filePath);
@@ -1,7 +1,7 @@
1
1
  const ts = require('typescript');
2
2
  const fs = require('fs');
3
3
  const path = require('path');
4
- const glob = require('glob');
4
+ const { globSync } = require('glob');
5
5
  const { loadConfig } = require('./config-loader');
6
6
  const { validateAppDataPath } = require('./app-data-path');
7
7
 
@@ -187,7 +187,11 @@ function findActionEntryFiles(workdir, options) {
187
187
  const scriptsPath = options.scriptsPath.endsWith('/')
188
188
  ? options.scriptsPath
189
189
  : `${options.scriptsPath}/`;
190
- const matches = glob.sync(`${scriptsPath}/**/*.dsa.ts`, { cwd: workdir });
190
+ const matches = globSync(`${scriptsPath}/**/*.dsa.ts`, { cwd: workdir })
191
+ .map((filePath) => {
192
+ const normalized = toPosix(filePath).replace(/^\.\//, '');
193
+ return `./${normalized}`;
194
+ });
191
195
 
192
196
  return matches.filter((filePath) => {
193
197
  const absolutePath = path.join(workdir, filePath);
@@ -197,7 +197,6 @@ function writeFile(filePath, content) {
197
197
 
198
198
  const fixtureBuildDependencies = [
199
199
  '@babel/core',
200
- '@babel/plugin-proposal-class-properties',
201
200
  '@babel/plugin-proposal-decorators',
202
201
  '@babel/plugin-transform-arrow-functions',
203
202
  '@babel/plugin-transform-block-scoping',
@@ -206,9 +205,7 @@ const fixtureBuildDependencies = [
206
205
  '@babel/plugin-transform-private-property-in-object',
207
206
  '@babel/preset-env',
208
207
  '@babel/preset-typescript',
209
- 'babel-core',
210
208
  'babel-loader',
211
- 'babel-plugin-transform-class-properties',
212
209
  'babel-plugin-transform-typescript-metadata',
213
210
  'glob',
214
211
  'ts-loader',
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dazscript-framework",
3
- "version": "1.0.39",
3
+ "version": "1.0.41",
4
4
  "author": "Freddy Diaz",
5
5
  "license": "MPL-2.0",
6
6
  "description": "",
@@ -14,6 +14,7 @@
14
14
  "installer": "node ./dist/scripts/cli.js installer --scripts-path ./src/examples --menu-path /DazScriptFramework",
15
15
  "test": "vitest run",
16
16
  "test:integration": "node ./dist/scripts/cli.js integration --fixture ./test/integration/fixtures/framework-integration.dsa.ts --require-content",
17
+ "test:integration:smoke": "node ./dist/scripts/cli.js integration --fixture ./test/integration/fixtures/framework-smoke.dsa.ts",
17
18
  "test:watch": "vitest"
18
19
  },
19
20
  "bin": {
@@ -47,32 +48,29 @@
47
48
  "dazscript-types": "^1.0.0"
48
49
  },
49
50
  "dependencies": {
50
- "@babel/core": "^7.23.2",
51
- "@babel/plugin-proposal-class-properties": "^7.18.6",
52
- "@babel/plugin-proposal-decorators": "^7.23.2",
53
- "@babel/plugin-transform-arrow-functions": "^7.22.5",
54
- "@babel/plugin-transform-block-scoping": "^7.23.0",
55
- "@babel/plugin-transform-class-properties": "^7.25.9",
56
- "@babel/plugin-transform-private-methods": "^7.27.1",
57
- "@babel/plugin-transform-private-property-in-object": "^7.27.1",
58
- "@babel/preset-env": "^7.23.2",
59
- "@babel/preset-typescript": "^7.23.2",
60
- "babel-core": "^6.26.3",
61
- "babel-loader": "^9.1.3",
62
- "babel-plugin-transform-class-properties": "^6.24.1",
51
+ "@babel/core": "^7.29.7",
52
+ "@babel/plugin-proposal-decorators": "^7.29.7",
53
+ "@babel/plugin-transform-arrow-functions": "^7.29.7",
54
+ "@babel/plugin-transform-block-scoping": "^7.29.7",
55
+ "@babel/plugin-transform-class-properties": "^7.29.7",
56
+ "@babel/plugin-transform-private-methods": "^7.29.7",
57
+ "@babel/plugin-transform-private-property-in-object": "^7.29.7",
58
+ "@babel/preset-env": "^7.29.7",
59
+ "@babel/preset-typescript": "^7.29.7",
60
+ "babel-loader": "^10.1.1",
63
61
  "babel-plugin-transform-typescript-metadata": "^0.3.2",
64
- "glob": "^7.2.0",
62
+ "glob": "^13.0.6",
65
63
  "ts-file-parser": "^0.0.21",
66
- "ts-loader": "^9.5.0",
64
+ "ts-loader": "^9.6.0",
67
65
  "tsconfig-paths-webpack-plugin": "^4.1.0",
68
66
  "typescript": "^5.2.2",
69
- "webpack": "^5.88.2"
67
+ "webpack": "^5.107.2"
70
68
  },
71
69
  "devDependencies": {
72
70
  "@types/node": "^22.10.5",
73
71
  "dazscript-types": "^1.0.1",
74
72
  "eslint": "^9.17.0",
75
73
  "typescript": "^5.7.3",
76
- "vitest": "^3.0.0"
74
+ "vitest": "^4.1.8"
77
75
  }
78
76
  }
@@ -0,0 +1,48 @@
1
+ import { beforeEach, describe, expect, it, vi } from 'vitest'
2
+
3
+ const dialog = vi.hoisted(() => ({ exec: vi.fn(() => true), close: vi.fn() }))
4
+ const restoreObjectName = vi.hoisted(() => vi.fn())
5
+
6
+ vi.mock('./builders/dialog-builder', () => ({
7
+ DialogBuilder: class {
8
+ context = { dialog }
9
+
10
+ build(setup: () => void) {
11
+ setup()
12
+ return dialog
13
+ }
14
+
15
+ restoreObjectName() {
16
+ restoreObjectName()
17
+ }
18
+ }
19
+ }))
20
+
21
+ import { BasicDialog } from './basic-dialog'
22
+
23
+ class TestDialog extends BasicDialog {
24
+ builds = 0
25
+
26
+ constructor() {
27
+ super('Test')
28
+ }
29
+
30
+ protected build(): void {
31
+ this.builds++
32
+ }
33
+ }
34
+
35
+ describe('BasicDialog.prepare', () => {
36
+ beforeEach(() => vi.clearAllMocks())
37
+
38
+ it('builds and returns the dialog without entering its modal event loop', () => {
39
+ const subject = new TestDialog()
40
+
41
+ const prepared = subject.prepare()
42
+
43
+ expect(prepared).toBe(dialog)
44
+ expect(subject.builds).toBe(1)
45
+ expect(dialog.exec).not.toHaveBeenCalled()
46
+ expect(restoreObjectName).toHaveBeenCalledOnce()
47
+ })
48
+ })
@@ -21,10 +21,14 @@ export abstract class BasicDialog {
21
21
  })
22
22
  }
23
23
 
24
- run(): boolean {
24
+ prepare(): DzBasicDialog {
25
25
  this.init()
26
26
  this.builder.restoreObjectName()
27
- return this.dialog.exec()
27
+ return this.dialog
28
+ }
29
+
30
+ run(): boolean {
31
+ return this.prepare().exec()
28
32
  }
29
33
 
30
34
  ok(): boolean {
@@ -0,0 +1,78 @@
1
+ import { beforeEach, describe, expect, it, vi } from 'vitest'
2
+ import { Observable } from '@dsf/lib/observable'
3
+ import { TreeNode } from '@dsf/lib/tree-node'
4
+
5
+ const listView = vi.hoisted(() => ({
6
+ items: [] as any[],
7
+ addColumn: vi.fn(),
8
+ clear: vi.fn(function (this: any) { this.items = [] }),
9
+ columnWidth: vi.fn(() => 100),
10
+ setColumnWidth: vi.fn(),
11
+ getItems: vi.fn(function (this: any) { return this.items.slice() }),
12
+ deleteItem: vi.fn(function (this: any, item: any) {
13
+ this.items = this.items.filter((candidate: any) => candidate !== item)
14
+ }),
15
+ setSorting: vi.fn(),
16
+ hide: vi.fn(),
17
+ show: vi.fn(),
18
+ selectedItem: vi.fn(() => null),
19
+ doubleClicked: { scriptConnect: vi.fn() },
20
+ contextMenuRequested: { scriptConnect: vi.fn() }
21
+ }))
22
+
23
+ vi.mock('./widget-builder', () => ({
24
+ createWidget: () => ({ build: () => listView })
25
+ }))
26
+
27
+ vi.mock('@dsf/helpers/list-view-helper', () => ({
28
+ clearColumns: vi.fn(),
29
+ filter: vi.fn(),
30
+ getDataItem: (item: any) => item?.data,
31
+ setDataItem: (item: any, data: any) => item.data = data
32
+ }))
33
+
34
+ class FakeListViewItem {
35
+ data: any
36
+ open = false
37
+ selectable = true
38
+ textByColumn: Record<number, string> = {}
39
+
40
+ constructor(parent: any, public id: number) {
41
+ parent.items.push(this)
42
+ }
43
+
44
+ setText(column: number, text: string) {
45
+ this.textByColumn[column] = text
46
+ }
47
+ }
48
+
49
+ vi.stubGlobal('DzListView', { All: 0, Extended: 1 })
50
+ vi.stubGlobal('DzListViewItem', FakeListViewItem)
51
+
52
+ import { ListViewBuilder } from './list-view-builder'
53
+
54
+ describe('ListViewBuilder item updates', () => {
55
+ beforeEach(() => {
56
+ listView.items = []
57
+ vi.clearAllMocks()
58
+ })
59
+
60
+ it('keeps unchanged rows when data has no explicit id', () => {
61
+ const items = new Observable([
62
+ new TreeNode('Action A', '', { name: 'ActionA' }),
63
+ new TreeNode('Action B', '', { name: 'ActionB' })
64
+ ])
65
+ const builder = new ListViewBuilder<any, any>({ dialog: {}, layout: null } as any)
66
+
67
+ builder
68
+ .items(items)
69
+ .columns(['Name'])
70
+ .text(item => [item.name])
71
+ .data(item => item.value)
72
+ .build()
73
+
74
+ items.value = [new TreeNode('Action B', '', { name: 'ActionB' })]
75
+
76
+ expect(listView.items.map(item => item.textByColumn[0])).toEqual(['Action B'])
77
+ })
78
+ })
@@ -0,0 +1,28 @@
1
+ import { beforeEach, describe, expect, it, vi } from 'vitest'
2
+
3
+ const actionPixmap = vi.hoisted(() => vi.fn())
4
+
5
+ vi.mock('@dsf/common/log', () => ({ debug: vi.fn(), warn: vi.fn() }))
6
+ vi.mock('@dsf/core/global', () => ({
7
+ mainWindow: { getActionMgr: () => ({}) }
8
+ }))
9
+
10
+ import { getActionPixmap } from './action-helper'
11
+
12
+ describe('getActionPixmap', () => {
13
+ beforeEach(() => {
14
+ actionPixmap.mockReset()
15
+ vi.stubGlobal('App', {
16
+ getStyle: () => ({ actionPixmap })
17
+ })
18
+ })
19
+
20
+ it('rejects a null pixmap even when DAZ reports nonzero height', () => {
21
+ actionPixmap.mockReturnValue({
22
+ height: 16,
23
+ isNull: () => true
24
+ })
25
+
26
+ expect(getActionPixmap('DzTestAction', '')).toBeNull()
27
+ })
28
+ })
@@ -166,12 +166,12 @@ export const getActionPixmap = (action: string, icon: string, maxSize?: number):
166
166
  } else {
167
167
  pixmap = new Pixmap(image.getFilename())
168
168
  }
169
- return pixmap
169
+ return isEmptyPixmap(pixmap) ? null : pixmap
170
170
  }
171
171
  }
172
172
  else if (!isCustom) {
173
173
  let pixmap = App.getStyle().actionPixmap(action, 0, 0)
174
- return pixmap?.height === 0 ? null : pixmap
174
+ return isEmptyPixmap(pixmap) ? null : pixmap
175
175
  }
176
176
  } catch (error) {
177
177
  warn(`Error while trying to get icon pixmap: ${error}`)
@@ -179,4 +179,9 @@ export const getActionPixmap = (action: string, icon: string, maxSize?: number):
179
179
  return null
180
180
  }
181
181
 
182
+ const isEmptyPixmap = (pixmap: Pixmap | null | undefined): boolean =>
183
+ !pixmap
184
+ || (typeof (pixmap as any).isNull === 'function' && (pixmap as any).isNull())
185
+ || pixmap.height === 0
186
+
182
187
 
@@ -0,0 +1,74 @@
1
+ import { beforeEach, describe, expect, it, vi } from 'vitest'
2
+
3
+ const open = vi.hoisted(() => vi.fn(() => false))
4
+ const write = vi.hoisted(() => vi.fn(() => 0))
5
+ const close = vi.hoisted(() => vi.fn())
6
+
7
+ vi.mock('@dsf/common/log', () => ({ error: vi.fn() }))
8
+
9
+ class FakeFile {
10
+ static WriteOnly = 2
11
+
12
+ open = open
13
+ write = write
14
+ close = close
15
+ deleteLater = vi.fn()
16
+ }
17
+
18
+ class FakeFileInfo {
19
+ absolutePath() {
20
+ return 'C:/temp'
21
+ }
22
+
23
+ deleteLater() { }
24
+ }
25
+
26
+ class FakeDir {
27
+ mkpath() {
28
+ return true
29
+ }
30
+ }
31
+
32
+ vi.stubGlobal('DzFile', FakeFile)
33
+ vi.stubGlobal('DzFileInfo', FakeFileInfo)
34
+ vi.stubGlobal('DzDir', FakeDir)
35
+
36
+ import { saveToFile } from './file-helper'
37
+
38
+ describe('saveToFile', () => {
39
+ beforeEach(() => {
40
+ vi.clearAllMocks()
41
+ open.mockReturnValue(false)
42
+ write.mockReturnValue(-1)
43
+ })
44
+
45
+ it('fails without writing when the file cannot be opened', () => {
46
+ expect(saveToFile('C:/temp/settings.json', '{}')).toBe(false)
47
+ expect(open).toHaveBeenCalledWith(FakeFile.WriteOnly)
48
+ expect(write).not.toHaveBeenCalled()
49
+ })
50
+
51
+ it('fails when writing reports an error', () => {
52
+ open.mockReturnValue(true)
53
+
54
+ expect(saveToFile('C:/temp/settings.json', '{}')).toBe(false)
55
+ expect(write).toHaveBeenCalledWith('{}')
56
+ expect(close).toHaveBeenCalledOnce()
57
+ })
58
+
59
+ it('fails when writing stores no bytes', () => {
60
+ open.mockReturnValue(true)
61
+ write.mockReturnValue(0)
62
+
63
+ expect(saveToFile('C:/temp/settings.json', '{}')).toBe(false)
64
+ expect(close).toHaveBeenCalledOnce()
65
+ })
66
+
67
+ it('succeeds when the file opens and writes', () => {
68
+ open.mockReturnValue(true)
69
+ write.mockReturnValue(2)
70
+
71
+ expect(saveToFile('C:/temp/settings.json', '{}')).toBe(true)
72
+ expect(close).toHaveBeenCalledOnce()
73
+ })
74
+ })
@@ -32,22 +32,26 @@ export const readFromFile = <T>(filePath: string, cache: boolean = false): T | n
32
32
  * @returns
33
33
  */
34
34
  export const saveToFile = (filePath: string, content: string): boolean => {
35
+ let fileInfo: DzFileInfo | null = null
36
+ let file: DzFile | null = null
37
+ let opened = false
35
38
  try {
36
39
  if (!filePath || !content) return false
37
- let fileInfo = new DzFileInfo(filePath)
40
+ fileInfo = new DzFileInfo(filePath)
38
41
  let path = fileInfo.absolutePath()
39
42
  var dzDir = new DzDir(path)
40
43
  dzDir.mkpath(path)
41
- var file = new DzFile(`${filePath}`)
42
- file.open(DzFile.WriteOnly)
43
- file.write(content)
44
- file.close()
45
- fileInfo.deleteLater()
46
- file.deleteLater()
47
- return true
44
+ file = new DzFile(`${filePath}`)
45
+ opened = file.open(DzFile.WriteOnly)
46
+ if (!opened) return false
47
+ return file.write(content) > 0
48
48
  } catch (error) {
49
49
  log.error(`Error while saving file ${filePath}`)
50
50
  return false
51
+ } finally {
52
+ if (opened) file?.close()
53
+ fileInfo?.deleteLater()
54
+ file?.deleteLater()
51
55
  }
52
56
  }
53
57
 
@@ -105,6 +105,15 @@ describe('probe option resolution', () => {
105
105
  })
106
106
 
107
107
  describe('integration command resolution', () => {
108
+ it('exposes a no-content smoke integration command', () => {
109
+ const packageJson = JSON.parse(fs.readFileSync(path.resolve(__dirname, '../../package.json'), 'utf8'))
110
+ const script = packageJson.scripts['test:integration:smoke']
111
+
112
+ expect(script).toContain('integration')
113
+ expect(script).toContain('./test/integration/fixtures/framework-smoke.dsa.ts')
114
+ expect(script).not.toContain('--require-content')
115
+ })
116
+
108
117
  it('uses node plus npm-cli on Windows so child_process can spawn without a shell', () => {
109
118
  const invocation = getNpmInvocation(['--version'], 'win32')
110
119
 
package/webpack.config.js CHANGED
@@ -1,5 +1,5 @@
1
1
  const path = require('path');
2
- const glob = require('glob');
2
+ const { globSync } = require('glob');
3
3
  const webpack = require('webpack');
4
4
  const TsconfigPathsPlugin = require('tsconfig-paths-webpack-plugin');
5
5
  const { createActionLaunchers } = require('./dist/scripts/launchers');
@@ -41,8 +41,7 @@ module.exports = (env, argv) => {
41
41
  const isFileSpecified = env && env.file;
42
42
  const entryFiles = isFileSpecified
43
43
  ? [path.resolve(sourceRoot, `${env.file}.dsa.ts`)]
44
- : glob
45
- .sync(path.join(sourceRoot, '**/*.dsa.ts').replace(/\\/g, '/'))
44
+ : globSync(path.join(sourceRoot, '**/*.dsa.ts').replace(/\\/g, '/'))
46
45
  .map((filePath) => path.resolve(filePath));
47
46
 
48
47
  const outputPath =