@tywalk/pcf-helper 1.5.6 ā 1.6.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/README.md +245 -21
- package/dist/__tests__/pcf-helper-build.test.js +7 -3
- package/dist/__tests__/pcf-helper-deploy.test.js +7 -3
- package/dist/__tests__/pcf-helper-import.test.js +7 -3
- package/dist/__tests__/pcf-helper-init.test.js +7 -3
- package/dist/__tests__/pcf-helper-session.test.js +7 -3
- package/dist/__tests__/pcf-helper-upgrade.test.js +7 -3
- package/dist/bin/session.js +13 -13
- package/dist/package.json +11 -2
- package/dist/tasks/session-pcf.js +213 -166
- package/dist/util/commandUtil.js +34 -0
- package/package.json +11 -2
- package/types/tasks/session-pcf.d.ts +15 -1
- package/types/util/commandUtil.d.ts +6 -0
package/README.md
CHANGED
|
@@ -1,33 +1,257 @@
|
|
|
1
|
-
# PCF Helper
|
|
1
|
+
# PCF Helper Core š§
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
[](https://badge.fury.io/js/%40tywalk%2Fpcf-helper)
|
|
4
|
+
[](https://www.typescriptlang.org/)
|
|
4
5
|
|
|
5
|
-
|
|
6
|
+
**Individual CLI commands and core library for Power Platform Component Framework (PCF) development.**
|
|
6
7
|
|
|
7
|
-
|
|
8
|
+
This package provides discrete command-line utilities for each PCF operation, making it ideal for automation scripts and developers who prefer granular control over their PCF workflows.
|
|
8
9
|
|
|
9
|
-
|
|
10
|
+
## š Table of Contents
|
|
10
11
|
|
|
11
|
-
|
|
12
|
-
|
|
12
|
+
- [Installation](#installation)
|
|
13
|
+
- [Available Commands](#available-commands)
|
|
14
|
+
- [Command Reference](#command-reference)
|
|
15
|
+
- [API Reference](#api-reference)
|
|
16
|
+
- [Troubleshooting](#troubleshooting)
|
|
13
17
|
|
|
14
|
-
##
|
|
18
|
+
## š¦ Installation
|
|
15
19
|
|
|
16
|
-
|
|
17
|
-
2. In your project's `package.json` file, add commands as npm scripts:
|
|
20
|
+
### Global Installation (Recommended)
|
|
18
21
|
|
|
19
|
-
```
|
|
20
|
-
|
|
21
|
-
"upgrade": "pcf-helper-upgrade --path <path to pcf project folder>",
|
|
22
|
-
"build": "pcf-helper-build --path <path to pcf project folder>",
|
|
23
|
-
"import": "pcf-helper-import --path <path to pcf project folder> --environment <environment guid or url>",
|
|
24
|
-
"deploy": "pcf-helper-deploy --path <path to pcf project folder> --environment <environment guid or url>",
|
|
25
|
-
"init": "pcf-helper-init --path <path to pcf project folder (optional)> --name <name of the pcf project> --publisher-name <powerapps publisher name> --publisher-prefix <powerapps publisher prefix>"
|
|
26
|
-
},
|
|
22
|
+
```bash
|
|
23
|
+
npm install -g @tywalk/pcf-helper
|
|
27
24
|
```
|
|
28
25
|
|
|
29
|
-
|
|
26
|
+
### Local Installation
|
|
30
27
|
|
|
31
|
-
|
|
28
|
+
```bash
|
|
29
|
+
npm install @tywalk/pcf-helper
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
## š ļø Available Commands
|
|
33
|
+
|
|
34
|
+
Each command is available as a standalone executable:
|
|
35
|
+
|
|
36
|
+
| Command | Purpose | Global Usage |
|
|
37
|
+
|---------|---------|--------------|
|
|
38
|
+
| `pcf-helper-init` | Initialize new PCF project | `pcf-helper-init [options]` |
|
|
39
|
+
| `pcf-helper-build` | Build PCF controls | `pcf-helper-build [options]` |
|
|
40
|
+
| `pcf-helper-import` | Import controls to solution | `pcf-helper-import [options]` |
|
|
41
|
+
| `pcf-helper-deploy` | Deploy controls (upgrade + build + import) | `pcf-helper-deploy [options]` |
|
|
42
|
+
| `pcf-helper-upgrade` | Upgrade project dependencies | `pcf-helper-upgrade [options]` |
|
|
43
|
+
| `pcf-helper-session` | Manage development sessions | `pcf-helper-session [options]` |
|
|
44
|
+
|
|
45
|
+
## š Command Reference
|
|
46
|
+
|
|
47
|
+
### šļø pcf-helper-init
|
|
48
|
+
|
|
49
|
+
Initialize a new PCF project with proper scaffolding.
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
pcf-helper-init -n <control-name> [options]
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
#### Options
|
|
56
|
+
|
|
57
|
+
| Option | Description | Required | Default |
|
|
58
|
+
|--------|-------------|----------|---------|
|
|
59
|
+
| `-n, --name <name>` | Name of the PCF control | ā
| - |
|
|
60
|
+
| `--publisher-name <name>` | Publisher name for the control | ā | - |
|
|
61
|
+
| `--publisher-prefix <prefix>` | Publisher prefix | ā | - |
|
|
62
|
+
| `-p, --path <path>` | Path to create the project | ā | Current directory |
|
|
63
|
+
| `--run-npm-install` | Run npm install after init | ā | `true` |
|
|
64
|
+
| `-V, --verbose` | Enable verbose logging | ā | `false` |
|
|
65
|
+
| `-v, --version` | Display version | ā | - |
|
|
66
|
+
|
|
67
|
+
#### Example
|
|
68
|
+
|
|
69
|
+
```bash
|
|
70
|
+
# Basic initialization
|
|
71
|
+
pcf-helper-init -n MyCustomControl
|
|
72
|
+
|
|
73
|
+
# Full initialization with custom settings
|
|
74
|
+
pcf-helper-init -n MyCustomControl \
|
|
75
|
+
--publisher-name "Contoso" \
|
|
76
|
+
--publisher-prefix "con" \
|
|
77
|
+
-p ./my-pcf-project \
|
|
78
|
+
--verbose
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
### ā” pcf-helper-build
|
|
82
|
+
|
|
83
|
+
Build and compile your PCF controls.
|
|
84
|
+
|
|
85
|
+
```bash
|
|
86
|
+
pcf-helper-build -p <solution-path> [options]
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
#### Options
|
|
90
|
+
|
|
91
|
+
| Option | Description | Required | Default |
|
|
92
|
+
|--------|-------------|----------|---------|
|
|
93
|
+
| `-p, --path <path>` | Path to solution folder | ā
| - |
|
|
94
|
+
| `-t, --timeout <ms>` | Timeout in milliseconds | ā | 300000 |
|
|
95
|
+
| `-V, --verbose` | Enable verbose logging | ā | `false` |
|
|
96
|
+
| `-v, --version` | Display version | ā | - |
|
|
97
|
+
|
|
98
|
+
#### Example
|
|
99
|
+
|
|
100
|
+
```bash
|
|
101
|
+
# Build with default settings
|
|
102
|
+
pcf-helper-build -p ./MySolution
|
|
103
|
+
|
|
104
|
+
# Build with custom timeout and verbose output
|
|
105
|
+
pcf-helper-build -p ./MySolution --timeout 120000 --verbose
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
### š¦ pcf-helper-import
|
|
109
|
+
|
|
110
|
+
Import PCF controls into your Dataverse solution.
|
|
111
|
+
|
|
112
|
+
```bash
|
|
113
|
+
pcf-helper-import -p <solution-path> [options]
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
#### Options
|
|
117
|
+
|
|
118
|
+
| Option | Description | Required | Default |
|
|
119
|
+
|--------|-------------|----------|---------|
|
|
120
|
+
| `-p, --path <path>` | Path to solution folder | ā
| - |
|
|
121
|
+
| `-e, --environment <environment>` | Target environment | ā | - |
|
|
122
|
+
| `-t, --timeout <ms>` | Timeout in milliseconds | ā | 300000 |
|
|
123
|
+
| `-V, --verbose` | Enable verbose logging | ā | `false` |
|
|
124
|
+
| `-v, --version` | Display version | ā | - |
|
|
125
|
+
|
|
126
|
+
### š pcf-helper-deploy
|
|
127
|
+
|
|
128
|
+
Deploy your PCF controls to the target environment. This command runs upgrade, build, and import in sequence.
|
|
129
|
+
|
|
130
|
+
```bash
|
|
131
|
+
pcf-helper-deploy -p <solution-path> [options]
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
#### Options
|
|
135
|
+
|
|
136
|
+
Same as pcf-helper-import, but runs the full deployment pipeline.
|
|
137
|
+
|
|
138
|
+
### š pcf-helper-upgrade
|
|
139
|
+
|
|
140
|
+
Upgrade project dependencies and framework versions.
|
|
141
|
+
|
|
142
|
+
```bash
|
|
143
|
+
pcf-helper-upgrade -p <solution-path> [options]
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
#### Options
|
|
147
|
+
|
|
148
|
+
| Option | Description | Required | Default |
|
|
149
|
+
|--------|-------------|----------|---------|
|
|
150
|
+
| `-p, --path <path>` | Path to solution folder | ā
| - |
|
|
151
|
+
| `-V, --verbose` | Enable verbose logging | ā | `false` |
|
|
152
|
+
| `-v, --version` | Display version | ā | - |
|
|
153
|
+
|
|
154
|
+
### šÆ pcf-helper-session
|
|
155
|
+
|
|
156
|
+
Manage development sessions with live reloading capabilities.
|
|
157
|
+
|
|
158
|
+
```bash
|
|
159
|
+
pcf-helper-session [options]
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
#### Options
|
|
163
|
+
|
|
164
|
+
| Option | Description | Required | Default |
|
|
165
|
+
|--------|-------------|----------|---------|
|
|
166
|
+
| `-u, --url <url>` | Remote environment URL | ā | - |
|
|
167
|
+
| `-s, --script <path>` | Remote script to intercept | ā | - |
|
|
168
|
+
| `-t, --stylesheet <path>` | Remote stylesheet to intercept | ā | - |
|
|
169
|
+
| `-b, --bundle <path>` | Local bundle path | ā | - |
|
|
170
|
+
| `-c, --css <path>` | Local CSS path | ā | - |
|
|
171
|
+
| `-f, --config <path>` | Config file path | ā | `session.config.json` |
|
|
172
|
+
| `-V, --verbose` | Enable verbose logging | ā | `false` |
|
|
173
|
+
| `-v, --version` | Display version | ā | - |
|
|
174
|
+
|
|
175
|
+
## š§ API Reference
|
|
176
|
+
|
|
177
|
+
You can also use PCF Helper programmatically in your Node.js applications:
|
|
178
|
+
|
|
179
|
+
```typescript
|
|
180
|
+
import * as pcfHelper from '@tywalk/pcf-helper';
|
|
181
|
+
|
|
182
|
+
// Build a PCF control
|
|
183
|
+
const result = pcfHelper.runBuild('./my-solution', true, 120000);
|
|
184
|
+
|
|
185
|
+
// Initialize a new project
|
|
186
|
+
const initResult = pcfHelper.runInit(
|
|
187
|
+
'./new-project',
|
|
188
|
+
'MyControl',
|
|
189
|
+
'My Publisher',
|
|
190
|
+
'mp',
|
|
191
|
+
true,
|
|
192
|
+
true
|
|
193
|
+
);
|
|
194
|
+
|
|
195
|
+
// Set logging level
|
|
196
|
+
pcfHelper.setLogLevel('debug');
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
### Available Functions
|
|
200
|
+
|
|
201
|
+
- `runBuild(path, verbose, timeout?)` - Build PCF controls
|
|
202
|
+
- `runInit(path, name, publisherName, publisherPrefix, runNpmInstall, verbose)` - Initialize new PCF project
|
|
203
|
+
- `runImport(path, environment, verbose, timeout?)` - Import controls to solution
|
|
204
|
+
- `runUpgrade(path, verbose)` - Upgrade project
|
|
205
|
+
- `runSession(...)` - Manage development sessions
|
|
206
|
+
- `setLogLevel(level)` - Set logging verbosity ('debug' | 'info' | 'warn' | 'error')
|
|
207
|
+
|
|
208
|
+
## š Troubleshooting
|
|
209
|
+
|
|
210
|
+
### Common Issues
|
|
211
|
+
|
|
212
|
+
#### Build Failures
|
|
213
|
+
|
|
214
|
+
```bash
|
|
215
|
+
# Enable verbose logging for detailed error information
|
|
216
|
+
pcf-helper-build -p . --verbose
|
|
217
|
+
|
|
218
|
+
# Check if PAC CLI is properly installed
|
|
219
|
+
pac --version
|
|
220
|
+
|
|
221
|
+
# Verify .NET SDK installation
|
|
222
|
+
dotnet --version
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
#### Timeout Errors
|
|
226
|
+
|
|
227
|
+
```bash
|
|
228
|
+
# Increase timeout for large projects
|
|
229
|
+
pcf-helper-build -p . --timeout 600000 # 10 minutes
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
### Getting Help
|
|
233
|
+
|
|
234
|
+
```bash
|
|
235
|
+
# Show help for any command
|
|
236
|
+
pcf-helper-build --help
|
|
237
|
+
pcf-helper-init --help
|
|
238
|
+
|
|
239
|
+
# Show version
|
|
240
|
+
pcf-helper-build --version
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
## š Additional Resources
|
|
244
|
+
|
|
245
|
+
- [Power Platform Component Framework Documentation](https://docs.microsoft.com/en-us/powerapps/developer/component-framework/)
|
|
246
|
+
- [Power Platform CLI Reference](https://docs.microsoft.com/en-us/powerapps/developer/data-platform/powerapps-cli)
|
|
247
|
+
|
|
248
|
+
## š Related Packages
|
|
249
|
+
|
|
250
|
+
- **[@tywalk/pcf-helper-run](../pcf-helper-run/README.md)** - Unified CLI interface
|
|
251
|
+
- **[@tywalk/color-logger](https://www.npmjs.com/package/@tywalk/color-logger)** - Enhanced logging utilities
|
|
252
|
+
|
|
253
|
+
---
|
|
254
|
+
|
|
255
|
+
## š [ā Back to Main Package](../../README.md)
|
|
32
256
|
|
|
33
|
-
|
|
257
|
+
For questions or issues, please visit our [GitHub Issues](https://github.com/tywalk/pcf-helper/issues) page.
|
|
@@ -12,14 +12,18 @@ test('build displays version', (done) => {
|
|
|
12
12
|
console.error(`stderr: ${data}`);
|
|
13
13
|
});
|
|
14
14
|
task.on('close', (code) => {
|
|
15
|
-
|
|
16
|
-
expect(output).toContain(`v${package_json_1.version}`);
|
|
15
|
+
expect(output).toContain(package_json_1.version);
|
|
17
16
|
expect(code).toBe(0);
|
|
18
17
|
done();
|
|
19
18
|
});
|
|
20
19
|
});
|
|
21
20
|
test('build errors if no path is provided', (done) => {
|
|
22
21
|
const task = (0, child_process_1.spawn)('node', ['./dist/bin/build.js', '-p']);
|
|
22
|
+
// Add timeout
|
|
23
|
+
const timeout = setTimeout(() => {
|
|
24
|
+
task.kill();
|
|
25
|
+
done.fail('Test timed out');
|
|
26
|
+
}, 5000);
|
|
23
27
|
let output = '';
|
|
24
28
|
task.stdout.on('data', (data) => {
|
|
25
29
|
output += data.toString();
|
|
@@ -28,8 +32,8 @@ test('build errors if no path is provided', (done) => {
|
|
|
28
32
|
console.error(`stderr: ${data}`);
|
|
29
33
|
});
|
|
30
34
|
task.on('close', (code) => {
|
|
31
|
-
console.log(output);
|
|
32
35
|
expect(code).toBe(1);
|
|
36
|
+
clearTimeout(timeout);
|
|
33
37
|
done();
|
|
34
38
|
});
|
|
35
39
|
});
|
|
@@ -12,14 +12,18 @@ test('deploy displays version', (done) => {
|
|
|
12
12
|
console.error(`stderr: ${data}`);
|
|
13
13
|
});
|
|
14
14
|
task.on('close', (code) => {
|
|
15
|
-
|
|
16
|
-
expect(output).toContain(`v${package_json_1.version}`);
|
|
15
|
+
expect(output).toContain(package_json_1.version);
|
|
17
16
|
expect(code).toBe(0);
|
|
18
17
|
done();
|
|
19
18
|
});
|
|
20
19
|
});
|
|
21
20
|
test('deploy errors if no path is provided', (done) => {
|
|
22
21
|
const task = (0, child_process_1.spawn)('node', ['./dist/bin/deploy.js', '-p']);
|
|
22
|
+
// Add timeout
|
|
23
|
+
const timeout = setTimeout(() => {
|
|
24
|
+
task.kill();
|
|
25
|
+
done.fail('Test timed out');
|
|
26
|
+
}, 5000);
|
|
23
27
|
let output = '';
|
|
24
28
|
task.stdout.on('data', (data) => {
|
|
25
29
|
output += data.toString();
|
|
@@ -28,8 +32,8 @@ test('deploy errors if no path is provided', (done) => {
|
|
|
28
32
|
console.error(`stderr: ${data}`);
|
|
29
33
|
});
|
|
30
34
|
task.on('close', (code) => {
|
|
31
|
-
console.log(output);
|
|
32
35
|
expect(code).toBe(1);
|
|
36
|
+
clearTimeout(timeout);
|
|
33
37
|
done();
|
|
34
38
|
});
|
|
35
39
|
});
|
|
@@ -9,14 +9,18 @@ test('import displays version', (done) => {
|
|
|
9
9
|
output += data.toString();
|
|
10
10
|
});
|
|
11
11
|
task.on('close', (code) => {
|
|
12
|
-
|
|
13
|
-
expect(output).toContain(`v${package_json_1.version}`);
|
|
12
|
+
expect(output).toContain(package_json_1.version);
|
|
14
13
|
expect(code).toBe(0);
|
|
15
14
|
done();
|
|
16
15
|
});
|
|
17
16
|
});
|
|
18
17
|
test('import errors if no path is provided', (done) => {
|
|
19
18
|
const task = (0, child_process_1.spawn)('node', ['./dist/bin/import.js', '-p']);
|
|
19
|
+
// Add timeout
|
|
20
|
+
const timeout = setTimeout(() => {
|
|
21
|
+
task.kill();
|
|
22
|
+
done.fail('Test timed out');
|
|
23
|
+
}, 5000);
|
|
20
24
|
let output = '';
|
|
21
25
|
task.stdout.on('data', (data) => {
|
|
22
26
|
output += data.toString();
|
|
@@ -25,8 +29,8 @@ test('import errors if no path is provided', (done) => {
|
|
|
25
29
|
console.error(`stderr: ${data}`);
|
|
26
30
|
});
|
|
27
31
|
task.on('close', (code) => {
|
|
28
|
-
console.log(output);
|
|
29
32
|
expect(code).toBe(1);
|
|
33
|
+
clearTimeout(timeout);
|
|
30
34
|
done();
|
|
31
35
|
});
|
|
32
36
|
});
|
|
@@ -9,14 +9,18 @@ test('init displays version', (done) => {
|
|
|
9
9
|
output += data.toString();
|
|
10
10
|
});
|
|
11
11
|
task.on('close', (code) => {
|
|
12
|
-
|
|
13
|
-
expect(output).toContain(`v${package_json_1.version}`);
|
|
12
|
+
expect(output).toContain(package_json_1.version);
|
|
14
13
|
expect(code).toBe(0);
|
|
15
14
|
done();
|
|
16
15
|
});
|
|
17
16
|
});
|
|
18
17
|
test('init errors if no path is provided', (done) => {
|
|
19
18
|
const task = (0, child_process_1.spawn)('node', ['./dist/bin/init.js', '-p']);
|
|
19
|
+
// Add timeout
|
|
20
|
+
const timeout = setTimeout(() => {
|
|
21
|
+
task.kill();
|
|
22
|
+
done.fail('Test timed out');
|
|
23
|
+
}, 5000);
|
|
20
24
|
let output = '';
|
|
21
25
|
task.stdout.on('data', (data) => {
|
|
22
26
|
output += data.toString();
|
|
@@ -25,8 +29,8 @@ test('init errors if no path is provided', (done) => {
|
|
|
25
29
|
console.error(`stderr: ${data}`);
|
|
26
30
|
});
|
|
27
31
|
task.on('close', (code) => {
|
|
28
|
-
console.log(output);
|
|
29
32
|
expect(code).toBe(1);
|
|
33
|
+
clearTimeout(timeout);
|
|
30
34
|
done();
|
|
31
35
|
});
|
|
32
36
|
});
|
|
@@ -9,14 +9,18 @@ test('session displays version', (done) => {
|
|
|
9
9
|
output += data.toString();
|
|
10
10
|
});
|
|
11
11
|
task.on('close', (code) => {
|
|
12
|
-
|
|
13
|
-
expect(output).toContain(`v${package_json_1.version}`);
|
|
12
|
+
expect(output).toContain(package_json_1.version);
|
|
14
13
|
expect(code).toBe(0);
|
|
15
14
|
done();
|
|
16
15
|
});
|
|
17
16
|
});
|
|
18
17
|
test('session errors if no args are provided', (done) => {
|
|
19
18
|
const task = (0, child_process_1.spawn)('node', ['./dist/bin/session.js', '-e']);
|
|
19
|
+
// Add timeout
|
|
20
|
+
const timeout = setTimeout(() => {
|
|
21
|
+
task.kill();
|
|
22
|
+
done.fail('Test timed out');
|
|
23
|
+
}, 5000);
|
|
20
24
|
let output = '';
|
|
21
25
|
task.stdout.on('data', (data) => {
|
|
22
26
|
output += data.toString();
|
|
@@ -25,8 +29,8 @@ test('session errors if no args are provided', (done) => {
|
|
|
25
29
|
console.error(`stderr: ${data}`);
|
|
26
30
|
});
|
|
27
31
|
task.on('close', (code) => {
|
|
28
|
-
console.log(output);
|
|
29
32
|
expect(code).toBe(1);
|
|
33
|
+
clearTimeout(timeout);
|
|
30
34
|
done();
|
|
31
35
|
});
|
|
32
36
|
});
|
|
@@ -9,14 +9,18 @@ test('upgrade displays version', (done) => {
|
|
|
9
9
|
output += data.toString();
|
|
10
10
|
});
|
|
11
11
|
task.on('close', (code) => {
|
|
12
|
-
|
|
13
|
-
expect(output).toContain(`v${package_json_1.version}`);
|
|
12
|
+
expect(output).toContain(package_json_1.version);
|
|
14
13
|
expect(code).toBe(0);
|
|
15
14
|
done();
|
|
16
15
|
});
|
|
17
16
|
});
|
|
18
17
|
test('upgrade errors if no path is provided', (done) => {
|
|
19
18
|
const task = (0, child_process_1.spawn)('node', ['./dist/bin/upgrade.js', '-p']);
|
|
19
|
+
// Add timeout
|
|
20
|
+
const timeout = setTimeout(() => {
|
|
21
|
+
task.kill();
|
|
22
|
+
done.fail('Test timed out');
|
|
23
|
+
}, 5000);
|
|
20
24
|
let output = '';
|
|
21
25
|
task.stdout.on('data', (data) => {
|
|
22
26
|
output += data.toString();
|
|
@@ -25,8 +29,8 @@ test('upgrade errors if no path is provided', (done) => {
|
|
|
25
29
|
console.error(`stderr: ${data}`);
|
|
26
30
|
});
|
|
27
31
|
task.on('close', (code) => {
|
|
28
|
-
console.log(output);
|
|
29
32
|
expect(code).toBe(1);
|
|
33
|
+
clearTimeout(timeout);
|
|
30
34
|
done();
|
|
31
35
|
});
|
|
32
36
|
});
|
package/dist/bin/session.js
CHANGED
|
@@ -33,14 +33,11 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
33
33
|
return result;
|
|
34
34
|
};
|
|
35
35
|
})();
|
|
36
|
-
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
37
|
-
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
38
|
-
};
|
|
39
36
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
40
37
|
const task = __importStar(require("../tasks/session-pcf"));
|
|
41
38
|
const package_json_1 = require("../package.json");
|
|
42
|
-
const color_logger_1 = __importDefault(require("@tywalk/color-logger"));
|
|
43
39
|
const commander_1 = require("commander");
|
|
40
|
+
const commandUtil_1 = require("../util/commandUtil");
|
|
44
41
|
const program = new commander_1.Command();
|
|
45
42
|
program
|
|
46
43
|
.name('pcf-helper-session')
|
|
@@ -53,12 +50,15 @@ program
|
|
|
53
50
|
.option('-b, --bundle <path>', 'local bundle path')
|
|
54
51
|
.option('-c, --css <path>', 'local CSS path')
|
|
55
52
|
.option('-f, --config <path>', 'config file path', 'session.config.json')
|
|
56
|
-
.
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
53
|
+
.option('-w, --watch', 'start pcf-scripts watch process')
|
|
54
|
+
.parse()
|
|
55
|
+
.action((options) => {
|
|
56
|
+
var _a, _b, _c, _d, _e, _f, _g;
|
|
57
|
+
const { logger, tick } = (0, commandUtil_1.setupExecutionContext)(options);
|
|
58
|
+
logger.log('PCF Helper version', package_json_1.version);
|
|
59
|
+
const config = task.loadConfig(options.config);
|
|
60
|
+
// Priority: CLI args > config file > environment variables
|
|
61
|
+
const startWatch = (_b = (_a = options.watch) !== null && _a !== void 0 ? _a : config.startWatch) !== null && _b !== void 0 ? _b : false;
|
|
62
|
+
task.runSession((_c = options.url) !== null && _c !== void 0 ? _c : config.remoteEnvironmentUrl, (_d = options.script) !== null && _d !== void 0 ? _d : config.remoteScriptToIntercept, (_e = options.stylesheet) !== null && _e !== void 0 ? _e : config.remoteStylesheetToIntercept, (_f = options.bundle) !== null && _f !== void 0 ? _f : config.localBundlePath, (_g = options.css) !== null && _g !== void 0 ? _g : config.localCssPath, startWatch);
|
|
63
|
+
(0, commandUtil_1.handleResults)('session', logger, tick, 0);
|
|
64
|
+
});
|
package/dist/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tywalk/pcf-helper",
|
|
3
|
-
"version": "1.5.
|
|
3
|
+
"version": "1.5.8",
|
|
4
4
|
"description": "Command line helper for building and publishing PCF controls to Dataverse.",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "./types/",
|
|
@@ -11,12 +11,17 @@
|
|
|
11
11
|
"repository": {
|
|
12
12
|
"url": "git+https://github.com/tywalk/pcf-helper.git"
|
|
13
13
|
},
|
|
14
|
+
"publishConfig": {
|
|
15
|
+
"access": "public",
|
|
16
|
+
"provenance": true
|
|
17
|
+
},
|
|
14
18
|
"scripts": {
|
|
15
19
|
"test": "jest",
|
|
16
20
|
"build": "tsc",
|
|
17
21
|
"upgrade": "npm version patch --no-git-tag-version",
|
|
18
22
|
"ready": "npm run upgrade && npm run build",
|
|
19
|
-
"update": "npm run ready && npm publish --access public"
|
|
23
|
+
"update": "npm run ready && npm publish --access public",
|
|
24
|
+
"temp": "echo $version"
|
|
20
25
|
},
|
|
21
26
|
"keywords": [
|
|
22
27
|
"pcf"
|
|
@@ -31,9 +36,13 @@
|
|
|
31
36
|
"pcf-helper-session": "dist/bin/session.js"
|
|
32
37
|
},
|
|
33
38
|
"devDependencies": {
|
|
39
|
+
"@semantic-release/git": "^10.0.1",
|
|
40
|
+
"@semantic-release/github": "^12.0.6",
|
|
41
|
+
"@semantic-release/npm": "^13.1.5",
|
|
34
42
|
"@types/jest": "^29.5.14",
|
|
35
43
|
"@types/node": "^22.13.11",
|
|
36
44
|
"jest": "^29.7.0",
|
|
45
|
+
"semantic-release": "^25.0.3",
|
|
37
46
|
"ts-jest": "^29.2.6",
|
|
38
47
|
"typescript": "^5.8.2"
|
|
39
48
|
},
|
|
@@ -18,6 +18,7 @@ const color_logger_1 = __importDefault(require("@tywalk/color-logger"));
|
|
|
18
18
|
const path_1 = __importDefault(require("path"));
|
|
19
19
|
const fs_1 = __importDefault(require("fs"));
|
|
20
20
|
const playwright_1 = require("playwright");
|
|
21
|
+
const child_process_1 = require("child_process");
|
|
21
22
|
/**
|
|
22
23
|
* Loads configuration for the session task, supporting a combination of config file, environment variables, and CLI arguments.
|
|
23
24
|
* The priority order is: CLI arguments > environment variables > config file > defaults.
|
|
@@ -76,6 +77,8 @@ function loadConfig(config) {
|
|
|
76
77
|
remoteStylesheetToIntercept: remoteStylesheetToIntercept,
|
|
77
78
|
localCssPath: (_a = process.env.LOCAL_CSS_PATH) !== null && _a !== void 0 ? _a : fileConfig.localCssPath,
|
|
78
79
|
localBundlePath: (_b = process.env.LOCAL_BUNDLE_PATH) !== null && _b !== void 0 ? _b : fileConfig.localBundlePath,
|
|
80
|
+
startWatch: process.env.START_WATCH === 'true' ||
|
|
81
|
+
fileConfig.startWatch || false,
|
|
79
82
|
};
|
|
80
83
|
}
|
|
81
84
|
/**
|
|
@@ -87,183 +90,227 @@ function loadConfig(config) {
|
|
|
87
90
|
* @param remoteStylesheetToIntercept The full URL of the remote stylesheet to intercept (e.g., https://app.your-remote-environment.com/static/css/remote-control-styles.css).
|
|
88
91
|
* @param localBundlePath The local file path to the JavaScript bundle that should be served when the remote script URL is requested.
|
|
89
92
|
* @param localCssPath The local file path to the CSS file that should be served when the remote stylesheet URL is requested.
|
|
93
|
+
* @param startWatch Optional flag to start the session in watch mode. If true, the process will kick off "pcf-scripts start watch" in parallel to automatically rebuild the bundle on changes.
|
|
94
|
+
* @returns A promise that resolves when the session is set up and running. The session will continue to run until the process is exited, at which point it will clean up and save state.
|
|
90
95
|
*/
|
|
91
|
-
function runSession(remoteEnvironmentUrl, remoteScriptToIntercept, remoteStylesheetToIntercept, localBundlePath, localCssPath) {
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
color_logger_1.default.error('ā Remote script URL to intercept is required. Please provide it via CLI, config file, or environment variable.');
|
|
98
|
-
process.exit(1);
|
|
99
|
-
}
|
|
100
|
-
if (!remoteStylesheetToIntercept) {
|
|
101
|
-
color_logger_1.default.error('ā Remote stylesheet URL to intercept is required. Please provide it via CLI, config file, or environment variable.');
|
|
102
|
-
process.exit(1);
|
|
103
|
-
}
|
|
104
|
-
if (!localBundlePath) {
|
|
105
|
-
color_logger_1.default.error('ā Local bundle path is required. Please provide it via CLI, config file, or environment variable.');
|
|
106
|
-
process.exit(1);
|
|
107
|
-
}
|
|
108
|
-
if (!localCssPath) {
|
|
109
|
-
color_logger_1.default.error('ā Local CSS path is required. Please provide it via CLI, config file, or environment variable.');
|
|
110
|
-
process.exit(1);
|
|
111
|
-
}
|
|
112
|
-
const REMOTE_ENVIRONMENT_URL = remoteEnvironmentUrl;
|
|
113
|
-
const REMOTE_SCRIPT_TO_INTERCEPT = remoteScriptToIntercept;
|
|
114
|
-
const REMOTE_STYLESHEET_TO_INTERCEPT = remoteStylesheetToIntercept;
|
|
115
|
-
const LOCAL_BUNDLE_PATH = path_1.default.resolve(localBundlePath);
|
|
116
|
-
const LOCAL_CSS_PATH = path_1.default.resolve(localCssPath);
|
|
117
|
-
// Debug logging for URL construction
|
|
118
|
-
color_logger_1.default.debug('š Debug - Final URLs:');
|
|
119
|
-
color_logger_1.default.debug(` Remote Environment: ${REMOTE_ENVIRONMENT_URL}`);
|
|
120
|
-
color_logger_1.default.debug(` Script to intercept: ${REMOTE_SCRIPT_TO_INTERCEPT}`);
|
|
121
|
-
color_logger_1.default.debug(` CSS to intercept: ${REMOTE_STYLESHEET_TO_INTERCEPT}`);
|
|
122
|
-
color_logger_1.default.debug(` Local bundle path: ${LOCAL_BUNDLE_PATH}`);
|
|
123
|
-
color_logger_1.default.debug(` Local CSS path: ${LOCAL_CSS_PATH}`);
|
|
124
|
-
color_logger_1.default.debug('');
|
|
125
|
-
// Path to store your session cookies
|
|
126
|
-
const AUTH_DIR = path_1.default.join(process.cwd(), '.auth');
|
|
127
|
-
const STATE_FILE = path_1.default.join(AUTH_DIR, 'state.json');
|
|
128
|
-
(() => __awaiter(this, void 0, void 0, function* () {
|
|
129
|
-
color_logger_1.default.log('š Starting ephemeral browser session...');
|
|
130
|
-
// 1. Prepare context options (load session if it exists)
|
|
131
|
-
let contextOptions = {};
|
|
132
|
-
if (fs_1.default.existsSync(STATE_FILE)) {
|
|
133
|
-
color_logger_1.default.log('š Loading previous login session...');
|
|
134
|
-
contextOptions.storageState = STATE_FILE;
|
|
96
|
+
function runSession(remoteEnvironmentUrl, remoteScriptToIntercept, remoteStylesheetToIntercept, localBundlePath, localCssPath, startWatch) {
|
|
97
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
98
|
+
var _a, _b;
|
|
99
|
+
if (!remoteEnvironmentUrl) {
|
|
100
|
+
color_logger_1.default.error('ā Remote environment URL is required. Please provide it via CLI, config file, or environment variable.');
|
|
101
|
+
process.exit(1);
|
|
135
102
|
}
|
|
136
|
-
|
|
137
|
-
color_logger_1.default.
|
|
103
|
+
if (!remoteScriptToIntercept) {
|
|
104
|
+
color_logger_1.default.error('ā Remote script URL to intercept is required. Please provide it via CLI, config file, or environment variable.');
|
|
105
|
+
process.exit(1);
|
|
138
106
|
}
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
107
|
+
if (!remoteStylesheetToIntercept) {
|
|
108
|
+
color_logger_1.default.error('ā Remote stylesheet URL to intercept is required. Please provide it via CLI, config file, or environment variable.');
|
|
109
|
+
process.exit(1);
|
|
110
|
+
}
|
|
111
|
+
if (!localBundlePath) {
|
|
112
|
+
color_logger_1.default.error('ā Local bundle path is required. Please provide it via CLI, config file, or environment variable.');
|
|
113
|
+
process.exit(1);
|
|
114
|
+
}
|
|
115
|
+
if (!localCssPath) {
|
|
116
|
+
color_logger_1.default.error('ā Local CSS path is required. Please provide it via CLI, config file, or environment variable.');
|
|
117
|
+
process.exit(1);
|
|
118
|
+
}
|
|
119
|
+
const REMOTE_ENVIRONMENT_URL = remoteEnvironmentUrl;
|
|
120
|
+
const REMOTE_SCRIPT_TO_INTERCEPT = remoteScriptToIntercept;
|
|
121
|
+
const REMOTE_STYLESHEET_TO_INTERCEPT = remoteStylesheetToIntercept;
|
|
122
|
+
const LOCAL_BUNDLE_PATH = path_1.default.resolve(localBundlePath);
|
|
123
|
+
const LOCAL_CSS_PATH = path_1.default.resolve(localCssPath);
|
|
124
|
+
// Debug logging for URL construction
|
|
125
|
+
color_logger_1.default.debug('š Debug - Final URLs:');
|
|
126
|
+
color_logger_1.default.debug(` Remote Environment: ${REMOTE_ENVIRONMENT_URL}`);
|
|
127
|
+
color_logger_1.default.debug(` Script to intercept: ${REMOTE_SCRIPT_TO_INTERCEPT}`);
|
|
128
|
+
color_logger_1.default.debug(` CSS to intercept: ${REMOTE_STYLESHEET_TO_INTERCEPT}`);
|
|
129
|
+
color_logger_1.default.debug(` Local bundle path: ${LOCAL_BUNDLE_PATH}`);
|
|
130
|
+
color_logger_1.default.debug(` Local CSS path: ${LOCAL_CSS_PATH}`);
|
|
131
|
+
color_logger_1.default.debug('');
|
|
132
|
+
// Path to store your session cookies
|
|
133
|
+
const AUTH_DIR = path_1.default.join(process.cwd(), '.auth');
|
|
134
|
+
const STATE_FILE = path_1.default.join(AUTH_DIR, 'state.json');
|
|
135
|
+
// Start watch process if requested
|
|
136
|
+
let watchProcess;
|
|
137
|
+
if (startWatch) {
|
|
138
|
+
color_logger_1.default.log('š§ Starting pcf-scripts watch process...');
|
|
139
|
+
watchProcess = (0, child_process_1.spawn)('pcf-scripts', ['start', 'watch'], {
|
|
140
|
+
cwd: process.cwd(),
|
|
141
|
+
stdio: ['inherit', 'pipe', 'pipe'],
|
|
142
|
+
shell: true
|
|
143
|
+
});
|
|
144
|
+
(_a = watchProcess.stdout) === null || _a === void 0 ? void 0 : _a.on('data', (data) => {
|
|
145
|
+
color_logger_1.default.log(`š¦ [PCF Watch] ${data.toString().trim()}`);
|
|
146
|
+
});
|
|
147
|
+
(_b = watchProcess.stderr) === null || _b === void 0 ? void 0 : _b.on('data', (data) => {
|
|
148
|
+
color_logger_1.default.warn(`ā ļø [PCF Watch] ${data.toString().trim()}`);
|
|
149
|
+
});
|
|
150
|
+
watchProcess.on('exit', (code) => {
|
|
151
|
+
if (code !== null && code !== 0) {
|
|
152
|
+
color_logger_1.default.error(`ā PCF watch process exited with code ${code}`);
|
|
152
153
|
}
|
|
153
154
|
else {
|
|
154
|
-
|
|
155
|
-
yield context.storageState({ path: STATE_FILE });
|
|
156
|
-
color_logger_1.default.log('š Tearing down rules and session.');
|
|
157
|
-
yield browser.close();
|
|
155
|
+
color_logger_1.default.log('ā
PCF watch process ended');
|
|
158
156
|
}
|
|
159
|
-
}
|
|
160
|
-
catch (error) {
|
|
161
|
-
color_logger_1.default.debug('Error during cleanup:', error);
|
|
162
|
-
}
|
|
163
|
-
});
|
|
164
|
-
// Handle process exit signals
|
|
165
|
-
process.on('SIGINT', () => __awaiter(this, void 0, void 0, function* () {
|
|
166
|
-
yield cleanup('SIGINT');
|
|
167
|
-
process.exit(0);
|
|
168
|
-
}));
|
|
169
|
-
process.on('SIGTERM', () => __awaiter(this, void 0, void 0, function* () {
|
|
170
|
-
yield cleanup('SIGTERM');
|
|
171
|
-
process.exit(0);
|
|
172
|
-
}));
|
|
173
|
-
process.on('beforeExit', () => __awaiter(this, void 0, void 0, function* () {
|
|
174
|
-
yield cleanup('beforeExit');
|
|
175
|
-
}));
|
|
176
|
-
// Handle uncaught exceptions
|
|
177
|
-
process.on('uncaughtException', (error) => __awaiter(this, void 0, void 0, function* () {
|
|
178
|
-
color_logger_1.default.error('Uncaught exception:', error);
|
|
179
|
-
yield cleanup('uncaughtException');
|
|
180
|
-
process.exit(1);
|
|
181
|
-
}));
|
|
182
|
-
process.on('unhandledRejection', (reason) => __awaiter(this, void 0, void 0, function* () {
|
|
183
|
-
color_logger_1.default.error('Unhandled promise rejection:', reason);
|
|
184
|
-
yield cleanup('unhandledRejection');
|
|
185
|
-
process.exit(1);
|
|
186
|
-
}));
|
|
187
|
-
// Handle browser disconnect
|
|
188
|
-
browser.on('disconnected', () => __awaiter(this, void 0, void 0, function* () {
|
|
189
|
-
color_logger_1.default.log('Browser disconnected');
|
|
190
|
-
yield cleanup('browser disconnected');
|
|
191
|
-
}));
|
|
192
|
-
// Handle context close (when all pages in context are closed)
|
|
193
|
-
context.on('close', () => __awaiter(this, void 0, void 0, function* () {
|
|
194
|
-
color_logger_1.default.log('Browser context closed');
|
|
195
|
-
yield cleanup('context closed');
|
|
196
|
-
process.exit(0);
|
|
197
|
-
}));
|
|
198
|
-
// 3. Programmatically apply your network interception rule with pattern matching
|
|
199
|
-
// Handle dynamic version segments in CRM URLs like /version?/webresources/...
|
|
200
|
-
const scriptPattern = REMOTE_SCRIPT_TO_INTERCEPT.replace(/^https?:\/\/[^\/]+/, '');
|
|
201
|
-
const stylesheetPattern = REMOTE_STYLESHEET_TO_INTERCEPT.replace(/^https?:\/\/[^\/]+/, '');
|
|
202
|
-
color_logger_1.default.debug(`š” Setting up interception patterns:`);
|
|
203
|
-
color_logger_1.default.debug(` Script pattern: **${scriptPattern}`);
|
|
204
|
-
color_logger_1.default.debug(` CSS pattern: **${stylesheetPattern}`);
|
|
205
|
-
yield context.route(route => {
|
|
206
|
-
if (!route.href) {
|
|
207
|
-
return false;
|
|
208
|
-
}
|
|
209
|
-
// Match script URLs that end with the same path structure
|
|
210
|
-
return route.href.includes(scriptPattern);
|
|
211
|
-
}, (route) => __awaiter(this, void 0, void 0, function* () {
|
|
212
|
-
color_logger_1.default.log(`ā
Intercepted script request: ${route.request().url()}`);
|
|
213
|
-
color_logger_1.default.log(` Serving local file: ${LOCAL_BUNDLE_PATH}`);
|
|
214
|
-
route.fulfill({
|
|
215
|
-
status: 200,
|
|
216
|
-
contentType: 'application/javascript',
|
|
217
|
-
body: fs_1.default.readFileSync(LOCAL_BUNDLE_PATH)
|
|
218
157
|
});
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
if (!route.href) {
|
|
222
|
-
return false;
|
|
223
|
-
}
|
|
224
|
-
// Match CSS URLs that end with the same path structure
|
|
225
|
-
return route.href.includes(stylesheetPattern);
|
|
226
|
-
}, (route) => __awaiter(this, void 0, void 0, function* () {
|
|
227
|
-
color_logger_1.default.log(`ā
Intercepted CSS request: ${route.request().url()}`);
|
|
228
|
-
color_logger_1.default.log(` Serving local file: ${LOCAL_CSS_PATH}`);
|
|
229
|
-
route.fulfill({
|
|
230
|
-
status: 200,
|
|
231
|
-
contentType: 'text/css',
|
|
232
|
-
body: fs_1.default.readFileSync(LOCAL_CSS_PATH)
|
|
158
|
+
watchProcess.on('error', (error) => {
|
|
159
|
+
color_logger_1.default.error('ā Failed to start PCF watch process:', error);
|
|
233
160
|
});
|
|
234
|
-
}
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
// This was the last page, trigger full cleanup
|
|
243
|
-
yield cleanup('last page closed');
|
|
244
|
-
process.exit(0);
|
|
161
|
+
}
|
|
162
|
+
yield (() => __awaiter(this, void 0, void 0, function* () {
|
|
163
|
+
color_logger_1.default.log('š Starting ephemeral browser session...');
|
|
164
|
+
// 1. Prepare context options (load session if it exists)
|
|
165
|
+
let contextOptions = {};
|
|
166
|
+
if (fs_1.default.existsSync(STATE_FILE)) {
|
|
167
|
+
color_logger_1.default.log('š Loading previous login session...');
|
|
168
|
+
contextOptions.storageState = STATE_FILE;
|
|
245
169
|
}
|
|
246
170
|
else {
|
|
247
|
-
color_logger_1.default.
|
|
171
|
+
color_logger_1.default.log('ā ļø No previous session found. You may need to log in.');
|
|
248
172
|
}
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
173
|
+
// 2. Launch browser and apply context
|
|
174
|
+
const browser = yield playwright_1.chromium.launch({ headless: false, args: ['--auto-open-devtools-for-tabs'] });
|
|
175
|
+
const context = yield browser.newContext(Object.assign(Object.assign({}, contextOptions), { viewport: null, serviceWorkers: 'block' }));
|
|
176
|
+
// Shared cleanup function to save state and close browser
|
|
177
|
+
const cleanup = (...args_1) => __awaiter(this, [...args_1], void 0, function* (reason = 'unknown') {
|
|
178
|
+
try {
|
|
179
|
+
color_logger_1.default.log(`š¾ Saving session state (${reason})...`);
|
|
180
|
+
// Kill the watch process if it's running
|
|
181
|
+
if (watchProcess && !watchProcess.killed) {
|
|
182
|
+
color_logger_1.default.log('š Terminating PCF watch process...');
|
|
183
|
+
watchProcess.kill('SIGTERM');
|
|
184
|
+
// Give it a chance to exit gracefully, then force kill if needed
|
|
185
|
+
setTimeout(() => {
|
|
186
|
+
if (watchProcess && !watchProcess.killed) {
|
|
187
|
+
color_logger_1.default.warn('ā ļø Force killing PCF watch process...');
|
|
188
|
+
watchProcess.kill('SIGKILL');
|
|
189
|
+
}
|
|
190
|
+
}, 2000);
|
|
261
191
|
}
|
|
262
|
-
|
|
263
|
-
|
|
192
|
+
// Ensure the .auth directory exists before saving
|
|
193
|
+
if (!fs_1.default.existsSync(AUTH_DIR)) {
|
|
194
|
+
fs_1.default.mkdirSync(AUTH_DIR, { recursive: true });
|
|
264
195
|
}
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
196
|
+
if (!browser.isConnected()) {
|
|
197
|
+
color_logger_1.default.log('Browser already disconnected.');
|
|
198
|
+
}
|
|
199
|
+
else {
|
|
200
|
+
// Save the cookies and local storage to the JSON file
|
|
201
|
+
yield context.storageState({ path: STATE_FILE });
|
|
202
|
+
color_logger_1.default.log('š Tearing down rules and session.');
|
|
203
|
+
yield browser.close();
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
catch (error) {
|
|
207
|
+
color_logger_1.default.debug('Error during cleanup:', error);
|
|
208
|
+
}
|
|
209
|
+
});
|
|
210
|
+
// Handle process exit signals
|
|
211
|
+
process.on('SIGINT', () => __awaiter(this, void 0, void 0, function* () {
|
|
212
|
+
yield cleanup('SIGINT');
|
|
213
|
+
process.exit(0);
|
|
214
|
+
}));
|
|
215
|
+
process.on('SIGTERM', () => __awaiter(this, void 0, void 0, function* () {
|
|
216
|
+
yield cleanup('SIGTERM');
|
|
217
|
+
process.exit(0);
|
|
218
|
+
}));
|
|
219
|
+
process.on('beforeExit', () => __awaiter(this, void 0, void 0, function* () {
|
|
220
|
+
yield cleanup('beforeExit');
|
|
221
|
+
}));
|
|
222
|
+
// Handle uncaught exceptions
|
|
223
|
+
process.on('uncaughtException', (error) => __awaiter(this, void 0, void 0, function* () {
|
|
224
|
+
color_logger_1.default.error('Uncaught exception:', error);
|
|
225
|
+
yield cleanup('uncaughtException');
|
|
226
|
+
process.exit(1);
|
|
227
|
+
}));
|
|
228
|
+
process.on('unhandledRejection', (reason) => __awaiter(this, void 0, void 0, function* () {
|
|
229
|
+
color_logger_1.default.error('Unhandled promise rejection:', reason);
|
|
230
|
+
yield cleanup('unhandledRejection');
|
|
231
|
+
process.exit(1);
|
|
232
|
+
}));
|
|
233
|
+
// Handle browser disconnect
|
|
234
|
+
browser.on('disconnected', () => __awaiter(this, void 0, void 0, function* () {
|
|
235
|
+
color_logger_1.default.log('Browser disconnected');
|
|
236
|
+
yield cleanup('browser disconnected');
|
|
237
|
+
}));
|
|
238
|
+
// Handle context close (when all pages in context are closed)
|
|
239
|
+
context.on('close', () => __awaiter(this, void 0, void 0, function* () {
|
|
240
|
+
color_logger_1.default.log('Browser context closed');
|
|
241
|
+
yield cleanup('context closed');
|
|
242
|
+
process.exit(0);
|
|
243
|
+
}));
|
|
244
|
+
// 3. Programmatically apply your network interception rule with pattern matching
|
|
245
|
+
// Handle dynamic version segments in CRM URLs like /version?/webresources/...
|
|
246
|
+
const scriptPattern = REMOTE_SCRIPT_TO_INTERCEPT.replace(/^https?:\/\/[^\/]+/, '');
|
|
247
|
+
const stylesheetPattern = REMOTE_STYLESHEET_TO_INTERCEPT.replace(/^https?:\/\/[^\/]+/, '');
|
|
248
|
+
color_logger_1.default.debug(`š” Setting up interception patterns:`);
|
|
249
|
+
color_logger_1.default.debug(` Script pattern: **${scriptPattern}`);
|
|
250
|
+
color_logger_1.default.debug(` CSS pattern: **${stylesheetPattern}`);
|
|
251
|
+
yield context.route(route => {
|
|
252
|
+
if (!route.href) {
|
|
253
|
+
return false;
|
|
254
|
+
}
|
|
255
|
+
// Match script URLs that end with the same path structure
|
|
256
|
+
return route.href.includes(scriptPattern);
|
|
257
|
+
}, (route) => __awaiter(this, void 0, void 0, function* () {
|
|
258
|
+
color_logger_1.default.log(`ā
Intercepted script request: ${route.request().url()}`);
|
|
259
|
+
color_logger_1.default.log(` Serving local file: ${LOCAL_BUNDLE_PATH}`);
|
|
260
|
+
route.fulfill({
|
|
261
|
+
status: 200,
|
|
262
|
+
contentType: 'application/javascript',
|
|
263
|
+
body: fs_1.default.readFileSync(LOCAL_BUNDLE_PATH)
|
|
264
|
+
});
|
|
265
|
+
}));
|
|
266
|
+
yield context.route(route => {
|
|
267
|
+
if (!route.href) {
|
|
268
|
+
return false;
|
|
269
|
+
}
|
|
270
|
+
// Match CSS URLs that end with the same path structure
|
|
271
|
+
return route.href.includes(stylesheetPattern);
|
|
272
|
+
}, (route) => __awaiter(this, void 0, void 0, function* () {
|
|
273
|
+
color_logger_1.default.log(`ā
Intercepted CSS request: ${route.request().url()}`);
|
|
274
|
+
color_logger_1.default.log(` Serving local file: ${LOCAL_CSS_PATH}`);
|
|
275
|
+
route.fulfill({
|
|
276
|
+
status: 200,
|
|
277
|
+
contentType: 'text/css',
|
|
278
|
+
body: fs_1.default.readFileSync(LOCAL_CSS_PATH)
|
|
279
|
+
});
|
|
280
|
+
}));
|
|
281
|
+
// 4. Open a new tab and navigate to your remote environment
|
|
282
|
+
const page = yield context.newPage();
|
|
283
|
+
yield page.goto(REMOTE_ENVIRONMENT_URL);
|
|
284
|
+
// 5. Clean up and save state when the page is closed (but others may still be open)
|
|
285
|
+
page.on('close', () => __awaiter(this, void 0, void 0, function* () {
|
|
286
|
+
const pages = context.pages();
|
|
287
|
+
if (pages.length <= 1) {
|
|
288
|
+
// This was the last page, trigger full cleanup
|
|
289
|
+
yield cleanup('last page closed');
|
|
290
|
+
process.exit(0);
|
|
291
|
+
}
|
|
292
|
+
else {
|
|
293
|
+
color_logger_1.default.debug(`Page closed, but ${pages.length - 1} pages still open. Keeping session alive.`);
|
|
294
|
+
}
|
|
295
|
+
}));
|
|
296
|
+
// 6. Watch the local bundle for changes and auto-reload
|
|
297
|
+
let reloadTimeout;
|
|
298
|
+
fs_1.default.watch(LOCAL_BUNDLE_PATH, (eventType) => {
|
|
299
|
+
if (eventType === 'change') {
|
|
300
|
+
// Clear the previous timer if the file changes again quickly
|
|
301
|
+
clearTimeout(reloadTimeout);
|
|
302
|
+
// Wait 300ms for the bundler to finish writing the file before reloading
|
|
303
|
+
reloadTimeout = setTimeout(() => __awaiter(this, void 0, void 0, function* () {
|
|
304
|
+
color_logger_1.default.log(`\nš Local bundle updated! Reloading the page...`);
|
|
305
|
+
try {
|
|
306
|
+
yield page.reload();
|
|
307
|
+
}
|
|
308
|
+
catch (err) {
|
|
309
|
+
color_logger_1.default.error('ā ļø Could not reload page (browser might be closed).', err);
|
|
310
|
+
}
|
|
311
|
+
}), 300);
|
|
312
|
+
}
|
|
313
|
+
});
|
|
314
|
+
}))();
|
|
315
|
+
});
|
|
269
316
|
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.handleResults = exports.setupExecutionContext = void 0;
|
|
4
|
+
const color_logger_1 = require("@tywalk/color-logger");
|
|
5
|
+
const performanceUtil_1 = require("./performanceUtil");
|
|
6
|
+
const setupExecutionContext = (options) => {
|
|
7
|
+
const logger = new color_logger_1.Logger('log');
|
|
8
|
+
if (options.verbose) {
|
|
9
|
+
logger.setDebug(true);
|
|
10
|
+
logger.setLevel('debug');
|
|
11
|
+
}
|
|
12
|
+
else {
|
|
13
|
+
logger.setLevel('info');
|
|
14
|
+
}
|
|
15
|
+
return { logger, tick: performance.now() };
|
|
16
|
+
};
|
|
17
|
+
exports.setupExecutionContext = setupExecutionContext;
|
|
18
|
+
// Helper function to execute tasks and handle results
|
|
19
|
+
const handleResults = (taskName, logger, tick, result) => {
|
|
20
|
+
if (taskName !== 'session') {
|
|
21
|
+
if (result === 0) {
|
|
22
|
+
logger.log(`[PCF Helper] ${taskName} completed successfully!`);
|
|
23
|
+
}
|
|
24
|
+
else {
|
|
25
|
+
logger.log(`[PCF Helper] ${taskName} completed with errors.`);
|
|
26
|
+
}
|
|
27
|
+
const tock = performance.now();
|
|
28
|
+
logger.log((0, performanceUtil_1.formatMsToSec)(`[PCF Helper] ${(0, performanceUtil_1.formatTime)(new Date())} ${taskName} finished in %is.`, tock - tick));
|
|
29
|
+
}
|
|
30
|
+
if (taskName !== 'session' || result === 1) {
|
|
31
|
+
process.exit(result);
|
|
32
|
+
}
|
|
33
|
+
};
|
|
34
|
+
exports.handleResults = handleResults;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tywalk/pcf-helper",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.6.0",
|
|
4
4
|
"description": "Command line helper for building and publishing PCF controls to Dataverse.",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "./types/",
|
|
@@ -11,12 +11,17 @@
|
|
|
11
11
|
"repository": {
|
|
12
12
|
"url": "git+https://github.com/tywalk/pcf-helper.git"
|
|
13
13
|
},
|
|
14
|
+
"publishConfig": {
|
|
15
|
+
"access": "public",
|
|
16
|
+
"provenance": true
|
|
17
|
+
},
|
|
14
18
|
"scripts": {
|
|
15
19
|
"test": "jest",
|
|
16
20
|
"build": "tsc",
|
|
17
21
|
"upgrade": "npm version patch --no-git-tag-version",
|
|
18
22
|
"ready": "npm run upgrade && npm run build",
|
|
19
|
-
"update": "npm run ready && npm publish --access public"
|
|
23
|
+
"update": "npm run ready && npm publish --access public",
|
|
24
|
+
"temp": "echo $version"
|
|
20
25
|
},
|
|
21
26
|
"keywords": [
|
|
22
27
|
"pcf"
|
|
@@ -31,9 +36,13 @@
|
|
|
31
36
|
"pcf-helper-session": "dist/bin/session.js"
|
|
32
37
|
},
|
|
33
38
|
"devDependencies": {
|
|
39
|
+
"@semantic-release/git": "^10.0.1",
|
|
40
|
+
"@semantic-release/github": "^12.0.6",
|
|
41
|
+
"@semantic-release/npm": "^13.1.5",
|
|
34
42
|
"@types/jest": "^29.5.14",
|
|
35
43
|
"@types/node": "^22.13.11",
|
|
36
44
|
"jest": "^29.7.0",
|
|
45
|
+
"semantic-release": "^25.0.3",
|
|
37
46
|
"ts-jest": "^29.2.6",
|
|
38
47
|
"typescript": "^5.8.2"
|
|
39
48
|
},
|
|
@@ -1,3 +1,13 @@
|
|
|
1
|
+
export type SessionOptions = {
|
|
2
|
+
verbose?: boolean;
|
|
3
|
+
url?: string;
|
|
4
|
+
script?: string;
|
|
5
|
+
stylesheet?: string;
|
|
6
|
+
bundle?: string;
|
|
7
|
+
css?: string;
|
|
8
|
+
config?: string;
|
|
9
|
+
watch?: boolean;
|
|
10
|
+
};
|
|
1
11
|
/**
|
|
2
12
|
* Loads configuration for the session task, supporting a combination of config file, environment variables, and CLI arguments.
|
|
3
13
|
* The priority order is: CLI arguments > environment variables > config file > defaults.
|
|
@@ -11,12 +21,14 @@ declare function loadConfig(config?: string): {
|
|
|
11
21
|
remoteStylesheetToIntercept?: undefined;
|
|
12
22
|
localCssPath?: undefined;
|
|
13
23
|
localBundlePath?: undefined;
|
|
24
|
+
startWatch?: undefined;
|
|
14
25
|
} | {
|
|
15
26
|
remoteEnvironmentUrl: any;
|
|
16
27
|
remoteScriptToIntercept: any;
|
|
17
28
|
remoteStylesheetToIntercept: any;
|
|
18
29
|
localCssPath: any;
|
|
19
30
|
localBundlePath: any;
|
|
31
|
+
startWatch: any;
|
|
20
32
|
};
|
|
21
33
|
/**
|
|
22
34
|
* Runs an ephemeral browser session that intercepts requests to the specified remote script and stylesheet URLs, serving local files instead.
|
|
@@ -27,6 +39,8 @@ declare function loadConfig(config?: string): {
|
|
|
27
39
|
* @param remoteStylesheetToIntercept The full URL of the remote stylesheet to intercept (e.g., https://app.your-remote-environment.com/static/css/remote-control-styles.css).
|
|
28
40
|
* @param localBundlePath The local file path to the JavaScript bundle that should be served when the remote script URL is requested.
|
|
29
41
|
* @param localCssPath The local file path to the CSS file that should be served when the remote stylesheet URL is requested.
|
|
42
|
+
* @param startWatch Optional flag to start the session in watch mode. If true, the process will kick off "pcf-scripts start watch" in parallel to automatically rebuild the bundle on changes.
|
|
43
|
+
* @returns A promise that resolves when the session is set up and running. The session will continue to run until the process is exited, at which point it will clean up and save state.
|
|
30
44
|
*/
|
|
31
|
-
declare function runSession(remoteEnvironmentUrl: string, remoteScriptToIntercept: string, remoteStylesheetToIntercept: string, localBundlePath: string, localCssPath: string): void
|
|
45
|
+
declare function runSession(remoteEnvironmentUrl: string, remoteScriptToIntercept: string, remoteStylesheetToIntercept: string, localBundlePath: string, localCssPath: string, startWatch?: boolean): Promise<void>;
|
|
32
46
|
export { runSession, loadConfig };
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { Logger } from '@tywalk/color-logger';
|
|
2
|
+
export declare const setupExecutionContext: (options: CommonOptions) => {
|
|
3
|
+
logger: Logger;
|
|
4
|
+
tick: number;
|
|
5
|
+
};
|
|
6
|
+
export declare const handleResults: (taskName: string, logger: Logger, tick: number, result: number) => void;
|