pcf-vite-harness
Version:
Modern Vite-based development harness for PowerApps Component Framework (PCF) with hot module replacement and PowerApps-like environment simulation
1,359 lines (1,346 loc) • 180 kB
JavaScript
'use strict';
var React3 = require('react');
var client = require('react-dom/client');
var react = require('@fluentui/react');
var jsxRuntime = require('react/jsx-runtime');
function _interopNamespace(e) {
if (e && e.__esModule) return e;
var n = Object.create(null);
if (e) {
Object.keys(e).forEach(function (k) {
if (k !== 'default') {
var d = Object.getOwnPropertyDescriptor(e, k);
Object.defineProperty(n, k, d.get ? d : {
enumerable: true,
get: function () { return e[k]; }
});
}
});
}
n.default = e;
return Object.freeze(n);
}
var React3__namespace = /*#__PURE__*/_interopNamespace(React3);
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
}) : x)(function(x) {
if (typeof require !== "undefined") return require.apply(this, arguments);
throw Error('Dynamic require of "' + x + '" is not supported');
});
var __esm = (fn, res) => function __init() {
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
};
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
// src/utils/datasetRecordConverter.ts
var datasetRecordConverter_exports = {};
__export(datasetRecordConverter_exports, {
convertEntitiesToDatasetRecords: () => convertEntitiesToDatasetRecords
});
async function fetchEntityMetadata(entityLogicalName) {
try {
const url = `/api/data/v9.1/EntityDefinitions(LogicalName='${entityLogicalName}')?$select=LogicalName,PrimaryIdAttribute,PrimaryNameAttribute`;
const response = await fetch(url);
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
const data = await response.json();
return data;
} catch (error) {
throw new Error(`Failed to fetch metadata for entity ${entityLogicalName}: ${error}`);
}
}
function getEntityPrimaryKey(entity, metadata) {
return entity[metadata.PrimaryIdAttribute] || null;
}
function getEntityPrimaryName(entity, metadata) {
const primaryName = entity[metadata.PrimaryNameAttribute];
if (primaryName) return primaryName;
return entity.name || entity.fullname || entity.title || `Record ${entity[metadata.PrimaryIdAttribute] || "Unknown"}`;
}
async function convertEntityToRecord(entity, metadata, webAPI) {
const recordId = getEntityPrimaryKey(entity, metadata);
const primaryName = getEntityPrimaryName(entity, metadata);
const entityType = metadata.LogicalName;
const record = {
_record: {
initialized: 2,
identifier: {
etn: entityType,
id: {
guid: recordId || ""
}
},
fields: {}
},
_columnAliasNameMap: {},
_primaryFieldName: metadata.PrimaryNameAttribute,
_isDirty: false,
_entityReference: {
_etn: entityType,
_id: recordId || "",
_name: primaryName
}
};
Object.entries(entity).forEach(([key, value]) => {
if (key.startsWith("@")) {
return;
}
const field = {
value,
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
validationResult: {
errorId: null,
errorMessage: null,
isValueValid: true,
userInput: null,
isOfflineSyncError: false
}
};
const formattedKey = `${key}${FORMATTED_VALUE_SUFFIX}`;
if (entity[formattedKey]) {
field.formatted = entity[formattedKey];
}
record._record.fields[key] = field;
});
record[metadata.PrimaryNameAttribute] = primaryName;
if (metadata.PrimaryNameAttribute !== "name") {
record.name = primaryName;
}
return record;
}
async function convertEntitiesToDatasetRecords(entities, entityLogicalName, webAPI) {
const records = {};
console.log(`\u{1F504} Converting ${entities.length} entities to dataset records`);
const recordIds = /* @__PURE__ */ new Set();
let duplicateCount = 0;
const entityMetadata = await fetchEntityMetadata(entityLogicalName);
for (const [index, entity] of entities.entries()) {
const recordId = getEntityPrimaryKey(entity, entityMetadata);
if (recordId) {
if (recordIds.has(recordId)) {
duplicateCount++;
console.warn(`\u26A0\uFE0F Duplicate record ID found: ${recordId} (entity ${index + 1})`);
}
recordIds.add(recordId);
records[recordId] = await convertEntityToRecord(entity, entityMetadata);
if (index < 5) {
console.log(`\u2705 Converted entity ${index + 1}: ${entityMetadata.PrimaryIdAttribute} = ${recordId}`);
}
} else {
console.warn(`\u26A0\uFE0F Could not find primary key for entity ${index + 1}:`, Object.keys(entity).slice(0, 10));
}
}
console.log(`\u{1F511} Using primary ID field: ${entityMetadata.PrimaryIdAttribute} for entity ${entityLogicalName}`);
if (duplicateCount > 0) {
console.warn(`\u26A0\uFE0F Found ${duplicateCount} duplicate record IDs!`);
}
console.log(`\u2705 Converted ${Object.keys(records).length} records successfully`);
return records;
}
var FORMATTED_VALUE_SUFFIX;
var init_datasetRecordConverter = __esm({
"src/utils/datasetRecordConverter.ts"() {
FORMATTED_VALUE_SUFFIX = "@OData.Community.Display.V1.FormattedValue";
}
});
// src/utils/manifestExtractor.ts
var manifestExtractor_exports = {};
__export(manifestExtractor_exports, {
autoDetectManifest: () => autoDetectManifest,
createTestProjectManifest: () => createTestProjectManifest,
extractDatasetsFromXml: () => extractDatasetsFromXml,
extractManifestFromBuiltXml: () => extractManifestFromBuiltXml,
extractManifestFromComponentClass: () => extractManifestFromComponentClass,
extractManifestFromXml: () => extractManifestFromXml
});
function extractDatasetsFromXml(xmlContent) {
try {
const datasets = [];
const datasetPattern = /<data-set\s+name=["']([^"']+)["'](?:\s+display-name-key=["']([^"']+)["'])?/g;
let match;
while ((match = datasetPattern.exec(xmlContent)) !== null) {
datasets.push({
name: match[1],
displayNameKey: match[2]
});
}
return datasets;
} catch (error) {
console.error("Error parsing datasets from manifest XML:", error);
return [];
}
}
function extractManifestFromXml(xmlContent) {
try {
const namespaceMatch = xmlContent.match(/namespace=["']([^"']+)["']/);
const constructorMatch = xmlContent.match(/constructor=["']([^"']+)["']/);
const versionMatch = xmlContent.match(/version=["']([^"']+)["']/);
const displayNameMatch = xmlContent.match(/display-name-key=["']([^"']+)["']/);
const descriptionMatch = xmlContent.match(/description-key=["']([^"']+)["']/);
if (!namespaceMatch || !constructorMatch || !versionMatch) {
return null;
}
const datasets = extractDatasetsFromXml(xmlContent);
return {
namespace: namespaceMatch[1],
constructor: constructorMatch[1],
version: versionMatch[1],
displayName: displayNameMatch?.[1],
description: descriptionMatch?.[1],
datasets: datasets.length > 0 ? datasets : void 0
};
} catch (error) {
console.error("Error parsing manifest XML:", error);
return null;
}
}
function extractManifestFromBuiltXml(xmlContent) {
try {
const namespaceMatch = xmlContent.match(/namespace=["']([^"']+)["']/);
const constructorMatch = xmlContent.match(/constructor=["']([^"']+)["']/);
const versionMatch = xmlContent.match(/version=["']([^"']+)["']/);
const displayNameMatch = xmlContent.match(/display-name-key=["']([^"']+)["']/);
const descriptionMatch = xmlContent.match(/description-key=["']([^"']+)["']/);
if (!namespaceMatch || !constructorMatch || !versionMatch) {
return null;
}
const datasets = extractDatasetsFromXml(xmlContent);
return {
namespace: namespaceMatch[1],
constructor: constructorMatch[1],
version: versionMatch[1],
displayName: displayNameMatch?.[1],
description: descriptionMatch?.[1],
datasets: datasets.length > 0 ? datasets : void 0
};
} catch (error) {
console.error("Error parsing built manifest XML:", error);
return null;
}
}
async function autoDetectManifest() {
try {
const possiblePaths = [
"./ControlManifest.Input.xml",
"./src/ControlManifest.Input.xml",
"./*/ControlManifest.Input.xml",
"./out/controls/*/ControlManifest.xml"
];
if (typeof window !== "undefined") {
console.warn(
"Auto-detection not supported in browser environment. Please provide manifestInfo manually."
);
return null;
}
return null;
} catch (error) {
console.warn("Error auto-detecting manifest:", error);
return null;
}
}
function extractManifestFromComponentClass(pcfClass, fallbackNamespace = "default") {
const className = pcfClass.name;
return {
namespace: fallbackNamespace,
constructor: className,
version: "1.0.0",
displayName: className,
description: `${className} PCF Control`
};
}
function createTestProjectManifest() {
return {
namespace: "test",
constructor: "dataset",
version: "0.0.1",
displayName: "dataset",
description: "dataset description"
};
}
var init_manifestExtractor = __esm({
"src/utils/manifestExtractor.ts"() {
}
});
// src/createMockContext.ts
function formatGuid(guid) {
return guid.replace(/[{}]/g, "");
}
async function getEntityMetadata(entityLogicalName) {
if (!entityLogicalName || entityLogicalName === "unknown" || entityLogicalName.trim() === "") {
console.warn(`\u26A0\uFE0F Invalid entity name for metadata fetch: "${entityLogicalName}"`);
return null;
}
const response = await fetch(
`/api/data/v9.2/EntityDefinitions(LogicalName='${entityLogicalName}')`
);
if (!response.ok) {
throw new Error(`Failed to get metadata for ${entityLogicalName}: ${response.status}`);
}
return response.json();
}
function extractViewId(options) {
if (options.includes("savedQuery=")) {
const match = options.match(/savedQuery=([^&]+)/);
return { viewId: match?.[1], isUserView: false };
}
if (options.includes("userQuery=")) {
const match = options.match(/userQuery=([^&]+)/);
return { viewId: match?.[1], isUserView: true };
}
return { isUserView: false };
}
async function getViewInfo(viewId, isUserView) {
try {
const entitySet = isUserView ? "userqueries" : "savedqueries";
const response = await fetch(
`/api/data/v9.2/${entitySet}(${viewId})?$select=name,returnedtypecode`
);
if (response.ok) {
const data = await response.json();
return {
name: data.name,
entityName: data.returnedtypecode
};
}
} catch (error) {
console.warn("Failed to get view info:", error);
}
return {};
}
function createDefaultWebAPI() {
return {
retrieveMultipleRecords: async (entityLogicalName, options) => {
try {
console.log(`\u{1F504} retrieveMultipleRecords called for ${entityLogicalName}`, {
options
});
const metadata = await getEntityMetadata(entityLogicalName);
const collectionName = metadata.LogicalCollectionName;
let url = `/api/data/v9.2/${collectionName}`;
if (options) {
url += options.startsWith("?") ? options : `?${options}`;
}
const isViewQuery = options && (options.includes("savedQuery=") || options.includes("userQuery=") || options.includes("fetchXml="));
let viewInfo = {};
if (isViewQuery && options) {
const { viewId, isUserView } = extractViewId(options);
if (viewId) {
viewInfo = await getViewInfo(viewId, isUserView);
console.log(
`\u{1F50D} ${isUserView ? "User" : "System"} view query detected for ${entityLogicalName}`,
{
viewId,
viewName: viewInfo.name || "Unknown View"
}
);
} else if (options.includes("fetchXml=")) {
console.log(`\u{1F50D} FetchXML query detected for ${entityLogicalName}`);
}
}
const response = await fetch(url);
if (!response.ok) {
const errorText = await response.text();
console.error(`API Error Response:`, errorText);
throw new Error(`HTTP error! status: ${response.status}`);
}
const result = await response.json();
const recordCount = result.value?.length || 0;
if (isViewQuery) {
const viewDescription = viewInfo.name ? ` via "${viewInfo.name}"` : " via view";
console.log(
`\u2705 Retrieved ${recordCount} records for ${entityLogicalName}${viewDescription}`
);
} else {
console.log(`\u2705 Retrieved ${recordCount} records for ${entityLogicalName}`);
}
if (result.value && !result.entities) {
return {
entities: result.value,
nextLink: result["@odata.nextLink"]
};
}
return result;
} catch (error) {
console.error(`Error retrieving multiple records for ${entityLogicalName}:`, error);
throw error;
}
},
retrieveRecord: async (entityLogicalName, id, options) => {
try {
const metadata = await getEntityMetadata(entityLogicalName);
const collectionName = metadata.LogicalCollectionName;
let url = `/api/data/v9.2/${collectionName}(${id})`;
if (options) {
url += options.startsWith("?") ? options : `?${options}`;
}
const response = await fetch(url);
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const result = await response.json();
console.log(`\u2705 Retrieved record ${id} for ${entityLogicalName}`);
return result;
} catch (error) {
console.error(`Error retrieving record ${id} for ${entityLogicalName}:`, error);
throw error;
}
},
createRecord: async (entityLogicalName, data) => {
try {
const metadata = await getEntityMetadata(entityLogicalName);
const collectionName = metadata.LogicalCollectionName;
const url = `/api/data/v9.2/${collectionName}`;
const response = await fetch(url, {
method: "POST",
headers: {
"Content-Type": "application/json",
Prefer: "return=representation"
},
body: JSON.stringify(data)
});
if (!response.ok) {
const errorText = await response.text();
console.error(`Create Error Response:`, errorText);
throw new Error(`HTTP error! status: ${response.status}`);
}
const result = await response.json();
const primaryIdAttribute = metadata.PrimaryIdAttribute;
if (!primaryIdAttribute) {
throw new Error(`No PrimaryIdAttribute found in metadata for ${entityLogicalName}`);
}
console.log(`\u2705 Created record for ${entityLogicalName}`);
return {
id: formatGuid(result[primaryIdAttribute]),
name: result[metadata.PrimaryNameAttribute] || "",
entityType: entityLogicalName
};
} catch (error) {
console.error(`Error creating record for ${entityLogicalName}:`, error);
throw error;
}
},
updateRecord: async (entityLogicalName, id, data) => {
try {
const metadata = await getEntityMetadata(entityLogicalName);
const collectionName = metadata.LogicalCollectionName;
const url = `/api/data/v9.2/${collectionName}(${id})`;
const response = await fetch(url, {
method: "PATCH",
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify(data)
});
if (!response.ok) {
const errorText = await response.text();
console.error(`Update Error Response:`, errorText);
throw new Error(`HTTP error! status: ${response.status}`);
}
console.log(`\u2705 Updated record ${id} for ${entityLogicalName}`);
return {
id: formatGuid(id),
name: "",
// For updates, we don't have the name without additional fetch
entityType: entityLogicalName
};
} catch (error) {
console.error(`Error updating record ${id} for ${entityLogicalName}:`, error);
throw error;
}
},
deleteRecord: async (entityLogicalName, id) => {
try {
const metadata = await getEntityMetadata(entityLogicalName);
const collectionName = metadata.LogicalCollectionName;
const url = `/api/data/v9.2/${collectionName}(${id})`;
const response = await fetch(url, {
method: "DELETE"
});
if (!response.ok) {
const errorText = await response.text();
console.error(`Delete Error Response:`, errorText);
throw new Error(`HTTP error! status: ${response.status}`);
}
console.log(`\u2705 Deleted record ${id} for ${entityLogicalName}`);
return {
id: formatGuid(id),
name: "",
// For deletes, we don't have the name without additional fetch
entityType: entityLogicalName
};
} catch (error) {
console.error(`Error deleting record ${id} for ${entityLogicalName}:`, error);
throw error;
}
}
};
}
function createMockDataSet(options) {
const viewId = undefined.VITE_PCF_VIEW_ID || undefined.VITE_PCF_DEFAULT_VIEW_ID || void 0;
if (viewId) {
console.log(`\u{1F4CB} Using view ID from environment: ${viewId}`);
} else {
console.log(`\u{1F4CA} No view ID found in environment, dataset will use default view`);
}
const defaultColumns = [
{
name: "name",
displayName: "Name",
dataType: "SingleLine.Text",
alias: "name",
order: 1,
visualSizeFactor: 1
}
];
return {
getViewId: () => viewId || "",
getTargetEntityType: () => options?.entityLogicalName || "account",
isUserView: () => false,
loading: false,
paging: {
totalResultCount: 0,
hasNextPage: false,
hasPreviousPage: false
},
sorting: [],
columns: options?.columns || defaultColumns,
records: {},
clearSelectedRecordIds: () => {
},
getSelectedRecordIds: () => [],
setSelectedRecordIds: () => {
},
refresh: () => Promise.resolve(),
openDatasetItem: () => {
},
...options
};
}
function createMockContext(options) {
const {
controlId = `id-${crypto.randomUUID()}`,
viewId = crypto.randomUUID(),
displayName = "Dev User",
userName = "devuser@contoso.com",
userId = "dev-user-id",
datasetOptions = {},
webAPI: customWebAPI = {},
entityType = "unknown",
manifestInfo
} = options || {};
console.log("\u{1F527} Creating mock context...", { entityType });
const envTargetTable = undefined.VITE_PCF_TARGET_TABLE || entityType;
const envPageTable = undefined.VITE_PCF_PAGE_TABLE || entityType;
const envPageRecordId = undefined.VITE_PCF_PAGE_RECORD_ID;
if (envTargetTable !== entityType) {
console.log(`\u{1F4CB} Using VITE_PCF_TARGET_TABLE environment variable: ${envTargetTable}`);
}
if (envPageRecordId) {
console.log(`\u{1F4CB} Using VITE_PCF_PAGE_RECORD_ID environment variable: ${envPageRecordId}`);
} else {
console.log(`\u26A0\uFE0F VITE_PCF_PAGE_RECORD_ID not found, will generate random UUID`);
}
console.log("\u{1F50D} All PCF environment variables:", {
VITE_PCF_PAGE_TABLE: undefined.VITE_PCF_PAGE_TABLE,
VITE_PCF_PAGE_TABLE_NAME: undefined.VITE_PCF_PAGE_TABLE_NAME,
VITE_PCF_PAGE_RECORD_ID: undefined.VITE_PCF_PAGE_RECORD_ID,
VITE_PCF_TARGET_TABLE: undefined.VITE_PCF_TARGET_TABLE,
VITE_PCF_TARGET_TABLE_NAME: undefined.VITE_PCF_TARGET_TABLE_NAME,
VITE_PCF_VIEW_ID: undefined.VITE_PCF_VIEW_ID,
VITE_PCF_VIEW_NAME: undefined.VITE_PCF_VIEW_NAME
});
if (!manifestInfo?.datasets || manifestInfo.datasets.length === 0) {
throw new Error(
"\u274C No dataset found in manifest. Dataset components require a <data-set> element in ControlManifest.Input.xml.\n Please check your manifest file or use a field component instead."
);
}
const firstDataset = manifestInfo.datasets[0];
if (!firstDataset || !firstDataset.name) {
throw new Error(
'\u274C Invalid dataset configuration in manifest. Dataset must have a name attribute.\n Example: <data-set name="myDataset" display-name-key="Dataset_Display_Key" />'
);
}
const datasetName = firstDataset.name;
const datasetDisplayName = firstDataset.displayNameKey || firstDataset.name;
console.log(`\u{1F4CB} Using dataset from manifest: "${datasetName}" (${datasetDisplayName})`);
const dataset = createMockDataSet({
name: datasetName,
displayName: datasetDisplayName,
entityLogicalName: envTargetTable !== "unknown" ? envTargetTable : "unknown",
columns: [],
// Will be populated based on discovered entity
...datasetOptions
});
dataset._targetEntityType = envTargetTable !== "unknown" ? envTargetTable : void 0;
Object.defineProperty(dataset, "getTargetEntityType", {
value: function() {
const envValue = undefined.VITE_PCF_TARGET_TABLE;
if (envValue && envValue !== "unknown") {
return envValue;
}
return this._targetEntityType || this.entityLogicalName || "unknown";
},
writable: true,
configurable: true
});
console.log(`\u{1F527} Created dataset "${datasetName}" with structure:`, {
entityType,
hasRecords: "records" in dataset,
hasColumns: "columns" in dataset,
recordCount: Object.keys(dataset.records || {}).length,
columnCount: dataset.columns?.length || 0
});
return {
accessibility: {
_customControlProperties: {
descriptor: {
DomId: controlId,
UniqueId: controlId + "_unique"
}
}
},
page: {
entityTypeName: envPageTable,
entityId: (() => {
const pageEntityId = envPageRecordId || crypto.randomUUID();
console.log(`\u{1F527} Page entity ID set to: ${pageEntityId} (from ${envPageRecordId ? "environment" : "generated UUID"})`);
return pageEntityId;
})(),
isVisible: true
},
parameters: {
[datasetName]: dataset
},
factory: {
requestRender: () => {
},
getPopupService: () => ({})
},
events: {},
copilot: {},
mode: {
trackContainerResize: () => {
},
isControlDisabled: false,
isVisible: true
},
client: {
getClient: () => "Web",
disableScroll: () => {
},
getFormFactor: () => 0,
isOffline: false,
isNetworkAvailable: true
},
device: {
captureAudio: () => Promise.resolve(),
captureImage: () => Promise.resolve(),
captureVideo: () => Promise.resolve(),
getBarcodeValue: () => Promise.resolve(),
getCurrentPosition: () => Promise.resolve(),
pickFile: () => Promise.resolve()
},
formatting: {
formatCurrency: (value) => `$${value}`,
formatDateLong: (value) => value.toLocaleDateString(),
formatDateShort: (value) => value.toLocaleDateString(),
formatDateYearMonth: (value) => value.toLocaleDateString(),
formatDecimal: (value) => value.toString(),
formatInteger: (value) => Math.round(value).toString(),
formatLanguage: (value) => value.toString(),
formatTime: (value) => value.toLocaleTimeString(),
getWeekOfYear: (value) => 1
},
navigation: {
openAlertDialog: () => Promise.resolve(),
openConfirmDialog: () => Promise.resolve(),
openErrorDialog: () => Promise.resolve(),
openFile: () => Promise.resolve(),
openForm: () => Promise.resolve(),
openUrl: () => {
},
openWebResource: () => {
}
},
resources: {
getString: (id) => id,
getResource: (id) => ""
},
updatedProperties: [],
userSettings: {
dateFormattingInfo: {
abbreviatedDayNames: [],
abbreviatedMonthNames: [],
amDesignator: "AM",
calendar: {},
calendarWeekRule: 0,
dayNames: [],
firstDayOfWeek: 0,
fullDateTimePattern: "",
longDatePattern: "",
longTimePattern: "",
monthDayPattern: "",
monthNames: [],
pmDesignator: "PM",
shortDatePattern: "",
shortTimePattern: "",
shortestDayNames: [],
sortableDateTimePattern: "",
timeSeparator: ":",
universalSortableDateTimePattern: "",
yearMonthPattern: ""
},
displayName,
isRTL: false,
languageId: 1033,
numberFormattingInfo: {
currencyDecimalDigits: 2,
currencyDecimalSeparator: ".",
currencyGroupSeparator: ",",
currencyGroupSizes: [3],
currencyNegativePattern: 0,
currencyPositivePattern: 0,
currencySymbol: "$",
naNSymbol: "NaN",
nativeDigits: ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9"],
negativeInfinitySymbol: "-\u221E",
negativeSign: "-",
numberDecimalDigits: 2,
numberDecimalSeparator: ".",
numberGroupSeparator: ",",
numberGroupSizes: [3],
numberNegativePattern: 1,
percentDecimalDigits: 2,
percentDecimalSeparator: ".",
percentGroupSeparator: ",",
percentGroupSizes: [3],
percentNegativePattern: 0,
percentPositivePattern: 0,
percentSymbol: "%",
perMilleSymbol: "\u2030",
positiveInfinitySymbol: "\u221E",
positiveSign: "+"
},
securityRoles: [],
userId,
userName
},
utils: {
getEntityMetadata: () => Promise.resolve({}),
hasEntityPrivilege: () => false,
lookupObjects: () => Promise.resolve([])
},
webAPI: {
...createDefaultWebAPI(),
...customWebAPI
}
};
}
// src/createViteConfig.ts
async function validateDataverseToken(dataverseUrl) {
try {
const { getAzureToken } = await import('dataverse-utilities');
console.log("\u{1F510} Fetching Dataverse token...");
console.log(` Resource URL: ${dataverseUrl}`);
const token = await getAzureToken({
resourceUrl: dataverseUrl,
enableLogging: false
// We'll handle our own logging
});
if (!token) {
throw new Error(
"Failed to obtain Dataverse token. Make sure you are authenticated with Azure CLI (az login) or have managed identity configured."
);
}
console.log("\u2705 Dataverse token acquired successfully");
console.log(" Server will start with authentication ready");
} catch (error) {
if (error instanceof Error && error.message.includes("Cannot resolve module")) {
throw new Error(
"\u274C dataverse-utilities package is required for Dataverse integration.\n Please install it: npm install dataverse-utilities"
);
}
console.error("\u274C Dataverse token validation failed:");
console.error(` ${error instanceof Error ? error.message : "Unknown error"}`);
console.error("");
console.error("\u{1F4A1} Troubleshooting steps:");
console.error(" 1. Ensure you are logged in to Azure CLI: az login");
console.error(" 2. Check that VITE_DATAVERSE_URL is correct");
console.error(" 3. Verify you have access to the Dataverse environment");
throw error;
}
}
async function createPCFViteConfig(options = {}) {
const { defineConfig, loadEnv } = await import('vite');
const react = (await import('@vitejs/plugin-react')).default;
const fs = await import('fs');
const path = await import('path');
return defineConfig(async ({ mode }) => {
const envDir = process.cwd().endsWith("/dev") ? path.resolve(process.cwd(), "..") : process.cwd();
const env = loadEnv(mode, envDir, "");
const {
dataverseUrl = env.VITE_DATAVERSE_URL,
port = 3e3,
hmrPort = 3001,
open = true,
viteConfig = {}
} = options;
let baseConfig = {
root: "./dev",
// Set root to dev directory for index.html resolution
envDir,
// Tell Vite where to look for .env files
plugins: [react()],
optimizeDeps: {
exclude: ["fsevents"]
// Prevent ESBuild native module errors on macOS
},
server: {
port,
open,
hmr: {
port: hmrPort
},
// Add middleware for /setup route
configureServer(server) {
server.middlewares.use((req, res, next) => {
if (req.url === "/setup") {
const indexPath = path.resolve(server.config.root, "index.html");
const html = fs.readFileSync(indexPath, "utf-8");
res.statusCode = 200;
res.setHeader("Content-Type", "text/html");
res.end(html);
} else {
next();
}
});
}
},
resolve: {
alias: {
"@": "../../"
// Default alias for PCF source
}
}
};
if (!dataverseUrl) {
throw new Error(
"\u274C VITE_DATAVERSE_URL environment variable is required.\n Please set VITE_DATAVERSE_URL in your .env file or pass dataverseUrl in the config options.\n Example: VITE_DATAVERSE_URL=https://yourorg.crm.dynamics.com/"
);
}
await validateDataverseToken(dataverseUrl);
const { createDataverseConfig } = __require("dataverse-utilities/vite");
const dataverseConfig = createDataverseConfig({
dataverseUrl
});
baseConfig = {
...dataverseConfig,
...baseConfig,
plugins: [
...baseConfig.plugins || [],
...Array.isArray(dataverseConfig.plugins) ? dataverseConfig.plugins : []
],
server: {
...baseConfig.server,
...dataverseConfig.server || {}
}
};
return {
...baseConfig,
...viteConfig,
plugins: [
...baseConfig.plugins || [],
...Array.isArray(viteConfig.plugins) ? viteConfig.plugins : []
],
server: {
...baseConfig.server,
...viteConfig.server || {}
}
};
});
}
// src/utils/pcfLifecycle.ts
function createPCFManager(pcfClass, context, container) {
return {
instance: null,
container,
context,
pcfClass
};
}
async function initPCF(manager) {
if (!manager.container) {
console.warn("Container not available for PCF initialization");
return;
}
try {
if (manager.instance) {
console.log("\u{1F504} Destroying existing PCF component before reinit");
manager.instance.destroy();
manager.instance = null;
}
manager.container.innerHTML = "";
console.log("\u{1F504} Initializing PCF component");
manager.instance = new manager.pcfClass();
await manager.instance.init(
manager.context,
() => console.log("PCF notifyOutputChanged called"),
{},
manager.container
);
await manager.instance.updateView(manager.context);
console.log("\u2705 PCF Component initialized successfully");
} catch (error) {
console.error("\u274C PCF Init failed:", error);
throw error;
}
}
async function updatePCFView(manager) {
if (!manager.instance) {
console.warn("No PCF component instance available for updateView");
return;
}
try {
console.log("\u{1F501} Calling PCF updateView");
await manager.instance.updateView(manager.context);
console.log("\u2705 PCF updateView completed");
} catch (error) {
console.error("\u274C PCF updateView failed:", error);
throw error;
}
}
async function destroyPCF(manager) {
if (!manager.instance) {
console.warn("No PCF component instance available for destroy");
return;
}
try {
console.log("\u{1F525} Destroying PCF component");
manager.instance.destroy();
manager.instance = null;
manager.container.innerHTML = "";
console.log("\u2705 PCF Component destroyed");
} catch (error) {
console.error("\u274C PCF destroy failed:", error);
throw error;
}
}
function isPCFInitialized(manager) {
return !!manager.instance;
}
// src/utils/envValidation.ts
function checkRequiredEnvVars() {
const required = ["VITE_PCF_TARGET_TABLE"];
const missing = [];
for (const varName of required) {
const value = undefined[varName];
if (!value || value.trim() === "") {
missing.push(varName);
}
}
const isValid = missing.length === 0;
console.log("\u{1F50D} Environment variable validation:", {
required,
missing,
isValid,
currentValues: {
VITE_PCF_PAGE_TABLE: undefined.VITE_PCF_PAGE_TABLE,
VITE_PCF_TARGET_TABLE: undefined.VITE_PCF_TARGET_TABLE
}
});
return { isValid, missing };
}
function redirectToSetupIfNeeded(componentType = "field") {
if (window.location.pathname === "/setup") {
return false;
}
if (componentType === "field") {
console.log("\u{1F527} Field component detected - skipping setup wizard");
return false;
}
const { isValid, missing } = checkRequiredEnvVars();
if (!isValid) {
console.log(`\u26A0\uFE0F Missing required environment variables: ${missing.join(", ")}`);
console.log("\u{1F504} Redirecting to setup wizard...");
window.history.replaceState(null, "", "/setup");
window.location.reload();
return true;
}
return false;
}
// src/utils/simpleDatasetLoader.ts
function detectDatasets(context) {
const datasets = [];
if (!context?.parameters) {
return { datasets, totalRecords: 0 };
}
Object.keys(context.parameters).forEach((key) => {
const param = context.parameters[key];
if (param && typeof param === "object" && "records" in param && "columns" in param) {
const entityType = undefined.VITE_PCF_TARGET_TABLE || "unknown";
datasets.push({
name: key,
entityLogicalName: entityType,
recordCount: param.sortedRecordIds?.length || 0
});
}
});
const totalRecords = datasets.reduce((sum, ds) => sum + ds.recordCount, 0);
return { datasets, totalRecords };
}
async function fetchDataForEntity(context, entityLogicalName) {
try {
const parentEntityId = undefined.VITE_PCF_PAGE_RECORD_ID;
const parentEntityType = undefined.VITE_PCF_PAGE_TABLE || "pum_initiative";
let options = `$select=*&$top=5000`;
if (parentEntityId && parentEntityType) {
const lookupField = `_${parentEntityType}_value`;
options += `&$filter=${lookupField} eq ${parentEntityId}`;
}
console.log(`\u{1F50D} Fetching data for ${entityLogicalName} with options: ${options}`);
const result = await context.webAPI.retrieveMultipleRecords(
entityLogicalName,
options
);
return result.entities || [];
} catch (error) {
console.error(`\u274C Failed to fetch ${entityLogicalName}:`, error);
return [];
}
}
async function injectRecordsIntoDataset(dataset, entities, entityLogicalName, webAPI) {
try {
const { convertEntitiesToDatasetRecords: convertEntitiesToDatasetRecords2 } = await Promise.resolve().then(() => (init_datasetRecordConverter(), datasetRecordConverter_exports));
const existingIds = dataset.sortedRecordIds || [];
existingIds.forEach((id) => {
if (dataset.records[id]) {
delete dataset.records[id];
}
});
const newRecords = await convertEntitiesToDatasetRecords2(entities, entityLogicalName, webAPI);
const newIds = Object.keys(newRecords);
Object.assign(dataset.records, newRecords);
dataset.sortedRecordIds = newIds;
console.log(`\u2705 Injected ${entities.length} records into dataset`);
return true;
} catch (error) {
console.error("\u274C Failed to inject records:", error);
return false;
}
}
async function loadDatasetData(context, onComplete) {
try {
console.log("\u{1F680} Simple dataset loader starting...");
const analysis = detectDatasets(context);
if (analysis.datasets.length === 0) {
console.log("\u{1F4ED} No datasets found");
return;
}
console.log(`\u{1F4CA} Found ${analysis.datasets.length} datasets`);
for (const datasetInfo of analysis.datasets) {
console.log(`\u{1F504} Loading ${datasetInfo.name} (${datasetInfo.entityLogicalName})`);
const entities = await fetchDataForEntity(context, datasetInfo.entityLogicalName);
if (entities.length > 0) {
const dataset = context.parameters[datasetInfo.name];
await injectRecordsIntoDataset(dataset, entities, datasetInfo.entityLogicalName, context.webAPI);
console.log(`\u2705 Loaded ${entities.length} records for ${datasetInfo.name}`);
} else {
console.log(`\u{1F4ED} No records found for ${datasetInfo.name}`);
}
}
console.log("\u{1F504} Dataset loading complete, triggering callback...");
if (onComplete) {
onComplete();
}
} catch (error) {
console.error("\u274C Simple dataset loader failed:", error);
}
}
function startAutoLoad(context, onComplete) {
const autoRefreshEnabled = undefined.VITE_PCF_AUTO_REFRESH !== "false";
if (!autoRefreshEnabled) {
console.log("\u{1F515} Auto-refresh disabled");
return;
}
console.log("\u{1F680} Starting auto-load immediately...");
loadDatasetData(context, onComplete);
}
var PowerAppsContainerInner = ({ context, pcfClass, className = "", manifestInfo, containerRef }) => {
const pcfManagerRef = React3__namespace.useRef(null);
React3__namespace.useEffect(() => {
if (containerRef.current && !pcfManagerRef.current) {
pcfManagerRef.current = createPCFManager(pcfClass, context, containerRef.current);
initPCF(pcfManagerRef.current).catch(console.error);
}
return () => {
if (pcfManagerRef.current) {
destroyPCF(pcfManagerRef.current).catch(console.error);
pcfManagerRef.current = null;
}
};
}, [pcfClass, context]);
React3__namespace.useEffect(() => {
if (context && pcfManagerRef.current) {
startAutoLoad(context, () => {
console.log("\u{1F504} Dataset loaded, triggering updateView...");
if (pcfManagerRef.current) {
updatePCFView(pcfManagerRef.current).catch(console.error);
}
});
}
}, [context]);
return /* @__PURE__ */ jsxRuntime.jsx(
"div",
{
id: "tab-section2",
className: `pa-g pa-ae pa-h pa-ht pa-cf pa-pb pa-du pa-bx flexbox ${className}`,
style: {
height: "100vh",
width: "100vw",
overflow: "hidden",
backgroundColor: "#f3f2f1",
fontFamily: '"Segoe UI", "Segoe UI Web (West European)", "Segoe UI", -apple-system, BlinkMacSystemFont, Roboto, "Helvetica Neue", sans-serif'
},
children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "pa-op pa-gm flexbox", style: { height: "100%", width: "100%" }, children: /* @__PURE__ */ jsxRuntime.jsx(
"div",
{
id: "id-875",
"aria-label": "PCF Control",
role: "tabpanel",
className: "pa-g pa-cf pa-no pa-qm pa-bx pa-bf pa-pd forceNewStackContext flexbox",
style: { height: "100%", width: "100%", flex: "1 1 auto" },
children: /* @__PURE__ */ jsxRuntime.jsx(
"div",
{
id: "id-872_2",
className: "pa-kr pa-cf pa-bu pa-bx pa-oe pa-qn flexbox",
style: { height: "100%", width: "100%" },
children: /* @__PURE__ */ jsxRuntime.jsx(
"div",
{
className: "pa-g pa-ct pa-h pa-j pa-hq flexbox",
style: { height: "100%", width: "100%" },
children: /* @__PURE__ */ jsxRuntime.jsx(
"div",
{
id: "dataSetRoot_Subgrid_new_2_outer",
className: "pa-g pa-ct pa-h pa-nk pa-nh flexbox",
style: { height: "100%", width: "100%" },
children: /* @__PURE__ */ jsxRuntime.jsx(
"div",
{
id: "DataSetHostContainer_dataSetRoot_Subgrid_new_2",
className: "pa-g pa-ct pa-h pa-nk pa-nh flexbox",
style: { height: "100%", width: "100%" },
children: /* @__PURE__ */ jsxRuntime.jsx(
"div",
{
id: "dataSetRoot_Subgrid_new_2",
className: "pa-g pa-ct pa-h pa-nk pa-nh flexbox",
style: { height: "100%", width: "100%" },
children: /* @__PURE__ */ jsxRuntime.jsx(
"div",
{
className: "pa-g pa-ct pa-bf pa-oe pa-bx pa-of pa-og flexbox",
style: { height: "100%", width: "100%" },
children: /* @__PURE__ */ jsxRuntime.jsx(
"div",
{
className: "pa-g pa-ct pa-k pa-ol flexbox",
style: { height: "100%", width: "100%" },
children: /* @__PURE__ */ jsxRuntime.jsx(
"div",
{
className: "pa-cf pa-ct pa-ox flexbox",
style: { height: "100%", width: "100%" },
children: /* @__PURE__ */ jsxRuntime.jsx(
"div",
{
id: "id-c98688f0-2676-483b-ae68-504795de5dfe-121_outer",
className: "pa-cf flexbox",
style: { height: "100%", width: "100%", position: "relative" },
children: /* @__PURE__ */ jsxRuntime.jsx(
"div",
{
id: "id-c98688f0-2676-483b-ae68-504795de5dfe-12",
className: "pa-cf flexbox",
style: { height: "100%", width: "100%" },
children: /* @__PURE__ */ jsxRuntime.jsx(
"div",
{
className: "customControl pcf-component flexbox",
"data-id": "pcf_container",
style: {
width: "100%",
height: "100%"
},
ref: containerRef
}
)
}
)
}
)
}
)
}
)
}
)
}
)
}
)
}
)
}
)
}
)
}
) })
}
);
};
var PowerAppsContainer = (props) => {
const containerRef = React3__namespace.useRef(null);
return /* @__PURE__ */ jsxRuntime.jsx(PowerAppsContainerInner, { ...props, containerRef });
};
// src/utils/manifestReader.ts
function readManifestFromFileSystem() {
if (typeof window !== "undefined") {
console.log("\u{1F310} Browser environment detected - cannot read manifest from filesystem directly");
return null;
}
try {
const fs = __require("fs");
const path = __require("path");
const possiblePaths = [
path.resolve(process.cwd(), "ControlManifest.Input.xml"),
path.resolve(process.cwd(), "dataset/ControlManifest.Input.xml"),
path.resolve(process.cwd(), "../dataset/ControlManifest.Input.xml"),
path.resolve(process.cwd(), "src/ControlManifest.Input.xml"),
path.resolve(process.cwd(), "control/ControlManifest.Input.xml")
];
const builtPaths = [
path.resolve(process.cwd(), "out/controls/dataset/ControlManifest.xml"),
path.resolve(process.cwd(), "out/controls/control/ControlManifest.xml"),
path.resolve(process.cwd(), "../out/controls/dataset/ControlManifest.xml")
];
const allPaths = [...possiblePaths, ...builtPaths];
for (const filePath of allPaths) {
try {
if (fs.existsSync(filePath)) {
const content = fs.readFileSync(filePath, "utf-8");
console.log(`\u{1F4C4} Found manifest file at ${filePath}`);
console.log(`\u{1F4C4} File content preview:`, content.substring(0, 200) + "...");
const manifest = parseManifestXml(content);
if (manifest) {
console.log(`\u2705 Successfully parsed manifest from ${filePath}:`, manifest);
return manifest;
} else {
console.log(`\u274C Failed to parse manifest from ${filePath}`);
}
}
} catch (error) {
}
}
console.log("\u{1F4DD} No manifest file found in any of the searched paths:", allPaths);
return null;
} catch (error) {
console.warn("Error reading manifest from file system:", error);
return null;
}
}
function parseManifestXml(xmlContent) {
try {
const { extractManifestFromXml: extractManifestFromXml2 } = (init_manifestExtractor(), __toCommonJS(manifestExtractor_exports));
const manifest = extractManifestFromXml2(xmlContent);
if (!manifest) {
const controlMatch = xmlContent.match(/<control[^>]*>/i);
if (!controlMatch) return null;
const controlElement = controlMatch[0];
const namespaceMatch = controlElement.match(/namespace=["']([^"']+)["']/i);
const constructorMatch = controlElement.match(/constructor=["']([^"']+)["']/i);
const versionMatch = controlElement.match(/version=["']([^"']+)["']/i);
const displayNameMatch = controlElement.match(/display-name-key=["']([^"']+)["']/i);
const descriptionMatch = controlElement.match(/description-key=["']([^"']+)["']/i);
if (!namespaceMatch || !constructorMatch || !versionMatch) {
return null;
}
const hasDataSet2 = xmlContent.includes("<data-set");
const componentType2 = hasDataSet2 ? "dataset" : "field";
return {
namespace: namespaceMatch[1],
constructor: constructorMatch[1],
version: versionMatch[1],
displayName: displayNameMatch?.[1],
description: descriptionMatch?.[1],
componentType: componentType2
};
}
const hasDataSet = manifest.datasets && manifest.datasets.length > 0;
const componentType = hasDataSet ? "dataset" : "field";
return {
namespace: manifest.namespace,
constructor: manifest.constructor,
version: manifest.version,
displayName: manifest.displayName,
description: manifest.description,
componentType,
datasets: manifest.datasets
};
} catch (error) {
console.error("Error parsing manifest XML:", error);
return null;
}
}
function detectManifestInfo(pcfClass) {
const fileSystemManifest = readManifestFromFileSystem();
if (fileSystemManifest) {
return fileSystemManifest;
}
const className = pcfClass.name;
console.log(`\u{1F4DD} No manifest file found, falling back to class name detection: ${className}`);
let namespace = "default";
let constructor = className.toLowerCase();
if (className.includes("_")) {
const parts = className.split("_");
namespace = parts[0] || "default";
constructor = parts.slice(1).join("_") || className;
} else if (/^[A-Z][a-z]+[A-Z]/.test(className)) {
const matches = className.match(/^([A-Z][a-z]+)(.+)$/);
if (matches) {
namespace = matches[1].toLowerCase();
constructor = matches[2].toLowerCase();
}
}
return {
namespace,
constructor,
version: "1.0.0",
displayName: constructor,
description: `${constructor} PCF Control`,
componentType: "field"
// Default to field when manifest not found
};
}
// src/utils/viewDiscovery.ts
async function getSystemViewsForEntity(entityLogicalName) {
const url = `/api/data/v9.2/savedqueries?$filter=returnedtypecode eq '${entityLogicalName}'&$select=savedqueryid,name,returne