dazscript-framework 0.2.3 → 0.2.5
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 +66 -35
- package/dist/scripts/cli.js +63 -0
- package/dist/scripts/init.js +3 -0
- package/dist/scripts/install-generator.js +195 -87
- package/dist/scripts/launchers.js +162 -0
- package/package.json +1 -1
- package/src/core/action-decorator.ts +1 -16
- package/src/core/action.ts +71 -0
- package/src/samples/hello-world.dsa.ts +5 -10
- package/src/samples/sample-dialog.dsa.ts +41 -46
- package/webpack.config.js +32 -0
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# DazScript Framework
|
|
2
2
|
|
|
3
|
-
> ⚠️
|
|
3
|
+
> ⚠️ This framework is under active development. The API may still evolve between releases. If you need a stable long-term surface, wait for `v1.0`.
|
|
4
4
|
|
|
5
5
|
The **DazScript Framework** is a TypeScript-based framework for writing Daz Studio scripts. It provides all the advantages of a typed language such as autocompletion, error checking, and method parameter documentation and hinting. The framework also includes a set of dialog helpers for rapid UI development.
|
|
6
6
|
|
|
@@ -13,7 +13,7 @@ The **DazScript Framework** is a TypeScript-based framework for writing Daz Stud
|
|
|
13
13
|
## Features
|
|
14
14
|
|
|
15
15
|
- TypeScript support with full IntelliSense.
|
|
16
|
-
- A
|
|
16
|
+
- A lightweight `action(...)` entrypoint plus helper methods for building interactive scripts.
|
|
17
17
|
- Easy integration with Daz Studio for quick script deployment.
|
|
18
18
|
|
|
19
19
|
## Installation
|
|
@@ -32,6 +32,8 @@ After installing the package, scaffold the project files:
|
|
|
32
32
|
npx dazscript init
|
|
33
33
|
```
|
|
34
34
|
|
|
35
|
+
If `--app-data-path` is not provided, `init` prompts for the AppData author namespace up front and uses the current folder name as the default product segment.
|
|
36
|
+
|
|
35
37
|
This generates:
|
|
36
38
|
|
|
37
39
|
- `dazscript.config.ts`
|
|
@@ -43,15 +45,36 @@ The generated package scripts use the framework CLI directly, so consumer projec
|
|
|
43
45
|
You can customize the generated defaults:
|
|
44
46
|
|
|
45
47
|
```bash
|
|
46
|
-
npx dazscript init --menu-path /MyScripts --scripts-path ./src --out-dir ./out
|
|
48
|
+
npx dazscript init --menu-path /MyScripts --scripts-path ./src --out-dir ./out --app-data-path YourName/my-project
|
|
47
49
|
```
|
|
48
50
|
|
|
49
|
-
- `--menu-path` sets which Daz Studio menu the scripts are added to by default. See [The
|
|
51
|
+
- `--menu-path` sets which Daz Studio menu the scripts are added to by default. See [The `action(...)` Entrypoint](#the-action-entrypoint) for how a script can override that with `menuPath`.
|
|
50
52
|
- `--scripts-path` tells the installer generator where to scan for runnable `.dsa.ts` entry files.
|
|
51
53
|
- `--out-dir` sets the webpack build output directory for generated `.dsa` files and copied icons.
|
|
54
|
+
- `--app-data-path` sets the AppData namespace used by launcher fallback resolution. Use a unique `Author/Product` path.
|
|
52
55
|
|
|
53
56
|
Use `--scripts-path ./src/scripts` for projects shaped like `scripts/common`, where runnable `.dsa.ts` files live under `src/scripts/`. Use `--scripts-path ./src` for packages shaped like `scripts/power-menu`, where runnable `.dsa.ts` files live at the source root.
|
|
54
57
|
|
|
58
|
+
Set `appDataPath` explicitly in `dazscript.config.ts` for every project. It is required for builds that generate launcher shims:
|
|
59
|
+
|
|
60
|
+
```typescript
|
|
61
|
+
import { defineConfig } from 'dazscript-framework/config';
|
|
62
|
+
|
|
63
|
+
export default defineConfig({
|
|
64
|
+
scriptsPath: './src',
|
|
65
|
+
outDir: './out',
|
|
66
|
+
defaultMenuPath: '/MyScripts',
|
|
67
|
+
appDataPath: 'YourName/my-project',
|
|
68
|
+
});
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
Built action outputs now use stable launcher shims by default:
|
|
72
|
+
|
|
73
|
+
- `out/<script>.dsa` is the stable launcher registered with Daz Studio menus, toolbars, and shortcuts
|
|
74
|
+
- `out/<folder>/lib/<script-name>/script.dsa` is the current implementation bundle that the launcher executes
|
|
75
|
+
|
|
76
|
+
Rebuilding updates the implementation bundle under the shim's sibling `lib/` folder. At runtime, each launcher checks that local `lib/` path first and falls back to `App.getAppDataPath()/...` second. Because the registered launcher path stays stable, action updates normally do not require reinstalling the action in Daz Studio.
|
|
77
|
+
|
|
55
78
|
## Usage
|
|
56
79
|
|
|
57
80
|
### Quick Start: Hello World
|
|
@@ -60,42 +83,45 @@ Create a simple script that logs to the console:
|
|
|
60
83
|
|
|
61
84
|
```typescript
|
|
62
85
|
import { debug } from '@dsf/common/log';
|
|
63
|
-
import { action } from '@dsf/core/action
|
|
64
|
-
import { BaseScript } from '@dsf/core/base-script';
|
|
86
|
+
import { action } from '@dsf/core/action';
|
|
65
87
|
import { info } from '@dsf/helpers/message-box-helper';
|
|
66
88
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
info('Hello World!');
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
new HelloWorldScript().exec();
|
|
89
|
+
action({ text: 'Hello World' }, () => {
|
|
90
|
+
debug('Hello World!');
|
|
91
|
+
info('Hello World!');
|
|
92
|
+
});
|
|
76
93
|
```
|
|
77
94
|
|
|
78
|
-
### The
|
|
95
|
+
### The `action(...)` Entrypoint
|
|
79
96
|
|
|
80
|
-
Use
|
|
97
|
+
Use `action(...)` at module scope to define how a runnable `.dsa.ts` file should appear in Daz Studio and what it should execute.
|
|
81
98
|
|
|
82
99
|
```typescript
|
|
83
|
-
|
|
100
|
+
action({
|
|
84
101
|
text: 'Hello World',
|
|
85
102
|
menuPath: '#{defaultMenuPath}/Examples',
|
|
86
103
|
shortcut: 'CTRL+SHIFT+H',
|
|
87
104
|
toolbar: 'MyToolbar',
|
|
88
105
|
group: 'Examples',
|
|
89
106
|
description: 'Runs the Hello World script',
|
|
90
|
-
})
|
|
91
|
-
|
|
92
|
-
|
|
107
|
+
}, () => {
|
|
108
|
+
info('Hello World!');
|
|
109
|
+
});
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
`action(...)` also accepts a reusable class with a `run()` method:
|
|
113
|
+
|
|
114
|
+
```typescript
|
|
115
|
+
class HelloWorldScript {
|
|
116
|
+
run(): void {
|
|
93
117
|
info('Hello World!');
|
|
94
118
|
}
|
|
95
119
|
}
|
|
120
|
+
|
|
121
|
+
action({ text: 'Hello World' }, HelloWorldScript);
|
|
96
122
|
```
|
|
97
123
|
|
|
98
|
-
Common
|
|
124
|
+
Common `action(...)` parameters:
|
|
99
125
|
|
|
100
126
|
- `text`: the label shown for the script in Daz Studio.
|
|
101
127
|
- `menuPath`: the menu path where the script should be added. Set it to `false` to skip adding the script to a menu. If omitted, the default menu from `--menu-path` is used.
|
|
@@ -105,6 +131,15 @@ Common `@action(...)` parameters:
|
|
|
105
131
|
- `description`: a longer description for the action.
|
|
106
132
|
- `bundle`: generates installer and uninstaller entries as a package bundle instead of a single action entry.
|
|
107
133
|
|
|
134
|
+
When an action is built, the framework emits two files for it:
|
|
135
|
+
|
|
136
|
+
- the stable launcher at the original output path
|
|
137
|
+
- the implementation bundle under a sibling `lib/<script-name>/script.dsa` path
|
|
138
|
+
|
|
139
|
+
Generated installers register the launcher path, so menu placement, toolbars, shortcuts, and icons keep pointing at a stable target across rebuilds.
|
|
140
|
+
|
|
141
|
+
If the local `lib/` implementation is missing, the launcher falls back to the configured `appDataPath`. Builds now require this value and validate it as a unique `Author/Product` style path.
|
|
142
|
+
|
|
108
143
|
### Building UIs with Observables & Dialogs
|
|
109
144
|
|
|
110
145
|
The framework uses a **Model-View pattern** with reactive data bindings:
|
|
@@ -153,14 +188,11 @@ export class MyDialog extends BasicDialog {
|
|
|
153
188
|
#### 3. Connect & Use in Your Script
|
|
154
189
|
|
|
155
190
|
```typescript
|
|
156
|
-
import { action } from '@dsf/core/action
|
|
157
|
-
import { BaseScript } from '@dsf/core/base-script';
|
|
191
|
+
import { action } from '@dsf/core/action';
|
|
158
192
|
import { getSelectedNode } from '@dsf/helpers/scene-helper';
|
|
159
193
|
import { MyDialog, MyDialogModel } from './my-dialog';
|
|
160
194
|
|
|
161
|
-
|
|
162
|
-
class MyDialogScript extends BaseScript {
|
|
163
|
-
protected run(): void {
|
|
195
|
+
action({ text: 'My Dialog Script' }, () => {
|
|
164
196
|
const model = new MyDialogModel();
|
|
165
197
|
const selectedNode = getSelectedNode();
|
|
166
198
|
|
|
@@ -185,10 +217,7 @@ class MyDialogScript extends BaseScript {
|
|
|
185
217
|
} else {
|
|
186
218
|
console.log('Dialog cancelled');
|
|
187
219
|
}
|
|
188
|
-
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
new MyDialogScript().exec();
|
|
220
|
+
});
|
|
192
221
|
```
|
|
193
222
|
|
|
194
223
|
### Core Concepts
|
|
@@ -305,7 +334,7 @@ my-daz-scripts/
|
|
|
305
334
|
│ │ ├── my-dialog.ts
|
|
306
335
|
│ │ └── my-dialog-script.dsa.ts
|
|
307
336
|
│ └── config.ts
|
|
308
|
-
├── out/ # Generated
|
|
337
|
+
├── out/ # Generated launchers, implementations, and copied icons
|
|
309
338
|
├── package.json
|
|
310
339
|
├── tsconfig.json
|
|
311
340
|
└── dazscript.config.ts
|
|
@@ -314,8 +343,10 @@ my-daz-scripts/
|
|
|
314
343
|
**Key points:**
|
|
315
344
|
- Scripts ending in `.dsa.ts` compile to `.dsa` files for Daz Studio
|
|
316
345
|
- Regular `.ts` files are utility, model, or helper classes
|
|
346
|
+
- Built action outputs are split into stable launchers plus sibling `lib/<script-name>/script.dsa` implementations
|
|
317
347
|
- Run `npm run build` to compile TypeScript → Daz Scripts
|
|
318
348
|
- Run `npm run watch` during development for live rebuild
|
|
349
|
+
- Rebuild after script changes; reinstalling Daz actions is usually not required because the launcher path stays stable
|
|
319
350
|
|
|
320
351
|
## Development & Publishing
|
|
321
352
|
|
|
@@ -325,15 +356,15 @@ This project uses **semantic-release** for automatic versioning and npm publishi
|
|
|
325
356
|
|
|
326
357
|
Use conventional commit messages to control version bumping:
|
|
327
358
|
|
|
328
|
-
- **`fix: description`** → Patch version bump (
|
|
359
|
+
- **`fix: description`** → Patch version bump (`x.y.z` → `x.y.(z+1)`)
|
|
329
360
|
- Bug fixes, patches, or minor improvements
|
|
330
361
|
- Example: `fix: resolve dialog builder layout issue`
|
|
331
362
|
|
|
332
|
-
- **`feat: description`** → Minor version bump (
|
|
363
|
+
- **`feat: description`** → Minor version bump (`x.y.z` → `x.(y+1).0`)
|
|
333
364
|
- New features or significant enhancements
|
|
334
365
|
- Example: `feat: add tree view builder component`
|
|
335
366
|
|
|
336
|
-
- **`BREAKING CHANGE: description`** → Major version bump (
|
|
367
|
+
- **`BREAKING CHANGE: description`** → Major version bump (`x.y.z` → `(x+1).0.0`)
|
|
337
368
|
- Add to commit body for breaking changes
|
|
338
369
|
- Example: `feat: refactor action decorator API\n\nBREAKING CHANGE: action() now requires explicit menu path`
|
|
339
370
|
|
package/dist/scripts/cli.js
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
'use strict';
|
|
3
3
|
|
|
4
|
+
const path = require('path');
|
|
5
|
+
const readline = require('readline');
|
|
4
6
|
const { runWebpack } = require('./build');
|
|
5
7
|
const { loadConfig } = require('./config-loader');
|
|
6
8
|
const { copyIcons } = require('./icons');
|
|
@@ -21,11 +23,58 @@ Options for init:
|
|
|
21
23
|
--menu-path <path> Default menu path. Default: /MyScripts
|
|
22
24
|
--scripts-path <path> Source directory to scan. Default: ./src
|
|
23
25
|
--out-dir <path> Build output directory. Default: ./out
|
|
26
|
+
--app-data-path <path> AppData namespace used by launcher fallbacks. Example: Author/Product
|
|
24
27
|
--force Overwrite generated files
|
|
25
28
|
--help Show this message
|
|
26
29
|
`);
|
|
27
30
|
}
|
|
28
31
|
|
|
32
|
+
function askQuestion(rl, question) {
|
|
33
|
+
return new Promise((resolve) => {
|
|
34
|
+
rl.question(question, (answer) => resolve(answer));
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
async function resolveInitOptions(workdir, options) {
|
|
39
|
+
if (options.appDataPath) {
|
|
40
|
+
return options;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
if (!process.stdin.isTTY || !process.stdout.isTTY) {
|
|
44
|
+
return options;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const defaultProductName = path.basename(workdir);
|
|
48
|
+
const rl = readline.createInterface({
|
|
49
|
+
input: process.stdin,
|
|
50
|
+
output: process.stdout,
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
try {
|
|
54
|
+
let author = '';
|
|
55
|
+
while (!author) {
|
|
56
|
+
const answer = await askQuestion(
|
|
57
|
+
rl,
|
|
58
|
+
'AppData author namespace (for example Vholf3D): '
|
|
59
|
+
);
|
|
60
|
+
author = answer.trim();
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const productAnswer = await askQuestion(
|
|
64
|
+
rl,
|
|
65
|
+
`AppData product namespace [${defaultProductName}]: `
|
|
66
|
+
);
|
|
67
|
+
const product = productAnswer.trim() || defaultProductName;
|
|
68
|
+
|
|
69
|
+
return {
|
|
70
|
+
...options,
|
|
71
|
+
appDataPath: `${author}/${product}`,
|
|
72
|
+
};
|
|
73
|
+
} finally {
|
|
74
|
+
rl.close();
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
29
78
|
function parseOptions(args, defaults) {
|
|
30
79
|
const options = { ...defaults };
|
|
31
80
|
|
|
@@ -55,6 +104,12 @@ function parseOptions(args, defaults) {
|
|
|
55
104
|
continue;
|
|
56
105
|
}
|
|
57
106
|
|
|
107
|
+
if (arg === '--app-data-path') {
|
|
108
|
+
options.appDataPath = args[index + 1];
|
|
109
|
+
index += 1;
|
|
110
|
+
continue;
|
|
111
|
+
}
|
|
112
|
+
|
|
58
113
|
if (arg === '--file') {
|
|
59
114
|
options.file = args[index + 1];
|
|
60
115
|
index += 1;
|
|
@@ -101,6 +156,7 @@ async function main(argv) {
|
|
|
101
156
|
menuPath: undefined,
|
|
102
157
|
scriptsPath: undefined,
|
|
103
158
|
outDir: undefined,
|
|
159
|
+
appDataPath: undefined,
|
|
104
160
|
file: undefined,
|
|
105
161
|
});
|
|
106
162
|
|
|
@@ -109,7 +165,14 @@ async function main(argv) {
|
|
|
109
165
|
return;
|
|
110
166
|
}
|
|
111
167
|
|
|
168
|
+
const commandOptions =
|
|
169
|
+
command === 'init'
|
|
170
|
+
? await resolveInitOptions(workdir, options)
|
|
171
|
+
: options;
|
|
112
172
|
const resolvedOptions = getResolvedOptions(workdir, options);
|
|
173
|
+
if (commandOptions !== options) {
|
|
174
|
+
Object.assign(resolvedOptions, commandOptions);
|
|
175
|
+
}
|
|
113
176
|
resolvedOptions.menuPath = resolvedOptions.menuPath || '/MyScripts';
|
|
114
177
|
resolvedOptions.scriptsPath = resolvedOptions.scriptsPath || './src';
|
|
115
178
|
resolvedOptions.outDir = resolvedOptions.outDir || './out';
|
package/dist/scripts/init.js
CHANGED
|
@@ -49,6 +49,7 @@ export default defineConfig({
|
|
|
49
49
|
scriptsPath: '${options.scriptsPath}',
|
|
50
50
|
outDir: '${options.outDir}',
|
|
51
51
|
defaultMenuPath: '${options.menuPath}',
|
|
52
|
+
appDataPath: '${options.appDataPath}',
|
|
52
53
|
});
|
|
53
54
|
`;
|
|
54
55
|
}
|
|
@@ -94,11 +95,13 @@ function updatePackageJson(workdir, options) {
|
|
|
94
95
|
}
|
|
95
96
|
|
|
96
97
|
function initProject(workdir, rawOptions) {
|
|
98
|
+
const projectName = path.basename(workdir);
|
|
97
99
|
const options = {
|
|
98
100
|
force: Boolean(rawOptions.force),
|
|
99
101
|
menuPath: normalizePath(rawOptions.menuPath, '/MyScripts'),
|
|
100
102
|
scriptsPath: normalizePath(rawOptions.scriptsPath, './src'),
|
|
101
103
|
outDir: normalizePath(rawOptions.outDir, './out'),
|
|
104
|
+
appDataPath: normalizePath(rawOptions.appDataPath, `YourName/${projectName}`),
|
|
102
105
|
};
|
|
103
106
|
|
|
104
107
|
writeFileIfNeeded(
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
const
|
|
1
|
+
const ts = require('typescript');
|
|
2
2
|
const fs = require('fs');
|
|
3
3
|
const path = require('path');
|
|
4
4
|
const glob = require('glob');
|
|
5
5
|
|
|
6
|
-
const
|
|
6
|
+
const nameofActionFunction = 'action';
|
|
7
7
|
|
|
8
8
|
function getPartialPath(filePath) {
|
|
9
9
|
const fileInfo = path.parse(filePath);
|
|
@@ -35,97 +35,207 @@ uninstall(${data});
|
|
|
35
35
|
`;
|
|
36
36
|
}
|
|
37
37
|
|
|
38
|
-
function
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
38
|
+
function literalValue(node) {
|
|
39
|
+
if (!node) {
|
|
40
|
+
return undefined;
|
|
41
|
+
}
|
|
42
42
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
43
|
+
if (ts.isStringLiteral(node) || ts.isNoSubstitutionTemplateLiteral(node)) {
|
|
44
|
+
return node.text;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
if (node.kind === ts.SyntaxKind.TrueKeyword) {
|
|
48
|
+
return true;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
if (node.kind === ts.SyntaxKind.FalseKeyword) {
|
|
52
|
+
return false;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
if (ts.isNumericLiteral(node)) {
|
|
56
|
+
return Number(node.text);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
if (ts.isPrefixUnaryExpression(node) && ts.isNumericLiteral(node.operand)) {
|
|
60
|
+
if (node.operator === ts.SyntaxKind.MinusToken) {
|
|
61
|
+
return -Number(node.operand.text);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
if (node.operator === ts.SyntaxKind.PlusToken) {
|
|
65
|
+
return Number(node.operand.text);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
return undefined;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function objectLiteralToObject(node) {
|
|
73
|
+
if (!node || !ts.isObjectLiteralExpression(node)) {
|
|
74
|
+
return null;
|
|
75
|
+
}
|
|
47
76
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
+
const result = {};
|
|
78
|
+
|
|
79
|
+
node.properties.forEach((property) => {
|
|
80
|
+
if (!ts.isPropertyAssignment(property)) {
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const name = ts.isIdentifier(property.name)
|
|
85
|
+
? property.name.text
|
|
86
|
+
: ts.isStringLiteral(property.name)
|
|
87
|
+
? property.name.text
|
|
88
|
+
: null;
|
|
89
|
+
|
|
90
|
+
if (!name) {
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
const value = literalValue(property.initializer);
|
|
95
|
+
if (value !== undefined) {
|
|
96
|
+
result[name] = value;
|
|
97
|
+
}
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
return result;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function getCallName(expression) {
|
|
104
|
+
if (ts.isIdentifier(expression)) {
|
|
105
|
+
return expression.text;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
if (ts.isPropertyAccessExpression(expression)) {
|
|
109
|
+
return expression.name.text;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
return null;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function findTopLevelActionCall(content, filePath) {
|
|
116
|
+
const sourceFile = ts.createSourceFile(
|
|
117
|
+
filePath,
|
|
118
|
+
content,
|
|
119
|
+
ts.ScriptTarget.Latest,
|
|
120
|
+
true,
|
|
121
|
+
ts.ScriptKind.TS
|
|
122
|
+
);
|
|
123
|
+
|
|
124
|
+
for (const statement of sourceFile.statements) {
|
|
125
|
+
if (!ts.isExpressionStatement(statement)) {
|
|
126
|
+
continue;
|
|
77
127
|
}
|
|
78
128
|
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
script.menuPath = decorator.menuPath === true ? script.menuPath : '';
|
|
83
|
-
} else {
|
|
84
|
-
script.menuPath = stringOrDefault(decorator.menuPath, script.menuPath);
|
|
85
|
-
}
|
|
86
|
-
script.menuPath = script.menuPath.replace(
|
|
87
|
-
'#{defaultMenuPath}',
|
|
88
|
-
defaultMenuPath
|
|
89
|
-
);
|
|
90
|
-
script.shortcut = decorator.shortcut;
|
|
129
|
+
const expression = statement.expression;
|
|
130
|
+
if (!ts.isCallExpression(expression)) {
|
|
131
|
+
continue;
|
|
91
132
|
}
|
|
92
133
|
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
// Check if the decorator has a "bundle" property
|
|
97
|
-
if (decorator.bundle !== undefined) {
|
|
98
|
-
let packageInstallerFilePath = `Install.dsa.ts`;
|
|
99
|
-
let packageUninstallerFilePath = `Uninstall.dsa.ts`;
|
|
100
|
-
|
|
101
|
-
if (decorator.bundle !== true) {
|
|
102
|
-
// If decorator.bundle is a string, use it as the bundle file name
|
|
103
|
-
packageInstallerFilePath = `Install ${decorator.bundle}.dsa.ts`;
|
|
104
|
-
packageUninstallerFilePath = `Uninstall ${decorator.bundle}.dsa.ts`;
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
// Generate a separate file with the specified or default name
|
|
108
|
-
let bundleScriptContent = generateInstallerTemplate(
|
|
109
|
-
JSON.stringify(container.scripts, null, 4)
|
|
110
|
-
);
|
|
111
|
-
let bundleScriptFilePath = path.join(
|
|
112
|
-
path.parse(filePath).dir,
|
|
113
|
-
packageInstallerFilePath
|
|
114
|
-
);
|
|
115
|
-
fs.writeFileSync(bundleScriptFilePath, bundleScriptContent);
|
|
116
|
-
|
|
117
|
-
bundleScriptContent = generateUninstallerTemplate(
|
|
118
|
-
JSON.stringify(container.scripts, null, 4)
|
|
119
|
-
);
|
|
120
|
-
bundleScriptFilePath = path.join(
|
|
121
|
-
path.parse(filePath).dir,
|
|
122
|
-
packageUninstallerFilePath
|
|
123
|
-
);
|
|
124
|
-
fs.writeFileSync(bundleScriptFilePath, bundleScriptContent);
|
|
134
|
+
if (getCallName(expression.expression) !== nameofActionFunction) {
|
|
135
|
+
continue;
|
|
125
136
|
}
|
|
137
|
+
|
|
138
|
+
return expression;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
return null;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
function findActionEntryFiles(workdir, options) {
|
|
145
|
+
const scriptsPath = options.scriptsPath.endsWith('/')
|
|
146
|
+
? options.scriptsPath
|
|
147
|
+
: `${options.scriptsPath}/`;
|
|
148
|
+
const matches = glob.sync(`${scriptsPath}/**/*.dsa.ts`, { cwd: workdir });
|
|
149
|
+
|
|
150
|
+
return matches.filter((filePath) => {
|
|
151
|
+
const absolutePath = path.join(workdir, filePath);
|
|
152
|
+
const content = fs.readFileSync(absolutePath, 'utf-8').toString();
|
|
153
|
+
return !!findTopLevelActionCall(content, absolutePath);
|
|
126
154
|
});
|
|
127
155
|
}
|
|
128
156
|
|
|
157
|
+
function processScript(filePath, container, defaultMenuPath) {
|
|
158
|
+
const fileInfo = path.parse(filePath);
|
|
159
|
+
const content = fs.readFileSync(filePath, 'utf-8').toString();
|
|
160
|
+
const actionCall = findTopLevelActionCall(content, filePath);
|
|
161
|
+
|
|
162
|
+
if (!actionCall) {
|
|
163
|
+
return;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
const decorator = objectLiteralToObject(actionCall.arguments[0]) ?? {};
|
|
167
|
+
|
|
168
|
+
const script = {
|
|
169
|
+
name: null,
|
|
170
|
+
text: fileInfo.name.replace('.dsa', ''),
|
|
171
|
+
filePath:
|
|
172
|
+
decorator.bundle === undefined
|
|
173
|
+
? getPartialPath(filePath)
|
|
174
|
+
: fileInfo.name.replace('.ts', ''),
|
|
175
|
+
menuPath: (() => {
|
|
176
|
+
const relativeDir = path.parse(getPartialPath(filePath)).dir;
|
|
177
|
+
return relativeDir && relativeDir !== '.'
|
|
178
|
+
? `${defaultMenuPath}${relativeDir}`
|
|
179
|
+
: defaultMenuPath.replace(/\/$/, '');
|
|
180
|
+
})(),
|
|
181
|
+
description: fileInfo.name.replace('.dsa', ''),
|
|
182
|
+
group: stringOrDefault(decorator.group, undefined),
|
|
183
|
+
shortcut: stringOrDefault(decorator.shortcut),
|
|
184
|
+
toolbar: stringOrDefault(decorator.toolbar, undefined),
|
|
185
|
+
};
|
|
186
|
+
|
|
187
|
+
const icon = filePath.replace('.ts', '.png');
|
|
188
|
+
if (fs.existsSync(icon)) {
|
|
189
|
+
script.icon =
|
|
190
|
+
decorator.bundle === undefined
|
|
191
|
+
? `${getPartialPath(filePath)}.png`
|
|
192
|
+
: fileInfo.name.replace('.dsa', '.dsa.png');
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
script.text = stringOrDefault(decorator.text, script.text);
|
|
196
|
+
if (typeof decorator.menuPath === 'boolean') {
|
|
197
|
+
script.menuPath = decorator.menuPath === true ? script.menuPath : '';
|
|
198
|
+
} else {
|
|
199
|
+
script.menuPath = stringOrDefault(decorator.menuPath, script.menuPath);
|
|
200
|
+
}
|
|
201
|
+
script.menuPath = script.menuPath.replace(
|
|
202
|
+
'#{defaultMenuPath}',
|
|
203
|
+
defaultMenuPath
|
|
204
|
+
);
|
|
205
|
+
script.shortcut = decorator.shortcut;
|
|
206
|
+
|
|
207
|
+
console.log(`Adding script: ${script.text}`, script);
|
|
208
|
+
container.scripts.push(script);
|
|
209
|
+
|
|
210
|
+
if (decorator.bundle !== undefined) {
|
|
211
|
+
let packageInstallerFilePath = `Install.dsa.ts`;
|
|
212
|
+
let packageUninstallerFilePath = `Uninstall.dsa.ts`;
|
|
213
|
+
|
|
214
|
+
if (decorator.bundle !== true) {
|
|
215
|
+
packageInstallerFilePath = `Install ${decorator.bundle}.dsa.ts`;
|
|
216
|
+
packageUninstallerFilePath = `Uninstall ${decorator.bundle}.dsa.ts`;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
let bundleScriptContent = generateInstallerTemplate(
|
|
220
|
+
JSON.stringify(container.scripts, null, 4)
|
|
221
|
+
);
|
|
222
|
+
let bundleScriptFilePath = path.join(
|
|
223
|
+
path.parse(filePath).dir,
|
|
224
|
+
packageInstallerFilePath
|
|
225
|
+
);
|
|
226
|
+
fs.writeFileSync(bundleScriptFilePath, bundleScriptContent);
|
|
227
|
+
|
|
228
|
+
bundleScriptContent = generateUninstallerTemplate(
|
|
229
|
+
JSON.stringify(container.scripts, null, 4)
|
|
230
|
+
);
|
|
231
|
+
bundleScriptFilePath = path.join(
|
|
232
|
+
path.parse(filePath).dir,
|
|
233
|
+
packageUninstallerFilePath
|
|
234
|
+
);
|
|
235
|
+
fs.writeFileSync(bundleScriptFilePath, bundleScriptContent);
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
|
|
129
239
|
function processScripts(paths, container, defaultMenuPath) {
|
|
130
240
|
paths.forEach((filePath) => {
|
|
131
241
|
console.log(`Processing ${filePath}`);
|
|
@@ -134,14 +244,11 @@ function processScripts(paths, container, defaultMenuPath) {
|
|
|
134
244
|
}
|
|
135
245
|
|
|
136
246
|
function generateInstallerFiles(workdir, options) {
|
|
137
|
-
const scriptsPath = options.scriptsPath.endsWith('/')
|
|
138
|
-
? options.scriptsPath
|
|
139
|
-
: `${options.scriptsPath}/`;
|
|
140
247
|
const defaultMenuPath = options.defaultMenuPath.endsWith('/')
|
|
141
248
|
? options.defaultMenuPath
|
|
142
249
|
: `${options.defaultMenuPath}/`;
|
|
143
250
|
const container = { scripts: [] };
|
|
144
|
-
const matches =
|
|
251
|
+
const matches = findActionEntryFiles(workdir, options);
|
|
145
252
|
|
|
146
253
|
processScripts(matches, container, defaultMenuPath);
|
|
147
254
|
|
|
@@ -195,4 +302,5 @@ if (require.main === module) {
|
|
|
195
302
|
|
|
196
303
|
module.exports = {
|
|
197
304
|
generateInstallerFiles,
|
|
305
|
+
findActionEntryFiles,
|
|
198
306
|
};
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
const { findActionEntryFiles } = require('./install-generator');
|
|
6
|
+
|
|
7
|
+
function toPosix(filePath) {
|
|
8
|
+
return filePath.replace(/\\/g, '/');
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
function getActionOutputPath(workdir, outDir, sourceFile) {
|
|
12
|
+
const sourceRoot = path.resolve(workdir, 'src');
|
|
13
|
+
const absoluteSourceFile = path.resolve(workdir, sourceFile);
|
|
14
|
+
const relativeSourceFile = toPosix(path.relative(sourceRoot, absoluteSourceFile));
|
|
15
|
+
return relativeSourceFile.replace(/\.ts$/, '');
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function getImplementationRelativePath(outputRelativePath) {
|
|
19
|
+
const outputDirectory = path.posix.dirname(outputRelativePath);
|
|
20
|
+
const outputBaseName = path.posix.basename(outputRelativePath, '.dsa');
|
|
21
|
+
const implementationDirectory = outputDirectory === '.'
|
|
22
|
+
? path.posix.join('lib', outputBaseName)
|
|
23
|
+
: path.posix.join(outputDirectory, 'lib', outputBaseName);
|
|
24
|
+
|
|
25
|
+
return path.posix.join(implementationDirectory, 'script.dsa');
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function validateAppDataPath(appDataPath, workdir) {
|
|
29
|
+
if (!appDataPath || typeof appDataPath !== 'string') {
|
|
30
|
+
throw new Error(
|
|
31
|
+
`[dazscript] Missing required appDataPath in ${workdir}. ` +
|
|
32
|
+
`Set appDataPath: 'Author/Product' in dazscript.config.ts.`
|
|
33
|
+
);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const normalized = toPosix(appDataPath).trim().replace(/^\/+|\/+$/g, '');
|
|
37
|
+
const segments = normalized.split('/').filter(Boolean);
|
|
38
|
+
|
|
39
|
+
if (segments.length < 2) {
|
|
40
|
+
throw new Error(
|
|
41
|
+
`[dazscript] Invalid appDataPath "${appDataPath}" in ${workdir}. ` +
|
|
42
|
+
`Use at least two segments, for example 'Author/Product'.`
|
|
43
|
+
);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const blockedSegments = new Set([
|
|
47
|
+
'appdata',
|
|
48
|
+
'cache',
|
|
49
|
+
'data',
|
|
50
|
+
'lib',
|
|
51
|
+
'libs',
|
|
52
|
+
'script',
|
|
53
|
+
'scripts',
|
|
54
|
+
'temp',
|
|
55
|
+
'tmp',
|
|
56
|
+
]);
|
|
57
|
+
|
|
58
|
+
const invalidSegment = segments.find((segment) => blockedSegments.has(segment.toLowerCase()));
|
|
59
|
+
if (invalidSegment) {
|
|
60
|
+
throw new Error(
|
|
61
|
+
`[dazscript] Invalid appDataPath "${appDataPath}" in ${workdir}. ` +
|
|
62
|
+
`Path segments like "${invalidSegment}" are too generic. Use a unique Author/Product path.`
|
|
63
|
+
);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
return normalized;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function makeLauncherSource(implementationRelativePath, appDataImplementationRelativePath) {
|
|
70
|
+
return [
|
|
71
|
+
'// Auto-generated launcher shim.',
|
|
72
|
+
'// The installed Daz action points at this stable file.',
|
|
73
|
+
'// The current implementation is resolved from the sibling lib/ directory first,',
|
|
74
|
+
'// and from the AppData fallback location second.',
|
|
75
|
+
'',
|
|
76
|
+
`var implementationRelativePath = '${implementationRelativePath}';`,
|
|
77
|
+
`var appDataImplementationRelativePath = '${appDataImplementationRelativePath}';`,
|
|
78
|
+
'var launcherFileName = getScriptFileName();',
|
|
79
|
+
'var launcherInfo = new DzFileInfo(launcherFileName);',
|
|
80
|
+
'var launcherDirectory = typeof launcherInfo.canonicalPath == "function"',
|
|
81
|
+
' ? launcherInfo.canonicalPath()',
|
|
82
|
+
' : launcherInfo.path();',
|
|
83
|
+
'launcherInfo.deleteLater();',
|
|
84
|
+
'',
|
|
85
|
+
"var implementationPath = launcherDirectory + '/' + implementationRelativePath;",
|
|
86
|
+
'var implementationFile = new DzFile(implementationPath);',
|
|
87
|
+
'if (!implementationFile.exists()) {',
|
|
88
|
+
' implementationFile.deleteLater();',
|
|
89
|
+
" implementationPath = App.getAppDataPath() + '/' + appDataImplementationRelativePath;",
|
|
90
|
+
' implementationFile = new DzFile(implementationPath);',
|
|
91
|
+
'}',
|
|
92
|
+
'',
|
|
93
|
+
'if (!implementationFile.exists()) {',
|
|
94
|
+
` MessageBox.warning('Unable to find script implementation:\\n' + implementationPath, 'Missing Script', '&OK;', '');`,
|
|
95
|
+
' implementationFile.deleteLater();',
|
|
96
|
+
'} else {',
|
|
97
|
+
' implementationFile.deleteLater();',
|
|
98
|
+
'',
|
|
99
|
+
' var script = new DzScript(implementationPath);',
|
|
100
|
+
' if (!script.loadFromFile(implementationPath, true)) {',
|
|
101
|
+
` MessageBox.warning('Unable to load script implementation:\\n' + implementationPath, 'Script Load Error', '&OK;', '');`,
|
|
102
|
+
' script.deleteLater();',
|
|
103
|
+
' } else {',
|
|
104
|
+
" var args = typeof getArguments == 'function' ? getArguments() : [];",
|
|
105
|
+
' script.execute(args);',
|
|
106
|
+
' script.deleteLater();',
|
|
107
|
+
' }',
|
|
108
|
+
'}',
|
|
109
|
+
'',
|
|
110
|
+
].join('\n');
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
function ensureParentDir(filePath) {
|
|
114
|
+
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function createActionLaunchers(workdir, options) {
|
|
118
|
+
const outDir = path.resolve(workdir, options.outDir || './out');
|
|
119
|
+
const appDataPath = validateAppDataPath(options.appDataPath, workdir);
|
|
120
|
+
const actionEntryFiles = findActionEntryFiles(workdir, {
|
|
121
|
+
scriptsPath: options.scriptsPath || './src',
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
actionEntryFiles.forEach((sourceFile) => {
|
|
125
|
+
const outputRelativePath = getActionOutputPath(workdir, outDir, sourceFile);
|
|
126
|
+
const launcherPath = path.join(outDir, outputRelativePath);
|
|
127
|
+
const implementationRelativePath = getImplementationRelativePath(outputRelativePath);
|
|
128
|
+
const implementationPath = path.join(outDir, implementationRelativePath);
|
|
129
|
+
|
|
130
|
+
if (!fs.existsSync(launcherPath)) {
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
ensureParentDir(implementationPath);
|
|
135
|
+
if (fs.existsSync(implementationPath)) {
|
|
136
|
+
fs.unlinkSync(implementationPath);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
fs.renameSync(launcherPath, implementationPath);
|
|
140
|
+
|
|
141
|
+
const launcherDir = path.dirname(outputRelativePath);
|
|
142
|
+
const relativeImplementationPath = toPosix(
|
|
143
|
+
path.relative(launcherDir || '.', implementationRelativePath)
|
|
144
|
+
);
|
|
145
|
+
const appDataImplementationRelativePath = toPosix(
|
|
146
|
+
path.posix.join(appDataPath, implementationRelativePath)
|
|
147
|
+
);
|
|
148
|
+
|
|
149
|
+
fs.writeFileSync(
|
|
150
|
+
launcherPath,
|
|
151
|
+
makeLauncherSource(
|
|
152
|
+
relativeImplementationPath,
|
|
153
|
+
appDataImplementationRelativePath
|
|
154
|
+
)
|
|
155
|
+
);
|
|
156
|
+
});
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
module.exports = {
|
|
160
|
+
createActionLaunchers,
|
|
161
|
+
validateAppDataPath,
|
|
162
|
+
};
|
package/package.json
CHANGED
|
@@ -1,16 +1 @@
|
|
|
1
|
-
|
|
2
|
-
text?: string
|
|
3
|
-
description?: string
|
|
4
|
-
icon?: string
|
|
5
|
-
menuPath?: string | boolean
|
|
6
|
-
toolbar?: string
|
|
7
|
-
sort?: number
|
|
8
|
-
group?: string
|
|
9
|
-
shortcut?: string
|
|
10
|
-
bundle?: string | boolean
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
export const action = (action?: CustomActionDefinition) => {
|
|
14
|
-
return (target: Function) => {
|
|
15
|
-
}
|
|
16
|
-
}
|
|
1
|
+
export { ActionDefinition, action } from './action';
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { debug, error, raise } from '@dsf/common/log';
|
|
2
|
+
|
|
3
|
+
export class ActionDefinition {
|
|
4
|
+
text?: string
|
|
5
|
+
description?: string
|
|
6
|
+
icon?: string
|
|
7
|
+
menuPath?: string | boolean
|
|
8
|
+
toolbar?: string
|
|
9
|
+
sort?: number
|
|
10
|
+
group?: string
|
|
11
|
+
shortcut?: string
|
|
12
|
+
bundle?: string | boolean
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
type ActionCallback = () => void
|
|
16
|
+
type ActionRunnable = { [key: string]: unknown }
|
|
17
|
+
type ActionConstructor = new (...args: any[]) => any
|
|
18
|
+
type ActionTarget = ActionCallback | ActionRunnable | ActionConstructor
|
|
19
|
+
|
|
20
|
+
const isFunction = (value: unknown): value is Function => typeof value === 'function'
|
|
21
|
+
|
|
22
|
+
const isConstructorTarget = (target: ActionTarget): target is ActionConstructor => {
|
|
23
|
+
if (!isFunction(target)) {
|
|
24
|
+
return false
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const prototype = (target as ActionConstructor).prototype as ActionRunnable | undefined
|
|
28
|
+
return !!prototype && (typeof prototype.run === 'function' || typeof prototype.exec === 'function')
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const invokeRunnable = (target: ActionRunnable) => {
|
|
32
|
+
if (typeof target.run === 'function') {
|
|
33
|
+
target.run()
|
|
34
|
+
return
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
if (typeof target.exec === 'function') {
|
|
38
|
+
target.exec()
|
|
39
|
+
return
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
throw new Error('Action target must expose run() or exec().')
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export function action(definition: ActionDefinition, target: ActionTarget): void {
|
|
46
|
+
const scriptName = definition?.text || 'Unnamed Script'
|
|
47
|
+
|
|
48
|
+
try {
|
|
49
|
+
const runnable = isConstructorTarget(target)
|
|
50
|
+
? new target()
|
|
51
|
+
: target
|
|
52
|
+
|
|
53
|
+
const announceExecution = !isFunction(runnable) && runnable.anounceExecution === false
|
|
54
|
+
? false
|
|
55
|
+
: true
|
|
56
|
+
|
|
57
|
+
if (announceExecution) {
|
|
58
|
+
debug(`=== Running script "${scriptName}" ===`)
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
if (isFunction(runnable)) {
|
|
62
|
+
runnable()
|
|
63
|
+
return
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
invokeRunnable(runnable)
|
|
67
|
+
} catch (err) {
|
|
68
|
+
error(`There was an error while running the script "${scriptName}"`)
|
|
69
|
+
raise(err)
|
|
70
|
+
}
|
|
71
|
+
}
|
|
@@ -1,13 +1,8 @@
|
|
|
1
1
|
import { debug } from '@dsf/common/log';
|
|
2
|
-
import { action } from '@dsf/core/action
|
|
3
|
-
import { BaseScript } from '@dsf/core/base-script';
|
|
2
|
+
import { action } from '@dsf/core/action';
|
|
4
3
|
import { info } from '@dsf/helpers/message-box-helper';
|
|
5
4
|
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
info('Hello World!');
|
|
11
|
-
}
|
|
12
|
-
}
|
|
13
|
-
new HelloWorldScript().exec();
|
|
5
|
+
action({ text: 'Hello World' }, () => {
|
|
6
|
+
debug('Hello World!');
|
|
7
|
+
info('Hello World!');
|
|
8
|
+
})
|
|
@@ -1,52 +1,47 @@
|
|
|
1
1
|
import { debug, dump } from '@dsf/common/log';
|
|
2
|
-
import { action } from '@dsf/core/action
|
|
3
|
-
import { BaseScript } from '@dsf/core/base-script';
|
|
2
|
+
import { action } from '@dsf/core/action';
|
|
4
3
|
import { error } from '@dsf/helpers/message-box-helper';
|
|
5
4
|
import { getNodes, getSelectedNode } from '@dsf/helpers/scene-helper';
|
|
6
5
|
import { SampleDialog, SampleDialogModel } from './sample-dialog';
|
|
7
6
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
error('Please select a node.')
|
|
15
|
-
return
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
let model = new SampleDialogModel()
|
|
19
|
-
model.nodes$.value = sceneNodes
|
|
20
|
-
|
|
21
|
-
model.nodeLabel$.connect((label) => {
|
|
22
|
-
if (label === model.selectedNode$.value.getLabel()) return
|
|
23
|
-
model.selectedNode$.value.setLabel(label)
|
|
24
|
-
model.nodes$.value = getNodes()
|
|
25
|
-
})
|
|
26
|
-
|
|
27
|
-
let dialog = new SampleDialog(model)
|
|
28
|
-
|
|
29
|
-
model.selectedNode$.connect((node) => {
|
|
30
|
-
if (!node) return
|
|
31
|
-
debug(`Selected Node: ${node.getLabel()}`)
|
|
32
|
-
node.select(true)
|
|
33
|
-
model.nodeLabel$.value = node.getLabel()
|
|
34
|
-
model.showNode$.value = node.isVisible()
|
|
35
|
-
model.hideNode$.value = !node.isVisible()
|
|
36
|
-
})
|
|
37
|
-
dialog.onNodeVisibilityChanged = (node, visible) => {
|
|
38
|
-
node.setVisible(visible)
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
model.selectedNode$.value = selectedNode
|
|
42
|
-
|
|
43
|
-
if (!dialog.run()) {
|
|
44
|
-
debug(`Dialog Cancelled`)
|
|
45
|
-
return
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
debug(`Dialog Closed`)
|
|
49
|
-
dump(model)
|
|
7
|
+
action({ text: 'Sample Dialog' }, () => {
|
|
8
|
+
let sceneNodes = getNodes()
|
|
9
|
+
let selectedNode = getSelectedNode()
|
|
10
|
+
if (!selectedNode) {
|
|
11
|
+
error('Please select a node.')
|
|
12
|
+
return
|
|
50
13
|
}
|
|
51
|
-
|
|
52
|
-
new
|
|
14
|
+
|
|
15
|
+
let model = new SampleDialogModel()
|
|
16
|
+
model.nodes$.value = sceneNodes
|
|
17
|
+
|
|
18
|
+
model.nodeLabel$.connect((label) => {
|
|
19
|
+
if (label === model.selectedNode$.value.getLabel()) return
|
|
20
|
+
model.selectedNode$.value.setLabel(label)
|
|
21
|
+
model.nodes$.value = getNodes()
|
|
22
|
+
})
|
|
23
|
+
|
|
24
|
+
let dialog = new SampleDialog(model)
|
|
25
|
+
|
|
26
|
+
model.selectedNode$.connect((node) => {
|
|
27
|
+
if (!node) return
|
|
28
|
+
debug(`Selected Node: ${node.getLabel()}`)
|
|
29
|
+
node.select(true)
|
|
30
|
+
model.nodeLabel$.value = node.getLabel()
|
|
31
|
+
model.showNode$.value = node.isVisible()
|
|
32
|
+
model.hideNode$.value = !node.isVisible()
|
|
33
|
+
})
|
|
34
|
+
dialog.onNodeVisibilityChanged = (node, visible) => {
|
|
35
|
+
node.setVisible(visible)
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
model.selectedNode$.value = selectedNode
|
|
39
|
+
|
|
40
|
+
if (!dialog.run()) {
|
|
41
|
+
debug(`Dialog Cancelled`)
|
|
42
|
+
return
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
debug(`Dialog Closed`)
|
|
46
|
+
dump(model)
|
|
47
|
+
})
|
package/webpack.config.js
CHANGED
|
@@ -1,10 +1,33 @@
|
|
|
1
1
|
const path = require('path');
|
|
2
2
|
const glob = require('glob');
|
|
3
3
|
const TsconfigPathsPlugin = require('tsconfig-paths-webpack-plugin');
|
|
4
|
+
const { createActionLaunchers, validateAppDataPath } = require('./dist/scripts/launchers');
|
|
5
|
+
const { loadConfig } = require('./dist/scripts/config-loader');
|
|
6
|
+
|
|
7
|
+
class ActionLauncherPlugin {
|
|
8
|
+
constructor(options) {
|
|
9
|
+
this.options = options;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
apply(compiler) {
|
|
13
|
+
compiler.hooks.done.tap('ActionLauncherPlugin', () => {
|
|
14
|
+
createActionLaunchers(this.options.workdir, {
|
|
15
|
+
outDir: this.options.outDir,
|
|
16
|
+
scriptsPath: this.options.scriptsPath,
|
|
17
|
+
appDataPath: this.options.appDataPath,
|
|
18
|
+
});
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
}
|
|
4
22
|
|
|
5
23
|
module.exports = (env, argv) => {
|
|
6
24
|
const projectRoot =
|
|
7
25
|
env && env.context ? path.resolve(env.context) : process.cwd();
|
|
26
|
+
const { config: projectConfig } = loadConfig(projectRoot);
|
|
27
|
+
const appDataPath = validateAppDataPath(
|
|
28
|
+
(env && env.appDataPath) || projectConfig.appDataPath,
|
|
29
|
+
projectRoot
|
|
30
|
+
);
|
|
8
31
|
const sourceRoot = path.resolve(projectRoot, 'src');
|
|
9
32
|
const projectNodeModules = path.resolve(projectRoot, 'node_modules');
|
|
10
33
|
const frameworkSourceRoot = path.resolve(
|
|
@@ -88,5 +111,14 @@ module.exports = (env, argv) => {
|
|
|
88
111
|
module: false,
|
|
89
112
|
},
|
|
90
113
|
},
|
|
114
|
+
plugins: [
|
|
115
|
+
new ActionLauncherPlugin({
|
|
116
|
+
workdir: projectRoot,
|
|
117
|
+
outDir: outputPath,
|
|
118
|
+
scriptsPath:
|
|
119
|
+
(env && env.scriptsPath) || projectConfig.scriptsPath || './src',
|
|
120
|
+
appDataPath,
|
|
121
|
+
}),
|
|
122
|
+
],
|
|
91
123
|
};
|
|
92
124
|
};
|