Skip to content
Merged
Show file tree
Hide file tree
Changes from 32 commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
6e7e827
Add integrate-serverlogic skill for Power Pages Server Logic
Mar 19, 2026
8534ae7
Fix server logic file location and add YAML metadata support
Mar 20, 2026
710354d
Add table permissions phase for Dataverse connector access
Mar 20, 2026
b5b2045
Add web role creation and permissions source choice to server logic s…
Mar 25, 2026
76bc1d5
Merge branch 'main' of https://github.com/microsoft/power-platform-sk…
priyanshu92 Mar 26, 2026
19443f9
Update command references in SKILL.md
priyanshu92 Mar 26, 2026
fa73210
Align server logic skill workflow
priyanshu92 Mar 26, 2026
adc1db3
Update server logic skill workflow and bump plugin to v1.2.0
priyanshu92 Mar 26, 2026
24e209f
Address Copilot review feedback on server logic skill
priyanshu92 Mar 26, 2026
23395fe
Enhance server logic validation and documentation
priyanshu92 Mar 30, 2026
aa1d028
Merge remote-tracking branch 'origin/main' into users/suyashpatel/ser…
Apr 1, 2026
800bdb5
Rename integrate-serverlogic skill to add-server-logic
Apr 1, 2026
176dda0
Fix server-logic validator regex and YAML string quoting
Apr 2, 2026
09e3ae7
Add cloud flow skill with intent-based suggestion and re-integration …
Apr 2, 2026
cbf4369
Add document.title requirement for create-site page components
Apr 2, 2026
ae2d377
Fix serverlogic metadata test to expect single-quoted YAML values
Apr 2, 2026
8c7baa1
Prevent render-template from overwriting existing HTML plan files
Apr 2, 2026
186ef9b
Fix server-logic skill accuracy and add secrets management workflow
Apr 2, 2026
8dfe89a
Add integrate-backend meta skill, fix review findings, add missing tests
Apr 2, 2026
2fb0ae3
Fix CI timeout in create-environment-variable test
Apr 2, 2026
c6114ca
Fix CI timeout in store-keyvault-secret test and update Playwright MC…
Apr 6, 2026
4b1e64c
Add Key Concepts and Implementation Order tabs to backend plan, enabl…
Apr 6, 2026
3d97f8a
Fix PR review findings across server-logic, cloud-flow, and integrate…
Apr 6, 2026
5a3e680
Fix PR review findings: fileSlug validation and list-cloud-flows test…
Apr 6, 2026
d4cae01
Update docs links and improve integrate-backend decision framework
Apr 8, 2026
70bc94b
Add plugin version check script and tests
Apr 8, 2026
c509d1b
Add plugin version check to all SKILL.md files and update conventions
Apr 8, 2026
d167f47
Add CI workflow to enforce version check in SKILL.md files
Apr 8, 2026
f90e7cd
Add Azure Key Vault visualization to server logic HTML plan
Apr 8, 2026
28fb565
Address PR review comments on validator, plan template, and SKILL.md
Apr 8, 2026
e5c7afc
Avoid secret exposure in process listings and fix CI fork safety
Apr 8, 2026
9ae9ec2
Fix innerHTML XSS, unused SITE_NAME in script blocks, and cloudflow U…
Apr 8, 2026
908459f
Fix cloudflow validator: flowtriggerurl is always empty in Power Pages
Apr 8, 2026
261ef57
Add Dataverse custom action discovery and wrapping to server logic skill
Apr 8, 2026
541ca31
Address PR review comments: security, robustness, and correctness fixes
Apr 8, 2026
074bf92
Fix ER diagram coloring for Mermaid v11 SVG ID prefix change
Apr 8, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
58 changes: 58 additions & 0 deletions .github/workflows/ensure-skill-version-check.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
name: validate-skill-version-check

on:
pull_request:
branches:
- main
paths:
- "plugins/power-pages/skills/**"

permissions:
contents: write

jobs:
validate-skill-version-check:
name: validate-skill-version-check
runs-on: ubuntu-latest
steps:
- name: generate app token
if: ${{ github.event.pull_request.head.repo.fork == false }}
id: app-token
uses: actions/create-github-app-token@v1
with:
app-id: 3189942
private-key: ${{ secrets.POWER_PLATFORM_SKILLS_APP_PRIVATE_KEY }}

- name: checkout (non-fork)
if: ${{ github.event.pull_request.head.repo.fork == false }}
uses: actions/checkout@v4
with:
ref: ${{ github.head_ref }}
token: ${{ steps.app-token.outputs.token }}

- name: checkout (fork)
if: ${{ github.event.pull_request.head.repo.fork == true }}
uses: actions/checkout@v4

- name: setup-node
uses: actions/setup-node@v4
with:
node-version: 20

- name: check-only (fork)
if: ${{ github.event.pull_request.head.repo.fork == true }}
run: node scripts/ensure-skill-version-check.js --check

- name: add missing version checks (non-fork)
if: ${{ github.event.pull_request.head.repo.fork == false }}
run: node scripts/ensure-skill-version-check.js

- name: commit and push if changed (non-fork)
if: ${{ github.event.pull_request.head.repo.fork == false }}
run: |
git diff --quiet && exit 0
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
git add "plugins/power-pages/skills/*/SKILL.md"
git commit -m "Auto-add plugin version check to SKILL.md files"
git push
2 changes: 1 addition & 1 deletion plugins/power-pages/.claude-plugin/plugin.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "power-pages",
"version": "1.1.2",
"version": "1.2.0",
"description": "Create and deploy Power Pages sites using modern development approaches. Supports code sites (SPAs) with React, Angular, Vue, or Astro, with more site types coming soon.",
Comment thread
priyanshu92 marked this conversation as resolved.
"author": {
"name": "Microsoft",
Expand Down
22 changes: 21 additions & 1 deletion plugins/power-pages/AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,17 @@ model: opus
---
```

Note: `allowed-tools` must be a comma-separated list, not JSON array or YAML list syntax.
Note: `allowed-tools` must be a comma-separated list, not JSON array or YAML list syntax. Do not add `hooks` to skill frontmatter; Power Pages skills register lifecycle hooks centrally.

### Plugin Version Check

Every SKILL.md must include the following line immediately after the closing `---` of the frontmatter (before the `#` title):

```markdown
> **Plugin check**: Run `node "${CLAUDE_PLUGIN_ROOT}/scripts/check-version.js"` — if it outputs a message, show it to the user before proceeding.
```

This runs a lightweight check comparing the local plugin version against `origin/main` and shows an update notice if a newer version is available.
Comment thread
priyanshu92 marked this conversation as resolved.

### Key Patterns

Expand All @@ -58,6 +68,16 @@ Note: `allowed-tools` must be a comma-separated list, not JSON array or YAML lis
- **Skill tracking** — Every skill must record usage in its final phase via `> Reference: ${CLAUDE_PLUGIN_ROOT}/references/skill-tracking-reference.md` (pointer pattern, not hardcoded command). When adding a new skill, also add its entry to the skill name mapping table in `references/skill-tracking-reference.md`.
- **Dataverse API calls** — Use deterministic Node.js scripts (in the skill's `scripts/` directory) for Dataverse API queries. Scripts should import `getAuthToken` and `makeRequest` from `scripts/lib/validation-helpers.js`. Never use inline PowerShell `Invoke-RestMethod` for API calls — scripts are more reliable, testable, and cross-platform.

## Common Review Pitfalls

These patterns have caused repeated PR review feedback. Check for them before submitting changes to skills, validators, or hooks.

- **Phase cross-references break silently** — When renumbering or reordering phases in a SKILL.md, also update: `references/` docs that mention phase numbers, the Key Decision Points section, and any other files that cross-reference this skill's phases. After any phase reorder, grep for the old phase number across the skill directory and its references.
- **Validators must match the exact constraint** — If the rule is "no exports at all", block all `module.exports`/`exports` — don't just check if exported names are in an allowlist. If the rule is "try/catch required", verify both `try` AND `catch` exist. Re-read the exact constraint from SKILL.md and test the boundary cases.
- **Hook scripts run on every Skill tool use** — The PostToolUse hook fires for all tracked skills, so unconditional `process.stderr.write` creates noise. Gate debug logging behind `process.env.DEBUG`. Only errors should go to stderr unconditionally.
- **Template placeholders in `<script>` blocks need special care** — `render-template.js` injects string values as-is (no encoding), which is safe for HTML text contexts but risky inside JavaScript. Avoid declaring JS variables with `"__PLACEHOLDER__"` in script blocks; prefer reading from the DOM or using `JSON.stringify` for JS contexts.
- **Guidance must be consistent within a skill** — If one section says "always use raw fetch", a framework-specific table in the same file must not recommend a different HTTP client without qualification. Reviewers will flag contradictions.

## Maintaining This File

Update when plugin structure or conventions change or you learn something which can be useful for new skills or agents.
Expand Down
13 changes: 7 additions & 6 deletions plugins/power-pages/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,8 @@ This keeps hook behavior in one place and avoids relying on skill-frontmatter ho
| Prerequisite | Required for | Install |
|---|---|---|
| [Node.js](https://nodejs.org/) (LTS) | All skills | `winget install OpenJS.NodeJS.LTS` |
| [PAC CLI](https://learn.microsoft.com/en-us/power-platform/developer/cli/introduction) | Deploy, activate, data model | `dotnet tool install -g Microsoft.PowerApps.CLI.Tool` |
| [Azure CLI](https://learn.microsoft.com/en-us/cli/azure/install-azure-cli) | Data model, sample data, activation | `winget install Microsoft.AzureCLI` |
| [PAC CLI](https://learn.microsoft.com/power-platform/developer/cli/introduction) | Deploy, activate, data model | `dotnet tool install -g Microsoft.PowerApps.CLI.Tool` |
| [Azure CLI](https://learn.microsoft.com/cli/azure/install-azure-cli) | Data model, sample data, activation | `winget install Microsoft.AzureCLI` |

## Skills

Expand Down Expand Up @@ -206,10 +206,11 @@ claude --dangerously-skip-permissions

## Documentation

- [Power Pages Code Sites](https://learn.microsoft.com/en-us/power-pages/configure/create-code-sites)
- [PAC CLI Reference](https://learn.microsoft.com/en-us/power-platform/developer/cli/reference/pages)
- [Power Pages REST API](https://learn.microsoft.com/en-us/rest/api/power-platform/powerpages/websites)
- [Dataverse Web API](https://learn.microsoft.com/en-us/power-apps/developer/data-platform/webapi/overview)
- [Power Pages AI Plugin Documentation](https://learn.microsoft.com/power-pages/configure/create-code-site-using-claude-code)
- [Power Pages Code Sites](https://learn.microsoft.com/power-pages/configure/create-code-sites)
- [PAC CLI Reference](https://learn.microsoft.com/power-platform/developer/cli/reference/pages)
- [Power Pages REST API](https://learn.microsoft.com/rest/api/power-platform/powerpages/websites)
- [Dataverse Web API](https://learn.microsoft.com/power-apps/developer/data-platform/webapi/overview)

## Testing validator scripts

Expand Down
2 changes: 2 additions & 0 deletions plugins/power-pages/agents/data-model-architect.md
Original file line number Diff line number Diff line change
Expand Up @@ -335,6 +335,8 @@ Include these rationale categories:
node "${CLAUDE_PLUGIN_ROOT}/scripts/render-data-model-plan.js" --output "<OUTPUT_PATH>" --data "<DATA_JSON_PATH>"
```

The render script refuses to overwrite existing files. Before calling it, check if the default output path (`<PROJECT_ROOT>/docs/data-model-plan.html`) already exists. If it does, choose a new descriptive filename based on context — e.g., `data-model-plan-support-tables.html`, `data-model-plan-apr-2026.html`. Pass the chosen name via `--output`.

3. Delete the temporary data JSON file after the script succeeds.

#### 4.5.4 Open in Browser
Expand Down
4 changes: 1 addition & 3 deletions plugins/power-pages/hooks/hooks.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
{
"description": "Centralized Power Pages lifecycle hooks",
"hooks": {
"PostToolUse": [
{
Expand All @@ -8,8 +7,7 @@
{
"type": "command",
"command": "node \"${CLAUDE_PLUGIN_ROOT}/hooks/run-skill-posttool-validation.js\"",
"timeout": 30,
"statusMessage": "Running Power Pages validator"
"timeout": 30
}
]
}
Expand Down
17 changes: 16 additions & 1 deletion plugins/power-pages/hooks/run-skill-posttool-validation.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,25 +7,38 @@ const {
getValidatorScript,
} = require('../scripts/lib/powerpages-hook-utils');

const DEBUG = process.env.DEBUG === '1' || process.env.DEBUG === 'true';

function debug(msg) {
if (DEBUG) process.stderr.write(msg);
}

debug('[power-pages hook] run-skill-posttool-validation.js started\n');

let inputData = '';

process.stdin.on('data', (chunk) => {
inputData += chunk;
});

process.stdin.on('end', () => {
debug(`[power-pages hook] stdin closed, received ${inputData.length} bytes\n`);
try {
const input = JSON.parse(inputData);
const skillName = getTrackedSkillFromToolInput(input.tool_input);
if (!skillName) {
debug('[power-pages hook] No tracked skill detected — skipping validation\n');
process.exit(0);
}

const validatorScript = getValidatorScript(skillName);
if (!validatorScript) {
debug(`[power-pages hook] Skill "${skillName}" has no validator — skipping\n`);
process.exit(0);
}

debug(`[power-pages hook] Running validator for skill "${skillName}": ${validatorScript}\n`);

const validatorPath = path.join(__dirname, '..', validatorScript);
const result = spawnSync(process.execPath, [validatorPath], {
input: inputData,
Expand All @@ -41,8 +54,10 @@ process.stdin.on('end', () => {
process.stderr.write(result.stderr);
}

debug(`[power-pages hook] Validator exited with code ${result.status ?? 0}\n`);
process.exit(result.status ?? 0);
} catch {
} catch (err) {
process.stderr.write(`[power-pages hook] Unexpected error: ${err.message}\n`);
process.exit(0);
}
});
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,8 @@ Run the render script (it creates the output directory if needed):
node "${CLAUDE_PLUGIN_ROOT}/scripts/render-permissions-plan.js" --output "<OUTPUT_PATH>" --data "<DATA_JSON_PATH>"
```

The render script refuses to overwrite existing files. Before calling it, check if the default output path (`<PROJECT_ROOT>/docs/permissions-plan.html`) already exists. If it does, choose a new descriptive filename based on context — e.g., `permissions-plan-support-tables.html`, `permissions-plan-apr-2026.html`. Pass the chosen name via `--output`.

Delete the temporary data JSON file after the script succeeds.

### Open in Browser
Expand Down
5 changes: 5 additions & 0 deletions plugins/power-pages/references/skill-tracking-reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ node "${CLAUDE_PLUGIN_ROOT}/scripts/update-skill-tracking.js" --projectRoot "<PR

The script exits silently if `.powerpages-site/site-settings/` does not exist, so it is safe to call unconditionally.

If the tracking script creates or updates site setting YAML files, include those tracking changes in the next git commit for the current phase or final summary commit.

## Skill Name Mapping

| Skill | PascalCase (`--skillName`) | Setting Name |
Expand All @@ -35,6 +37,9 @@ The script exits silently if `.powerpages-site/site-settings/` does not exist, s
| setup-auth | SetupAuth | Site/AI/Skills/SetupAuth |
| test-site | TestSite | Site/AI/Skills/TestSite |
| audit-permissions | AuditPermissions | Site/AI/Skills/AuditPermissions |
| add-server-logic | AddServerLogic | Site/AI/Skills/AddServerLogic |
| add-cloud-flow | AddCloudFlow | Site/AI/Skills/AddCloudFlow |
| integrate-backend | IntegrateBackend | Site/AI/Skills/IntegrateBackend |

Comment thread
priyanshu92 marked this conversation as resolved.
## YAML Format

Expand Down
106 changes: 106 additions & 0 deletions plugins/power-pages/scripts/check-version.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
#!/usr/bin/env node

/**
* Plugin version check. Compares local plugin.json version against
* origin/main and prints an update notice if remote is newer.
* Exits silently if versions match or on any error.
*
* Usage: node check-version.js
* Functions are also exported for testing.
*/

const { execSync } = require('child_process');
const path = require('path');
const fs = require('fs');

/**
* Compare two semver strings (major.minor.patch).
* Returns 1 if b > a (remote newer), -1 if a > b (local newer), 0 if equal.
*/
function compareSemver(a, b) {
const pa = a.split('.').map(Number);
const pb = b.split('.').map(Number);
for (let i = 0; i < 3; i++) {
if ((pb[i] || 0) > (pa[i] || 0)) return 1;
if ((pb[i] || 0) < (pa[i] || 0)) return -1;
}
return 0;
}

/**
* Format the update notification as plain text.
* Includes marketplace update (first) and plugin update (second).
*/
function formatUpdateMessage(pluginName, localVersion, remoteVersion, marketplaceName) {
const qualifiedName = marketplaceName ? `${pluginName}@${marketplaceName}` : pluginName;
let msg = `\nPlugin update available: ${pluginName} ${localVersion} → ${remoteVersion}.\n`;
if (marketplaceName) {
msg += `Run:\n claude plugin marketplace update ${marketplaceName}\n claude plugin update ${qualifiedName}`;
} else {
msg += `Run: claude plugin update ${qualifiedName}`;
}
return msg;
}

/**
* Read the marketplace name from .claude-plugin/marketplace.json at the git root.
* Returns null if not found.
*/
function readMarketplaceName(gitRoot) {
try {
const marketplacePath = path.join(gitRoot, '.claude-plugin', 'marketplace.json');
const marketplace = JSON.parse(fs.readFileSync(marketplacePath, 'utf8'));
return marketplace.name || null;
} catch {
return null;
}
}

module.exports = { compareSemver, formatUpdateMessage, readMarketplaceName };

if (require.main === module) {
try {
const pluginRoot = path.resolve(__dirname, '..');
const pluginJsonPath = path.join(pluginRoot, '.claude-plugin', 'plugin.json');

const localPlugin = JSON.parse(fs.readFileSync(pluginJsonPath, 'utf8'));
const localVersion = localPlugin.version;
if (!localVersion) process.exit(0);

const gitRoot = execSync('git rev-parse --show-toplevel', {
encoding: 'utf8',
timeout: 5000,
stdio: ['pipe', 'pipe', 'pipe'],
}).trim();

const relPath = path.relative(gitRoot, pluginJsonPath).replace(/\\/g, '/');

// Best-effort fetch
try {
execSync('git fetch origin main --quiet', {
encoding: 'utf8',
timeout: 10000,
stdio: ['pipe', 'pipe', 'pipe'],
});
} catch {
// Use cached origin/main
}

const remoteContent = execSync(`git show origin/main:${relPath}`, {
encoding: 'utf8',
timeout: 5000,
stdio: ['pipe', 'pipe', 'pipe'],
});
const remotePlugin = JSON.parse(remoteContent);
const remoteVersion = remotePlugin.version;
if (!remoteVersion) process.exit(0);

if (compareSemver(localVersion, remoteVersion) > 0) {
const pluginName = localPlugin.name || 'power-pages';
const marketplaceName = readMarketplaceName(gitRoot);
console.log(formatUpdateMessage(pluginName, localVersion, remoteVersion, marketplaceName));
}
} catch {
// Silent on error — don't block skill execution
}
}
Loading
Loading