Skip to content

Commit cbc5aca

Browse files
priyanshu92Priyanshu AgrawalCopilot
authored
[Pages] Flag server logic with-pattern issues (#129)
* Flag server logic with-pattern issues - Detect raw with( substrings before server-side validation fails at runtime - Document OData startswith/endswith literal splitting workaround - Add tests for rejected and accepted startswith patterns Co-authored-by: GPT-5.5 <223556219+Copilot@users.noreply.github.com> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Update troubleshooting section in SKILL.md - 📝 Clarified the diagnostic log retrieval process. - 🔄 Removed specific references to the Power Pages design studio diagnostics view. - 🔍 Updated instructions for using Playwright MCP tools. - ❌ Removed unnecessary steps and streamlined the troubleshooting flow. -Priyanshu --------- Co-authored-by: Priyanshu Agrawal <priyanshuag@Priyanshus-MacBook-Pro.local> Co-authored-by: GPT-5.5 <223556219+Copilot@users.noreply.github.com>
1 parent e9ae4c8 commit cbc5aca

3 files changed

Lines changed: 118 additions & 0 deletions

File tree

plugins/power-pages/scripts/tests/validate-serverlogic.test.js

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -228,3 +228,42 @@ test('yml name mismatch is flagged', (t) => {
228228
assert.equal(result.status, 2);
229229
assert.match(result.stderr, /does not match folder name/);
230230
});
231+
232+
test('startswith( inside string literal is flagged (with-pattern)', (t) => {
233+
const projectRoot = createTempProject(t);
234+
setupProject(projectRoot);
235+
const startsWithJs = `function get() {
236+
try {
237+
Server.Logger.Log("test-endpoint GET called");
238+
var query = "$filter=startswith(name,'INV-')";
239+
return JSON.stringify({ status: "success", query: query });
240+
} catch (err) {
241+
Server.Logger.Error("test-endpoint GET failed: " + err.message);
242+
return JSON.stringify({ status: "error", message: err.message });
243+
}
244+
}`;
245+
writeServerLogic(projectRoot, 'test-endpoint', startsWithJs, VALID_YML);
246+
247+
const result = runValidator(projectRoot);
248+
assert.equal(result.status, 2);
249+
assert.match(result.stderr, /contains the substring 'with\('/);
250+
});
251+
252+
test('split startswith( workaround passes validation', (t) => {
253+
const projectRoot = createTempProject(t);
254+
setupProject(projectRoot);
255+
const splitJs = `function get() {
256+
try {
257+
Server.Logger.Log("test-endpoint GET called");
258+
var query = "$filter=startswith" + "(name,'INV-')";
259+
return JSON.stringify({ status: "success", query: query });
260+
} catch (err) {
261+
Server.Logger.Error("test-endpoint GET failed: " + err.message);
262+
return JSON.stringify({ status: "error", message: err.message });
263+
}
264+
}`;
265+
writeServerLogic(projectRoot, 'test-endpoint', splitJs, VALID_YML);
266+
267+
const result = runValidator(projectRoot);
268+
assert.equal(result.status, 0, result.stderr);
269+
});

plugins/power-pages/skills/add-server-logic/SKILL.md

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -432,6 +432,26 @@ Repeat this step for each approved server logic item. Create or update `<PROJECT
432432
6. **No browser APIs**: No `fetch`, `XMLHttpRequest`, `setTimeout`, `setInterval`, `console.log`, or DOM APIs.
433433
7. **Async when needed**: Mark functions as `async` only when they use `await` (HttpClient calls). Dataverse connector methods (`Server.Connector.Dataverse.*`) are **synchronous** — do NOT use `async`/`await` with them.
434434

435+
#### Prohibited Script Patterns
436+
437+
The Power Pages server-side script validator rejects scripts containing certain patterns at runtime. Violations surface as `RTSL01: Script validation failed: prohibited pattern found - Pattern: <regex>` in diagnostics, and the function silently falls through without executing user code.
438+
439+
| Pattern | Regex | Caveat |
440+
|---------|-------|--------|
441+
| JavaScript `with` statement | `with\s*\(` | The regex matches the substring `with(` **anywhere** in the file — including inside string literals and inside other identifiers. OData filter functions like `startswith(`, `endswith(`, and `groupwith(` will trip it because they end with `with(`. |
442+
443+
**Workaround for OData functions** — split the literal so `with(` is not contiguous in source:
444+
445+
```javascript
446+
// ❌ Triggers validator: "startswith(" contains the substring "with("
447+
var query = "$filter=startswith(name,'INV-')";
448+
449+
// ✅ Split the literal — server still receives "startswith(name,...)"
450+
var query = "$filter=startswith" + "(name,'INV-')";
451+
```
452+
453+
The same trick applies to `endswith(`, `groupwith(`, and any other identifier that ends with `with(`.
454+
435455
#### Code Template
436456

437457
```javascript
@@ -1112,6 +1132,7 @@ Provide testing instructions:
11121132
Use the frontend integration reference from Phase 9 for the exact calling pattern that matches the site's stack.
11131133

11141134
5. **Check diagnostics** — Server.Logger output can be viewed in Power Pages design studio diagnostics
1135+
6. **If the endpoint returns an error or unexpected response** — see [Troubleshooting Server Logic Execution Errors](#troubleshooting-server-logic-execution-errors) for the Playwright + `X-Ms-UserTrace` debugging flow
11151136

11161137
**Output**: Code validated, API URL provided, test guidance given
11171138

@@ -1188,6 +1209,46 @@ After deployment (or if skipped), remind the user:
11881209

11891210
---
11901211

1212+
## Troubleshooting Server Logic Execution Errors
1213+
1214+
When a deployed server logic endpoint returns an error or unexpected response, the underlying cause is usually hidden inside the `X-Ms-UserTrace` response header — a base64-encoded blob containing the runtime diagnostic logs. The Power Pages Edge browser extension shows the same data, but inspecting the response header is the fastest path when iterating against a live site.
1215+
1216+
Use this flow whenever a server logic call fails or returns a different response than expected:
1217+
1218+
### 1. Open the Live Site in a Browser via Playwright
1219+
1220+
Use the Playwright MCP tools to drive the site:
1221+
1222+
1. Navigate to the deployed site URL (the `websiteUrl` returned by `/activate-site` or shown in the Power Pages admin center).
1223+
2. Ask the user to sign in if the endpoint requires authentication and wait for confirmation.
1224+
3. Trigger the action that calls the failing server logic endpoint (click the button, submit the form, etc.) — or call the endpoint directly with `fetch()`.
1225+
1226+
### 2. Capture the Network Response
1227+
1228+
Use `mcp__plugin_power-pages_playwright__browser_network_requests` to list network activity, then locate the request to `/_api/serverlogics/<name>`. Note:
1229+
1230+
- The HTTP status code (e.g., 200, 400, 500)
1231+
- The response body (often a generic error or empty payload when validation fails)
1232+
- **Most importantly: the `X-Ms-UserTrace` response header** — this is where the actual diagnostic logs live
1233+
1234+
If `browser_network_requests` does not surface the response headers directly, fall back to `mcp__plugin_power-pages_playwright__browser_evaluate` and read the headers from a `fetch()` call:
1235+
1236+
```javascript
1237+
const res = await fetch('/_api/serverlogics/<name>', { method: 'GET', credentials: 'include' });
1238+
const trace = res.headers.get('X-Ms-UserTrace');
1239+
return { status: res.status, body: await res.text(), trace };
1240+
```
1241+
1242+
### 3. Decode the `X-Ms-UserTrace` Header
1243+
1244+
The header value is base64-encoded JSON. Decode it.
1245+
1246+
The decoded payload contains the diagnostic log entries — including the actual error message, the prohibited pattern (if script validation failed).
1247+
1248+
After fixing, redeploy via `/deploy-site` and restart the site so the change is picked up immediately.
1249+
1250+
---
1251+
11911252
## Important Notes
11921253

11931254
### Throughout All Phases

plugins/power-pages/skills/add-server-logic/scripts/validate-serverlogic.js

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -202,6 +202,24 @@ runValidation((cwd) => {
202202
if (/(?:async\s+)?function\s+delete\s*\(/m.test(strippedContent)) {
203203
errors.push(`${dirName}.js: uses 'function delete()' — 'delete' is a reserved word, use 'del' instead`);
204204
}
205+
206+
// Check: no 'with(' substring anywhere in the raw source.
207+
// The Power Pages server validator rejects scripts matching /with\s*\(/ to block
208+
// the JavaScript `with` statement, but the regex also matches the substring inside
209+
// identifiers like `startswith(`, `endswith(`, and `groupwith(` — even inside string
210+
// literals — which causes runtime error: "Script validation failed: prohibited
211+
// pattern found - Pattern: with\s*\(". Run this on the raw content (not stripped)
212+
// because the server validator does the same.
213+
const withMatches = [...content.matchAll(/with\s*\(/g)];
214+
if (withMatches.length > 0) {
215+
const lineNumbers = withMatches.map(m => content.slice(0, m.index).split('\n').length);
216+
errors.push(
217+
`${dirName}.js: contains the substring 'with(' on line(s) ${lineNumbers.join(', ')} — ` +
218+
`the Power Pages server validator's regex /with\\s*\\(/ blocks this even inside string literals ` +
219+
`(e.g., OData functions like startswith(, endswith(). Split the literal so 'with(' is not contiguous, ` +
220+
`e.g., \`"startswith" + "(crd50_name,..."\` instead of \`"startswith(crd50_name,..."\`.`
221+
);
222+
}
205223
}
206224

207225
if (errors.length > 0) {

0 commit comments

Comments
 (0)