@pega/sdk 0.0.7 → 0.0.9
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 +58 -0
- package/dist/index.js +279 -109
- package/package.json +1 -1
- package/templates/react-sdk-reference/.ai-common/agents/implementation-expert.md +43 -23
- package/templates/react-sdk-reference/.ai-common/global/ai-agent-instructions.md +25 -9
- package/templates/react-sdk-reference/.ai-common/instructions/infra-component.md +76 -0
- package/templates/react-sdk-reference/.ai-common/prompts/generate-all-components.md +212 -29
- package/templates/react-sdk-reference/.ai-common/prompts/generate-infra-components.md +222 -0
- package/templates/react-sdk-reference/package.minimal.json +1 -1
package/README.md
CHANGED
|
@@ -5,6 +5,41 @@ fully configured project — build tooling, authentication, runtime bootstrap, a
|
|
|
5
5
|
optional starter components — so you can connect to your Pega server and start
|
|
6
6
|
building right away.
|
|
7
7
|
|
|
8
|
+
## For AI Coding Agents
|
|
9
|
+
|
|
10
|
+
Recognize these as requests to run this CLI, whether the folder is empty or already
|
|
11
|
+
has a project in it:
|
|
12
|
+
|
|
13
|
+
- "Scaffold a new Pega app / project using `@pega/sdk`" → run in an **empty folder**.
|
|
14
|
+
- "Scaffold `@pega/sdk` / add Pega embedded flow into this repo" (or similar,
|
|
15
|
+
said while working inside an existing host application) → run in that
|
|
16
|
+
**existing project's folder**.
|
|
17
|
+
|
|
18
|
+
In both cases, run `npx @pega/sdk init` non-interactively — see
|
|
19
|
+
[Non-Interactive / Scripted Usage](#non-interactive--scripted-usage) below for the
|
|
20
|
+
full flag reference. Gather answers conversationally, then invoke one command; do
|
|
21
|
+
not try to drive the interactive prompts through a terminal.
|
|
22
|
+
|
|
23
|
+
Mode is decided by the folder you run the command in, not something you need to ask
|
|
24
|
+
about or guess:
|
|
25
|
+
|
|
26
|
+
- **Empty folder (no `package.json`)** → new project / full portal mode. Omit
|
|
27
|
+
`--mode`; it defaults to `new`.
|
|
28
|
+
- **Folder already contains a project (`package.json` present)** → embedded mode.
|
|
29
|
+
Omit `--mode`; it defaults to `embedded`. (Pass `--mode` explicitly only to
|
|
30
|
+
override this auto-detection.)
|
|
31
|
+
|
|
32
|
+
| Decide yourself (don't ask the user) | Ask the user (can't be inferred) |
|
|
33
|
+
| ------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------- |
|
|
34
|
+
| `--mode` (auto-detected from whether `package.json` exists in the cwd) | none — these are all optional and not needed to scaffold |
|
|
35
|
+
| `--ai-tool` (you know which tool you are) | |
|
|
36
|
+
| `--framework`, `--name`, `--templates`, `--categories` (infer from the request, or ask only if genuinely ambiguous) | |
|
|
37
|
+
|
|
38
|
+
`--server-url` / `--app-alias` / `--client-id` / `--mcp-rule-name` / `--mcp-rule-ins-key`
|
|
39
|
+
are optional, CI-only flags. Do not prompt the user for them at scaffold time —
|
|
40
|
+
they're configured afterward by editing `sdk-config.json` and the generated MCP
|
|
41
|
+
config file directly (see [Configure Your Pega Server](#configure-your-pega-server)).
|
|
42
|
+
|
|
8
43
|
## Requirements
|
|
9
44
|
|
|
10
45
|
- **Node.js** 20 or later
|
|
@@ -58,6 +93,29 @@ Running `init` walks you through an interactive setup:
|
|
|
58
93
|
If you skip starter components in a React project, the scaffold uses a minimal
|
|
59
94
|
dependency manifest for a lighter baseline app.
|
|
60
95
|
|
|
96
|
+
Server connection details (Pega server URL, app alias, OAuth client ID) and MCP
|
|
97
|
+
rule details are no longer asked interactively — edit `sdk-config.json` and the
|
|
98
|
+
generated MCP config file after scaffolding (see below).
|
|
99
|
+
|
|
100
|
+
## Non-Interactive / Scripted Usage
|
|
101
|
+
|
|
102
|
+
Pass any flag to `init` (or `--json`) to scaffold in one shot, with no prompts:
|
|
103
|
+
|
|
104
|
+
```bash
|
|
105
|
+
# New project
|
|
106
|
+
npx @pega/sdk init --name my-app --framework react --ai-tool copilot --templates false --yes
|
|
107
|
+
|
|
108
|
+
# Embedded flow, inside an existing project
|
|
109
|
+
npx @pega/sdk init --mode embedded --ai-tool copilot --conflict-policy merge
|
|
110
|
+
|
|
111
|
+
# Or supply everything via a JSON answers file (values here win over individual flags)
|
|
112
|
+
npx @pega/sdk init --json ./sdk-answers.json
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
Run `npx @pega/sdk init --help` for the full flag reference. On invalid or
|
|
116
|
+
missing required input, the CLI prints an error to stderr and exits with a
|
|
117
|
+
non-zero code — no hanging prompt.
|
|
118
|
+
|
|
61
119
|
## What You Get
|
|
62
120
|
|
|
63
121
|
The generated project is ready to run and includes:
|
package/dist/index.js
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
// src/index.ts
|
|
4
4
|
import * as p from "@clack/prompts";
|
|
5
5
|
import pc from "picocolors";
|
|
6
|
-
import
|
|
6
|
+
import path4 from "path";
|
|
7
7
|
import { createRequire } from "module";
|
|
8
8
|
|
|
9
9
|
// src/scaffold.ts
|
|
@@ -547,7 +547,7 @@ async function scaffold(choices, targetDir, onProgress) {
|
|
|
547
547
|
}
|
|
548
548
|
}
|
|
549
549
|
}
|
|
550
|
-
generateAiConfigs(targetDir,
|
|
550
|
+
generateAiConfigs(targetDir, choices);
|
|
551
551
|
}
|
|
552
552
|
report("Finalizing project configuration\u2026");
|
|
553
553
|
selectReactPackageManifest(targetDir, framework, includeTemplates, componentCategories);
|
|
@@ -791,6 +791,7 @@ function cursorSkillDescription(relPath) {
|
|
|
791
791
|
if (subdir === "template-skills") return `Template component skill: ${name}`;
|
|
792
792
|
if (subdir === "widget-skills") return `Widget component skill: ${name}`;
|
|
793
793
|
if (subdir === "design-system-extension-skills") return `DSE component skill: ${name}`;
|
|
794
|
+
if (subdir === "infra-skills") return `Infra component skill: ${name}`;
|
|
794
795
|
}
|
|
795
796
|
return relPath.replace(/\.md$/, "").split("/").pop();
|
|
796
797
|
}
|
|
@@ -1013,10 +1014,26 @@ function applyPlannedFiles(files, policy, options = {}) {
|
|
|
1013
1014
|
}
|
|
1014
1015
|
return counts;
|
|
1015
1016
|
}
|
|
1016
|
-
function generateAiConfigs(targetDir,
|
|
1017
|
+
function generateAiConfigs(targetDir, choices) {
|
|
1017
1018
|
const aiCommonDir = path2.join(targetDir, ".ai-common");
|
|
1018
1019
|
if (!fs2.existsSync(aiCommonDir)) return;
|
|
1019
|
-
|
|
1020
|
+
if (choices.aiTool) {
|
|
1021
|
+
applyPlannedFiles(planAiConfigs(aiCommonDir, targetDir, choices.aiTool), "override", { markers: false });
|
|
1022
|
+
}
|
|
1023
|
+
if (choices.aiTool && choices.serverUrl && choices.appAlias && choices.clientId) {
|
|
1024
|
+
const { config: mcpConfig, destRelPath: mcpDestRelPath } = resolveMcpConfig(aiCommonDir, {
|
|
1025
|
+
aiTool: choices.aiTool,
|
|
1026
|
+
serverUrl: choices.serverUrl,
|
|
1027
|
+
appAlias: choices.appAlias,
|
|
1028
|
+
clientId: choices.clientId,
|
|
1029
|
+
mcpRuleName: choices.mcpRuleName,
|
|
1030
|
+
mcpRuleInsKey: choices.mcpRuleInsKey
|
|
1031
|
+
});
|
|
1032
|
+
writeManagedJson(path2.join(targetDir, mcpDestRelPath), mcpConfig, "override", MCP_CONFIG_USER_KEYS);
|
|
1033
|
+
if (choices.aiTool === "cursor") {
|
|
1034
|
+
ensureGitignoreEntry(targetDir, mcpDestRelPath);
|
|
1035
|
+
}
|
|
1036
|
+
}
|
|
1020
1037
|
fs2.rmSync(aiCommonDir, { recursive: true });
|
|
1021
1038
|
}
|
|
1022
1039
|
var EMBEDDED_GLOBAL_FILE = "ai-agent-instructions-embedded.md";
|
|
@@ -1029,14 +1046,8 @@ function resolveEmbeddedSource() {
|
|
|
1029
1046
|
}
|
|
1030
1047
|
return { sampleDir, aiCommonSrc };
|
|
1031
1048
|
}
|
|
1032
|
-
var SDK_CONFIG_USER_KEYS = [
|
|
1033
|
-
|
|
1034
|
-
"serverConfig.appAlias",
|
|
1035
|
-
"authConfig.mashupClientId",
|
|
1036
|
-
"authConfig.mashupUserIdentifier",
|
|
1037
|
-
"authConfig.portalClientId"
|
|
1038
|
-
];
|
|
1039
|
-
var MCP_CONFIG_USER_KEYS = ["url", "clientId"];
|
|
1049
|
+
var SDK_CONFIG_USER_KEYS = [];
|
|
1050
|
+
var MCP_CONFIG_USER_KEYS = [];
|
|
1040
1051
|
var MCP_CONFIG_FILES = {
|
|
1041
1052
|
copilot: { source: "copilot.mcp.json", dest: path2.join(".vscode", "mcp.json") },
|
|
1042
1053
|
cursor: { source: "cursor.mcp.json", dest: path2.join(".cursor", "mcp.json") },
|
|
@@ -1048,11 +1059,10 @@ function resolveMcpConfig(aiCommonSrc, choices) {
|
|
|
1048
1059
|
if (!fs2.existsSync(srcPath)) {
|
|
1049
1060
|
throw new Error(`MCP config template not found at ${srcPath}. Ensure the template source is available.`);
|
|
1050
1061
|
}
|
|
1051
|
-
const tokens = {
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
};
|
|
1062
|
+
const tokens = {};
|
|
1063
|
+
if (choices.serverUrl) tokens["{InfinityURL}"] = choices.serverUrl;
|
|
1064
|
+
if (choices.appAlias) tokens["{AppAlias}"] = choices.appAlias;
|
|
1065
|
+
if (choices.clientId) tokens["{ClientId}"] = choices.clientId;
|
|
1056
1066
|
if (choices.mcpRuleName) tokens["{MCPRuleName}"] = choices.mcpRuleName;
|
|
1057
1067
|
if (choices.mcpRuleInsKey) tokens["{MCPRuleInsKey}"] = choices.mcpRuleInsKey;
|
|
1058
1068
|
const content = applyTokens(fs2.readFileSync(srcPath, "utf-8"), tokens);
|
|
@@ -1068,8 +1078,8 @@ function ensureGitignoreEntry(targetDir, entry) {
|
|
|
1068
1078
|
function buildSdkConfig(choices) {
|
|
1069
1079
|
return {
|
|
1070
1080
|
serverConfig: {
|
|
1071
|
-
infinityRestServerUrl: choices.serverUrl,
|
|
1072
|
-
appAlias: choices.appAlias,
|
|
1081
|
+
infinityRestServerUrl: choices.serverUrl ?? "",
|
|
1082
|
+
appAlias: choices.appAlias ?? "",
|
|
1073
1083
|
sdkContentServerUrl: "",
|
|
1074
1084
|
appPortal: "",
|
|
1075
1085
|
appMashupCaseType: "",
|
|
@@ -1084,10 +1094,10 @@ function buildSdkConfig(choices) {
|
|
|
1084
1094
|
},
|
|
1085
1095
|
authConfig: {
|
|
1086
1096
|
authService: "pega",
|
|
1087
|
-
mashupClientId: choices.clientId,
|
|
1088
|
-
mashupUserIdentifier:
|
|
1097
|
+
mashupClientId: choices.clientId ?? "",
|
|
1098
|
+
mashupUserIdentifier: "",
|
|
1089
1099
|
mashupPassword: "",
|
|
1090
|
-
portalClientId: choices.clientId,
|
|
1100
|
+
portalClientId: choices.clientId ?? "",
|
|
1091
1101
|
portalClientSecret: "",
|
|
1092
1102
|
portalGrantType: ""
|
|
1093
1103
|
}
|
|
@@ -1230,22 +1240,250 @@ function copyDir(src, dest) {
|
|
|
1230
1240
|
}
|
|
1231
1241
|
}
|
|
1232
1242
|
|
|
1243
|
+
// src/cli-args.ts
|
|
1244
|
+
import { parseArgs } from "util";
|
|
1245
|
+
import fs3 from "fs";
|
|
1246
|
+
import path3 from "path";
|
|
1247
|
+
var ALL_CATEGORIES = ["fields", "templates", "widgets", "infra", "designSystemExtension"];
|
|
1248
|
+
var FRAMEWORKS = ["react", "angular", "web-components", "react-native"];
|
|
1249
|
+
var AI_TOOLS = ["copilot", "cursor", "claude-code"];
|
|
1250
|
+
var PROJECT_NAME_RE = /^[a-z0-9@][a-z0-9._/-]*$/;
|
|
1251
|
+
var CliValidationError = class extends Error {
|
|
1252
|
+
};
|
|
1253
|
+
var HELP_TEXT = `
|
|
1254
|
+
Usage: npx @pega/sdk init [flags]
|
|
1255
|
+
|
|
1256
|
+
Run with no flags for the interactive setup. Pass any flag below (or --json)
|
|
1257
|
+
to scaffold non-interactively in one shot \u2014 no prompts are shown.
|
|
1258
|
+
|
|
1259
|
+
Common:
|
|
1260
|
+
--json <path|-> Answers file (JSON), matching the shapes documented in
|
|
1261
|
+
README.md. Use "-" to read from stdin. Values here win
|
|
1262
|
+
over the individual flags below when both are given.
|
|
1263
|
+
--mode <new|embedded> Override auto-detection of new-project vs embedded.
|
|
1264
|
+
--ai-tool <tool> copilot | cursor | claude-code (embedded: required; new: also accepts "none")
|
|
1265
|
+
--help Show this help.
|
|
1266
|
+
|
|
1267
|
+
New project:
|
|
1268
|
+
--name <name> Project directory name (required).
|
|
1269
|
+
--framework <fw> react | angular | web-components | react-native (required).
|
|
1270
|
+
--templates <bool> Include starter components. Default: true.
|
|
1271
|
+
--categories <list> Comma-separated: fields,templates,widgets,infra,designSystemExtension.
|
|
1272
|
+
Default: all categories when --templates is true.
|
|
1273
|
+
|
|
1274
|
+
Embedded:
|
|
1275
|
+
--conflict-policy <p> merge | override. Default: merge.
|
|
1276
|
+
|
|
1277
|
+
Optional, either mode (CI/scripted use only \u2014 a chat agent should not ask a
|
|
1278
|
+
user for these; they're configured post-scaffold by editing sdk-config.json /
|
|
1279
|
+
the generated MCP config file):
|
|
1280
|
+
--server-url <url>
|
|
1281
|
+
--app-alias <alias>
|
|
1282
|
+
--client-id <id>
|
|
1283
|
+
--mcp-rule-name <name>
|
|
1284
|
+
--mcp-rule-ins-key <key>
|
|
1285
|
+
|
|
1286
|
+
Examples:
|
|
1287
|
+
npx @pega/sdk init --name my-app --framework react --ai-tool copilot --templates false --yes
|
|
1288
|
+
npx @pega/sdk init --mode embedded --ai-tool copilot --conflict-policy merge
|
|
1289
|
+
npx @pega/sdk init --json ./sdk-answers.json
|
|
1290
|
+
`.trim();
|
|
1291
|
+
function parseCliArgs(argv) {
|
|
1292
|
+
let values;
|
|
1293
|
+
try {
|
|
1294
|
+
({ values } = parseArgs({
|
|
1295
|
+
args: argv,
|
|
1296
|
+
options: {
|
|
1297
|
+
help: { type: "boolean" },
|
|
1298
|
+
json: { type: "string" },
|
|
1299
|
+
yes: { type: "boolean" },
|
|
1300
|
+
mode: { type: "string" },
|
|
1301
|
+
name: { type: "string" },
|
|
1302
|
+
framework: { type: "string" },
|
|
1303
|
+
"ai-tool": { type: "string" },
|
|
1304
|
+
"server-url": { type: "string" },
|
|
1305
|
+
"app-alias": { type: "string" },
|
|
1306
|
+
"client-id": { type: "string" },
|
|
1307
|
+
"mcp-rule-name": { type: "string" },
|
|
1308
|
+
"mcp-rule-ins-key": { type: "string" },
|
|
1309
|
+
templates: { type: "string" },
|
|
1310
|
+
categories: { type: "string" },
|
|
1311
|
+
"conflict-policy": { type: "string" }
|
|
1312
|
+
},
|
|
1313
|
+
strict: true,
|
|
1314
|
+
allowPositionals: true
|
|
1315
|
+
}));
|
|
1316
|
+
} catch (err) {
|
|
1317
|
+
throw new CliValidationError(err instanceof Error ? err.message : String(err));
|
|
1318
|
+
}
|
|
1319
|
+
return {
|
|
1320
|
+
help: Boolean(values.help),
|
|
1321
|
+
json: values.json,
|
|
1322
|
+
yes: Boolean(values.yes),
|
|
1323
|
+
mode: values.mode,
|
|
1324
|
+
name: values.name,
|
|
1325
|
+
framework: values.framework,
|
|
1326
|
+
aiTool: values["ai-tool"],
|
|
1327
|
+
serverUrl: values["server-url"],
|
|
1328
|
+
appAlias: values["app-alias"],
|
|
1329
|
+
clientId: values["client-id"],
|
|
1330
|
+
mcpRuleName: values["mcp-rule-name"],
|
|
1331
|
+
mcpRuleInsKey: values["mcp-rule-ins-key"],
|
|
1332
|
+
templates: values.templates,
|
|
1333
|
+
categories: values.categories,
|
|
1334
|
+
conflictPolicy: values["conflict-policy"]
|
|
1335
|
+
};
|
|
1336
|
+
}
|
|
1337
|
+
function isNonInteractive(args) {
|
|
1338
|
+
return Boolean(
|
|
1339
|
+
args.help || args.json || args.mode || args.name || args.framework || args.aiTool || args.serverUrl || args.appAlias || args.clientId || args.mcpRuleName || args.mcpRuleInsKey || args.templates || args.categories || args.conflictPolicy || args.yes
|
|
1340
|
+
);
|
|
1341
|
+
}
|
|
1342
|
+
function readJsonAnswers(source) {
|
|
1343
|
+
const raw = source === "-" ? fs3.readFileSync(0, "utf-8") : fs3.readFileSync(path3.resolve(source), "utf-8");
|
|
1344
|
+
try {
|
|
1345
|
+
return JSON.parse(raw);
|
|
1346
|
+
} catch {
|
|
1347
|
+
throw new CliValidationError(`--json source "${source}" is not valid JSON.`);
|
|
1348
|
+
}
|
|
1349
|
+
}
|
|
1350
|
+
function pickFrom(answers, key, flagValue) {
|
|
1351
|
+
return answers[key] ?? flagValue;
|
|
1352
|
+
}
|
|
1353
|
+
function assertFramework(value) {
|
|
1354
|
+
if (typeof value === "string" && FRAMEWORKS.includes(value)) return value;
|
|
1355
|
+
throw new CliValidationError(`--framework is required and must be one of: ${FRAMEWORKS.join(", ")}`);
|
|
1356
|
+
}
|
|
1357
|
+
function parseAiTool(value) {
|
|
1358
|
+
if (value === void 0 || value === "none") return null;
|
|
1359
|
+
if (typeof value === "string" && AI_TOOLS.includes(value)) return value;
|
|
1360
|
+
throw new CliValidationError(`--ai-tool must be one of: ${AI_TOOLS.join(", ")}, or "none"`);
|
|
1361
|
+
}
|
|
1362
|
+
function parseRequiredAiTool(value) {
|
|
1363
|
+
if (typeof value === "string" && AI_TOOLS.includes(value)) return value;
|
|
1364
|
+
throw new CliValidationError(
|
|
1365
|
+
`--ai-tool is required for the embedded flow and must be one of: ${AI_TOOLS.join(", ")}`
|
|
1366
|
+
);
|
|
1367
|
+
}
|
|
1368
|
+
function parseBoolean(value, fallback) {
|
|
1369
|
+
if (value === void 0) return fallback;
|
|
1370
|
+
if (value === true || value === "true") return true;
|
|
1371
|
+
if (value === false || value === "false") return false;
|
|
1372
|
+
throw new CliValidationError(`Expected "true" or "false", got "${String(value)}"`);
|
|
1373
|
+
}
|
|
1374
|
+
function parseCategories(value) {
|
|
1375
|
+
if (value === void 0) return ALL_CATEGORIES;
|
|
1376
|
+
if (Array.isArray(value)) return value;
|
|
1377
|
+
const entries = String(value).split(",").map((entry) => entry.trim()).filter(Boolean);
|
|
1378
|
+
const valid = new Set(ALL_CATEGORIES);
|
|
1379
|
+
for (const entry of entries) {
|
|
1380
|
+
if (!valid.has(entry)) {
|
|
1381
|
+
throw new CliValidationError(`--categories entry "${entry}" is not one of: ${ALL_CATEGORIES.join(", ")}`);
|
|
1382
|
+
}
|
|
1383
|
+
}
|
|
1384
|
+
return entries;
|
|
1385
|
+
}
|
|
1386
|
+
function resolveNonInteractiveUserChoices(args) {
|
|
1387
|
+
const answers = args.json ? readJsonAnswers(args.json) : {};
|
|
1388
|
+
const projectName = pickFrom(answers, "projectName", args.name);
|
|
1389
|
+
if (typeof projectName !== "string" || !projectName) {
|
|
1390
|
+
throw new CliValidationError('Project name is required (--name, or "projectName" in --json).');
|
|
1391
|
+
}
|
|
1392
|
+
if (!PROJECT_NAME_RE.test(projectName)) {
|
|
1393
|
+
throw new CliValidationError("Invalid project name. Use lowercase letters, numbers, hyphens, and dots.");
|
|
1394
|
+
}
|
|
1395
|
+
const framework = assertFramework(pickFrom(answers, "framework", args.framework));
|
|
1396
|
+
const aiTool = parseAiTool(pickFrom(answers, "aiTool", args.aiTool));
|
|
1397
|
+
const isReactNative = framework === "react-native";
|
|
1398
|
+
const includeTemplates = isReactNative ? true : parseBoolean(pickFrom(answers, "includeTemplates", args.templates), true);
|
|
1399
|
+
const componentCategories = isReactNative ? ["fields", "templates", "infra"] : includeTemplates ? parseCategories(pickFrom(answers, "componentCategories", args.categories)) : [];
|
|
1400
|
+
return {
|
|
1401
|
+
projectName,
|
|
1402
|
+
framework,
|
|
1403
|
+
aiTool,
|
|
1404
|
+
includeTemplates,
|
|
1405
|
+
componentCategories,
|
|
1406
|
+
serverUrl: pickFrom(answers, "serverUrl", args.serverUrl),
|
|
1407
|
+
appAlias: pickFrom(answers, "appAlias", args.appAlias),
|
|
1408
|
+
clientId: pickFrom(answers, "clientId", args.clientId),
|
|
1409
|
+
mcpRuleName: pickFrom(answers, "mcpRuleName", args.mcpRuleName),
|
|
1410
|
+
mcpRuleInsKey: pickFrom(answers, "mcpRuleInsKey", args.mcpRuleInsKey)
|
|
1411
|
+
};
|
|
1412
|
+
}
|
|
1413
|
+
function resolveNonInteractiveEmbeddedChoices(args) {
|
|
1414
|
+
const answers = args.json ? readJsonAnswers(args.json) : {};
|
|
1415
|
+
const aiTool = parseRequiredAiTool(pickFrom(answers, "aiTool", args.aiTool));
|
|
1416
|
+
const conflictPolicyRaw = pickFrom(answers, "conflictPolicy", args.conflictPolicy);
|
|
1417
|
+
let conflictPolicy;
|
|
1418
|
+
if (conflictPolicyRaw !== void 0) {
|
|
1419
|
+
if (conflictPolicyRaw !== "merge" && conflictPolicyRaw !== "override") {
|
|
1420
|
+
throw new CliValidationError('--conflict-policy must be "merge" or "override"');
|
|
1421
|
+
}
|
|
1422
|
+
conflictPolicy = conflictPolicyRaw;
|
|
1423
|
+
}
|
|
1424
|
+
return {
|
|
1425
|
+
aiTool,
|
|
1426
|
+
serverUrl: pickFrom(answers, "serverUrl", args.serverUrl),
|
|
1427
|
+
appAlias: pickFrom(answers, "appAlias", args.appAlias),
|
|
1428
|
+
clientId: pickFrom(answers, "clientId", args.clientId),
|
|
1429
|
+
mcpRuleName: pickFrom(answers, "mcpRuleName", args.mcpRuleName),
|
|
1430
|
+
mcpRuleInsKey: pickFrom(answers, "mcpRuleInsKey", args.mcpRuleInsKey),
|
|
1431
|
+
conflictPolicy
|
|
1432
|
+
};
|
|
1433
|
+
}
|
|
1434
|
+
|
|
1233
1435
|
// src/index.ts
|
|
1234
1436
|
var require2 = createRequire(import.meta.url);
|
|
1235
1437
|
var { version: sdkVersion } = require2("../package.json");
|
|
1438
|
+
async function runNonInteractive(cwd, rawArgs) {
|
|
1439
|
+
const flags = parseCliArgs(rawArgs);
|
|
1440
|
+
if (flags.help) {
|
|
1441
|
+
console.log(HELP_TEXT);
|
|
1442
|
+
return;
|
|
1443
|
+
}
|
|
1444
|
+
try {
|
|
1445
|
+
const mode = flags.mode ?? (detectExistingProject(cwd) ? "embedded" : "new");
|
|
1446
|
+
if (mode === "embedded") {
|
|
1447
|
+
const choices = resolveNonInteractiveEmbeddedChoices(flags);
|
|
1448
|
+
const conflicts = collectEmbeddedConflicts(cwd, choices);
|
|
1449
|
+
if (conflicts.length > 0) {
|
|
1450
|
+
console.log(
|
|
1451
|
+
pc.dim(
|
|
1452
|
+
`Found ${conflicts.length} existing file(s); using conflict policy "${choices.conflictPolicy ?? "merge"}".`
|
|
1453
|
+
)
|
|
1454
|
+
);
|
|
1455
|
+
}
|
|
1456
|
+
await scaffoldEmbedded(choices, cwd, (message) => console.log(pc.dim(message)));
|
|
1457
|
+
console.log(pc.green("Embedded flow ready!"));
|
|
1458
|
+
} else {
|
|
1459
|
+
const choices = resolveNonInteractiveUserChoices(flags);
|
|
1460
|
+
const targetDir = path4.resolve(cwd, choices.projectName);
|
|
1461
|
+
await scaffold(choices, targetDir, (message) => console.log(pc.dim(message)));
|
|
1462
|
+
console.log(pc.green("Project scaffolded successfully!"));
|
|
1463
|
+
}
|
|
1464
|
+
} catch (err) {
|
|
1465
|
+
console.error(pc.red(err instanceof CliValidationError || err instanceof Error ? err.message : String(err)));
|
|
1466
|
+
process.exit(1);
|
|
1467
|
+
}
|
|
1468
|
+
}
|
|
1236
1469
|
async function main() {
|
|
1237
|
-
console.clear();
|
|
1238
|
-
p.intro(pc.bgCyan(pc.black(" @pega/sdk ")));
|
|
1239
|
-
p.log.info(`Using @pega/sdk v${sdkVersion}`);
|
|
1240
1470
|
const args = process.argv.slice(2);
|
|
1241
1471
|
const command = args[0];
|
|
1242
1472
|
if (command !== "init") {
|
|
1243
|
-
|
|
1473
|
+
console.error(`Unknown command "${command ?? ""}".
|
|
1244
1474
|
|
|
1245
|
-
Usage:
|
|
1475
|
+
Usage: npx @pega/sdk init`);
|
|
1246
1476
|
process.exit(1);
|
|
1247
1477
|
}
|
|
1248
1478
|
const cwd = process.cwd();
|
|
1479
|
+
const rest = args.slice(1);
|
|
1480
|
+
if (isNonInteractive(parseCliArgs(rest))) {
|
|
1481
|
+
await runNonInteractive(cwd, rest);
|
|
1482
|
+
return;
|
|
1483
|
+
}
|
|
1484
|
+
console.clear();
|
|
1485
|
+
p.intro(pc.bgCyan(pc.black(" @pega/sdk ")));
|
|
1486
|
+
p.log.info(`Using @pega/sdk v${sdkVersion}`);
|
|
1249
1487
|
const projectMode = detectExistingProject(cwd) ? "embedded" : "new";
|
|
1250
1488
|
if (projectMode === "embedded") {
|
|
1251
1489
|
const continueEmbedded = await p.confirm({
|
|
@@ -1286,84 +1524,12 @@ Usage: ${pc.cyan("npx @pega/sdk init")}`);
|
|
|
1286
1524
|
p.cancel("Operation cancelled.");
|
|
1287
1525
|
process.exit(0);
|
|
1288
1526
|
}
|
|
1289
|
-
p.log.info(pc.dim("\nNow let's connect to your Pega server."));
|
|
1290
|
-
const serverUrlResult = await p.text({
|
|
1291
|
-
message: "What is your Pega server URL?",
|
|
1292
|
-
placeholder: "https://your-pega-server.com/prweb",
|
|
1293
|
-
validate(value) {
|
|
1294
|
-
if (!value) return "Server URL is required.";
|
|
1295
|
-
if (!value.startsWith("http")) return "Must be a valid URL starting with http:// or https://";
|
|
1296
|
-
}
|
|
1297
|
-
});
|
|
1298
|
-
if (p.isCancel(serverUrlResult)) {
|
|
1299
|
-
p.cancel("Operation cancelled.");
|
|
1300
|
-
process.exit(0);
|
|
1301
|
-
}
|
|
1302
|
-
const serverUrl = serverUrlResult.replace(/\/+$/, "");
|
|
1303
|
-
const appAlias = await p.text({
|
|
1304
|
-
message: "What is your Pega application alias?",
|
|
1305
|
-
placeholder: "MyApp",
|
|
1306
|
-
validate(value) {
|
|
1307
|
-
if (!value) return "Application alias is required.";
|
|
1308
|
-
}
|
|
1309
|
-
});
|
|
1310
|
-
if (p.isCancel(appAlias)) {
|
|
1311
|
-
p.cancel("Operation cancelled.");
|
|
1312
|
-
process.exit(0);
|
|
1313
|
-
}
|
|
1314
|
-
const clientId = await p.text({
|
|
1315
|
-
message: "What is the OAuth Client ID for your app?",
|
|
1316
|
-
placeholder: "Your OAuth 2.0 client identifier",
|
|
1317
|
-
validate(value) {
|
|
1318
|
-
if (!value) return "Client ID is required for authentication.";
|
|
1319
|
-
}
|
|
1320
|
-
});
|
|
1321
|
-
if (p.isCancel(clientId)) {
|
|
1322
|
-
p.cancel("Operation cancelled.");
|
|
1323
|
-
process.exit(0);
|
|
1324
|
-
}
|
|
1325
|
-
const userIdentifier = await p.text({
|
|
1326
|
-
message: "What is the operator ID for the embedded session?",
|
|
1327
|
-
placeholder: "user@yourapp",
|
|
1328
|
-
validate(value) {
|
|
1329
|
-
if (!value) return "Operator ID is required.";
|
|
1330
|
-
}
|
|
1331
|
-
});
|
|
1332
|
-
if (p.isCancel(userIdentifier)) {
|
|
1333
|
-
p.cancel("Operation cancelled.");
|
|
1334
|
-
process.exit(0);
|
|
1335
|
-
}
|
|
1336
|
-
p.log.info(pc.dim("\nIf you've configured an MCP server for this app, we can wire it up too (optional)."));
|
|
1337
|
-
const mcpRuleNameResult = await p.text({
|
|
1338
|
-
message: "What is your Pega MCP rule name? (press Enter to skip)",
|
|
1339
|
-
placeholder: "SDKMCP"
|
|
1340
|
-
});
|
|
1341
|
-
if (p.isCancel(mcpRuleNameResult)) {
|
|
1342
|
-
p.cancel("Operation cancelled.");
|
|
1343
|
-
process.exit(0);
|
|
1344
|
-
}
|
|
1345
|
-
const mcpRuleInsKeyResult = await p.text({
|
|
1346
|
-
message: "What is your Pega MCP rule instance key? (press Enter to skip)",
|
|
1347
|
-
placeholder: "@baseclass!SDKMCP"
|
|
1348
|
-
});
|
|
1349
|
-
if (p.isCancel(mcpRuleInsKeyResult)) {
|
|
1350
|
-
p.cancel("Operation cancelled.");
|
|
1351
|
-
process.exit(0);
|
|
1352
|
-
}
|
|
1353
|
-
const mcpRuleName = mcpRuleNameResult || void 0;
|
|
1354
|
-
const mcpRuleInsKey = mcpRuleInsKeyResult || void 0;
|
|
1355
1527
|
const embeddedTargetDir = cwd;
|
|
1356
1528
|
p.note(
|
|
1357
1529
|
[
|
|
1358
1530
|
`${pc.bold("Mode")}: Embed Pega flow`,
|
|
1359
1531
|
`${pc.bold("Directory")}: ${embeddedTargetDir}`,
|
|
1360
|
-
`${pc.bold("AI Tool")}: ${embeddedAiTool}
|
|
1361
|
-
`${pc.bold("Server")}: ${serverUrl}`,
|
|
1362
|
-
`${pc.bold("App Alias")}: ${appAlias}`,
|
|
1363
|
-
`${pc.bold("Client ID")}: ${clientId}`,
|
|
1364
|
-
`${pc.bold("Operator")}: ${userIdentifier}`,
|
|
1365
|
-
`${pc.bold("MCP Rule")}: ${mcpRuleName ?? pc.dim("(not configured \u2014 markers left in generated file)")}`,
|
|
1366
|
-
`${pc.bold("MCP Ins Key")}: ${mcpRuleInsKey ?? pc.dim("(not configured \u2014 markers left in generated file)")}`
|
|
1532
|
+
`${pc.bold("AI Tool")}: ${embeddedAiTool}`
|
|
1367
1533
|
].join("\n"),
|
|
1368
1534
|
"Configuration"
|
|
1369
1535
|
);
|
|
@@ -1376,13 +1542,7 @@ Usage: ${pc.cyan("npx @pega/sdk init")}`);
|
|
|
1376
1542
|
process.exit(0);
|
|
1377
1543
|
}
|
|
1378
1544
|
const embeddedChoices = {
|
|
1379
|
-
aiTool: embeddedAiTool
|
|
1380
|
-
serverUrl,
|
|
1381
|
-
appAlias,
|
|
1382
|
-
clientId,
|
|
1383
|
-
userIdentifier,
|
|
1384
|
-
mcpRuleName,
|
|
1385
|
-
mcpRuleInsKey
|
|
1545
|
+
aiTool: embeddedAiTool
|
|
1386
1546
|
};
|
|
1387
1547
|
let conflicts = [];
|
|
1388
1548
|
try {
|
|
@@ -1471,6 +1631,11 @@ Usage: ${pc.cyan("npx @pega/sdk init")}`);
|
|
|
1471
1631
|
process.exit(0);
|
|
1472
1632
|
}
|
|
1473
1633
|
let aiTool = null;
|
|
1634
|
+
let serverUrl;
|
|
1635
|
+
let appAlias;
|
|
1636
|
+
let clientId;
|
|
1637
|
+
let mcpRuleName;
|
|
1638
|
+
let mcpRuleInsKey;
|
|
1474
1639
|
if (aiIntegration) {
|
|
1475
1640
|
const aiToolResult = await p.select({
|
|
1476
1641
|
message: "Which AI coding tool will you use?",
|
|
@@ -1581,7 +1746,7 @@ Usage: ${pc.cyan("npx @pega/sdk init")}`);
|
|
|
1581
1746
|
componentCategories = categories;
|
|
1582
1747
|
}
|
|
1583
1748
|
}
|
|
1584
|
-
const targetDir =
|
|
1749
|
+
const targetDir = path4.resolve(process.cwd(), projectName);
|
|
1585
1750
|
p.note(
|
|
1586
1751
|
[
|
|
1587
1752
|
`${pc.bold("Project")}: ${projectName}`,
|
|
@@ -1605,7 +1770,12 @@ Usage: ${pc.cyan("npx @pega/sdk init")}`);
|
|
|
1605
1770
|
framework,
|
|
1606
1771
|
aiTool,
|
|
1607
1772
|
includeTemplates,
|
|
1608
|
-
componentCategories
|
|
1773
|
+
componentCategories,
|
|
1774
|
+
serverUrl,
|
|
1775
|
+
appAlias,
|
|
1776
|
+
clientId,
|
|
1777
|
+
mcpRuleName,
|
|
1778
|
+
mcpRuleInsKey
|
|
1609
1779
|
};
|
|
1610
1780
|
const spinner2 = p.spinner();
|
|
1611
1781
|
spinner2.start("Scaffolding your project\u2026");
|