Loading...
Loading...
Compare original and translation side by side
Plugin check: Run— if it outputs a message, show it to the user before proceeding.node "${CLAUDE_PLUGIN_ROOT}/scripts/check-version.js"
插件检查:运行— 如果脚本输出消息,请在继续操作前展示给用户。node "${CLAUDE_PLUGIN_ROOT}/scripts/check-version.js"
fetchXMLHttpRequestsetTimeoutgetpostputpatchdeldeleteJSON.stringify()Prerequisites:
- An existing Power Pages code site created
- The site must be deployed at least once (
folder must exist) — server logic files live inside.powerpages-site, so deployment is required before any server logic can be created.powerpages-site/server-logic/
fetchXMLHttpRequestsetTimeoutgetpostputpatchdeldeleteJSON.stringify()前提条件:
- 已创建现有的Power Pages代码站点
- 站点必须至少部署过一次(必须存在
文件夹)——Server Logic文件存储在.powerpages-site目录下,因此在创建任何Server Logic前必须完成部署.powerpages-site/server-logic/
.js.serverlogic.yml.powerpages-site/server-logic/<name>/.powerpages-site/server-logic/<name>/.js.serverlogic.ymlpowerpages.config.json/create-sitepowerpages.config.json/create-sitepowerpages.config.jsonpowerpages.config.jsonpackage.json${CLAUDE_PLUGIN_ROOT}/references/framework-conventions.mdpackage.json${CLAUDE_PLUGIN_ROOT}/references/framework-conventions.mdTaskagent_type: "explore""Analyze this Power Pages code site for server logic context. Check:
- Does
exist? If yes, list all subdirectories and their .js files. Summarize what each server logic does (which functions it implements, what SDK features it uses). Also read the corresponding .serverlogic.yml files to check web role assignments..powerpages-site/server-logic/- Search the frontend source code (
) for any existing calls tosrc/**/*.{ts,tsx,js,jsx,vue,astro}— these indicate server logic endpoints already being consumed./_api/serverlogics/- Look for CSRF token handling patterns (
,__RequestVerificationToken) — these show how the site currently makes authenticated API calls._layout/tokenhtml- Check for any TODO/FIXME comments mentioning server logic, backend, or server-side processing.
- Look for hardcoded API URLs, mock data, or placeholder fetch calls that might need to be replaced with server logic calls.
- Check for any existing service layer or API utility files in
,src/shared/, or similar directories that could be reused for server logic integration.src/services/- Read
files to list available web roles and their GUIDs — these are needed when creating the server logic metadata YAML..powerpages-site/web-roles/*.webrole.yml- For each existing server logic, assess whether it can be reused or safely extended for the requested capability instead of creating a brand-new server logic file. Call out any strong reuse candidates and explain why. Report all findings so we can avoid duplicating work and match existing patterns."
Taskagent_type: "explore""分析此Power Pages代码站点的Server Logic上下文。检查:
- 是否存在
?如果存在,列出所有子目录及其.js文件。总结每个Server Logic的功能(实现了哪些函数、使用了哪些SDK功能)。同时读取对应的.serverlogic.yml文件以检查Web角色分配情况。.powerpages-site/server-logic/- 在前端源代码(
)中搜索对src/**/*.{ts,tsx,js,jsx,vue,astro}的现有调用——这些调用表明Server Logic端点已在被使用。/_api/serverlogics/- 查找CSRF令牌处理模式(
、__RequestVerificationToken)——这些模式展示了站点当前如何进行已认证的API调用。_layout/tokenhtml- 查找提及Server Logic、后端或服务器端处理的任何TODO/FIXME注释。
- 查找可能需要替换为Server Logic调用的硬编码API URL、模拟数据或占位符fetch调用。
- 查找
、src/shared/或类似目录中可能可重用于Server Logic集成的现有服务层或API工具文件。src/services/- 读取
文件以列出可用的Web角色及其GUID——这些信息在创建Server Logic元数据YAML时需要。.powerpages-site/web-roles/*.webrole.yml- 对于每个现有Server Logic,评估是否可以重用或安全扩展以实现所需功能,而非创建全新的Server Logic文件。指出强重用候选对象并说明原因。 报告所有发现,以避免重复工作并匹配现有模式。"
.powerpages-site.powerpages-site/server-logic/"Thefolder was not found. Server logic files are stored inside this folder, so the site must be deployed at least once before creating server logic. Would you like to deploy now?".powerpages-site
AskUserQuestion| Question | Options |
|---|---|
The | Yes, deploy now (Required), Cancel |
/deploy-site.powerpages-site.powerpages-site.powerpages-site"未找到文件夹。Server Logic文件存储在此文件夹中,因此站点必须至少部署一次才能创建Server Logic。是否现在部署?".powerpages-site
AskUserQuestion| 问题 | 选项 |
|---|---|
创建Server Logic需要 | 是,立即部署(必填),取消 |
/deploy-site.powerpages-site.powerpages-sitegetpostputpatchdelgetpostputpatchdel| Condition | Example |
|---|---|
| It enforces a state machine or lifecycle | Order status: Draft → Submitted → Approved |
| The write is conditional on a business rule that must be tamper-proof | "Only allow bid submission before the deadline" |
| The operation spans multiple tables atomically | Award a bid + reject all others + update event status |
| The write involves a computed or derived value | Server calculates a score and writes it |
| The client should not have direct write access to the field | Status fields with strict transition rules |
{ valid: true/false }| 条件 | 示例 |
|---|---|
| 强制执行状态机或生命周期 | 订单状态:草稿 → 已提交 → 已批准 |
| 写入操作取决于必须防篡改的业务规则 | "仅允许在截止日期前提交投标" |
| 操作跨多个表原子执行 | 授予投标 + 拒绝所有其他投标 + 更新活动状态 |
| 写入操作涉及计算或派生值 | 服务器计算分数并写入 |
| 客户端不应具有对字段的直接写入权限 | 具有严格转换规则的状态字段 |
{ valid: true/false }node "${CLAUDE_PLUGIN_ROOT}/scripts/list-custom-actions.js" "<ENV_URL>"customApiscustomProcessActionstotalnamedisplayNamedescriptiontypeactionfunctionbindingunboundentityentityCollectionboundEntitysourcecustomApicustomProcessActionrequestParametersresponsePropertiestotal > 0AskUserQuestion| Question | Options |
|---|---|
Your Dataverse environment has | Yes, let me choose which ones to use; No, build everything from scratch |
AskUserQuestion| Question | Context |
|---|---|
For the | Present the list of custom actions with their names, descriptions, and binding types. Include "None — build from scratch" as an option. |
InvokeCustomApiGETPOSTnode "${CLAUDE_PLUGIN_ROOT}/scripts/list-custom-actions.js" "<ENV_URL>"customApiscustomProcessActionstotalnamedisplayNamedescriptiontypeactionfunctionbindingunboundentityentityCollectionboundEntitysourcecustomApicustomProcessActionrequestParametersresponsePropertiestotal > 0AskUserQuestion| 问题 | 选项 |
|---|---|
您的Dataverse环境有 | 是,让我选择要使用的操作;否,从头构建所有内容 |
AskUserQuestion| 问题 | 上下文 |
|---|---|
对于 | 展示自定义操作列表及其名称、描述和绑定类型。包含**“无——从头构建”**作为选项。 |
InvokeCustomApiGETPOST| Feature | When to use |
|---|---|
| Calling external REST APIs (NOT Dataverse) |
| Reading/writing Dataverse records (CRUD + |
| Accessing request parameters, headers, body |
| User-scoped operations, role checks |
| Always — every function should log entry/exit and errors |
| Reading site setting configuration values |
| Reading Dataverse environment variable values directly via |
| Accessing site metadata |
| 功能 | 使用场景 |
|---|---|
| 调用外部REST API(非Dataverse) |
| 读取/写入Dataverse记录(CRUD + 使用 |
| 访问请求参数、标头、主体 |
| 用户范围操作、角色检查 |
| 始终使用——每个函数都应记录进入/退出和错误 |
| 读取站点设置配置值 |
| 通过 |
| 访问站点元数据 |
| Scenario | Secret needed |
|---|---|
| Calling an authenticated external API | API key, client secret, bearer token |
| Connecting to a third-party service | Connection string, access token |
| OAuth2 client credentials flow | Client ID + client secret |
| Webhook verification | Signing secret, shared key |
ExchangeRateApiKeyPaymentGatewaySecretServer.Sitesetting.Get()ExternalApi/ExchangeRateApiKeycr5b4_ExchangeRateApiKey| 场景 | 需要的密钥 |
|---|---|
| 调用已认证的外部API | API密钥、客户端密钥、Bearer令牌 |
| 连接到第三方服务 | 连接字符串、访问令牌 |
| OAuth2客户端凭据流 | 客户端ID + 客户端密钥 |
| Webhook验证 | 签名密钥、共享密钥 |
ExchangeRateApiKeyPaymentGatewaySecretServer.Sitesetting.Get()ExternalApi/ExchangeRateApiKeycr5b4_ExchangeRateApiKeyAskUserQuestion| Question | Options |
|---|---|
| This server logic requires secret values (e.g., API keys, client secrets). Azure Key Vault is the recommended way to store secrets securely. Would you like to use Azure Key Vault? | Yes, use Azure Key Vault (Recommended), No, store directly as environment variable |
AskUserQuestion| 问题 | 选项 |
|---|---|
| 此Server Logic需要密钥值(例如API密钥、客户端密钥)。Azure Key Vault是安全存储密钥的推荐方式。是否要使用Azure Key Vault? | 是,使用Azure Key Vault(推荐);否,直接存储为环境变量 |
AskUserQuestion| Question | Context |
|---|---|
| What should this server logic solution do overall? | If the purpose is unclear |
| Should this be one server logic or multiple server logic? | If the request could reasonably be modeled either way |
| Which HTTP methods does each server logic need? | If not specified — suggest based on the use case (e.g., read-only = GET, form processing = POST) |
| Does each server logic need to call external APIs, Dataverse, or both? | Determines which connectors to use |
| What should each server logic be named? | Suggest URL-friendly names based on the responsibilities |
| Does the server logic need any secret or sensitive values (API keys, client secrets, tokens)? | If the server logic calls authenticated external APIs or services |
AskUserQuestion| 问题 | 上下文 |
|---|---|
| 此Server Logic解决方案总体应实现什么功能? | 如果用途不明确 |
| 应使用一个还是多个Server Logic? | 如果请求可以合理建模为两种方式 |
| 每个Server Logic需要哪些HTTP方法? | 如果未指定——根据用例建议(例如只读=GET,表单处理=POST) |
| 每个Server Logic是否需要调用外部API、Dataverse或两者? | 确定要使用的连接器 |
| 每个Server Logic应命名为什么? | 根据职责建议URL友好的名称 |
| Server Logic是否需要任何机密或敏感值(API密钥、客户端密钥、令牌)? | 如果Server Logic调用已认证的外部API或服务 |
Reference:${CLAUDE_PLUGIN_ROOT}/skills/add-server-logic/references/server-logic-docs.md
参考:${CLAUDE_PLUGIN_ROOT}/skills/add-server-logic/references/server-logic-docs.md
Reference:${CLAUDE_PLUGIN_ROOT}/skills/add-server-logic/references/server-logic-plan-data-format.md
SECRETS_DATAuseKeyVault: trueSECRETS_DATAnull参考:${CLAUDE_PLUGIN_ROOT}/skills/add-server-logic/references/server-logic-plan-data-format.md
SECRETS_DATAuseKeyVault: trueSECRETS_DATAnull<PROJECT_ROOT>/docs/serverlogic-plan.htmldocs/node "${CLAUDE_PLUGIN_ROOT}/scripts/render-serverlogic-plan.js" --output "<OUTPUT_PATH>" --data "<DATA_JSON_PATH>"<PROJECT_ROOT>/docs/serverlogic-plan.htmlserverlogic-plan-exchange-rate.htmlserverlogic-plan-apr-2026.html--output<PROJECT_ROOT>/docs/serverlogic-plan.htmldocs/node "${CLAUDE_PLUGIN_ROOT}/scripts/render-serverlogic-plan.js" --output "<OUTPUT_PATH>" --data "<DATA_JSON_PATH>"<PROJECT_ROOT>/docs/serverlogic-plan.htmlserverlogic-plan-exchange-rate.htmlserverlogic-plan-apr-2026.html--outputAskUserQuestion| Question | Options |
|---|---|
| Here's the implementation plan for this server logic work. Does it look correct? | Approve and implement (Recommended), Request changes, Cancel |
AskUserQuestion| 问题 | 选项 |
|---|---|
| 这是此Server Logic工作的实施计划。看起来是否正确? | 批准并实施(推荐),请求更改,取消 |
.js.jsreuseupdate.js.serverlogic.ymlcreate.powerpages-site/server-logic/server-logicreuseupdate.js.serverlogic.ymlcreate.powerpages-site/server-logic/server-logicadx_anonymoususersrole: false
adx_authenticatedusersrole: true
description: Role for authenticated users
id: a1b2c3d4-e5f6-7890-abcd-ef1234567890
name: Authenticated Usersidnameadx_anonymoususersrole: false
adx_authenticatedusersrole: true
description: Role for authenticated users
id: a1b2c3d4-e5f6-7890-abcd-ef1234567890
name: Authenticated Usersidname<PROJECT_ROOT>/.powerpages-site/server-logic/<name>/<name>.jscreateupdatereusecreateupdatereuse<PROJECT_ROOT>/.powerpages-site/server-logic/<name>/<name>.jsgetpostputpatchdelJSON.stringify()Server.Logger.Log()Server.Logger.Error()importrequirefetchXMLHttpRequestsetTimeoutsetIntervalconsole.logasyncawaitServer.Connector.Dataverse.*asyncawaitgetpostputpatchdelJSON.stringify()Server.Logger.Log()Server.Logger.Error()importrequirefetchXMLHttpRequestsetTimeoutsetIntervalconsole.logawaitasyncServer.Connector.Dataverse.*asyncawait// Server Logic: <name>
// Purpose: <description>
// API URL: https://<site-url>/_api/serverlogics/<name>
function get() {
try {
Server.Logger.Log("<name> GET called");
// Access query parameters
// const id = Server.Context.QueryParameters["id"];
// Your logic here...
return JSON.stringify({
status: "success",
method: "GET",
data: null // replace with actual data
});
} catch (err) {
Server.Logger.Error("<name> GET failed: " + err.message);
return JSON.stringify({
status: "error",
method: "GET",
message: err.message
});
}
}// Server Logic: <name>
// Purpose: <description>
// API URL: https://<site-url>/_api/serverlogics/<name>
function get() {
try {
Server.Logger.Log("<name> GET called");
// Access query parameters
// const id = Server.Context.QueryParameters["id"];
// Your logic here...
return JSON.stringify({
status: "success",
method: "GET",
data: null // replace with actual data
});
} catch (err) {
Server.Logger.Error("<name> GET failed: " + err.message);
return JSON.stringify({
status: "error",
method: "GET",
message: err.message
});
}
}// Server Logic: <name>
// Purpose: Validate and execute <describe the operation>
// Pattern: Validate-and-execute — this endpoint both validates the business rule
// and performs the Dataverse write. The client should NOT write <protected fields>
// via Web API — all writes to those fields go through this endpoint.
// API URL: https://<site-url>/_api/serverlogics/<name>
function post() {
try {
Server.Logger.Log("<name> POST called");
const body = JSON.parse(Server.Context.Body);
const entityId = body.entityId;
const targetStatus = body.targetStatus;
// 1. Read the current record from Dataverse
const current = Server.Connector.Dataverse.RetrieveRecord("<table-name>", entityId, "?$select=<status-field>");
const currentStatus = current["<status-field>"];
// 2. Validate the transition
const allowedTransitions = {
"Draft": ["Submitted"],
"Submitted": ["Approved", "Rejected"],
"Approved": ["Fulfilled"]
};
const allowed = allowedTransitions[currentStatus] || [];
if (!allowed.includes(targetStatus)) {
return JSON.stringify({
status: "error",
message: "Invalid transition: " + currentStatus + " → " + targetStatus + " is not allowed",
currentStatus: currentStatus,
targetStatus: targetStatus,
allowedTargets: allowed
});
}
// 3. Execute the write — server performs the Dataverse update
const updateData = {};
updateData["<status-field>"] = targetStatus;
Server.Connector.Dataverse.UpdateRecord("<table-name>", entityId, JSON.stringify(updateData));
Server.Logger.Log("<name> transition executed: " + currentStatus + " → " + targetStatus);
// 4. Return the result — client receives confirmation, not a validation flag
return JSON.stringify({
status: "success",
previousStatus: currentStatus,
newStatus: targetStatus,
entityId: entityId
});
} catch (err) {
Server.Logger.Error("<name> POST failed: " + err.message);
return JSON.stringify({
status: "error",
message: err.message
});
}
}Server.Connector.Dataverse.UpdateRecord{ valid: true/false }// Server Logic: <name>
// Purpose: Validate and execute <describe the operation>
// Pattern: Validate-and-execute — this endpoint both validates the business rule
// and performs the Dataverse write. The client should NOT write <protected fields>
// via Web API — all writes to those fields go through this endpoint.
// API URL: https://<site-url>/_api/serverlogics/<name>
function post() {
try {
Server.Logger.Log("<name> POST called");
const body = JSON.parse(Server.Context.Body);
const entityId = body.entityId;
const targetStatus = body.targetStatus;
// 1. Read the current record from Dataverse
const current = Server.Connector.Dataverse.RetrieveRecord("<table-name>", entityId, "?$select=<status-field>");
const currentStatus = current["<status-field>"];
// 2. Validate the transition
const allowedTransitions = {
"Draft": ["Submitted"],
"Submitted": ["Approved", "Rejected"],
"Approved": ["Fulfilled"]
};
const allowed = allowedTransitions[currentStatus] || [];
if (!allowed.includes(targetStatus)) {
return JSON.stringify({
status: "error",
message: "Invalid transition: " + currentStatus + " → " + targetStatus + " is not allowed",
currentStatus: currentStatus,
targetStatus: targetStatus,
allowedTargets: allowed
});
}
// 3. Execute the write — server performs the Dataverse update
const updateData = {};
updateData["<status-field>"] = targetStatus;
Server.Connector.Dataverse.UpdateRecord("<table-name>", entityId, JSON.stringify(updateData));
Server.Logger.Log("<name> transition executed: " + currentStatus + " → " + targetStatus);
// 4. Return the result — client receives confirmation, not a validation flag
return JSON.stringify({
status: "success",
previousStatus: currentStatus,
newStatus: targetStatus,
entityId: entityId
});
} catch (err) {
Server.Logger.Error("<name> POST failed: " + err.message);
return JSON.stringify({
status: "error",
message: err.message
});
}
}Server.Connector.Dataverse.UpdateRecord{ valid: true/false }Server.Connector.Dataverse.InvokeCustomApi/_api/serverlogics/<name>// Server Logic: <name>
// Purpose: Wraps Dataverse custom action "<custom-action-name>" for portal consumption
// Custom Action: <custom-action-name> (unbound, action)
// API URL: https://<site-url>/_api/serverlogics/<name>
function post() {
try {
Server.Logger.Log("<name> POST called — invoking custom action <custom-action-name>");
const body = JSON.parse(Server.Context.Body);
// Build the request payload matching the custom action's input parameters
const payload = JSON.stringify({
// "<ParameterName>": body.<clientFieldName>
});
const result = Server.Connector.Dataverse.InvokeCustomApi(
"POST",
"<custom-action-name>",
payload
);
Server.Logger.Log("<name> custom action completed successfully");
return JSON.stringify({
status: "success",
data: result
});
} catch (err) {
Server.Logger.Error("<name> POST failed: " + err.message);
return JSON.stringify({
status: "error",
message: err.message
});
}
}function post() {
try {
Server.Logger.Log("<name> POST called — invoking bound action <custom-action-name>");
const body = JSON.parse(Server.Context.Body);
const entityId = body.entityId;
const payload = JSON.stringify({
// "<ParameterName>": body.<clientFieldName>
});
// Include the entity set and record ID, followed by the fully qualified action name
const result = Server.Connector.Dataverse.InvokeCustomApi(
"POST",
"<entity-set-name>(" + entityId + ")/Microsoft.Dynamics.CRM.<custom-action-name>",
payload
);
Server.Logger.Log("<name> bound action completed for entity " + entityId);
return JSON.stringify({
status: "success",
data: result,
entityId: entityId
});
} catch (err) {
Server.Logger.Error("<name> POST failed: " + err.message);
return JSON.stringify({
status: "error",
message: err.message
});
}
}function get() {
try {
Server.Logger.Log("<name> GET called — invoking custom function <custom-function-name>");
// Pass parameters as query string for functions
const param1 = Server.Context.QueryParameters["param1"];
const queryString = "<custom-function-name>(Param1='" + param1 + "')";
const result = Server.Connector.Dataverse.InvokeCustomApi(
"GET",
queryString,
null
);
Server.Logger.Log("<name> custom function completed successfully");
return JSON.stringify({
status: "success",
data: result
});
} catch (err) {
Server.Logger.Error("<name> GET failed: " + err.message);
return JSON.stringify({
status: "error",
message: err.message
});
}
}Microsoft.Dynamics.CRM.<action-name>"GET""POST"InvokeCustomApiasyncawaitServer.Connector.Dataverse.InvokeCustomApi/_api/serverlogics/<name>// Server Logic: <name>
// Purpose: Wraps Dataverse custom action "<custom-action-name>" for portal consumption
// Custom Action: <custom-action-name> (unbound, action)
// API URL: https://<site-url>/_api/serverlogics/<name>
function post() {
try {
Server.Logger.Log("<name> POST called — invoking custom action <custom-action-name>");
const body = JSON.parse(Server.Context.Body);
// Build the request payload matching the custom action's input parameters
const payload = JSON.stringify({
// "<ParameterName>": body.<clientFieldName>
});
const result = Server.Connector.Dataverse.InvokeCustomApi(
"POST",
"<custom-action-name>",
payload
);
Server.Logger.Log("<name> custom action completed successfully");
return JSON.stringify({
status: "success",
data: result
});
} catch (err) {
Server.Logger.Error("<name> POST failed: " + err.message);
return JSON.stringify({
status: "error",
message: err.message
});
}
}function post() {
try {
Server.Logger.Log("<name> POST called — invoking bound action <custom-action-name>");
const body = JSON.parse(Server.Context.Body);
const entityId = body.entityId;
const payload = JSON.stringify({
// "<ParameterName>": body.<clientFieldName>
});
// Include the entity set and record ID, followed by the fully qualified action name
const result = Server.Connector.Dataverse.InvokeCustomApi(
"POST",
"<entity-set-name>(" + entityId + ")/Microsoft.Dynamics.CRM.<custom-action-name>",
payload
);
Server.Logger.Log("<name> bound action completed for entity " + entityId);
return JSON.stringify({
status: "success",
data: result,
entityId: entityId
});
} catch (err) {
Server.Logger.Error("<name> POST failed: " + err.message);
return JSON.stringify({
status: "error",
message: err.message
});
}
}function get() {
try {
Server.Logger.Log("<name> GET called — invoking custom function <custom-function-name>");
// Pass parameters as query string for functions
const param1 = Server.Context.QueryParameters["param1"];
const queryString = "<custom-function-name>(Param1='" + param1 + "')";
const result = Server.Connector.Dataverse.InvokeCustomApi(
"GET",
queryString,
null
);
Server.Logger.Log("<name> custom function completed successfully");
return JSON.stringify({
status: "success",
data: result
});
} catch (err) {
Server.Logger.Error("<name> GET failed: " + err.message);
return JSON.stringify({
status: "error",
message: err.message
});
}
}Microsoft.Dynamics.CRM.<action-name>"GET""POST"InvokeCustomApiasyncawaitconst apiKey = Server.Sitesetting.Get("ExternalApi/ExchangeRateApiKey");const apiKey = Server.Sitesetting.Get("ExternalApi/ExchangeRateApiKey");createupdatereusenode "${CLAUDE_PLUGIN_ROOT}/skills/add-server-logic/scripts/create-serverlogic-metadata.js" --projectRoot "<PROJECT_ROOT>" --name "<name>" --displayName "<human-readable display name>" --description "<description of what this server logic does>" --webRoleIds "<uuid1,uuid2,uuid3>"<PROJECT_ROOT>/.powerpages-site/server-logic/<name>/<name>.serverlogic.ymladx_serverlogic_adx_webrole:
- <web-role-guid-1>
- <web-role-guid-2>
- <web-role-guid-3>
description: <description of what this server logic does>
display_name: <human-readable display name>
id: <generated-uuid>
name: <name>idExpected Guid for primary key 'id'adx_serverlogic_adx_webrolename.js/_api/serverlogics/<name>display_nameadx_serverlogic_adx_webroledescriptiondisplay_nameidnamecreateupdatereusenode "${CLAUDE_PLUGIN_ROOT}/skills/add-server-logic/scripts/create-serverlogic-metadata.js" --projectRoot "<PROJECT_ROOT>" --name "<name>" --displayName "<human-readable display name>" --description "<description of what this server logic does>" --webRoleIds "<uuid1,uuid2,uuid3>"<PROJECT_ROOT>/.powerpages-site/server-logic/<name>/<name>.serverlogic.ymladx_serverlogic_adx_webrole:
- <web-role-guid-1>
- <web-role-guid-2>
- <web-role-guid-3>
description: <description of what this server logic does>
display_name: <human-readable display name>
id: <generated-uuid>
name: <name>idExpected Guid for primary key 'id'adx_serverlogic_adx_webrolename.js/_api/serverlogics/<name>display_nameadx_serverlogic_adx_webroledescriptiondisplay_nameidname| Constraint | Check |
|---|---|
| Only allowed top-level functions | No functions other than get, post, put, patch, del |
| Every function returns a string | All code paths return a string (including catch blocks) |
| try/catch in every function | Every function body is wrapped in try/catch |
| Server.Logger in every function | Log at entry, Error in catch |
| No external dependencies | No |
| No browser APIs | No |
| Async only when needed | Only functions using |
| ECMAScript 2023 compliant | Standard JS features only (optional chaining, nullish coalescing, etc. are fine) |
| 约束 | 检查内容 |
|---|---|
| 仅允许顶级函数 | 无get、post、put、patch、del之外的函数 |
| 每个函数返回字符串 | 所有代码路径返回字符串(包括catch块) |
| 每个函数包含try/catch | 每个函数体都包装在try/catch中 |
| 每个函数使用Server.Logger | 入口处记录日志,catch块中记录错误 |
| 无外部依赖 | 无 |
| 无浏览器API | 无 |
| 仅必要时使用Async | 仅使用 |
| 符合ECMAScript 2023标准 | 仅使用标准JS功能(可选链、空值合并等是允许的) |
.js.serverlogic.yml.js.serverlogic.ymlServer.Connector.DataverseServer.Connector.DataverseServer.Connector.HttpClientServer.Connector.DataverseServer.Connector.DataverseServer.Connector.DataverseServer.Connector.HttpClientServer.Connector.Dataverse.js| Dataverse SDK Method | Required Table Permission |
|---|---|
| Read |
| Read |
| Create |
| Write |
| Delete |
| Table (entity set name) | Read | Create | Write | Delete |
|---|---|---|---|---|
| Yes | — | — | — |
| Yes | Yes | — | — |
.js| Dataverse SDK方法 | 所需表权限 |
|---|---|
| 读取 |
| 读取 |
| 创建 |
| 写入 |
| 删除 |
| 表(实体集名称) | 读取 | 创建 | 写入 | 删除 |
|---|---|---|---|---|
| 是 | — | — | — |
| 是 | 是 | — | — |
Server.Connector.Dataversetable-permissions-architect${CLAUDE_PLUGIN_ROOT}/agents/table-permissions-architect.md"Analyze this Power Pages code site and propose table permissions for Dataverse tables accessed by the approved server logic plan. The following tables need permissions:[list each table with required CRUD privileges from step 6.1, grouped by server logic item]Context:
- These permissions are needed because the server logic uses
, which respects table permissions — without them, the connector silently returns 0 records.Server.Connector.Dataverse- The scope should typically be Global for server logic that fetches all records, unless the server logic filters by the current user (in which case use Contact scope).
- The web roles assigned to these server logic items are: [list web role names and GUIDs from Phase 5.2]
- Project root: [path]
Check for existing table permissions and web roles. If new web roles are needed, create them using the create-web-role.js script. Propose a plan, then after approval create the table permission YAML files using the deterministic scripts."
create-web-role.js.powerpages-site/table-permissions/create-table-permission.jsServer.Connector.Dataverse${CLAUDE_PLUGIN_ROOT}/agents/table-permissions-architect.mdtable-permissions-architect"分析此Power Pages代码站点,并为经批准的Server Logic计划访问的Dataverse表提出表权限建议。以下表需要权限:[列出步骤6.1中每个表的所需CRUD权限,按Server Logic项分组]上下文:
- 需要这些权限是因为Server Logic使用
,它会遵守表权限——没有权限的话,连接器会静默返回0条记录。Server.Connector.Dataverse- 范围通常应为全局,适用于获取所有记录的Server Logic,除非Server Logic按当前用户过滤(在这种情况下使用联系人范围)。
- 分配给这些Server Logic项的Web角色是:[列出阶段5.2中的Web角色名称和GUID]
- 项目根目录:[路径]
检查现有表权限和Web角色。如果需要新的Web角色,请使用create-web-role.js脚本创建。提出计划,然后在批准后使用确定性脚本创建表权限YAML文件。"
create-web-role.jscreate-table-permission.js.powerpages-site/table-permissions/node "${CLAUDE_PLUGIN_ROOT}/scripts/list-azure-keyvaults.js"nameresourceGrouplocationAskUserQuestion| Question | Context |
|---|---|
| Which Azure Key Vault would you like to use for storing secrets? | Present the names from the script output |
AskUserQuestion| Question | Options |
|---|---|
| No Azure Key Vaults were found in your subscription. Would you like to create one, or fall back to storing secrets directly as environment variables? | Create a new Key Vault (Recommended), Store directly as environment variable |
AskUserQuestion| Question | Context |
|---|---|
| What name, resource group, and Azure region would you like for the new Key Vault? | Vault names must be 3-24 characters, globally unique, start with a letter, and contain only alphanumerics and hyphens. Suggest a name based on the project/site name. |
node "${CLAUDE_PLUGIN_ROOT}/scripts/create-azure-keyvault.js" \
--name "<vault-name>" \
--resourceGroup "<resource-group>" \
--location "<location>"nameresourceGrouplocationFor each secret, run the following command (replacing <YOUR_SECRET_VALUE> with the actual value):
1. <secret-name>:
printf '%s' '<YOUR_SECRET_VALUE>' | node "${CLAUDE_PLUGIN_ROOT}/scripts/store-keyvault-secret.js" \
--vaultName "<selected-vault>" \
--secretName "<secret-name>"secretUri1. Go to the Azure Portal (https://portal.azure.com)
2. Search for "Key vaults" in the top search bar and select it
3. Select the Key Vault: <selected-vault>
4. In the left menu under "Objects", click "Secrets"
5. Click "+ Generate/Import" at the top
6. Fill in the fields:
- Upload options: Manual
- Name: <secret-name>
- Secret value: paste your secret value here
- Leave other fields as defaults
7. Click "Create"
8. After creation, click on the secret name, then click the current version
9. Copy the "Secret Identifier" URI and share it here so the workflow can continuehttps://<vault-name>.vault.azure.net/secrets/<secret-name>/<version>secretUrisecretnode "${CLAUDE_PLUGIN_ROOT}/scripts/create-environment-variable.js" "<ENV_URL>" \
--schemaName "<prefix_SecretName>" \
--displayName "<Secret Display Name>" \
--type "secret" \
--value "<secretUri-from-step-3>"node "${CLAUDE_PLUGIN_ROOT}/scripts/create-site-setting.js" \
--projectRoot "<PROJECT_ROOT>" \
--name "<SiteSetting/Name>" \
--envVarSchema "<schemaName-from-step-4>"envvar_schemasource: 1node "${CLAUDE_PLUGIN_ROOT}/scripts/list-azure-keyvaults.js"nameresourceGrouplocationAskUserQuestion| 问题 | 上下文 |
|---|---|
| 您想使用哪个Azure Key Vault存储密钥? | 展示脚本输出中的名称 |
AskUserQuestion| 问题 | 选项 |
|---|---|
| 在您的订阅中未找到Azure Key Vault。您想创建一个,还是回退到直接将密钥存储为环境变量? | 创建新的Key Vault(推荐),直接存储为环境变量 |
AskUserQuestion| 问题 | 上下文 |
|---|---|
| 您希望新Key Vault使用什么名称、资源组和Azure区域? | Vault名称必须为3-24个字符,全局唯一,以字母开头,仅包含字母数字和连字符。根据项目/站点名称建议一个名称。 |
node "${CLAUDE_PLUGIN_ROOT}/scripts/create-azure-keyvault.js" \
--name "<vault-name>" \
--resourceGroup "<resource-group>" \
--location "<location>"nameresourceGrouplocation对于每个密钥,运行以下命令(将<YOUR_SECRET_VALUE>替换为实际值):
1. <secret-name>:
printf '%s' '<YOUR_SECRET_VALUE>' | node "${CLAUDE_PLUGIN_ROOT}/scripts/store-keyvault-secret.js" \
--vaultName "<selected-vault>" \
--secretName "<secret-name>"secretUri1. 转到Azure门户(https://portal.azure.com)
2. 在顶部搜索栏中搜索“Key vaults”并选择
3. 选择Key Vault:<selected-vault>
4. 在左侧菜单的“对象”下,点击“Secrets”
5. 点击顶部的“+ 生成/导入”
6. 填写字段:
- 上传选项:手动
- 名称:<secret-name>
- 密钥值:在此粘贴您的密钥值
- 其他字段保留默认值
7. 点击“创建”
8. 创建后,点击密钥名称,然后点击当前版本
9. 复制“密钥标识符”URI并在此分享,以便工作流程继续https://<vault-name>.vault.azure.net/secrets/<secret-name>/<version>secretUrisecretnode "${CLAUDE_PLUGIN_ROOT}/scripts/create-environment-variable.js" "<ENV_URL>" \
--schemaName "<prefix_SecretName>" \
--displayName "<Secret Display Name>" \
--type "secret" \
--value "<secretUri-from-step-3>"node "${CLAUDE_PLUGIN_ROOT}/scripts/create-site-setting.js" \
--projectRoot "<PROJECT_ROOT>" \
--name "<SiteSetting/Name>" \
--envVarSchema "<schemaName-from-step-4>"envvar_schemasource: 1node "${CLAUDE_PLUGIN_ROOT}/scripts/create-environment-variable.js" "<ENV_URL>" \
--schemaName "<prefix_SecretName>" \
--displayName "<Secret Display Name>" \
--value "PLACEHOLDER_SET_ACTUAL_VALUE"node "${CLAUDE_PLUGIN_ROOT}/scripts/create-site-setting.js" \
--projectRoot "<PROJECT_ROOT>" \
--name "<SiteSetting/Name>" \
--envVarSchema "<schemaName-from-step-1>"node "${CLAUDE_PLUGIN_ROOT}/scripts/create-environment-variable.js" "<ENV_URL>" \
--schemaName "<prefix_SecretName>" \
--displayName "<Secret Display Name>" \
--value "PLACEHOLDER_SET_ACTUAL_VALUE"node "${CLAUDE_PLUGIN_ROOT}/scripts/create-site-setting.js" \
--projectRoot "<PROJECT_ROOT>" \
--name "<SiteSetting/Name>" \
--envVarSchema "<schemaName-from-step-1>".powerpages-site/site-settings/envvar_schemasource: 1Server.Sitesetting.Get("<SiteSetting/Name>").powerpages-site/site-settings/envvar_schemasource: 1Server.Sitesetting.Get("<SiteSetting/Name>").powerpages-site| Setting | Description | Default | When to configure |
|---|---|---|---|
| Enable/disable server logic feature | | Only if explicitly disabled and needs re-enabling |
| Restrict which external domains HttpClient can call | All domains | When the server logic calls external APIs and you want to restrict to specific domains for security |
| Maximum execution time | | The platform caps this at 120 seconds — values above 120 are silently clamped. Only configure when you need to lower the timeout, not raise it. |
| Allow networking across domains | | Set to |
node "${CLAUDE_PLUGIN_ROOT}/scripts/create-site-setting.js" --projectRoot "<PROJECT_ROOT>" --name "ServerLogic/AllowedDomains" --value "api.example.com,api.other.com" --description "Restrict server logic external API calls to these domains".powerpages-site| 设置 | 描述 | 默认值 | 配置场景 |
|---|---|---|---|
| 启用/禁用Server Logic功能 | | 仅当明确禁用并需要重新启用时 |
| 限制HttpClient可以调用的外部域 | 所有域 | 当Server Logic调用外部API并希望限制为特定域以提高安全性时 |
| 最大执行时间 | | 平台将此值上限为120秒——超过120的值会被静默限制。仅在需要降低超时时配置,不要提高。 |
| 允许跨域联网 | | 当通过AllowedDomains限制时设置为 |
node "${CLAUDE_PLUGIN_ROOT}/scripts/create-site-setting.js" --projectRoot "<PROJECT_ROOT>" --name "ServerLogic/AllowedDomains" --value "api.example.com,api.other.com" --description "Restrict server logic external API calls to these domains"AskUserQuestion| Question | Options |
|---|---|
| I've created the server logic backend. Would you like me to also fully integrate it into the frontend UI? | Yes, fully integrate it into the UI (Recommended), No, I'll handle the frontend myself |
AskUserQuestion| 问题 | 选项 |
|---|---|
| 我已创建Server Logic后端。是否要将其完全集成到前端UI中? | 是,完全集成到UI中(推荐);否,我将自行处理前端 |
Reference:${CLAUDE_PLUGIN_ROOT}/skills/add-server-logic/references/frontend-integration-reference.md
参考:${CLAUDE_PLUGIN_ROOT}/skills/add-server-logic/references/frontend-integration-reference.md
.jsimportrequirefetchXMLHttpRequestsetTimeoutconsole.logdocumentwindow.serverlogic.ymlidadx_serverlogic_adx_webrolename.jsdisplay_namedescription.js.serverlogic.ymlname.jsimportrequirefetchXMLHttpRequestsetTimeoutconsole.logdocumentwindow.serverlogic.ymlidadx_serverlogic_adx_webrolename.jsdisplay_namedescription.js.serverlogic.ymlnamehttps://<site-url>/_api/serverlogics/<server-logic-name>https://<site-url>/_api/serverlogics/<server-logic-name>/deploy-site/_layout/tokenhtml__RequestVerificationToken/deploy-site/_layout/tokenhtml__RequestVerificationTokenReference:${CLAUDE_PLUGIN_ROOT}/references/skill-tracking-reference.md
--skillName "AddServerLogic"参考:${CLAUDE_PLUGIN_ROOT}/references/skill-tracking-reference.md
--skillName "AddServerLogic"| Step | Status | Details |
|---|---|---|
| Server Logic JS | Created | List each created |
| Server Logic YAML | Created | List each created |
| HTML Plan | Created/Updated | Actual path from render script output |
| Functions | Implemented | Summarize methods implemented per server logic item |
| SDK Features Used | — | Summarize features used per server logic item |
| Table Permissions | Created/Skipped | |
| Secrets & Env Vars | Created/Skipped | Environment variables (Key Vault-backed or direct), site settings with |
| Site Settings | Created/Skipped | ServerLogic/AllowedDomains, etc. |
| Client-Side Service | Created/Skipped | List created or updated frontend service files |
| UI Integration | Created/Skipped | Pages, components, forms, or actions fully wired to the server logic endpoints |
| API URL | — | List each |
| 步骤 | 状态 | 详细信息 |
|---|---|---|
| Server Logic JS | 已创建 | 列出每个已创建的 |
| Server Logic YAML | 已创建 | 列出每个已创建的 |
| HTML计划 | 已创建/更新 | 渲染脚本输出的实际路径 |
| 函数 | 已实现 | 总结每个Server Logic项实现的方法 |
| 使用的SDK功能 | — | 总结每个Server Logic项使用的功能 |
| 表权限 | 已创建/已跳过 | |
| 密钥与环境变量 | 已创建/已跳过 | 环境变量(Key Vault支持或直接存储)、带 |
| 站点设置 | 已创建/已跳过 | ServerLogic/AllowedDomains等 |
| 客户端服务 | 已创建/已跳过 | 列出已创建或更新的前端服务文件 |
| UI集成 | 已创建/已跳过 | 已完全接入Server Logic端点的页面、组件、表单或操作 |
| API URL | — | 列出每个 |
AskUserQuestion| Question | Options |
|---|---|
| The server logic work is ready. To make it live, the site needs to be deployed. Would you like to deploy now? | Yes, deploy now (Recommended), No, I'll deploy later |
/deploy-siteAskUserQuestion| Question | Options |
|---|---|
The site has been deployed. Would you like me to run | Yes, run |
/test-site/test-site"No problem! Remember to deploy your site usingwhen you're ready. The server logic endpoints won't be accessible until the site is deployed."/deploy-site
AskUserQuestion| 问题 | 选项 |
|---|---|
| Server Logic工作已准备就绪。要使其生效,需要部署站点。是否现在部署? | 是,立即部署(推荐);否,我稍后部署 |
/deploy-siteAskUserQuestion| 问题 | 选项 |
|---|---|
站点已部署。是否要运行 | 是,运行 |
/test-site/test-site"没问题!准备好后请使用部署站点。Server Logic端点在站点部署前无法访问。"/deploy-site
/_api/serverlogics/<name>/test-siteServer.Connector.Dataverse/_api/serverlogics/<name>/test-siteServer.Connector.Dataverse.powerpages-sitetable-permissions-architect.powerpages-sitetable-permissions-architectTaskCreate| Task subject | activeForm | Description |
|---|---|---|
| Verify site exists | Verifying site prerequisites | Locate project root, detect framework, explore existing server logics and frontend patterns, verify .powerpages-site exists (mandatory) |
| Understand requirements | Gathering requirements | Determine user intent, whether one or more server logic files are needed, the methods/features for each item, discover Dataverse custom actions, and any secrets required |
| Fetch latest documentation | Fetching Microsoft Learn docs | Query Microsoft Learn for current Server Logic SDK reference and samples |
| Review implementation plan | Reviewing plan with user | Present plan (server logic inventory, functions, SDK features, external APIs, secrets) and confirm before writing code |
| Implement server logic | Writing server logic code | Determine/create required web roles, create approved |
| Configure table permissions | Setting up Dataverse table permissions | (Conditional) Parse |
| Manage secrets and environment variables | Configuring secrets and env vars | (Conditional) Recommend Azure Key Vault, list vaults, store secrets, create environment variables in Dataverse, create site settings with envvar_schema |
| Configure site settings | Configuring site settings | Set up ServerLogic/* site settings if needed |
| Client-side integration | Wiring frontend to server logic | Follow the frontend integration reference, create/update service files as needed, and fully wire the UI to the server logic endpoints |
| Verify and test guidance | Validating and providing test guidance | Final validation, API URLs, CSRF token instructions, testing guide |
| Review and deploy | Reviewing summary and deploying | Present summary, ask about deployment, provide post-deploy guidance |
in_progresscompletedTaskUpdateTaskListTaskCreate| 任务主题 | activeForm | 描述 |
|---|---|---|
| 验证站点存在 | Verifying site prerequisites | 定位项目根目录、检测框架、探索现有Server Logic和前端模式、验证.powerpages-site存在(必填) |
| 理解需求 | Gathering requirements | 确定用户意图、是否需要一个或多个Server Logic文件、每个项的方法/功能、发现Dataverse自定义操作以及任何所需密钥 |
| 获取最新文档 | Fetching Microsoft Learn docs | 查询Microsoft Learn获取当前Server Logic SDK参考和示例 |
| 审核实施计划 | Reviewing plan with user | 展示计划(Server Logic清单、函数、SDK功能、外部API、密钥)并在编写代码前确认 |
| 实现Server Logic | Writing server logic code | 确定/创建所需Web角色、创建经批准的.js和.serverlogic.yml文件、验证代码 |
| 配置表权限 | Setting up Dataverse table permissions | (条件性)解析.js文件查找Dataverse表、启动table-permissions-architect、创建权限YAML文件 |
| 管理密钥与环境变量 | Configuring secrets and env vars | (条件性)推荐Azure Key Vault、列出Vault、存储密钥、在Dataverse中创建环境变量、创建带envvar_schema的站点设置 |
| 配置站点设置 | Configuring site settings | 根据需要设置ServerLogic/*站点设置 |
| 客户端集成 | Wiring frontend to server logic | 遵循前端集成参考、根据需要创建/更新服务文件、并将UI完全接入Server Logic端点 |
| 验证与测试指南 | Validating and providing test guidance | 最终验证、API URL、CSRF令牌说明、测试指南 |
| 审核与部署 | Reviewing summary and deploying | 展示摘要、询问部署情况、提供部署后指南 |
in_progressTaskUpdatecompletedTaskList