create-ec-app 1.5.0 → 1.7.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/README.md +86 -21
- package/dist/cssScope.d.ts +4 -0
- package/dist/cssScope.d.ts.map +1 -0
- package/dist/cssScope.js +124 -0
- package/dist/cssScope.js.map +1 -0
- package/dist/index.js +28 -94
- package/dist/index.js.map +1 -1
- package/dist/pcf.d.ts.map +1 -1
- package/dist/pcf.js +13 -84
- package/dist/pcf.js.map +1 -1
- package/dist/portalContainers.d.ts +7 -0
- package/dist/portalContainers.d.ts.map +1 -0
- package/dist/portalContainers.js +118 -0
- package/dist/portalContainers.js.map +1 -0
- package/package.json +6 -3
- package/scripts/check-generated-css-scope.mjs +98 -37
- package/templates/base/src/App.tsx +1 -1
- package/templates/base/src/index.css +1 -4
- package/templates/base/src/main.tsx +1 -4
- package/templates/pcf/base/README.md +28 -9
- package/templates/pcf/base/index.ts +30 -6
- package/templates/pcf/base/runtime/PcfAppShell.tsx +17 -0
- package/templates/pcf/base/runtime/emptyStyles.js +1 -0
- package/templates/pcf/base/webpack.config.js +13 -0
- package/templates/targets/code-apps/README.md +246 -0
- package/templates/targets/code-apps/package.patch.json +8 -0
- package/templates/targets/code-apps/power.config.example.json +14 -0
- package/templates/targets/code-apps/vite.config.patch.ts +15 -0
- package/templates/targets/power-pages/src/App.patch.tsx +1 -1
- package/templates/targets/webresource/README.md +21 -7
- package/templates/ui/kendo/src/main.patch.tsx +1 -4
- package/templates/ui/shadcn-ui/components.json +1 -1
- package/templates/ui/shadcn-ui/src/index.patch.css +9 -26
- package/templates/base/src/runtime/EcAppShell.tsx +0 -29
package/dist/pcf.js
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import path, { dirname } from "node:path";
|
|
2
2
|
import { fileURLToPath } from "node:url";
|
|
3
3
|
import fs from "fs-extra";
|
|
4
|
+
import { PCF_SCOPED_CSS_FILE, scopeCssForPcf, } from "./cssScope.js";
|
|
4
5
|
import { applyLayer, replaceTokensRecursively } from "./libFunctions.js";
|
|
6
|
+
import { ensurePortalContainerRuntime, localizeShadcnPortals, } from "./portalContainers.js";
|
|
5
7
|
const __filename = fileURLToPath(import.meta.url);
|
|
6
8
|
const __dirname = dirname(__filename);
|
|
7
9
|
const RUNTIME_TYPES_TEMPLATE = `export interface PcfWebApi {
|
|
@@ -20,38 +22,6 @@ export interface PcfRuntimeContext {
|
|
|
20
22
|
\twebApi: PcfWebApi;
|
|
21
23
|
}
|
|
22
24
|
`;
|
|
23
|
-
function createAppShellTemplate(appId) {
|
|
24
|
-
return `import * as React from "react";
|
|
25
|
-
|
|
26
|
-
export const EC_APP_SCOPE_CLASS = "ec-app";
|
|
27
|
-
export const EC_APP_ID = ${JSON.stringify(appId)};
|
|
28
|
-
export const EC_PCF_SCOPE_CLASS = "ec-pcf-shell-control";
|
|
29
|
-
|
|
30
|
-
const EcPortalContainerContext = React.createContext<HTMLElement | null>(null);
|
|
31
|
-
|
|
32
|
-
export function useEcPortalContainer() {
|
|
33
|
-
\treturn React.useContext(EcPortalContainerContext);
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
export function EcAppShell({ children }: { children: React.ReactNode }) {
|
|
37
|
-
\tconst [portalContainer, setPortalContainer] =
|
|
38
|
-
\t\tReact.useState<HTMLDivElement | null>(null);
|
|
39
|
-
|
|
40
|
-
\treturn (
|
|
41
|
-
\t\t<div
|
|
42
|
-
\t\t\tclassName={EC_APP_SCOPE_CLASS}
|
|
43
|
-
\t\t\tdata-ec-app-id={EC_APP_ID}
|
|
44
|
-
\t\t\tdata-ec-app-root=""
|
|
45
|
-
\t\t>
|
|
46
|
-
\t\t\t<EcPortalContainerContext.Provider value={portalContainer}>
|
|
47
|
-
\t\t\t\t{children}
|
|
48
|
-
\t\t\t\t<div data-ec-portal-root="" ref={setPortalContainer} />
|
|
49
|
-
\t\t\t</EcPortalContainerContext.Provider>
|
|
50
|
-
\t\t</div>
|
|
51
|
-
\t);
|
|
52
|
-
}
|
|
53
|
-
`;
|
|
54
|
-
}
|
|
55
25
|
export async function generatePcfFromExistingWebresource(options) {
|
|
56
26
|
const projectDir = path.resolve(process.cwd(), options.pcfDir);
|
|
57
27
|
const packageJson = await readJson(path.join(projectDir, "package.json"));
|
|
@@ -72,19 +42,24 @@ export async function generatePcfFromExistingWebresource(options) {
|
|
|
72
42
|
const packageNameToken = options.packageName ?? toKebabCase(constructorName);
|
|
73
43
|
const relToProject = toPosixPath(path.relative(outputDir, projectDir) || ".");
|
|
74
44
|
const appImportPath = ensureRelativeImport(toPosixPath(path.relative(outputDir, path.join(projectDir, "src", "App"))));
|
|
75
|
-
const
|
|
45
|
+
const portalContainerImportPath = ensureRelativeImport(toPosixPath(path.relative(path.join(outputDir, "runtime"), path.join(projectDir, "src", "runtime", "PortalContainer"))));
|
|
76
46
|
const runtimeTypesImportPath = ensureRelativeImport(toPosixPath(path.relative(outputDir, path.join(projectDir, "src", "runtime", "types"))));
|
|
77
|
-
const
|
|
47
|
+
const sourceCssPath = path.join(projectDir, distDirName, "main.css");
|
|
48
|
+
const cssImportPath = ensureRelativeImport(PCF_SCOPED_CSS_FILE);
|
|
78
49
|
await assertFileExists(path.join(projectDir, "src", "App.tsx"), `Could not find src/App.tsx in ${projectDir}.`);
|
|
79
|
-
await assertFileExists(
|
|
50
|
+
await assertFileExists(sourceCssPath, `Could not find ${distDirName}/main.css in ${projectDir}. Run the webresource build first.`);
|
|
80
51
|
await ensureRuntimeTypes(projectDir);
|
|
81
|
-
await
|
|
82
|
-
await
|
|
52
|
+
await ensurePortalContainerRuntime(projectDir);
|
|
53
|
+
await localizeShadcnPortals(projectDir, {
|
|
54
|
+
includeGeneratedCompatibility: false,
|
|
55
|
+
});
|
|
56
|
+
const pcfCss = scopeCssForPcf(await fs.readFile(sourceCssPath, "utf8"), constructorName);
|
|
83
57
|
await fs.remove(outputDir);
|
|
84
58
|
await applyLayer(templateDir, outputDir);
|
|
85
59
|
for (const layerDir of layerDirs) {
|
|
86
60
|
await applyLayer(layerDir, outputDir);
|
|
87
61
|
}
|
|
62
|
+
await fs.writeFile(path.join(outputDir, PCF_SCOPED_CSS_FILE), pcfCss, "utf8");
|
|
88
63
|
await replaceTokensRecursively(outputDir, {
|
|
89
64
|
CONTROL_DESCRIPTION: controlDescription,
|
|
90
65
|
CONTROL_DISPLAY_NAME: controlDisplayName,
|
|
@@ -94,7 +69,7 @@ export async function generatePcfFromExistingWebresource(options) {
|
|
|
94
69
|
PCF_VERSION: version,
|
|
95
70
|
PROJECT_APP_IMPORT: appImportPath,
|
|
96
71
|
PROJECT_CSS_IMPORT: cssImportPath,
|
|
97
|
-
|
|
72
|
+
PROJECT_PORTAL_CONTAINER_IMPORT: portalContainerImportPath,
|
|
98
73
|
PROJECT_NODE_MODULES_TYPES_ROOT: `${relToProject}/node_modules/@types`,
|
|
99
74
|
PROJECT_REACT_ALIAS: `${relToProject}/node_modules/react`,
|
|
100
75
|
PROJECT_REACT_DOM_ALIAS: `${relToProject}/node_modules/react-dom`,
|
|
@@ -118,52 +93,6 @@ async function ensureRuntimeTypes(projectDir) {
|
|
|
118
93
|
await fs.ensureDir(path.dirname(runtimeTypesPath));
|
|
119
94
|
await fs.writeFile(runtimeTypesPath, RUNTIME_TYPES_TEMPLATE, "utf8");
|
|
120
95
|
}
|
|
121
|
-
async function ensureAppShell(projectDir, appId) {
|
|
122
|
-
const appShellPath = path.join(projectDir, "src", "runtime", "EcAppShell.tsx");
|
|
123
|
-
if (await fs.pathExists(appShellPath)) {
|
|
124
|
-
return;
|
|
125
|
-
}
|
|
126
|
-
await fs.ensureDir(path.dirname(appShellPath));
|
|
127
|
-
await fs.writeFile(appShellPath, createAppShellTemplate(appId), "utf8");
|
|
128
|
-
}
|
|
129
|
-
async function warnIfCssAppearsUnsafe(cssPath) {
|
|
130
|
-
const css = await fs.readFile(cssPath, "utf8");
|
|
131
|
-
const unsafeChecks = [
|
|
132
|
-
{ name: "global body rule", pattern: /(^|})\s*body\s*\{/ },
|
|
133
|
-
{
|
|
134
|
-
name: "global shadcn :root token rule",
|
|
135
|
-
pattern: /(^|})\s*:root\s*\{[^}]*--background\s*:/,
|
|
136
|
-
},
|
|
137
|
-
{
|
|
138
|
-
name: "global shadcn dark token rule",
|
|
139
|
-
pattern: /(^|})\s*\.dark\s*\{[^}]*--background\s*:/,
|
|
140
|
-
},
|
|
141
|
-
{
|
|
142
|
-
name: "Tailwind Preflight universal reset",
|
|
143
|
-
pattern: /(^|})\s*\*,\s*::before,\s*::after,\s*::backdrop\b/,
|
|
144
|
-
},
|
|
145
|
-
{ name: "unprefixed flex utility", pattern: /(^|})\s*\.flex\s*\{/ },
|
|
146
|
-
{ name: "unprefixed grid utility", pattern: /(^|})\s*\.grid\s*\{/ },
|
|
147
|
-
{ name: "unprefixed hidden utility", pattern: /(^|})\s*\.hidden\s*\{/ },
|
|
148
|
-
{ name: "unprefixed border utility", pattern: /(^|})\s*\.border\s*\{/ },
|
|
149
|
-
{ name: "unprefixed text-sm utility", pattern: /(^|})\s*\.text-sm\s*\{/ },
|
|
150
|
-
{
|
|
151
|
-
name: "unprefixed bg-background utility",
|
|
152
|
-
pattern: /(^|})\s*\.bg-background\s*\{/,
|
|
153
|
-
},
|
|
154
|
-
];
|
|
155
|
-
const matches = unsafeChecks
|
|
156
|
-
.filter((check) => check.pattern.test(css))
|
|
157
|
-
.map((check) => check.name);
|
|
158
|
-
if (matches.length === 0) {
|
|
159
|
-
return;
|
|
160
|
-
}
|
|
161
|
-
console.warn([
|
|
162
|
-
"Warning: the built CSS appears to use unscoped Tailwind/shadcn styles and may leak into the host page when deployed as PCF.",
|
|
163
|
-
`Detected: ${matches.join(", ")}.`,
|
|
164
|
-
"Regenerate or migrate the source app to the scoped CSS template before deploying to Dynamics 365.",
|
|
165
|
-
].join("\n"));
|
|
166
|
-
}
|
|
167
96
|
async function readJson(filePath) {
|
|
168
97
|
try {
|
|
169
98
|
return await fs.readJson(filePath);
|
package/dist/pcf.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"pcf.js","sourceRoot":"","sources":["../src/pcf.ts"],"names":[],"mappings":"AAAA,OAAO,IAAI,EAAE,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAC1C,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AACzC,OAAO,EAAE,MAAM,UAAU,CAAC;AAC1B,OAAO,EAAE,UAAU,EAAE,wBAAwB,EAAE,MAAM,mBAAmB,CAAC;
|
|
1
|
+
{"version":3,"file":"pcf.js","sourceRoot":"","sources":["../src/pcf.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export declare function ensurePortalContainerRuntime(projectDir: string): Promise<void>;
|
|
2
|
+
interface LocalizeShadcnPortalsOptions {
|
|
3
|
+
includeGeneratedCompatibility?: boolean;
|
|
4
|
+
}
|
|
5
|
+
export declare function localizeShadcnPortals(projectDir: string, options?: LocalizeShadcnPortalsOptions): Promise<void>;
|
|
6
|
+
export {};
|
|
7
|
+
//# sourceMappingURL=portalContainers.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"portalContainers.d.ts","sourceRoot":"","sources":["../src/portalContainers.ts"],"names":[],"mappings":"AAaA,wBAAsB,4BAA4B,CACjD,UAAU,EAAE,MAAM,GAChB,OAAO,CAAC,IAAI,CAAC,CAcf;AAED,UAAU,4BAA4B;IACrC,6BAA6B,CAAC,EAAE,OAAO,CAAC;CACxC;AAED,wBAAsB,qBAAqB,CAC1C,UAAU,EAAE,MAAM,EAClB,OAAO,GAAE,4BAAiC,GACxC,OAAO,CAAC,IAAI,CAAC,CA2Bf"}
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import fs from "fs-extra";
|
|
3
|
+
const PORTAL_CONTAINER_TEMPLATE = `import * as React from "react";
|
|
4
|
+
|
|
5
|
+
export const PortalContainerContext =
|
|
6
|
+
\tReact.createContext<HTMLElement | null>(null);
|
|
7
|
+
|
|
8
|
+
export function usePortalContainer() {
|
|
9
|
+
\treturn React.useContext(PortalContainerContext);
|
|
10
|
+
}
|
|
11
|
+
`;
|
|
12
|
+
export async function ensurePortalContainerRuntime(projectDir) {
|
|
13
|
+
const portalContainerPath = path.join(projectDir, "src", "runtime", "PortalContainer.ts");
|
|
14
|
+
if (await fs.pathExists(portalContainerPath)) {
|
|
15
|
+
return;
|
|
16
|
+
}
|
|
17
|
+
await fs.ensureDir(path.dirname(portalContainerPath));
|
|
18
|
+
await fs.writeFile(portalContainerPath, PORTAL_CONTAINER_TEMPLATE, "utf8");
|
|
19
|
+
}
|
|
20
|
+
export async function localizeShadcnPortals(projectDir, options = {}) {
|
|
21
|
+
const componentsDir = path.join(projectDir, "src", "components", "ui");
|
|
22
|
+
if (!(await fs.pathExists(componentsDir))) {
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
await ensurePortalContainerRuntime(projectDir);
|
|
26
|
+
const entries = await fs.readdir(componentsDir, { withFileTypes: true });
|
|
27
|
+
for (const entry of entries) {
|
|
28
|
+
if (!entry.isFile() || !entry.name.endsWith(".tsx")) {
|
|
29
|
+
continue;
|
|
30
|
+
}
|
|
31
|
+
const filePath = path.join(componentsDir, entry.name);
|
|
32
|
+
const source = await fs.readFile(filePath, "utf8");
|
|
33
|
+
const withPortalContainers = withPortalRuntime(source, filePath);
|
|
34
|
+
const updated = options.includeGeneratedCompatibility === false
|
|
35
|
+
? withPortalContainers
|
|
36
|
+
: withGeneratedShadcnCompatibility(withPortalContainers);
|
|
37
|
+
if (updated !== source) {
|
|
38
|
+
await fs.writeFile(filePath, updated, "utf8");
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
function withPortalRuntime(source, filePath) {
|
|
43
|
+
const migratedSource = source.replace(/from ["']@\/runtime\/EcAppShell["']/g, 'from "@/runtime/PortalContainer"');
|
|
44
|
+
const withGenericPortalRuntime = migratedSource
|
|
45
|
+
.replace(/from ["']@\/runtime\/EcPortalContainer["']/g, 'from "@/runtime/PortalContainer"')
|
|
46
|
+
.replace(/\buseEcPortalContainer\b/g, "usePortalContainer");
|
|
47
|
+
const withPortalContainers = withGenericPortalRuntime.replace(/<([A-Za-z][A-Za-z0-9]*Primitive)\.Portal\b(?![^>]*\bcontainer=)/g, "<$1.Portal container={portalContainer ?? undefined}");
|
|
48
|
+
if (withPortalContainers === withGenericPortalRuntime) {
|
|
49
|
+
return withGenericPortalRuntime;
|
|
50
|
+
}
|
|
51
|
+
return addPortalImport(addPortalHookDeclarations(withPortalContainers, filePath));
|
|
52
|
+
}
|
|
53
|
+
function withGeneratedShadcnCompatibility(source) {
|
|
54
|
+
const withChartAttributeSelectors = removeStandaloneClassPrefixes(source)
|
|
55
|
+
.replace(/\[stroke=#ccc\]/g, "[stroke='#ccc']")
|
|
56
|
+
.replace(/\[stroke=#fff\]/g, "[stroke='#fff']");
|
|
57
|
+
if (!withChartAttributeSelectors.includes('from "react-day-picker"')) {
|
|
58
|
+
return withChartAttributeSelectors;
|
|
59
|
+
}
|
|
60
|
+
return withChartAttributeSelectors.replace(/(\n\s*)table:/g, "$1month_grid:");
|
|
61
|
+
}
|
|
62
|
+
function removeStandaloneClassPrefixes(source) {
|
|
63
|
+
let updated = source;
|
|
64
|
+
let previous;
|
|
65
|
+
do {
|
|
66
|
+
previous = updated;
|
|
67
|
+
updated = updated.replace(/(^|[\s"'])ec:(?=\s|["'])/g, "$1");
|
|
68
|
+
} while (updated !== previous);
|
|
69
|
+
return updated;
|
|
70
|
+
}
|
|
71
|
+
function addPortalImport(source) {
|
|
72
|
+
if (source.includes('from "@/runtime/PortalContainer"')) {
|
|
73
|
+
return source;
|
|
74
|
+
}
|
|
75
|
+
const importPattern = /import[\s\S]*?from\s+["'][^"']+["'];?\n|import\s+["'][^"']+["'];?\n/g;
|
|
76
|
+
let insertAt = 0;
|
|
77
|
+
for (const match of source.matchAll(importPattern)) {
|
|
78
|
+
insertAt = (match.index ?? 0) + match[0].length;
|
|
79
|
+
}
|
|
80
|
+
const importLine = 'import { usePortalContainer } from "@/runtime/PortalContainer"\n';
|
|
81
|
+
return `${source.slice(0, insertAt)}${importLine}${source.slice(insertAt)}`;
|
|
82
|
+
}
|
|
83
|
+
function addPortalHookDeclarations(source, filePath) {
|
|
84
|
+
const lines = source.split("\n");
|
|
85
|
+
const hookBodyLines = new Set();
|
|
86
|
+
for (let index = 0; index < lines.length; index += 1) {
|
|
87
|
+
if (!lines[index]?.includes("container={portalContainer ?? undefined}")) {
|
|
88
|
+
continue;
|
|
89
|
+
}
|
|
90
|
+
const bodyLine = findContainingFunctionBodyLine(lines, index);
|
|
91
|
+
if (bodyLine === undefined) {
|
|
92
|
+
throw new Error(`Could not locate a function body for a shadcn Portal in ${filePath}.`);
|
|
93
|
+
}
|
|
94
|
+
hookBodyLines.add(bodyLine);
|
|
95
|
+
}
|
|
96
|
+
for (const bodyLine of [...hookBodyLines].sort((a, b) => b - a)) {
|
|
97
|
+
if (lines[bodyLine + 1]?.includes("const portalContainer")) {
|
|
98
|
+
continue;
|
|
99
|
+
}
|
|
100
|
+
const indent = lines[bodyLine]?.match(/^(\s*)/)?.[1] ?? "";
|
|
101
|
+
lines.splice(bodyLine + 1, 0, `${indent} const portalContainer = usePortalContainer()`);
|
|
102
|
+
}
|
|
103
|
+
return lines.join("\n");
|
|
104
|
+
}
|
|
105
|
+
function findContainingFunctionBodyLine(lines, portalLine) {
|
|
106
|
+
for (let index = portalLine; index >= 0; index -= 1) {
|
|
107
|
+
if (!/^\s*function\s+\w+/.test(lines[index] ?? "")) {
|
|
108
|
+
continue;
|
|
109
|
+
}
|
|
110
|
+
for (let bodyLine = index; bodyLine <= portalLine; bodyLine += 1) {
|
|
111
|
+
if (/\)\s*\{\s*$/.test(lines[bodyLine] ?? "")) {
|
|
112
|
+
return bodyLine;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
return undefined;
|
|
117
|
+
}
|
|
118
|
+
//# sourceMappingURL=portalContainers.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"portalContainers.js","sourceRoot":"","sources":["../src/portalContainers.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "create-ec-app",
|
|
3
|
-
"version": "1.
|
|
4
|
-
"description": "Unified CLI tool to create different types of EC applications: Webresource, Portal, Power Pages",
|
|
3
|
+
"version": "1.7.0",
|
|
4
|
+
"description": "Unified CLI tool to create different types of EC applications: Webresource, Portal, Power Pages, Power Apps Code Apps",
|
|
5
5
|
"bin": {
|
|
6
6
|
"create-ec-app": "./dist/index.js"
|
|
7
7
|
},
|
|
@@ -20,6 +20,8 @@
|
|
|
20
20
|
"tailwind",
|
|
21
21
|
"cli",
|
|
22
22
|
"powerpages",
|
|
23
|
+
"code-apps",
|
|
24
|
+
"power-apps-code-apps",
|
|
23
25
|
"portal",
|
|
24
26
|
"webresource",
|
|
25
27
|
"dynamics",
|
|
@@ -38,7 +40,8 @@
|
|
|
38
40
|
"type": "module",
|
|
39
41
|
"dependencies": {
|
|
40
42
|
"@clack/prompts": "^0.11.0",
|
|
41
|
-
"fs-extra": "^11.3.2"
|
|
43
|
+
"fs-extra": "^11.3.2",
|
|
44
|
+
"postcss": "^8.5.15"
|
|
42
45
|
},
|
|
43
46
|
"devDependencies": {
|
|
44
47
|
"@semantic-release/commit-analyzer": "^13.0.1",
|
|
@@ -2,60 +2,121 @@
|
|
|
2
2
|
|
|
3
3
|
import fs from "node:fs";
|
|
4
4
|
import path from "node:path";
|
|
5
|
+
import postcss from "postcss";
|
|
5
6
|
|
|
6
|
-
const
|
|
7
|
+
const pcfPath = process.argv[2];
|
|
7
8
|
|
|
8
|
-
if (!
|
|
9
|
-
console.error("Usage: node scripts/check-generated-css-scope.mjs <generated-
|
|
9
|
+
if (!pcfPath) {
|
|
10
|
+
console.error("Usage: node scripts/check-generated-css-scope.mjs <generated-pcf-control>");
|
|
10
11
|
process.exit(1);
|
|
11
12
|
}
|
|
12
13
|
|
|
13
|
-
const cssPath = path.join(path.resolve(
|
|
14
|
+
const cssPath = path.join(path.resolve(pcfPath), "pcf-scoped.css");
|
|
14
15
|
|
|
15
16
|
if (!fs.existsSync(cssPath)) {
|
|
16
|
-
console.error(`Could not find ${cssPath}.
|
|
17
|
+
console.error(`Could not find ${cssPath}. Generate the PCF control first.`);
|
|
17
18
|
process.exit(1);
|
|
18
19
|
}
|
|
19
20
|
|
|
20
21
|
const css = fs.readFileSync(cssPath, "utf8");
|
|
22
|
+
const root = postcss.parse(css, { from: cssPath });
|
|
23
|
+
const failures = [];
|
|
21
24
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
const required = [
|
|
42
|
-
{ name: "ec app scope", pattern: /\.ec-app\b/ },
|
|
43
|
-
{ name: "prefixed flex utility", pattern: /\.ec\\:flex\b/ },
|
|
44
|
-
];
|
|
45
|
-
|
|
46
|
-
const failures = [
|
|
47
|
-
...forbidden.filter((check) => check.pattern.test(css)).map((check) => check.name),
|
|
48
|
-
...required
|
|
49
|
-
.filter((check) => !check.pattern.test(css))
|
|
50
|
-
.map((check) => `missing ${check.name}`),
|
|
51
|
-
];
|
|
25
|
+
root.walkRules((rule) => {
|
|
26
|
+
if (isKeyframesRule(rule)) {
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
for (const selector of splitSelectorList(rule.selector)) {
|
|
31
|
+
if (!selector.includes(".pcf-shell-control")) {
|
|
32
|
+
failures.push(`unscoped PCF rule: ${selector}`);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
if (!css.includes("[data-pcf-control=")) {
|
|
38
|
+
failures.push("missing PCF control data attribute scope");
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
if (css.includes(".ec-pcf-shell-control") || css.includes("[data-ec-pcf-control=")) {
|
|
42
|
+
failures.push("legacy EC PCF scope is still present");
|
|
43
|
+
}
|
|
52
44
|
|
|
53
45
|
if (failures.length > 0) {
|
|
54
|
-
console.error("CSS scope check failed:");
|
|
46
|
+
console.error("PCF CSS scope check failed:");
|
|
55
47
|
for (const failure of failures) {
|
|
56
48
|
console.error(`- ${failure}`);
|
|
57
49
|
}
|
|
58
50
|
process.exit(1);
|
|
59
51
|
}
|
|
60
52
|
|
|
61
|
-
console.log("CSS scope check passed.");
|
|
53
|
+
console.log("PCF CSS scope check passed.");
|
|
54
|
+
|
|
55
|
+
function isKeyframesRule(rule) {
|
|
56
|
+
let parent = rule.parent;
|
|
57
|
+
|
|
58
|
+
while (parent) {
|
|
59
|
+
if (
|
|
60
|
+
parent.type === "atrule" &&
|
|
61
|
+
parent.name.toLowerCase().endsWith("keyframes")
|
|
62
|
+
) {
|
|
63
|
+
return true;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
parent = parent.parent;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
return false;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function splitSelectorList(selectorList) {
|
|
73
|
+
const selectors = [];
|
|
74
|
+
let start = 0;
|
|
75
|
+
let nesting = 0;
|
|
76
|
+
let quote = null;
|
|
77
|
+
let escaped = false;
|
|
78
|
+
|
|
79
|
+
for (let index = 0; index < selectorList.length; index += 1) {
|
|
80
|
+
const char = selectorList[index];
|
|
81
|
+
|
|
82
|
+
if (escaped) {
|
|
83
|
+
escaped = false;
|
|
84
|
+
continue;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
if (char === "\\") {
|
|
88
|
+
escaped = true;
|
|
89
|
+
continue;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
if (quote) {
|
|
93
|
+
if (char === quote) {
|
|
94
|
+
quote = null;
|
|
95
|
+
}
|
|
96
|
+
continue;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
if (char === '"' || char === "'") {
|
|
100
|
+
quote = char;
|
|
101
|
+
continue;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
if (char === "(" || char === "[") {
|
|
105
|
+
nesting += 1;
|
|
106
|
+
continue;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
if (char === ")" || char === "]") {
|
|
110
|
+
nesting = Math.max(0, nesting - 1);
|
|
111
|
+
continue;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
if (char === "," && nesting === 0) {
|
|
115
|
+
selectors.push(selectorList.slice(start, index).trim());
|
|
116
|
+
start = index + 1;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
selectors.push(selectorList.slice(start).trim());
|
|
121
|
+
return selectors.filter(Boolean);
|
|
122
|
+
}
|
|
@@ -4,7 +4,6 @@ import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
|
|
|
4
4
|
|
|
5
5
|
import "./index.css";
|
|
6
6
|
import App from "./App.tsx";
|
|
7
|
-
import { EcAppShell } from "./runtime/EcAppShell.tsx";
|
|
8
7
|
|
|
9
8
|
const queryClient = new QueryClient({
|
|
10
9
|
defaultOptions: {
|
|
@@ -24,9 +23,7 @@ const root = createRoot(document.getElementById("root")!);
|
|
|
24
23
|
root.render(
|
|
25
24
|
<StrictMode>
|
|
26
25
|
<QueryClientProvider client={queryClient}>
|
|
27
|
-
<
|
|
28
|
-
<App />
|
|
29
|
-
</EcAppShell>
|
|
26
|
+
<App />
|
|
30
27
|
</QueryClientProvider>
|
|
31
28
|
</StrictMode>
|
|
32
29
|
);
|
|
@@ -9,6 +9,29 @@ npm install
|
|
|
9
9
|
npm run build
|
|
10
10
|
```
|
|
11
11
|
|
|
12
|
+
## Regenerate From Webresource Changes
|
|
13
|
+
|
|
14
|
+
Do not edit this generated PCF folder as the durable source of truth. From the webresource root, rebuild and regenerate:
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
npm run build
|
|
18
|
+
npx create-ec-app@latest \
|
|
19
|
+
--pcf-dir . \
|
|
20
|
+
--output ./pcf/{{PCF_CONSTRUCTOR}} \
|
|
21
|
+
--namespace {{PCF_NAMESPACE}} \
|
|
22
|
+
--constructor {{PCF_CONSTRUCTOR}} \
|
|
23
|
+
--display-name "{{CONTROL_DISPLAY_NAME}}"
|
|
24
|
+
cd pcf/{{PCF_CONSTRUCTOR}}
|
|
25
|
+
npm install
|
|
26
|
+
npm run build
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
Then run the harness:
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
npm run start -- --no-open
|
|
33
|
+
```
|
|
34
|
+
|
|
12
35
|
## Control Info
|
|
13
36
|
|
|
14
37
|
- Namespace: `{{PCF_NAMESPACE}}`
|
|
@@ -18,19 +41,15 @@ npm run build
|
|
|
18
41
|
|
|
19
42
|
## Notes
|
|
20
43
|
|
|
21
|
-
- The wrapper imports `src/App` directly
|
|
44
|
+
- The wrapper imports `src/App` directly, renders it through the local PCF shell, and uses generated `pcf-scoped.css` derived from the webresource build.
|
|
45
|
+
- Source-app CSS imports are ignored by the PCF webpack config because the built CSS is already included through `pcf-scoped.css`.
|
|
22
46
|
- Regenerate this folder after rebuilding the webresource whenever the app changes.
|
|
23
47
|
- The project includes both `pcf-scripts` build support and a `.pcfproj` for Dataverse solution packaging flows.
|
|
24
48
|
|
|
25
49
|
## CSS scoping
|
|
26
50
|
|
|
27
|
-
This PCF control renders the app inside
|
|
28
|
-
|
|
29
|
-
Generated Tailwind/shadcn styles are scoped for embeddability:
|
|
51
|
+
This PCF control renders the app inside the PCF host container class `.pcf-shell-control`.
|
|
30
52
|
|
|
31
|
-
-
|
|
32
|
-
- Tailwind utilities use the `ec:` prefix.
|
|
33
|
-
- shadcn theme variables are defined under `.ec-app`.
|
|
34
|
-
- Radix/shadcn portals render into the app-local portal root where supported.
|
|
53
|
+
During PCF generation, `create-ec-app` reads the webresource's built `dist/main.css`, scopes CSS selectors to this control's `.pcf-shell-control[data-pcf-control="{{PCF_CONSTRUCTOR}}"]` host selector, and writes the result to `pcf-scoped.css`.
|
|
35
54
|
|
|
36
|
-
|
|
55
|
+
Tailwind utilities and base selectors are isolated under the generated PCF host selector. Radix/shadcn portals render into the PCF-local portal root where supported.
|