create-ec-app 1.0.0 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +50 -0
- package/dist/index.js.map +1 -1
- package/dist/libFunctions.js +1 -1
- package/dist/libFunctions.js.map +1 -1
- package/dist/pcf.d.ts +20 -0
- package/dist/pcf.d.ts.map +1 -0
- package/dist/pcf.js +130 -0
- package/dist/pcf.js.map +1 -0
- package/package.json +1 -1
- package/templates/pcf/base/ControlHost.pcfproj +15 -0
- package/templates/pcf/base/README.md +23 -0
- package/templates/pcf/base/control/ControlManifest.Input.xml +26 -0
- package/templates/pcf/base/featureconfig.json +3 -0
- package/templates/pcf/base/global.d.ts +1 -0
- package/templates/pcf/base/index.ts +152 -0
- package/templates/pcf/base/package.json +20 -0
- package/templates/pcf/base/strings/control.1033.resx +27 -0
- package/templates/pcf/base/tsconfig.json +31 -0
- package/templates/pcf/base/webpack.config.js +11 -0
- package/templates/targets/webresource/AGENTS.md +123 -0
- package/templates/targets/webresource/CLAUDE.md +1 -0
- package/templates/targets/webresource/README.md +70 -0
package/dist/index.js
CHANGED
|
@@ -4,10 +4,21 @@ import { fileURLToPath } from "node:url";
|
|
|
4
4
|
import { cancel, intro, isCancel, log, outro, select, spinner, text, } from "@clack/prompts";
|
|
5
5
|
import fs from "fs-extra";
|
|
6
6
|
import { applyLayer, replaceTokensRecursively } from "./libFunctions.js";
|
|
7
|
+
import { generatePcfFromExistingWebresource } from "./pcf.js";
|
|
7
8
|
const { execSync } = await import("node:child_process");
|
|
8
9
|
const __filename = fileURLToPath(import.meta.url);
|
|
9
10
|
const __dirname = dirname(__filename);
|
|
10
11
|
async function main() {
|
|
12
|
+
const cliArgs = parseCliArgs(process.argv.slice(2));
|
|
13
|
+
if (cliArgs.pcfDir) {
|
|
14
|
+
const { pcfDir, ...rest } = cliArgs;
|
|
15
|
+
const result = await generatePcfFromExistingWebresource({
|
|
16
|
+
pcfDir,
|
|
17
|
+
...stripUndefined(rest),
|
|
18
|
+
});
|
|
19
|
+
outro(`Generated PCF control at ${result.outputDir} using template ${result.templateDir}`);
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
11
22
|
intro("Create EC App");
|
|
12
23
|
const name = await text({
|
|
13
24
|
message: "Project name",
|
|
@@ -114,6 +125,45 @@ main().catch((err) => {
|
|
|
114
125
|
console.error(err);
|
|
115
126
|
process.exit(1);
|
|
116
127
|
});
|
|
128
|
+
function parseCliArgs(argv) {
|
|
129
|
+
const read = (name) => {
|
|
130
|
+
const index = argv.indexOf(name);
|
|
131
|
+
return index >= 0 ? argv[index + 1] : undefined;
|
|
132
|
+
};
|
|
133
|
+
const readAll = (name) => {
|
|
134
|
+
const values = [];
|
|
135
|
+
for (let index = 0; index < argv.length; index += 1) {
|
|
136
|
+
if (argv[index] === name && argv[index + 1]) {
|
|
137
|
+
values.push(argv[index + 1]);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
return values;
|
|
141
|
+
};
|
|
142
|
+
const readNumber = (name) => {
|
|
143
|
+
const value = read(name);
|
|
144
|
+
if (!value)
|
|
145
|
+
return undefined;
|
|
146
|
+
const parsed = Number(value);
|
|
147
|
+
return Number.isFinite(parsed) ? parsed : undefined;
|
|
148
|
+
};
|
|
149
|
+
return {
|
|
150
|
+
pcfDir: read("--pcf-dir"),
|
|
151
|
+
controlConstructor: read("--constructor"),
|
|
152
|
+
description: read("--description"),
|
|
153
|
+
displayName: read("--display-name"),
|
|
154
|
+
dist: read("--dist"),
|
|
155
|
+
layers: readAll("--layer"),
|
|
156
|
+
namespace: read("--namespace"),
|
|
157
|
+
output: read("--output"),
|
|
158
|
+
packageName: read("--package-name"),
|
|
159
|
+
template: read("--template"),
|
|
160
|
+
version: read("--version"),
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
function stripUndefined(value) {
|
|
164
|
+
const entries = Object.entries(value).filter(([, entryValue]) => entryValue !== undefined);
|
|
165
|
+
return Object.fromEntries(entries);
|
|
166
|
+
}
|
|
117
167
|
// NOTE: Constants
|
|
118
168
|
const POWER_PAGES_KENDO_MAIN_TSX = `import { StrictMode } from "react";
|
|
119
169
|
import { createRoot } from "react-dom/client";
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AAEA,OAAO,IAAI,EAAE,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAC1C,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AACzC,OAAO,EACN,MAAM,EACN,KAAK,EACL,QAAQ,EACR,GAAG,EACH,KAAK,EACL,MAAM,EACN,OAAO,EACP,IAAI,GACJ,MAAM,gBAAgB,CAAC;AACxB,OAAO,EAAE,MAAM,UAAU,CAAC;AAC1B,OAAO,EAAE,UAAU,EAAE,wBAAwB,EAAE,MAAM,mBAAmB,CAAC;
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
package/dist/libFunctions.js
CHANGED
|
@@ -24,7 +24,7 @@ export async function applyLayer(layerDir, projectDir) {
|
|
|
24
24
|
await fs.writeJson(targetPath, merged, { spaces: 2 });
|
|
25
25
|
continue;
|
|
26
26
|
}
|
|
27
|
-
if (/\.(patch\.ts|patch\.tsx|patch\.js|patch\.jsx|patch\.css)$/.test(entry.name)) {
|
|
27
|
+
if (/\.(patch\.ts|patch\.tsx|patch\.js|patch\.jsx|patch\.css|patch\.xml|patch\.md)$/.test(entry.name)) {
|
|
28
28
|
const targetRel = relPath.replace(".patch.", ".");
|
|
29
29
|
const targetPath = path.join(projectDir, targetRel);
|
|
30
30
|
await fs.ensureDir(path.dirname(targetPath));
|
package/dist/libFunctions.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"libFunctions.js","sourceRoot":"","sources":["../src/libFunctions.ts"],"names":[],"mappings":"AAAA,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,EAAE,MAAM,UAAU,CAAC;AAE1B;;;;KAIK;AACL,MAAM,CAAC,KAAK,UAAU,UAAU,CAAC,QAAgB,EAAE,UAAkB;IACpE,MAAM,OAAO,GAAG,MAAM,EAAE,CAAC,OAAO,CAAC,QAAQ,EAAE,EAAE,aAAa,EAAE,IAAI,EAAE,CAAC,CAAC;IAEpE,KAAK,MAAM,KAAK,IAAI,OAAO,EAAE,CAAC;QAC7B,MAAM,SAAS,GAAG,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,KAAK,CAAC,IAAI,CAAC,CAAC;QAClD,MAAM,OAAO,GAAG,IAAI,CAAC,QAAQ,CAAC,QAAQ,EAAE,SAAS,CAAC,CAAC;QAEnD,IAAI,KAAK,CAAC,WAAW,EAAE,EAAE,CAAC;YACzB,MAAM,UAAU,CAAC,SAAS,EAAE,IAAI,CAAC,IAAI,CAAC,UAAU,EAAE,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC;YAC/D,SAAS;QACV,CAAC;QAED,IAAI,KAAK,CAAC,IAAI,CAAC,QAAQ,CAAC,aAAa,CAAC,EAAE,CAAC;YACxC,MAAM,SAAS,GAAG,OAAO,CAAC,OAAO,CAAC,gBAAgB,EAAE,OAAO,CAAC,CAAC;YAC7D,MAAM,UAAU,GAAG,IAAI,CAAC,IAAI,CAAC,UAAU,EAAE,SAAS,CAAC,CAAC;YAEpD,MAAM,EAAE,CAAC,SAAS,CAAC,IAAI,CAAC,OAAO,CAAC,UAAU,CAAC,CAAC,CAAC;YAE7C,MAAM,QAAQ,GAAG,CAAC,MAAM,gBAAgB,CAAC,UAAU,CAAC,CAAC,IAAI,EAAE,CAAC;YAC5D,MAAM,SAAS,GAAG,MAAM,EAAE,CAAC,QAAQ,CAAC,SAAS,CAAC,CAAC;YAE/C,MAAM,MAAM,GAAG,SAAS,CAAC,QAAQ,EAAE,SAAS,CAAC,CAAC;YAC9C,MAAM,EAAE,CAAC,SAAS,CAAC,UAAU,EAAE,MAAM,EAAE,EAAE,MAAM,EAAE,CAAC,EAAE,CAAC,CAAC;YAEtD,SAAS;QACV,CAAC;QAED,IACC,
|
|
1
|
+
{"version":3,"file":"libFunctions.js","sourceRoot":"","sources":["../src/libFunctions.ts"],"names":[],"mappings":"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"}
|
package/dist/pcf.d.ts
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
export interface PcfCliOptions {
|
|
2
|
+
pcfDir: string;
|
|
3
|
+
controlConstructor?: string | undefined;
|
|
4
|
+
description?: string | undefined;
|
|
5
|
+
displayName?: string | undefined;
|
|
6
|
+
dist?: string | undefined;
|
|
7
|
+
layers?: string[] | undefined;
|
|
8
|
+
namespace?: string | undefined;
|
|
9
|
+
output?: string | undefined;
|
|
10
|
+
packageName?: string | undefined;
|
|
11
|
+
template?: string | undefined;
|
|
12
|
+
version?: string | undefined;
|
|
13
|
+
}
|
|
14
|
+
export declare function generatePcfFromExistingWebresource(options: PcfCliOptions): Promise<{
|
|
15
|
+
constructorName: string;
|
|
16
|
+
namespace: string;
|
|
17
|
+
outputDir: string;
|
|
18
|
+
templateDir: string;
|
|
19
|
+
}>;
|
|
20
|
+
//# sourceMappingURL=pcf.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pcf.d.ts","sourceRoot":"","sources":["../src/pcf.ts"],"names":[],"mappings":"AAyBA,MAAM,WAAW,aAAa;IAC7B,MAAM,EAAE,MAAM,CAAC;IACf,kBAAkB,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACxC,WAAW,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACjC,WAAW,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACjC,IAAI,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC1B,MAAM,CAAC,EAAE,MAAM,EAAE,GAAG,SAAS,CAAC;IAC9B,SAAS,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC/B,MAAM,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC5B,WAAW,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACjC,QAAQ,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC9B,OAAO,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;CAC7B;AAED,wBAAsB,kCAAkC,CACvD,OAAO,EAAE,aAAa,GACpB,OAAO,CAAC;IACV,eAAe,EAAE,MAAM,CAAC;IACxB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,EAAE,MAAM,CAAC;CACpB,CAAC,CA0FD"}
|
package/dist/pcf.js
ADDED
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
import path, { dirname } from "node:path";
|
|
2
|
+
import { fileURLToPath } from "node:url";
|
|
3
|
+
import fs from "fs-extra";
|
|
4
|
+
import { applyLayer, replaceTokensRecursively } from "./libFunctions.js";
|
|
5
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
6
|
+
const __dirname = dirname(__filename);
|
|
7
|
+
const RUNTIME_TYPES_TEMPLATE = `export interface PcfWebApi {
|
|
8
|
+
\tretrieve<T = Record<string, unknown>>(entitySet: string, id: string, query?: string): Promise<T>;
|
|
9
|
+
\tretrieveMultiple<T = Record<string, unknown>>(entitySet: string, query?: string): Promise<T[]>;
|
|
10
|
+
\tcreate<T = unknown>(entitySet: string, data: unknown): Promise<T>;
|
|
11
|
+
\tupdate(entitySet: string, id: string, data: unknown): Promise<void>;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export interface PcfRuntimeContext {
|
|
15
|
+
\thost: "pcf";
|
|
16
|
+
\trecordId: string | null;
|
|
17
|
+
\tentityName: string | null;
|
|
18
|
+
\tclientUrl: string | null;
|
|
19
|
+
\tuserId: string | null;
|
|
20
|
+
\twebApi: PcfWebApi;
|
|
21
|
+
}
|
|
22
|
+
`;
|
|
23
|
+
export async function generatePcfFromExistingWebresource(options) {
|
|
24
|
+
const projectDir = path.resolve(process.cwd(), options.pcfDir);
|
|
25
|
+
const packageJson = await readJson(path.join(projectDir, "package.json"));
|
|
26
|
+
const folderName = path.basename(projectDir);
|
|
27
|
+
const packageName = typeof packageJson?.name === "string" ? packageJson.name : folderName;
|
|
28
|
+
const displayName = toDisplayName(folderName);
|
|
29
|
+
const constructorName = options.controlConstructor ??
|
|
30
|
+
`${toPascalCase(packageName.replace(/[^a-z0-9]+/gi, " "))}Host`;
|
|
31
|
+
const namespace = options.namespace ?? "EC";
|
|
32
|
+
const version = options.version ?? "1.0.0";
|
|
33
|
+
const distDirName = options.dist ?? "dist";
|
|
34
|
+
const outputDir = path.resolve(projectDir, options.output ?? path.join("pcf", constructorName));
|
|
35
|
+
const templateDir = path.resolve(options.template ?? path.join(__dirname, "..", "templates", "pcf", "base"));
|
|
36
|
+
const layerDirs = (options.layers ?? []).map((layerDir) => path.resolve(projectDir, layerDir));
|
|
37
|
+
const controlDisplayName = options.displayName ?? `${displayName} Host`;
|
|
38
|
+
const controlDescription = options.description ??
|
|
39
|
+
`PCF wrapper that renders the ${displayName} React app directly inside a PCF control.`;
|
|
40
|
+
const packageNameToken = options.packageName ?? toKebabCase(constructorName);
|
|
41
|
+
const relToProject = toPosixPath(path.relative(outputDir, projectDir) || ".");
|
|
42
|
+
const appImportPath = ensureRelativeImport(toPosixPath(path.relative(outputDir, path.join(projectDir, "src", "App"))));
|
|
43
|
+
const runtimeTypesImportPath = ensureRelativeImport(toPosixPath(path.relative(outputDir, path.join(projectDir, "src", "runtime", "types"))));
|
|
44
|
+
const cssImportPath = ensureRelativeImport(toPosixPath(path.relative(outputDir, path.join(projectDir, distDirName, "main.css"))));
|
|
45
|
+
await assertFileExists(path.join(projectDir, "src", "App.tsx"), `Could not find src/App.tsx in ${projectDir}.`);
|
|
46
|
+
await assertFileExists(path.join(projectDir, distDirName, "main.css"), `Could not find ${distDirName}/main.css in ${projectDir}. Run the webresource build first.`);
|
|
47
|
+
await ensureRuntimeTypes(projectDir);
|
|
48
|
+
await fs.remove(outputDir);
|
|
49
|
+
await applyLayer(templateDir, outputDir);
|
|
50
|
+
for (const layerDir of layerDirs) {
|
|
51
|
+
await applyLayer(layerDir, outputDir);
|
|
52
|
+
}
|
|
53
|
+
await replaceTokensRecursively(outputDir, {
|
|
54
|
+
CONTROL_DESCRIPTION: controlDescription,
|
|
55
|
+
CONTROL_DISPLAY_NAME: controlDisplayName,
|
|
56
|
+
PCF_CONSTRUCTOR: constructorName,
|
|
57
|
+
PCF_NAMESPACE: namespace,
|
|
58
|
+
PCF_PACKAGE_NAME: packageNameToken,
|
|
59
|
+
PCF_VERSION: version,
|
|
60
|
+
PROJECT_APP_IMPORT: appImportPath,
|
|
61
|
+
PROJECT_CSS_IMPORT: cssImportPath,
|
|
62
|
+
PROJECT_NODE_MODULES_TYPES_ROOT: `${relToProject}/node_modules/@types`,
|
|
63
|
+
PROJECT_REACT_ALIAS: `${relToProject}/node_modules/react`,
|
|
64
|
+
PROJECT_REACT_DOM_ALIAS: `${relToProject}/node_modules/react-dom`,
|
|
65
|
+
PROJECT_ROOT_REL: relToProject,
|
|
66
|
+
PROJECT_RUNTIME_TYPES_IMPORT: runtimeTypesImportPath,
|
|
67
|
+
PROJECT_SRC_ALIAS: `${relToProject}/src`,
|
|
68
|
+
});
|
|
69
|
+
await renameIfExists(path.join(outputDir, "ControlHost.pcfproj"), path.join(outputDir, `${constructorName}.pcfproj`));
|
|
70
|
+
return {
|
|
71
|
+
constructorName,
|
|
72
|
+
namespace,
|
|
73
|
+
outputDir,
|
|
74
|
+
templateDir,
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
async function ensureRuntimeTypes(projectDir) {
|
|
78
|
+
const runtimeTypesPath = path.join(projectDir, "src", "runtime", "types.ts");
|
|
79
|
+
if (await fs.pathExists(runtimeTypesPath)) {
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
await fs.ensureDir(path.dirname(runtimeTypesPath));
|
|
83
|
+
await fs.writeFile(runtimeTypesPath, RUNTIME_TYPES_TEMPLATE, "utf8");
|
|
84
|
+
}
|
|
85
|
+
async function readJson(filePath) {
|
|
86
|
+
try {
|
|
87
|
+
return await fs.readJson(filePath);
|
|
88
|
+
}
|
|
89
|
+
catch {
|
|
90
|
+
return null;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
async function renameIfExists(fromPath, toPath) {
|
|
94
|
+
if (!(await fs.pathExists(fromPath))) {
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
await fs.move(fromPath, toPath, { overwrite: true });
|
|
98
|
+
}
|
|
99
|
+
async function assertFileExists(filePath, message) {
|
|
100
|
+
if (!(await fs.pathExists(filePath))) {
|
|
101
|
+
throw new Error(message);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
function toPascalCase(value) {
|
|
105
|
+
return value
|
|
106
|
+
.split(/[^a-z0-9]+/i)
|
|
107
|
+
.filter(Boolean)
|
|
108
|
+
.map((part) => part[0]?.toUpperCase() + part.slice(1).toLowerCase())
|
|
109
|
+
.join("");
|
|
110
|
+
}
|
|
111
|
+
function toKebabCase(value) {
|
|
112
|
+
return value
|
|
113
|
+
.replace(/([a-z0-9])([A-Z])/g, "$1-$2")
|
|
114
|
+
.replace(/[^a-z0-9]+/gi, "-")
|
|
115
|
+
.replace(/^-+|-+$/g, "")
|
|
116
|
+
.toLowerCase();
|
|
117
|
+
}
|
|
118
|
+
function toDisplayName(value) {
|
|
119
|
+
return value.replace(/[._-]+/g, " ").replace(/\s+/g, " ").trim();
|
|
120
|
+
}
|
|
121
|
+
function toPosixPath(value) {
|
|
122
|
+
return value.split(path.sep).join("/");
|
|
123
|
+
}
|
|
124
|
+
function ensureRelativeImport(value) {
|
|
125
|
+
if (value.startsWith(".")) {
|
|
126
|
+
return value;
|
|
127
|
+
}
|
|
128
|
+
return `./${value}`;
|
|
129
|
+
}
|
|
130
|
+
//# sourceMappingURL=pcf.js.map
|
package/dist/pcf.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pcf.js","sourceRoot":"","sources":["../src/pcf.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
<Project Sdk="Microsoft.NET.Sdk">
|
|
2
|
+
<PropertyGroup>
|
|
3
|
+
<TargetFramework>net462</TargetFramework>
|
|
4
|
+
<PowerAppsToolsVersion>1.38.3</PowerAppsToolsVersion>
|
|
5
|
+
<ControlManifestInputFile>control\ControlManifest.Input.xml</ControlManifestInputFile>
|
|
6
|
+
<OutputPath>$(MSBuildThisFileDirectory)out\controls\</OutputPath>
|
|
7
|
+
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
|
|
8
|
+
<RootNamespace>{{PCF_NAMESPACE}}.{{PCF_CONSTRUCTOR}}</RootNamespace>
|
|
9
|
+
<Name>{{PCF_CONSTRUCTOR}}</Name>
|
|
10
|
+
</PropertyGroup>
|
|
11
|
+
|
|
12
|
+
<ItemGroup>
|
|
13
|
+
<PackageReference Include="Microsoft.PowerApps.MSBuild.Pcf" Version="1.38.3" />
|
|
14
|
+
</ItemGroup>
|
|
15
|
+
</Project>
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# {{CONTROL_DISPLAY_NAME}}
|
|
2
|
+
|
|
3
|
+
This folder was generated from the webresource source using the checked-in PCF base template.
|
|
4
|
+
|
|
5
|
+
## Build
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install
|
|
9
|
+
npm run build
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
## Control Info
|
|
13
|
+
|
|
14
|
+
- Namespace: `{{PCF_NAMESPACE}}`
|
|
15
|
+
- Constructor: `{{PCF_CONSTRUCTOR}}`
|
|
16
|
+
- React app import: `{{PROJECT_APP_IMPORT}}`
|
|
17
|
+
- CSS import: `{{PROJECT_CSS_IMPORT}}`
|
|
18
|
+
|
|
19
|
+
## Notes
|
|
20
|
+
|
|
21
|
+
- The wrapper imports `src/App` directly and reuses the built `dist/main.css`.
|
|
22
|
+
- Regenerate this folder after rebuilding the webresource whenever the app changes.
|
|
23
|
+
- The project includes both `pcf-scripts` build support and a `.pcfproj` for Dataverse solution packaging flows.
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
<?xml version="1.0" encoding="utf-8" ?>
|
|
2
|
+
<manifest>
|
|
3
|
+
<control
|
|
4
|
+
namespace="{{PCF_NAMESPACE}}"
|
|
5
|
+
constructor="{{PCF_CONSTRUCTOR}}"
|
|
6
|
+
version="{{PCF_VERSION}}"
|
|
7
|
+
display-name-key="Control_Display_Name"
|
|
8
|
+
description-key="Control_Description"
|
|
9
|
+
control-type="standard">
|
|
10
|
+
<external-service-usage enabled="false" />
|
|
11
|
+
<property
|
|
12
|
+
name="hostField"
|
|
13
|
+
display-name-key="HostField_Display_Name"
|
|
14
|
+
description-key="HostField_Description"
|
|
15
|
+
of-type="SingleLine.Text"
|
|
16
|
+
usage="bound"
|
|
17
|
+
required="false" />
|
|
18
|
+
<feature-usage>
|
|
19
|
+
<uses-feature name="WebAPI" required="true" />
|
|
20
|
+
</feature-usage>
|
|
21
|
+
<resources>
|
|
22
|
+
<code path="../index.ts" order="1" />
|
|
23
|
+
<resx path="../strings/control.1033.resx" version="1.0.0" />
|
|
24
|
+
</resources>
|
|
25
|
+
</control>
|
|
26
|
+
</manifest>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
declare module "*.css";
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
import React, { StrictMode } from "react";
|
|
2
|
+
import { createRoot, type Root } from "react-dom/client";
|
|
3
|
+
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
|
|
4
|
+
|
|
5
|
+
import App from "{{PROJECT_APP_IMPORT}}";
|
|
6
|
+
import "{{PROJECT_CSS_IMPORT}}";
|
|
7
|
+
import type { IInputs, IOutputs } from "./control/generated/ManifestTypes";
|
|
8
|
+
import type {
|
|
9
|
+
PcfRuntimeContext,
|
|
10
|
+
PcfWebApi,
|
|
11
|
+
} from "{{PROJECT_RUNTIME_TYPES_IMPORT}}";
|
|
12
|
+
|
|
13
|
+
function sanitizeGuid(value: string | null | undefined): string | null {
|
|
14
|
+
if (!value) return null;
|
|
15
|
+
return value.replace(/[{}]/g, "");
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function getContextInfo(context: ComponentFramework.Context<IInputs>) {
|
|
19
|
+
const pageContext = (
|
|
20
|
+
context as ComponentFramework.Context<IInputs> & {
|
|
21
|
+
page?: {
|
|
22
|
+
entityId?: string;
|
|
23
|
+
entityTypeName?: string;
|
|
24
|
+
getClientUrl?: () => string;
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
).page;
|
|
28
|
+
|
|
29
|
+
const modeContextInfo = (
|
|
30
|
+
context.mode as ComponentFramework.Mode & {
|
|
31
|
+
contextInfo?: { entityId?: string; entityTypeName?: string };
|
|
32
|
+
}
|
|
33
|
+
).contextInfo;
|
|
34
|
+
|
|
35
|
+
return {
|
|
36
|
+
recordId: sanitizeGuid(
|
|
37
|
+
modeContextInfo?.entityId ?? pageContext?.entityId ?? null,
|
|
38
|
+
),
|
|
39
|
+
entityName:
|
|
40
|
+
modeContextInfo?.entityTypeName ?? pageContext?.entityTypeName ?? null,
|
|
41
|
+
clientUrl: pageContext?.getClientUrl?.() ?? null,
|
|
42
|
+
userId: sanitizeGuid(context.userSettings.userId),
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function createPcfWebApi(
|
|
47
|
+
context: ComponentFramework.Context<IInputs>,
|
|
48
|
+
): PcfWebApi {
|
|
49
|
+
return {
|
|
50
|
+
async retrieve<T>(entitySet: string, id: string, query = ""): Promise<T> {
|
|
51
|
+
return (await context.webAPI.retrieveRecord(
|
|
52
|
+
entitySet,
|
|
53
|
+
id,
|
|
54
|
+
query,
|
|
55
|
+
)) as T;
|
|
56
|
+
},
|
|
57
|
+
async retrieveMultiple<T>(entitySet: string, query = ""): Promise<T[]> {
|
|
58
|
+
const response = await context.webAPI.retrieveMultipleRecords(
|
|
59
|
+
entitySet,
|
|
60
|
+
query,
|
|
61
|
+
);
|
|
62
|
+
return response.entities as T[];
|
|
63
|
+
},
|
|
64
|
+
async create<T>(entitySet: string, data: unknown): Promise<T> {
|
|
65
|
+
return (await context.webAPI.createRecord(
|
|
66
|
+
entitySet,
|
|
67
|
+
data as ComponentFramework.WebApi.Entity,
|
|
68
|
+
)) as T;
|
|
69
|
+
},
|
|
70
|
+
async update(entitySet: string, id: string, data: unknown): Promise<void> {
|
|
71
|
+
await context.webAPI.updateRecord(
|
|
72
|
+
entitySet,
|
|
73
|
+
id,
|
|
74
|
+
data as ComponentFramework.WebApi.Entity,
|
|
75
|
+
);
|
|
76
|
+
},
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function createRuntime(
|
|
81
|
+
context: ComponentFramework.Context<IInputs>,
|
|
82
|
+
): PcfRuntimeContext {
|
|
83
|
+
return {
|
|
84
|
+
host: "pcf",
|
|
85
|
+
...getContextInfo(context),
|
|
86
|
+
webApi: createPcfWebApi(context),
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
export class {{PCF_CONSTRUCTOR}}
|
|
91
|
+
implements ComponentFramework.StandardControl<IInputs, IOutputs>
|
|
92
|
+
{
|
|
93
|
+
private root: Root | null = null;
|
|
94
|
+
private runtime!: PcfRuntimeContext;
|
|
95
|
+
private readonly queryClient = new QueryClient({
|
|
96
|
+
defaultOptions: {
|
|
97
|
+
queries: {
|
|
98
|
+
refetchOnWindowFocus: false,
|
|
99
|
+
retry: 3,
|
|
100
|
+
staleTime: 5 * 60 * 1000,
|
|
101
|
+
},
|
|
102
|
+
mutations: {
|
|
103
|
+
retry: 1,
|
|
104
|
+
},
|
|
105
|
+
},
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
public init(
|
|
109
|
+
context: ComponentFramework.Context<IInputs>,
|
|
110
|
+
_notifyOutputChanged: () => void,
|
|
111
|
+
_state: ComponentFramework.Dictionary,
|
|
112
|
+
container: HTMLDivElement,
|
|
113
|
+
): void {
|
|
114
|
+
container.classList.add("ec-pcf-shell-control");
|
|
115
|
+
this.runtime = createRuntime(context);
|
|
116
|
+
this.root = createRoot(container);
|
|
117
|
+
this.render();
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
public updateView(context: ComponentFramework.Context<IInputs>): void {
|
|
121
|
+
this.runtime = {
|
|
122
|
+
...this.runtime,
|
|
123
|
+
...getContextInfo(context),
|
|
124
|
+
};
|
|
125
|
+
this.render();
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
public getOutputs(): IOutputs {
|
|
129
|
+
return {
|
|
130
|
+
hostField: this.runtime.recordId ?? undefined,
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
public destroy(): void {
|
|
135
|
+
this.root?.unmount();
|
|
136
|
+
this.root = null;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
private render(): void {
|
|
140
|
+
this.root?.render(
|
|
141
|
+
React.createElement(
|
|
142
|
+
StrictMode,
|
|
143
|
+
null,
|
|
144
|
+
React.createElement(
|
|
145
|
+
QueryClientProvider,
|
|
146
|
+
{ client: this.queryClient },
|
|
147
|
+
React.createElement(App, { runtime: this.runtime }),
|
|
148
|
+
),
|
|
149
|
+
),
|
|
150
|
+
);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "{{PCF_PACKAGE_NAME}}",
|
|
3
|
+
"version": "{{PCF_VERSION}}",
|
|
4
|
+
"private": true,
|
|
5
|
+
"description": "{{CONTROL_DESCRIPTION}}",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"build": "pcf-scripts build",
|
|
8
|
+
"clean": "pcf-scripts clean",
|
|
9
|
+
"lint": "pcf-scripts lint",
|
|
10
|
+
"rebuild": "npm run clean && npm run build",
|
|
11
|
+
"start": "pcf-scripts start",
|
|
12
|
+
"push": "pcf-scripts push"
|
|
13
|
+
},
|
|
14
|
+
"devDependencies": {
|
|
15
|
+
"@types/powerapps-component-framework": "^1.3.14",
|
|
16
|
+
"pcf-start": "^1.48.2",
|
|
17
|
+
"pcf-scripts": "^1.48.2",
|
|
18
|
+
"typescript": "^5.9.3"
|
|
19
|
+
}
|
|
20
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
<?xml version="1.0" encoding="utf-8"?>
|
|
2
|
+
<root>
|
|
3
|
+
<resheader name="resmimetype">
|
|
4
|
+
<value>text/microsoft-resx</value>
|
|
5
|
+
</resheader>
|
|
6
|
+
<resheader name="version">
|
|
7
|
+
<value>2.0</value>
|
|
8
|
+
</resheader>
|
|
9
|
+
<resheader name="reader">
|
|
10
|
+
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
|
11
|
+
</resheader>
|
|
12
|
+
<resheader name="writer">
|
|
13
|
+
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
|
14
|
+
</resheader>
|
|
15
|
+
<data name="Control_Display_Name" xml:space="preserve">
|
|
16
|
+
<value>{{CONTROL_DISPLAY_NAME}}</value>
|
|
17
|
+
</data>
|
|
18
|
+
<data name="Control_Description" xml:space="preserve">
|
|
19
|
+
<value>{{CONTROL_DESCRIPTION}}</value>
|
|
20
|
+
</data>
|
|
21
|
+
<data name="HostField_Display_Name" xml:space="preserve">
|
|
22
|
+
<value>Host field</value>
|
|
23
|
+
</data>
|
|
24
|
+
<data name="HostField_Description" xml:space="preserve">
|
|
25
|
+
<value>Optional bound field used to surface the current record id.</value>
|
|
26
|
+
</data>
|
|
27
|
+
</root>
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2022",
|
|
4
|
+
"module": "ESNext",
|
|
5
|
+
"lib": ["ES2022", "DOM", "DOM.Iterable"],
|
|
6
|
+
"jsx": "react-jsx",
|
|
7
|
+
"moduleResolution": "bundler",
|
|
8
|
+
"strict": true,
|
|
9
|
+
"skipLibCheck": true,
|
|
10
|
+
"types": ["react", "react-dom", "powerapps-component-framework"],
|
|
11
|
+
"typeRoots": ["./node_modules/@types", "{{PROJECT_NODE_MODULES_TYPES_ROOT}}"],
|
|
12
|
+
"baseUrl": "{{PROJECT_ROOT_REL}}",
|
|
13
|
+
"paths": {
|
|
14
|
+
"@/*": ["src/*"],
|
|
15
|
+
"react": ["node_modules/@types/react"],
|
|
16
|
+
"react/jsx-runtime": ["node_modules/@types/react/jsx-runtime"],
|
|
17
|
+
"react-dom": ["node_modules/@types/react-dom"],
|
|
18
|
+
"react-dom/client": ["node_modules/@types/react-dom/client"]
|
|
19
|
+
}
|
|
20
|
+
},
|
|
21
|
+
"include": [
|
|
22
|
+
"./**/*.ts",
|
|
23
|
+
"./**/*.tsx",
|
|
24
|
+
"{{PROJECT_ROOT_REL}}/src/**/*.ts",
|
|
25
|
+
"{{PROJECT_ROOT_REL}}/src/**/*.tsx"
|
|
26
|
+
],
|
|
27
|
+
"exclude": [
|
|
28
|
+
"{{PROJECT_ROOT_REL}}/src/main.tsx",
|
|
29
|
+
"{{PROJECT_ROOT_REL}}/src/services/AuthService.ts"
|
|
30
|
+
]
|
|
31
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
const path = require("path");
|
|
2
|
+
|
|
3
|
+
module.exports = {
|
|
4
|
+
resolve: {
|
|
5
|
+
alias: {
|
|
6
|
+
"@": path.resolve(__dirname, "{{PROJECT_SRC_ALIAS}}"),
|
|
7
|
+
react: path.resolve(__dirname, "{{PROJECT_REACT_ALIAS}}"),
|
|
8
|
+
"react-dom": path.resolve(__dirname, "{{PROJECT_REACT_DOM_ALIAS}}"),
|
|
9
|
+
},
|
|
10
|
+
},
|
|
11
|
+
};
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
## Purpose
|
|
2
|
+
|
|
3
|
+
This is a Dynamics 365 / Dataverse web resource template using React + TypeScript + Vite.
|
|
4
|
+
Treat it as a **Dynamics-hosted frontend** — not a generic SPA. Do not modernise it into something else unless explicitly asked.
|
|
5
|
+
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## Hard Constraints
|
|
9
|
+
|
|
10
|
+
1. **Dynamics runtime must keep working** — preserve `window.parent.Xrm`/`window.top.Xrm` detection and `ClientGlobalContext.js.aspx` where present.
|
|
11
|
+
2. **Local dev must keep working** — `token.json` drives local auth; never bundle it into output or commit real values.
|
|
12
|
+
3. **Build output must stay web-resource-friendly** — predictable filenames, no uncontrolled chunking, deployable via Webresource Manager or pipeline upload.
|
|
13
|
+
4. **Keep it client-side** — no SSR, no Next.js, no backend coupling unless explicitly requested.
|
|
14
|
+
5. **Make surgical changes** — extend existing patterns before inventing new ones; don't refactor unrelated areas.
|
|
15
|
+
|
|
16
|
+
---
|
|
17
|
+
|
|
18
|
+
## Runtime Modes
|
|
19
|
+
|
|
20
|
+
**Dynamics-hosted:** detect via `window.parent.Xrm`, derive base URL from `getGlobalContext().getClientUrl()`, no bearer token needed.
|
|
21
|
+
|
|
22
|
+
**Local dev:** load `token.json` dynamically, use bearer token, call Dataverse directly.
|
|
23
|
+
|
|
24
|
+
Never mix the two modes or duplicate their logic — reuse `authService.ts`.
|
|
25
|
+
|
|
26
|
+
---
|
|
27
|
+
|
|
28
|
+
## Critical Files — Don't Break These
|
|
29
|
+
|
|
30
|
+
| File | Why it matters |
|
|
31
|
+
|---|---|
|
|
32
|
+
| `src/services/authService.ts` | Single source of truth for env detection, base URL, and auth headers |
|
|
33
|
+
| `src/main.tsx` | App bootstrap — preserve providers and theme imports |
|
|
34
|
+
| `vite.config.ts` | Controls deployment shape: `base: "./"`, predictable output names, `main.css` |
|
|
35
|
+
| `index.html` | Dynamics integration boundary — may inject `ClientGlobalContext.js.aspx` |
|
|
36
|
+
| `token.json` | Local dev only — never commit real values, never bundle |
|
|
37
|
+
|
|
38
|
+
---
|
|
39
|
+
|
|
40
|
+
## API / Data Access
|
|
41
|
+
|
|
42
|
+
- Put reusable API logic in `src/services`
|
|
43
|
+
- Always reuse `getApiUrl()` and `getAuthHeaders()` — never duplicate them
|
|
44
|
+
- Use narrow `$select` queries; throw on non-OK responses
|
|
45
|
+
- No raw fetch calls scattered across UI components
|
|
46
|
+
|
|
47
|
+
**Preferred pattern:**
|
|
48
|
+
```ts
|
|
49
|
+
// service.ts — fetch function + TanStack Query hook
|
|
50
|
+
// invalidate relevant queryKey on mutation success
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
See the example at the bottom of this file.
|
|
54
|
+
|
|
55
|
+
---
|
|
56
|
+
|
|
57
|
+
## State Management
|
|
58
|
+
|
|
59
|
+
- **TanStack Query** → server state
|
|
60
|
+
- **Zustand** → shared client state
|
|
61
|
+
- **Local component state** → local UI behaviour
|
|
62
|
+
- No Redux unless explicitly requested; don't store server state in Zustand
|
|
63
|
+
|
|
64
|
+
---
|
|
65
|
+
|
|
66
|
+
## UI & Styling
|
|
67
|
+
|
|
68
|
+
- Kendo UI or Shadcn/ui — stay consistent with whichever the project uses; don't mix
|
|
69
|
+
- Tailwind is the default styling approach; preserve `main.css` output
|
|
70
|
+
- Reuse existing components and utilities before building new ones
|
|
71
|
+
|
|
72
|
+
---
|
|
73
|
+
|
|
74
|
+
## Planning Rule
|
|
75
|
+
|
|
76
|
+
For changes touching multiple files, auth, build config, or new service patterns — write a short plan first:
|
|
77
|
+
- current state
|
|
78
|
+
- intended change
|
|
79
|
+
- files to touch
|
|
80
|
+
- risks / compatibility concerns
|
|
81
|
+
|
|
82
|
+
For large or risky changes, write an `ExecPlan` in `PLANS.md` before implementing.
|
|
83
|
+
|
|
84
|
+
---
|
|
85
|
+
|
|
86
|
+
## What To Avoid
|
|
87
|
+
|
|
88
|
+
Unless explicitly asked: don't replace Vite, don't add SSR, don't remove Dynamics runtime logic, don't remove the local token flow, don't hardcode org-specific values, don't rename output files in ways that complicate deployment.
|
|
89
|
+
|
|
90
|
+
---
|
|
91
|
+
|
|
92
|
+
## Example Service Pattern
|
|
93
|
+
|
|
94
|
+
```ts
|
|
95
|
+
import { getApiUrl, getAuthHeaders } from "@/services/authService";
|
|
96
|
+
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
|
|
97
|
+
|
|
98
|
+
export interface Account {
|
|
99
|
+
accountid: string;
|
|
100
|
+
name?: string | null;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
export const listAccounts = async (): Promise<Account[]> => {
|
|
104
|
+
const res = await fetch(
|
|
105
|
+
`${getApiUrl()}/accounts?$select=accountid,name&$top=50`,
|
|
106
|
+
{ headers: await getAuthHeaders() },
|
|
107
|
+
);
|
|
108
|
+
if (!res.ok) throw new Error(`Failed to fetch accounts: ${res.status}`);
|
|
109
|
+
return (await res.json()).value as Account[];
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
export const useAccounts = () =>
|
|
113
|
+
useQuery({ queryKey: ["accounts"], queryFn: listAccounts });
|
|
114
|
+
|
|
115
|
+
export const useUpdateAccount = () => {
|
|
116
|
+
const queryClient = useQueryClient();
|
|
117
|
+
return useMutation({
|
|
118
|
+
mutationFn: ({ id, data }: { id: string; data: Partial<Account> }) =>
|
|
119
|
+
patchAccount(id, data),
|
|
120
|
+
onSuccess: () => queryClient.invalidateQueries({ queryKey: ["accounts"] }),
|
|
121
|
+
});
|
|
122
|
+
};
|
|
123
|
+
```
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
[AGENTS.md](AGENTS.md)
|
|
@@ -258,6 +258,76 @@ export function AccountsList() {
|
|
|
258
258
|
- Upload your index.html, index.js and main.css to your folder.
|
|
259
259
|
- This will now allow you to use auto publisher to bind to your deployed resources.
|
|
260
260
|
|
|
261
|
+
## Generate a PCF Wrapper
|
|
262
|
+
|
|
263
|
+
If you want to host the React webresource inside a PCF control instead of loading the HTML webresource directly in an iframe, use `create-ec-app` itself to generate the wrapper for an existing webresource project.
|
|
264
|
+
|
|
265
|
+
Basic flow:
|
|
266
|
+
|
|
267
|
+
1. Build the webresource:
|
|
268
|
+
|
|
269
|
+
```bash
|
|
270
|
+
npm run build
|
|
271
|
+
```
|
|
272
|
+
|
|
273
|
+
2. Run the generator against the current project directory:
|
|
274
|
+
|
|
275
|
+
```bash
|
|
276
|
+
npx create-ec-app --pcf-dir . --namespace EC --constructor FusionNotebookHost
|
|
277
|
+
```
|
|
278
|
+
|
|
279
|
+
If you are not inside the webresource folder, point `--pcf-dir` at that folder explicitly:
|
|
280
|
+
|
|
281
|
+
```bash
|
|
282
|
+
npx create-ec-app --pcf-dir /path/to/my-webresource --namespace EC --constructor FusionNotebookHost
|
|
283
|
+
```
|
|
284
|
+
|
|
285
|
+
This writes a standalone PCF project to `pcf/FusionNotebookHost` by default. The generated control:
|
|
286
|
+
|
|
287
|
+
- imports `src/App.tsx` directly instead of wrapping built HTML in an iframe
|
|
288
|
+
- reuses the built stylesheet from `dist/main.css`
|
|
289
|
+
- creates `src/runtime/types.ts` only if that file does not already exist
|
|
290
|
+
- provides a runtime object with record context and `context.webAPI` access inside the generated PCF shell, following the `PcfBase` pattern
|
|
291
|
+
- mounts your React app directly into the PCF container
|
|
292
|
+
|
|
293
|
+
Typical conversion flow from inside a generated webresource project:
|
|
294
|
+
|
|
295
|
+
```bash
|
|
296
|
+
npm install
|
|
297
|
+
npm run build
|
|
298
|
+
npx create-ec-app --pcf-dir . --namespace EC --constructor FusionNotebookHost --display-name "Fusion Notebook Host"
|
|
299
|
+
cd pcf/FusionNotebookHost
|
|
300
|
+
npm install
|
|
301
|
+
npm run build
|
|
302
|
+
```
|
|
303
|
+
|
|
304
|
+
Useful options:
|
|
305
|
+
|
|
306
|
+
- `--dist dist` to point at a different build folder
|
|
307
|
+
- `--output pcf/MyControl` to choose the target folder
|
|
308
|
+
- `--template /path/to/create-ec-app/templates/pcf/base` to swap the base shell
|
|
309
|
+
- `--layer path/to/layer` to apply one or more patch layers after the base copy
|
|
310
|
+
|
|
311
|
+
Build the generated PCF project from inside the generated folder:
|
|
312
|
+
|
|
313
|
+
```bash
|
|
314
|
+
cd pcf/FusionNotebookHost
|
|
315
|
+
npm install
|
|
316
|
+
npm run build
|
|
317
|
+
```
|
|
318
|
+
|
|
319
|
+
What gets generated:
|
|
320
|
+
|
|
321
|
+
- a minimal PCF wrapper project under `pcf/<ConstructorName>`
|
|
322
|
+
- a checked-in PCF shell stamped out from `create-ec-app/templates/pcf/base`
|
|
323
|
+
- direct imports back to your webresource source and built CSS
|
|
324
|
+
|
|
325
|
+
What does not happen:
|
|
326
|
+
|
|
327
|
+
- your existing webresource project is not converted in place
|
|
328
|
+
- your React source is not moved into the PCF project
|
|
329
|
+
- the generated PCF project does not automatically get added to a Dataverse solution
|
|
330
|
+
|
|
261
331
|
## Notes
|
|
262
332
|
|
|
263
333
|
- If you change the build, ensure code splitting stays disabled and asset names remain predictable to simplify web resource updates.
|