dazscript-framework 1.0.6 → 1.0.7
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 +10 -0
- package/dist/scripts/install-generator.js +52 -6
- package/package.json +1 -1
- package/src/scripts/install-generator.test.ts +105 -0
package/README.md
CHANGED
|
@@ -238,6 +238,7 @@ action({ text: 'My Script' }, MyScript);
|
|
|
238
238
|
| `toolbar` | Toolbar name the action should appear on |
|
|
239
239
|
| `group` | Grouping label for related actions in Daz Studio |
|
|
240
240
|
| `description` | Longer description for the action |
|
|
241
|
+
| `icon` | Image path used for the installed custom action. Overrides discovered icon files. |
|
|
241
242
|
| `bundle` | Generates a setup script beside the action. `true` → `Setup.dsa.ts`, a string → `Setup <name>.dsa.ts` |
|
|
242
243
|
|
|
243
244
|
---
|
|
@@ -289,6 +290,15 @@ Applying the dialog:
|
|
|
289
290
|
- Affected toolbars are rebuilt; empty framework-created toolbars are removed
|
|
290
291
|
- Selected keyboard shortcut rows are applied after actions are installed
|
|
291
292
|
|
|
293
|
+
Custom action icons are selected from `action(...)` metadata and sibling image files in this order:
|
|
294
|
+
|
|
295
|
+
1. Explicit `action({ icon: '...' })`
|
|
296
|
+
2. `scriptname.action.png`
|
|
297
|
+
3. `scriptname.png`
|
|
298
|
+
4. `scriptname.dsa.png` legacy fallback
|
|
299
|
+
|
|
300
|
+
`scriptname.action.png` is the installed custom action icon. Daz Studio uses the same action icon for menu and toolbar placements. `scriptname.png` is the preferred script/content icon fallback. `scriptname.dsa.png` is a legacy fallback kept for older projects and will be removed in a future breaking release.
|
|
301
|
+
|
|
292
302
|
This replaces the older `Install.dsa.ts` / `Uninstall.dsa.ts` pattern. The installer generator removes those legacy files if they exist.
|
|
293
303
|
|
|
294
304
|
### Setup Keyboard Shortcuts
|
|
@@ -21,6 +21,54 @@ function stringOrDefault(str, defaultValue) {
|
|
|
21
21
|
return str !== undefined && str !== null && str !== '' ? str : defaultValue;
|
|
22
22
|
}
|
|
23
23
|
|
|
24
|
+
function replaceEntrySourceSuffix(filePath, suffix) {
|
|
25
|
+
if (filePath.endsWith('.dsa.ts')) {
|
|
26
|
+
return `${filePath.slice(0, -'.dsa.ts'.length)}${suffix}`;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
return filePath.replace(/\.ts$/, suffix);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function replaceEntryOutputSuffix(filePath, suffix) {
|
|
33
|
+
if (filePath.endsWith('.dsa')) {
|
|
34
|
+
return `${filePath.slice(0, -'.dsa'.length)}${suffix}`;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
return `${filePath}${suffix}`;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function getActionIconPath(filePath, decorator) {
|
|
41
|
+
if (decorator.icon) {
|
|
42
|
+
return decorator.icon;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const actionIcon = replaceEntrySourceSuffix(filePath, '.action.png');
|
|
46
|
+
const scriptIcon = replaceEntrySourceSuffix(filePath, '.png');
|
|
47
|
+
const legacyScriptIcon = filePath.replace('.ts', '.png');
|
|
48
|
+
|
|
49
|
+
if (!fs.existsSync(actionIcon) && !fs.existsSync(scriptIcon) && !fs.existsSync(legacyScriptIcon)) {
|
|
50
|
+
return undefined;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const isBundleAction = decorator.bundle !== undefined;
|
|
54
|
+
|
|
55
|
+
if (fs.existsSync(actionIcon)) {
|
|
56
|
+
return isBundleAction
|
|
57
|
+
? replaceEntryOutputSuffix(path.parse(filePath).name, '.action.png')
|
|
58
|
+
: replaceEntryOutputSuffix(getPartialPath(filePath), '.action.png');
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
if (fs.existsSync(scriptIcon)) {
|
|
62
|
+
return isBundleAction
|
|
63
|
+
? replaceEntryOutputSuffix(path.parse(filePath).name, '.png')
|
|
64
|
+
: replaceEntryOutputSuffix(getPartialPath(filePath), '.png');
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
return isBundleAction
|
|
68
|
+
? path.parse(filePath).name.replace('.dsa', '.dsa.png')
|
|
69
|
+
: `${getPartialPath(filePath)}.png`;
|
|
70
|
+
}
|
|
71
|
+
|
|
24
72
|
function generateInstallerTemplate(data, options) {
|
|
25
73
|
return `
|
|
26
74
|
import { showSetupCustomActionsDialog as setup } from '@dsf/helpers/custom-action-installer-helper';
|
|
@@ -178,12 +226,9 @@ function processScript(filePath, container, defaultMenuPath, setupOptions) {
|
|
|
178
226
|
toolbar: stringOrDefault(decorator.toolbar, undefined),
|
|
179
227
|
};
|
|
180
228
|
|
|
181
|
-
const icon = filePath
|
|
182
|
-
if (
|
|
183
|
-
script.icon =
|
|
184
|
-
decorator.bundle === undefined
|
|
185
|
-
? `${getPartialPath(filePath)}.png`
|
|
186
|
-
: fileInfo.name.replace('.dsa', '.dsa.png');
|
|
229
|
+
const icon = getActionIconPath(filePath, decorator);
|
|
230
|
+
if (icon) {
|
|
231
|
+
script.icon = icon;
|
|
187
232
|
}
|
|
188
233
|
|
|
189
234
|
script.text = stringOrDefault(decorator.text, script.text);
|
|
@@ -364,4 +409,5 @@ if (require.main === module) {
|
|
|
364
409
|
module.exports = {
|
|
365
410
|
generateInstallerFiles,
|
|
366
411
|
findActionEntryFiles,
|
|
412
|
+
getActionIconPath,
|
|
367
413
|
};
|
package/package.json
CHANGED
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import fs from 'node:fs'
|
|
2
|
+
import os from 'node:os'
|
|
3
|
+
import path from 'node:path'
|
|
4
|
+
import { afterEach, describe, expect, it } from 'vitest'
|
|
5
|
+
|
|
6
|
+
const { generateInstallerFiles } = require('../../dist/scripts/install-generator')
|
|
7
|
+
|
|
8
|
+
const tempDirs: string[] = []
|
|
9
|
+
|
|
10
|
+
const makeProject = (): string => {
|
|
11
|
+
const projectDir = fs.mkdtempSync(path.join(os.tmpdir(), 'dsf-install-generator-'))
|
|
12
|
+
tempDirs.push(projectDir)
|
|
13
|
+
fs.mkdirSync(path.join(projectDir, 'src'), { recursive: true })
|
|
14
|
+
fs.writeFileSync(
|
|
15
|
+
path.join(projectDir, 'dazscript.config.cjs'),
|
|
16
|
+
"module.exports = { appDataPath: 'Test/ActionIcons' }\n"
|
|
17
|
+
)
|
|
18
|
+
return projectDir
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const writeScript = (projectDir: string, name: string, actionOptions: string = ''): void => {
|
|
22
|
+
fs.writeFileSync(
|
|
23
|
+
path.join(projectDir, 'src', `${name}.dsa.ts`),
|
|
24
|
+
`action({ text: '${name}'${actionOptions} }, function() {})\n`
|
|
25
|
+
)
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const writePng = (projectDir: string, fileName: string): void => {
|
|
29
|
+
fs.writeFileSync(path.join(projectDir, 'src', fileName), 'png')
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const generateSetup = (projectDir: string): string => {
|
|
33
|
+
const previousCwd = process.cwd()
|
|
34
|
+
process.chdir(projectDir)
|
|
35
|
+
try {
|
|
36
|
+
generateInstallerFiles(projectDir, {
|
|
37
|
+
scriptsPath: './src',
|
|
38
|
+
defaultMenuPath: '/Test',
|
|
39
|
+
appDataPath: undefined,
|
|
40
|
+
})
|
|
41
|
+
} finally {
|
|
42
|
+
process.chdir(previousCwd)
|
|
43
|
+
}
|
|
44
|
+
return fs.readFileSync(path.join(projectDir, 'src', 'Setup.dsa.ts'), 'utf8')
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
afterEach(() => {
|
|
48
|
+
while (tempDirs.length > 0) {
|
|
49
|
+
const dir = tempDirs.pop()
|
|
50
|
+
if (dir) fs.rmSync(dir, { recursive: true, force: true })
|
|
51
|
+
}
|
|
52
|
+
})
|
|
53
|
+
|
|
54
|
+
describe('install generator action icons', () => {
|
|
55
|
+
it('prefers the action icon convention over the script icon fallback', () => {
|
|
56
|
+
const projectDir = makeProject()
|
|
57
|
+
writeScript(projectDir, 'render-tools')
|
|
58
|
+
writePng(projectDir, 'render-tools.action.png')
|
|
59
|
+
writePng(projectDir, 'render-tools.png')
|
|
60
|
+
writePng(projectDir, 'render-tools.dsa.png')
|
|
61
|
+
|
|
62
|
+
const setup = generateSetup(projectDir)
|
|
63
|
+
|
|
64
|
+
expect(setup).toContain('"icon": "./render-tools.action.png"')
|
|
65
|
+
expect(setup).not.toContain('"icon": "./render-tools.png"')
|
|
66
|
+
expect(setup).not.toContain('"icon": "./render-tools.dsa.png"')
|
|
67
|
+
})
|
|
68
|
+
|
|
69
|
+
it('falls back to the script icon when no action icon exists', () => {
|
|
70
|
+
const projectDir = makeProject()
|
|
71
|
+
writeScript(projectDir, 'power-menu')
|
|
72
|
+
writePng(projectDir, 'power-menu.png')
|
|
73
|
+
writePng(projectDir, 'power-menu.dsa.png')
|
|
74
|
+
|
|
75
|
+
const setup = generateSetup(projectDir)
|
|
76
|
+
|
|
77
|
+
expect(setup).toContain('"icon": "./power-menu.png"')
|
|
78
|
+
expect(setup).not.toContain('"icon": "./power-menu.dsa.png"')
|
|
79
|
+
})
|
|
80
|
+
|
|
81
|
+
it('keeps the dsa-named script icon as a legacy fallback', () => {
|
|
82
|
+
const projectDir = makeProject()
|
|
83
|
+
writeScript(projectDir, 'legacy-icon')
|
|
84
|
+
writePng(projectDir, 'legacy-icon.dsa.png')
|
|
85
|
+
|
|
86
|
+
const setup = generateSetup(projectDir)
|
|
87
|
+
|
|
88
|
+
expect(setup).toContain('"icon": "./legacy-icon.dsa.png"')
|
|
89
|
+
})
|
|
90
|
+
|
|
91
|
+
it('lets explicit action icon metadata override discovered icon files', () => {
|
|
92
|
+
const projectDir = makeProject()
|
|
93
|
+
writeScript(projectDir, 'custom-icon', ", icon: 'icons/custom-action.png'")
|
|
94
|
+
writePng(projectDir, 'custom-icon.action.png')
|
|
95
|
+
writePng(projectDir, 'custom-icon.png')
|
|
96
|
+
writePng(projectDir, 'custom-icon.dsa.png')
|
|
97
|
+
|
|
98
|
+
const setup = generateSetup(projectDir)
|
|
99
|
+
|
|
100
|
+
expect(setup).toContain('"icon": "icons/custom-action.png"')
|
|
101
|
+
expect(setup).not.toContain('"icon": "custom-icon.action.png"')
|
|
102
|
+
expect(setup).not.toContain('"icon": "custom-icon.png"')
|
|
103
|
+
expect(setup).not.toContain('"icon": "custom-icon.dsa.png"')
|
|
104
|
+
})
|
|
105
|
+
})
|