push neon reactor
This commit is contained in:
Generated
Vendored
+39
@@ -0,0 +1,39 @@
|
||||
/*!
|
||||
* Copyright (C) Microsoft Corporation. All rights reserved.
|
||||
*/
|
||||
import type { ILogger } from '../services/index.js';
|
||||
import { DataverseMetadataService } from '../services/index.js';
|
||||
import type { AddDataSourceContext } from '../Types/ActionTypes.js';
|
||||
/**
|
||||
* Adds a data source based on the provided Config
|
||||
* Pre-requisites:
|
||||
* Host must call `initializeDataSource` before this function.
|
||||
* Required scopes for this function:
|
||||
* PPAPI - Connectivity.Connectors.Read, Connectivity.Connections.Read
|
||||
* APIHub - user_impersonation
|
||||
* DV - user_impersonation
|
||||
* @param config - Parameters and file paths configuration. Set `config.actionsParams.skipCodeGen` to true to skip model service generation.
|
||||
* @returns Promise<void>
|
||||
*/
|
||||
export declare function addDataSourceAsync(config: AddDataSourceContext): Promise<void>;
|
||||
/**
|
||||
* Ensures a Dataverse entity data source exists for the given logical name. If the entity is
|
||||
* already tracked in `power.config.json` (identified by `logicalName` in any
|
||||
* `databaseReferences[*].dataSources` entry), this is a no-op. Otherwise fetches the entity
|
||||
* definition from Dataverse, saves the schema file, and updates `power.config.json`.
|
||||
*
|
||||
* Code generation is intentionally skipped — the caller is responsible for running
|
||||
* `generateModelService` after all entity data sources are ensured.
|
||||
*/
|
||||
export declare function ensureDataverseEntityDataSource(params: {
|
||||
orgUrl: string;
|
||||
logicalName: string;
|
||||
localFilePaths: {
|
||||
schemaPath: string;
|
||||
codeGenPath: string;
|
||||
powerConfigPath: string;
|
||||
};
|
||||
metadataService: DataverseMetadataService;
|
||||
logger: ILogger;
|
||||
}): Promise<string | undefined>;
|
||||
//# sourceMappingURL=AddDataSource.d.ts.map
|
||||
Generated
Vendored
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"AddDataSource.d.ts","sourceRoot":"","sources":["../../src/Actions/AddDataSource.ts"],"names":[],"mappings":"AAAA;;GAEG;AAmBH,OAAO,KAAK,EAA+B,OAAO,EAAE,MAAM,aAAa,CAAC;AACxE,OAAO,EACL,wBAAwB,EASzB,MAAM,aAAa,CAAC;AACrB,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,sBAAsB,CAAC;AA8CjE;;;;;;;;;;GAUG;AACH,wBAAsB,kBAAkB,CAAC,MAAM,EAAE,oBAAoB,GAAG,OAAO,CAAC,IAAI,CAAC,CAoDpF;AAqQD;;;;;;;;GAQG;AACH,wBAAsB,+BAA+B,CAAC,MAAM,EAAE;IAC5D,MAAM,EAAE,MAAM,CAAC;IACf,WAAW,EAAE,MAAM,CAAC;IACpB,cAAc,EAAE;QAAE,UAAU,EAAE,MAAM,CAAC;QAAC,WAAW,EAAE,MAAM,CAAC;QAAC,eAAe,EAAE,MAAM,CAAA;KAAE,CAAC;IACrF,eAAe,EAAE,wBAAwB,CAAC;IAC1C,MAAM,EAAE,OAAO,CAAC;CACjB,GAAG,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAiC9B"}
|
||||
Generated
Vendored
+482
@@ -0,0 +1,482 @@
|
||||
/*!
|
||||
* Copyright (C) Microsoft Corporation. All rights reserved.
|
||||
*/
|
||||
import { processDataSourceInfo } from '../CodeGen/dataSourceInfoProcessor.js';
|
||||
import { generateModelService } from '../CodeGen/modelServiceGenerator.js';
|
||||
import { tryExtractDisplayNameFromCdpMetadata } from '../CodeGen/shared/cdpUtils.js';
|
||||
import { sanitizeTabularResourceName } from '../CodeGen/shared/nameUtils.js';
|
||||
import { DataSources } from '../Config/Config.types.js';
|
||||
import { readRepoConfig, writeRepoConfig } from '../Config/ConfigManager.js';
|
||||
import { initializePlayerActions } from '../initializePlayerActions.js';
|
||||
import { getExistingCdpNames, upsertConnectionReference, } from '../ReferenceStores/connectionReferences.js';
|
||||
import { getExistingDataverseNames, upsertDatabaseReference, } from '../ReferenceStores/databaseReferences.js';
|
||||
import { DataverseMetadataService, DataverseSystemDataService, getConnectionByNameAsync, getConnectorAsync, getPlayerServiceConfig, getSqlStoredProcedureMetadataAsync, getTableMetadataAsync, getTabularConnectorVersion, isConnectionShareable, } from '../services/index.js';
|
||||
import { getVfs } from '../VfsManager.js';
|
||||
import { getDataverseOrgUrl, getDataverseOrgUrlByEnvironmentName, getEnvironmentVariableName, isEnvironmentVariable, normalizeParam, resolveEnvironmentVariable, } from './DataUtils.js';
|
||||
function trackInfoEvent(logger, suffix, eventData) {
|
||||
try {
|
||||
logger?.trackActivityEvent?.(`AddDataSource.${suffix}`, eventData);
|
||||
}
|
||||
catch {
|
||||
/* swallow */
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Adds a data source based on the provided Config
|
||||
* Pre-requisites:
|
||||
* Host must call `initializeDataSource` before this function.
|
||||
* Required scopes for this function:
|
||||
* PPAPI - Connectivity.Connectors.Read, Connectivity.Connections.Read
|
||||
* APIHub - user_impersonation
|
||||
* DV - user_impersonation
|
||||
* @param config - Parameters and file paths configuration. Set `config.actionsParams.skipCodeGen` to true to skip model service generation.
|
||||
* @returns Promise<void>
|
||||
*/
|
||||
export async function addDataSourceAsync(config) {
|
||||
const apiId = await normalizeParam(config.actionsParams.apiId);
|
||||
if (!apiId) {
|
||||
throw new Error('Argument apiId is required for connector data sources. Use --apiId or -a to specify it.');
|
||||
}
|
||||
const { logger } = config;
|
||||
initializePlayerActions({
|
||||
vfs: config.vfs,
|
||||
authProvider: config.authProvider,
|
||||
region: config.region,
|
||||
environmentName: config.environmentName,
|
||||
logger: config.logger,
|
||||
});
|
||||
const dataSourceType = apiId.toLowerCase() === DataSources.Dataverse.toLowerCase()
|
||||
? DataSources.Dataverse
|
||||
: DataSources.Connector;
|
||||
if (dataSourceType === DataSources.Dataverse) {
|
||||
const addDataverseScenario = logger.trackScenario('AddDataSource.Dataverse', {
|
||||
tableName: config.actionsParams.tableName,
|
||||
envUrl: config.actionsParams.envUrl,
|
||||
solutionId: config.actionsParams.solutionId,
|
||||
});
|
||||
try {
|
||||
await addDataverseDataSourceAsync(config);
|
||||
addDataverseScenario.complete();
|
||||
}
|
||||
catch (err) {
|
||||
addDataverseScenario.failure({
|
||||
error: err instanceof Error ? err.message : String(err),
|
||||
});
|
||||
throw err;
|
||||
}
|
||||
return;
|
||||
}
|
||||
const addCdpScenario = logger.trackScenario('AddDataSource.Connector', config);
|
||||
try {
|
||||
await addCdpDataSourceAsync(config, apiId);
|
||||
addCdpScenario.complete();
|
||||
}
|
||||
catch (err) {
|
||||
addCdpScenario.failure({
|
||||
error: err instanceof Error ? err.message : String(err),
|
||||
});
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Adds a connector data source based on the provided environment variables.
|
||||
* Expects the following environment variables:
|
||||
* - API_ID: The ID of the API (e.g., 'dataverse' or a connector ID)
|
||||
* - CONNECTION_ID: The ID of the connection (for connector data sources)
|
||||
* - DATASET?: The dataset name for tabular/stored proc connector data sources
|
||||
* @param config - Local file paths configuration. Set `config.actionsParams.skipCodeGen` to true to skip model service generation.
|
||||
* @param apiId - The resolved API ID for the connector
|
||||
* @returns Promise<void>
|
||||
*/
|
||||
async function addCdpDataSourceAsync(config, apiId) {
|
||||
let connectionId;
|
||||
let resourceName;
|
||||
let dataset;
|
||||
let isProcedure = false;
|
||||
// The Power Platform environment variables that override the defaults
|
||||
let datasetOverride;
|
||||
let resourceNameOverride;
|
||||
const logger = config.logger;
|
||||
const { schemaPath, codeGenPath, powerConfigPath } = config.localFilePaths;
|
||||
const connectionReferenceLogicalName = await normalizeParam(config.actionsParams.connectionRef);
|
||||
if (!apiId) {
|
||||
throw new Error('Argument apiId is required for connector data sources. Use --apiId or -a to specify it.');
|
||||
}
|
||||
if (connectionReferenceLogicalName && connectionReferenceLogicalName?.length > 0) {
|
||||
const dataverseAPI = new DataverseSystemDataService(getPlayerServiceConfig().httpClient);
|
||||
const orgUrl = await getDataverseOrgUrl(powerConfigPath);
|
||||
if (!orgUrl) {
|
||||
throw new Error('Unable to determine the Dataverse organization URL. This is required for resolving connection references.');
|
||||
}
|
||||
const resolvedConnectionId = await dataverseAPI.getConnectionIdForReference(connectionReferenceLogicalName, orgUrl, logger);
|
||||
if (resolvedConnectionId) {
|
||||
connectionId = resolvedConnectionId;
|
||||
}
|
||||
else {
|
||||
throw new Error(`Failed to resolve connection ID for reference '${connectionReferenceLogicalName}'`);
|
||||
}
|
||||
}
|
||||
// If we did not get an ID from the connection reference, try to get it from the CONNECTION_ID param
|
||||
if (!connectionId) {
|
||||
connectionId = await normalizeParam(config.actionsParams.connectionId);
|
||||
}
|
||||
// If no connection ID is provided after checking both the connection reference and CONNECTION_ID param, throw an error
|
||||
if (!connectionId) {
|
||||
throw new Error('Argument connectionId is required for connector data sources. Use --connectionId or -c to specify it.');
|
||||
}
|
||||
// Fetch connector and connection
|
||||
const connector = await getConnectorAsync(apiId, logger);
|
||||
if (!connector) {
|
||||
trackInfoEvent(logger, 'ServiceCall.GetConnector.NotFound', { apiId });
|
||||
throw new Error(`Connector with ID ${apiId} not found.`);
|
||||
}
|
||||
const connection = await getConnectionByNameAsync(connectionId, apiId, logger);
|
||||
if (!connection) {
|
||||
trackInfoEvent(logger, 'ServiceCall.GetConnectionByName.NotFound', { connectionId, apiId });
|
||||
throw new Error(`Connection with name ${connectionId} not found for connector ${apiId}.`);
|
||||
}
|
||||
const connectorName = connector.name;
|
||||
const apiName = connectorName.startsWith('shared_')
|
||||
? connectorName.replace('shared_', '')
|
||||
: connectorName;
|
||||
// Read resourceName and dataset only for tabular connectors (those that advertise the
|
||||
// "tabular" capability), such as SQL, SharePoint, Oracle, MySQL, PostgreSQL, etc.
|
||||
const isTabular = connector.properties.capabilities?.includes('tabular') ?? false;
|
||||
if (isTabular) {
|
||||
resourceName = await normalizeParam(config.actionsParams.tableName);
|
||||
dataset = await normalizeParam(config.actionsParams.dataset);
|
||||
}
|
||||
// If SQL, check if the resourceName is actually a stored procedure
|
||||
if (apiName === 'sql') {
|
||||
const storedProcedure = await normalizeParam(config.actionsParams.sqlStoredProcedure);
|
||||
if (storedProcedure) {
|
||||
resourceName = storedProcedure;
|
||||
isProcedure = true;
|
||||
}
|
||||
else if (!resourceName) {
|
||||
throw new Error('Either SQL_STORED_PROCEDURE or TABLE must be provided for SQL data sources.');
|
||||
}
|
||||
}
|
||||
if (isEnvironmentVariable(dataset)) {
|
||||
// Handle environment variable case
|
||||
const orgUrl = await getDataverseOrgUrl(powerConfigPath);
|
||||
if (!orgUrl) {
|
||||
throw new Error('Unable to determine the Dataverse organization URL.');
|
||||
}
|
||||
datasetOverride = getEnvironmentVariableName(dataset);
|
||||
const resolvedEnvVariable = await resolveEnvironmentVariable(dataset, getPlayerServiceConfig().httpClient, orgUrl, logger);
|
||||
// the APIM metadata call expects the dataset name to be double encoded
|
||||
if (resolvedEnvVariable) {
|
||||
dataset = encodeURIComponent(encodeURIComponent(resolvedEnvVariable));
|
||||
}
|
||||
}
|
||||
if (isEnvironmentVariable(resourceName)) {
|
||||
const orgUrl = await getDataverseOrgUrl(powerConfigPath);
|
||||
if (!orgUrl) {
|
||||
throw new Error('Unable to determine the Dataverse organization URL.');
|
||||
}
|
||||
resourceNameOverride = getEnvironmentVariableName(resourceName);
|
||||
const resolvedEnvVariable = await resolveEnvironmentVariable(resourceName, getPlayerServiceConfig().httpClient, orgUrl, logger);
|
||||
if (resolvedEnvVariable) {
|
||||
resourceName = resolvedEnvVariable;
|
||||
}
|
||||
}
|
||||
let tabularMetadata;
|
||||
if (resourceName) {
|
||||
if (dataset) {
|
||||
tabularMetadata = {
|
||||
dataset,
|
||||
resourceName,
|
||||
isProcedure,
|
||||
datasetOverride,
|
||||
resourceNameOverride,
|
||||
};
|
||||
}
|
||||
else {
|
||||
throw new Error(`Dataset is required when specifying a ${isProcedure ? 'SQL stored procedure' : 'table name'}.`);
|
||||
}
|
||||
}
|
||||
if (!tabularMetadata && !connector.properties.swagger) {
|
||||
throw new Error(`Connector ${config.actionsParams.apiId} did not return a Swagger definition.`);
|
||||
}
|
||||
const authenticationType = connection.properties?.connectionParametersSet?.name;
|
||||
let sharedConnectionId;
|
||||
if (isConnectionShareable(connector, authenticationType)) {
|
||||
sharedConnectionId = connection.id;
|
||||
}
|
||||
let metadata;
|
||||
if (dataset && resourceName) {
|
||||
metadata = isProcedure
|
||||
? await getSqlStoredProcedureMetadataAsync(connection, connector, dataset, resourceName, logger)
|
||||
: await getTableMetadataAsync(connection, connector, dataset, resourceName, logger);
|
||||
}
|
||||
else {
|
||||
metadata = connector;
|
||||
}
|
||||
await handleAddCdpDataSourceAsync({
|
||||
metadata,
|
||||
connector,
|
||||
schemaPath,
|
||||
codeGenPath,
|
||||
configPath: powerConfigPath,
|
||||
tabularMetadata,
|
||||
authenticationType,
|
||||
sharedConnectionId,
|
||||
connectionReferenceLogicalName,
|
||||
dataSourceShareLink: config.actionsParams.dataSourceShareLink,
|
||||
siteId: config.actionsParams.siteId,
|
||||
}, logger, config.actionsParams.skipCodeGen);
|
||||
}
|
||||
/**
|
||||
* Adds a Dataverse data source by fetching entity definition metadata
|
||||
* Expects the following environment variables:
|
||||
* - TABLE: The name of the Dataverse table
|
||||
* @param config - Local file paths configuration. Set `config.actionsParams.skipCodeGen` to true to skip model service generation.
|
||||
* @returns Promise that resolves when the data source is added
|
||||
*/
|
||||
async function addDataverseDataSourceAsync(config) {
|
||||
const logger = config.logger;
|
||||
const { schemaPath, codeGenPath, powerConfigPath } = config.localFilePaths;
|
||||
const tableName = (await normalizeParam(config.actionsParams.tableName))?.toLowerCase();
|
||||
if (!tableName) {
|
||||
throw new Error('Argument resourceName is required for Dataverse data sources. Use --resourceName or -t to specify it.');
|
||||
}
|
||||
// The org url might be provided by PAC CLI as an environment variable
|
||||
let orgUrl = await normalizeParam(config.actionsParams.envUrl);
|
||||
if (!orgUrl && config.environmentName && config.environmentName !== 'default') {
|
||||
orgUrl = await getDataverseOrgUrlByEnvironmentName(config.environmentName);
|
||||
}
|
||||
if (!orgUrl) {
|
||||
throw new Error('Unable to determine the Dataverse organization URL');
|
||||
}
|
||||
const { httpClient } = getPlayerServiceConfig();
|
||||
const metadataService = new DataverseMetadataService(httpClient);
|
||||
const entityDefinition = await metadataService.getEntityDefinitionAsync(orgUrl, tableName, true, logger);
|
||||
const schema = metadataService.convertEntityDefinitionToSchema(entityDefinition);
|
||||
const entitySetName = entityDefinition.EntitySetName || tableName + 's';
|
||||
await handleAddDataverseDataSource({
|
||||
schema,
|
||||
schemaPath,
|
||||
codeGenPath,
|
||||
configPath: powerConfigPath,
|
||||
orgUrl,
|
||||
entitySetName,
|
||||
logicalName: tableName,
|
||||
}, logger, config.actionsParams.skipCodeGen);
|
||||
}
|
||||
/**
|
||||
* Ensures a Dataverse entity data source exists for the given logical name. If the entity is
|
||||
* already tracked in `power.config.json` (identified by `logicalName` in any
|
||||
* `databaseReferences[*].dataSources` entry), this is a no-op. Otherwise fetches the entity
|
||||
* definition from Dataverse, saves the schema file, and updates `power.config.json`.
|
||||
*
|
||||
* Code generation is intentionally skipped — the caller is responsible for running
|
||||
* `generateModelService` after all entity data sources are ensured.
|
||||
*/
|
||||
export async function ensureDataverseEntityDataSource(params) {
|
||||
const { orgUrl, logicalName, localFilePaths, metadataService, logger } = params;
|
||||
const { schemaPath, codeGenPath, powerConfigPath } = localFilePaths;
|
||||
const config = await readRepoConfig(powerConfigPath);
|
||||
const alreadyPresent = Object.values(config.databaseReferences ?? {}).some((dbRef) => Object.values(dbRef.dataSources ?? {}).some((ds) => ds.logicalName === logicalName));
|
||||
if (alreadyPresent)
|
||||
return undefined;
|
||||
const entityDefinition = await metadataService.getEntityDefinitionAsync(orgUrl, logicalName, true, logger);
|
||||
const schema = metadataService.convertEntityDefinitionToSchema(entityDefinition);
|
||||
const entitySetName = entityDefinition.EntitySetName || logicalName + 's';
|
||||
const dataSourceName = await handleAddDataverseDataSource({
|
||||
schema,
|
||||
schemaPath,
|
||||
codeGenPath,
|
||||
configPath: powerConfigPath,
|
||||
orgUrl,
|
||||
entitySetName,
|
||||
logicalName,
|
||||
}, logger, true // skipCodeGen — caller runs generateModelService after all entities are ensured
|
||||
);
|
||||
const vfs = getVfs();
|
||||
return vfs.join(schemaPath, DataSources.Dataverse.toLowerCase(), `${dataSourceName}.Schema.json`);
|
||||
}
|
||||
/**
|
||||
* Handles adding a Dataverse data source by updating database references,
|
||||
* saving metadata, processing data source info, and optionally generating model services.
|
||||
* @param params - Dataverse data source parameters (schema, paths, org URL, entity info)
|
||||
* @param logger - Logger instance for telemetry
|
||||
* @param skipCodeGen - If true, skips the code generation step (model service generation) after adding the data source
|
||||
* @returns The data source name
|
||||
*/
|
||||
async function handleAddDataverseDataSource(params, logger, skipCodeGen) {
|
||||
const { schema, schemaPath, codeGenPath, configPath, orgUrl, entitySetName, logicalName } = params;
|
||||
const dataSourceName = await updateDatabaseReferences(configPath, {
|
||||
orgUrl,
|
||||
entitySetName,
|
||||
logicalName,
|
||||
displayName: schema.title,
|
||||
}, logger);
|
||||
const metadataFilePath = await saveMetadata(schema, DataSources.Dataverse.toLowerCase(), dataSourceName, schemaPath, undefined, logger);
|
||||
await processDataSourceInfo(schemaPath, logger);
|
||||
if (!skipCodeGen) {
|
||||
await generateModelService({
|
||||
schemaFolderPath: schemaPath,
|
||||
codeGenPath,
|
||||
schemaFilePath: metadataFilePath,
|
||||
logger,
|
||||
});
|
||||
}
|
||||
return dataSourceName;
|
||||
}
|
||||
/**
|
||||
* Updates database references for Dataverse data sources
|
||||
*/
|
||||
async function updateDatabaseReferences(configPath, params, logger) {
|
||||
try {
|
||||
trackInfoEvent(logger, 'UpdateDatabaseReferences.Start', { logicalName: params.logicalName });
|
||||
// Check if config file exists
|
||||
const vfs = getVfs();
|
||||
const configExists = await vfs.exists(configPath);
|
||||
if (!configExists) {
|
||||
throw new Error(`Configuration file not found at ${configPath}`);
|
||||
}
|
||||
const config = await readRepoConfig(configPath);
|
||||
if (!config.databaseReferences) {
|
||||
config.databaseReferences = {};
|
||||
}
|
||||
const dataSourceName = upsertDatabaseReference(config.databaseReferences, {
|
||||
...params,
|
||||
// Provide the existing CDP data source names to avoid conflicts
|
||||
cdpDataSourceNames: getExistingCdpNames(config.connectionReferences ?? {}),
|
||||
});
|
||||
await writeRepoConfig(configPath, config);
|
||||
trackInfoEvent(logger, 'UpdateDatabaseReferences.Success', {
|
||||
logicalName: params.logicalName,
|
||||
dataSourceName,
|
||||
});
|
||||
return dataSourceName;
|
||||
}
|
||||
catch (error) {
|
||||
trackInfoEvent(logger, 'UpdateDatabaseReferences.Failure', {
|
||||
logicalName: params.logicalName,
|
||||
error: error instanceof Error ? error.message : String(error),
|
||||
});
|
||||
throw new Error(`Failed to update database references: ${error instanceof Error ? error.message : String(error)}`);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Handles adding a CDP (connector) data source by updating connection references,
|
||||
* saving metadata, processing data source info, and optionally generating model services.
|
||||
* @param params - CDP data source parameters (metadata, connector, paths, auth info)
|
||||
* @param logger - Logger instance for telemetry
|
||||
* @param skipCodeGen - If true, skips the code generation step (model service generation) after adding the data source
|
||||
* @returns The data source name
|
||||
*/
|
||||
async function handleAddCdpDataSourceAsync(params, logger, skipCodeGen) {
|
||||
const { metadata, connector, schemaPath, codeGenPath, configPath, tabularMetadata, authenticationType, sharedConnectionId, connectionReferenceLogicalName, siteId, } = params;
|
||||
// Compute values inside the function
|
||||
const apiId = connector.id;
|
||||
const connectorDisplayName = connector.properties.displayName || connector.name;
|
||||
const apiName = connector.name.toLowerCase().replace(/^shared_/, '');
|
||||
const dataSourceShareLink = params.dataSourceShareLink;
|
||||
if (tabularMetadata) {
|
||||
const resourceDisplayName = tryExtractDisplayNameFromCdpMetadata(metadata) ?? tabularMetadata.resourceName;
|
||||
tabularMetadata.friendlyName = sanitizeTabularResourceName(resourceDisplayName);
|
||||
}
|
||||
const { dataSourceName, spEntity } = await updateConnectionReferences(configPath, {
|
||||
apiId,
|
||||
apiName,
|
||||
connectorDisplayName,
|
||||
tabularMetadata,
|
||||
authenticationType,
|
||||
sharedConnectionId,
|
||||
connectionReferenceLogicalName,
|
||||
dataSourceShareLink,
|
||||
siteId,
|
||||
}, logger);
|
||||
trackInfoEvent(logger, 'UpdateConnectionReferences.Success', {
|
||||
apiName,
|
||||
dataSourceName,
|
||||
hasSpEntity: !!spEntity,
|
||||
});
|
||||
const connectorVersion = tabularMetadata ? getTabularConnectorVersion(connector) : undefined;
|
||||
const schemaFilePath = await saveMetadata(metadata, apiName, dataSourceName, schemaPath, spEntity, logger, connectorVersion);
|
||||
await processDataSourceInfo(schemaPath, logger);
|
||||
if (!skipCodeGen) {
|
||||
await generateModelService({
|
||||
schemaFolderPath: schemaPath,
|
||||
codeGenPath,
|
||||
schemaFilePath: schemaFilePath,
|
||||
logger,
|
||||
});
|
||||
}
|
||||
return dataSourceName;
|
||||
}
|
||||
/**
|
||||
* Writes the metadata as a JSON file {dsName}.Schema.json under schemaPath/apiName/(spEntity)/
|
||||
* Returns the file path of the saved metadata
|
||||
*/
|
||||
async function saveMetadata(metadata, apiName, dsName, schemaPath, spEntity, logger, connectorVersion) {
|
||||
const scenario = logger?.trackScenario('AddDataSource.SaveMetadata', {
|
||||
apiName,
|
||||
dsName,
|
||||
spEntity,
|
||||
});
|
||||
try {
|
||||
const vfs = getVfs();
|
||||
// Annotate tabular metadata with the connector's API version so that
|
||||
// processDataSourceInfo can use it instead of guessing from the table name.
|
||||
const annotatedMetadata = connectorVersion !== undefined
|
||||
? { ...metadata, 'x-ms-connection-version': connectorVersion }
|
||||
: metadata;
|
||||
// Convert the metadata object to a JSON string
|
||||
const tabularJson = JSON.stringify(annotatedMetadata, null, 2);
|
||||
// Define the directory and file path
|
||||
let directoryPath = vfs.join(schemaPath, apiName);
|
||||
if (spEntity) {
|
||||
directoryPath = vfs.join(directoryPath, spEntity);
|
||||
}
|
||||
const filePath = vfs.join(directoryPath, `${dsName}.Schema.json`);
|
||||
// Ensure the directory exists
|
||||
await vfs.mkdir(directoryPath, { recursive: true });
|
||||
// Save the JSON string to a file
|
||||
await vfs.writeFile(filePath, tabularJson, { encoding: 'utf-8' });
|
||||
scenario?.complete();
|
||||
return filePath;
|
||||
}
|
||||
catch (error) {
|
||||
scenario?.failure({ error });
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Updates connection references for connector data sources
|
||||
*/
|
||||
async function updateConnectionReferences(configPath, params, logger) {
|
||||
const vfs = getVfs();
|
||||
try {
|
||||
trackInfoEvent(logger, 'UpdateConnectionReferences.Start', { apiName: params.apiName });
|
||||
// Check if config file exists
|
||||
if (!(await vfs.exists(configPath))) {
|
||||
throw new Error(`Configuration file not found at ${configPath}`);
|
||||
}
|
||||
const config = await readRepoConfig(configPath);
|
||||
if (!config.connectionReferences) {
|
||||
config.connectionReferences = {};
|
||||
}
|
||||
const { dataSourceName, spEntity } = upsertConnectionReference(config.connectionReferences, {
|
||||
...params,
|
||||
dataverseDataSourceNames: getExistingDataverseNames(config.databaseReferences ?? {}),
|
||||
});
|
||||
await writeRepoConfig(configPath, config);
|
||||
trackInfoEvent(logger, 'UpdateConnectionReferences.Commit', {
|
||||
apiName: params.apiName,
|
||||
dataSourceName,
|
||||
});
|
||||
return { dataSourceName, spEntity };
|
||||
}
|
||||
catch (error) {
|
||||
trackInfoEvent(logger, 'UpdateConnectionReferences.Failure', {
|
||||
apiName: params.apiName,
|
||||
error: error instanceof Error ? error.message : String(error),
|
||||
});
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
//# sourceMappingURL=AddDataSource.js.map
|
||||
Generated
Vendored
+1
File diff suppressed because one or more lines are too long
Generated
Vendored
+34
@@ -0,0 +1,34 @@
|
||||
/*!
|
||||
* Copyright (C) Microsoft Corporation. All rights reserved.
|
||||
*/
|
||||
import type { CsdlOperation } from '../services/index.js';
|
||||
import type { AddDataverseApiContext } from '../Types/ActionTypes.js';
|
||||
/**
|
||||
* Converts a parsed `CsdlOperation` (from Dataverse `$metadata` XML) into the `.Schema.json`
|
||||
* file format consumed by the codegen pipeline.
|
||||
*
|
||||
* The output uses the connector schema format (`properties.swagger`) so the existing
|
||||
* `handleSwaggerSchema` codegen path processes it without a separate handler. All parameter
|
||||
* metadata — `in`, `type`, `x-ms-dataverse-type`, `x-ms-csdl-type` — is resolved at write-time
|
||||
* and baked into the swagger. Downstream processors read the swagger spec directly.
|
||||
*
|
||||
* The `x-ms-csdl-type` extension preserves the original CSDL type string (e.g.
|
||||
* `"mscrm.opportunity"`) so that `modelServiceGenerator` can resolve entity references to their
|
||||
* generated TypeScript `*Base` interfaces instead of falling back to plain `object`.
|
||||
*
|
||||
* @param operation - The parsed CSDL operation (kind, parameters, return type, binding info)
|
||||
* @param entitySetName - OData entity set name for the binding entity (e.g. `"opportunities"`),
|
||||
* or `undefined` for unbound operations
|
||||
* @param apiName - The user-supplied API name, used verbatim as `title` and lowercased as `name`
|
||||
*/
|
||||
export declare function buildDataverseOperationSchema(operation: CsdlOperation, entitySetName: string | undefined, apiName: string): Record<string, unknown>;
|
||||
/**
|
||||
* Adds a Dataverse API (custom action, custom function, SDK message, or first-party
|
||||
* action/function) to the app by fetching its definition from the $metadata CSDL,
|
||||
* persisting a schema file, and updating dataSourcesInfo.
|
||||
*
|
||||
* @param {AddDataverseApiContext} context
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
export declare function addDataverseApiAsync(context: AddDataverseApiContext): Promise<void>;
|
||||
//# sourceMappingURL=AddDataverseApi.d.ts.map
|
||||
CodeApp_NeonReactor/node_modules/@microsoft/power-apps-actions/dist/Actions/AddDataverseApi.d.ts.map
Generated
Vendored
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"AddDataverseApi.d.ts","sourceRoot":"","sources":["../../src/Actions/AddDataverseApi.ts"],"names":[],"mappings":"AAAA;;GAEG;AAQH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAMjD,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,sBAAsB,CAAC;AAoInE;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,6BAA6B,CAC3C,SAAS,EAAE,aAAa,EACxB,aAAa,EAAE,MAAM,GAAG,SAAS,EACjC,OAAO,EAAE,MAAM,GACd,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAgGzB;AAED;;;;;;;GAOG;AACH,wBAAsB,oBAAoB,CAAC,OAAO,EAAE,sBAAsB,GAAG,OAAO,CAAC,IAAI,CAAC,CAqHzF"}
|
||||
Generated
Vendored
+383
@@ -0,0 +1,383 @@
|
||||
/*!
|
||||
* Copyright (C) Microsoft Corporation. All rights reserved.
|
||||
*/
|
||||
import { processDataSourceInfo } from '../CodeGen/dataSourceInfoProcessor.js';
|
||||
import { generateModelService } from '../CodeGen/modelServiceGenerator.js';
|
||||
import { readRepoConfig, writeRepoConfig } from '../Config/ConfigManager.js';
|
||||
import { initializePlayerActions } from '../initializePlayerActions.js';
|
||||
import { getExistingCdpNames } from '../ReferenceStores/connectionReferences.js';
|
||||
import { upsertDatabaseReference } from '../ReferenceStores/databaseReferences.js';
|
||||
import { DataverseMetadataService, getEnvironmentByName, getPlayerServiceConfig, } from '../services/index.js';
|
||||
import { getVfs } from '../VfsManager.js';
|
||||
import { ensureDataverseEntityDataSource } from './AddDataSource.js';
|
||||
/**
|
||||
* Maps a CSDL type string to the JSON Schema `type` field used in Swagger parameter and response
|
||||
* objects. This is the structural type that tooling (e.g. the Swagger UI, codegen) uses to
|
||||
* understand the wire shape — not the Dataverse-specific semantic type.
|
||||
*
|
||||
* Entity references (`mscrm.*`) map to `object` because they serialize as JSON objects on the
|
||||
* wire. Collections of any kind map to `array`. All string-like Edm types (dates, GUIDs, durations)
|
||||
* collapse to `string` because they have no richer JSON primitive.
|
||||
*/
|
||||
function mapCsdlTypeToJsonSchemaType(csdlType) {
|
||||
if (csdlType.startsWith('Collection('))
|
||||
return 'array';
|
||||
if (csdlType.startsWith('mscrm.'))
|
||||
return 'object';
|
||||
switch (csdlType) {
|
||||
case 'Edm.Boolean':
|
||||
return 'boolean';
|
||||
case 'Edm.Int16':
|
||||
case 'Edm.Int32':
|
||||
case 'Edm.Byte':
|
||||
case 'Edm.SByte':
|
||||
case 'Edm.Int64':
|
||||
case 'Edm.Decimal':
|
||||
case 'Edm.Double':
|
||||
case 'Edm.Single':
|
||||
return 'number';
|
||||
default:
|
||||
return 'string';
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Maps a CSDL type string to the `x-ms-dataverse-type` extension field written into the schema
|
||||
* file. This is the Dataverse semantic type used downstream by `modelServiceGenerator` to produce
|
||||
* correctly typed TypeScript — for example, `LookupType` signals that the parameter is an entity
|
||||
* reference that should resolve to a generated `*Base` interface rather than plain `object`.
|
||||
*
|
||||
* The `mscrm.*` prefix check must come before the generic `Collection(` check so that
|
||||
* `Collection(mscrm.*)` resolves to `LookupCollectionType` rather than the generic `CollectionType`.
|
||||
*/
|
||||
function mapCsdlTypeToDataverseType(csdlType) {
|
||||
// Check entity collection before generic collection — order matters here
|
||||
if (csdlType.startsWith('Collection(mscrm.'))
|
||||
return 'LookupCollectionType';
|
||||
if (csdlType.startsWith('Collection('))
|
||||
return 'CollectionType';
|
||||
if (csdlType.startsWith('mscrm.'))
|
||||
return 'LookupType';
|
||||
switch (csdlType) {
|
||||
case 'Edm.String':
|
||||
case 'Edm.Duration':
|
||||
case 'Edm.TimeOfDay':
|
||||
return 'StringType';
|
||||
case 'Edm.Guid':
|
||||
return 'UniqueidentifierType';
|
||||
case 'Edm.Boolean':
|
||||
return 'BooleanType';
|
||||
case 'Edm.Int16':
|
||||
case 'Edm.Int32':
|
||||
case 'Edm.Byte':
|
||||
case 'Edm.SByte':
|
||||
return 'IntegerType';
|
||||
case 'Edm.Int64':
|
||||
// Int64 is kept separate from Int32 because some runtimes treat 64-bit integers specially
|
||||
// (e.g. precision loss in JavaScript). Downstream codegen can choose to map this to bigint.
|
||||
return 'BigIntType';
|
||||
case 'Edm.Decimal':
|
||||
return 'DecimalType';
|
||||
case 'Edm.Double':
|
||||
return 'DoubleType';
|
||||
case 'Edm.Single':
|
||||
return 'FloatType';
|
||||
case 'Edm.DateTimeOffset':
|
||||
case 'Edm.Date':
|
||||
return 'DateTimeType';
|
||||
case 'Edm.Binary':
|
||||
return 'FileType';
|
||||
default:
|
||||
// Unknown CSDL types are treated as strings — this preserves forward compatibility when
|
||||
// new Edm types appear in Dataverse metadata before this mapping is updated.
|
||||
return 'StringType';
|
||||
}
|
||||
}
|
||||
const UNKNOWN_LOGICAL_NAME = 'unknown';
|
||||
function extractEntityLogicalName(csdlType, options) {
|
||||
const collectionMatch = csdlType.match(/^Collection\(mscrm\.(\w+)\)$/);
|
||||
if (collectionMatch) {
|
||||
return collectionMatch[1];
|
||||
}
|
||||
if (!options?.collectionOnly) {
|
||||
const entityMatch = csdlType.match(/^mscrm\.(\w+)$/);
|
||||
if (entityMatch) {
|
||||
return entityMatch[1];
|
||||
}
|
||||
}
|
||||
if (csdlType.startsWith('mscrm.') || csdlType.startsWith('Collection(mscrm.')) {
|
||||
return UNKNOWN_LOGICAL_NAME;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
/**
|
||||
* Extracts the Dataverse entity logical names from the non-binding parameters and return type of a
|
||||
* CSDL operation. These are the entities that must have their data sources ensured before codegen
|
||||
* runs, so that `buildEntityModelMap` in `modelServiceGenerator` finds schema files for them.
|
||||
*
|
||||
* The binding parameter (index 0 of bound operations) is excluded — it is handled separately by
|
||||
* `addDataverseApiAsync` when resolving the entity set name for the operation path.
|
||||
*/
|
||||
function extractEntityLogicalNames(operation) {
|
||||
const startIdx = operation.isBound ? 1 : 0;
|
||||
const names = new Set();
|
||||
for (const param of operation.parameters.slice(startIdx)) {
|
||||
const logicalName = extractEntityLogicalName(param.type);
|
||||
if (logicalName)
|
||||
names.add(logicalName);
|
||||
}
|
||||
if (operation.returnType) {
|
||||
const logicalName = extractEntityLogicalName(operation.returnType.type, {
|
||||
collectionOnly: true,
|
||||
});
|
||||
if (logicalName)
|
||||
names.add(logicalName);
|
||||
}
|
||||
return [...names];
|
||||
}
|
||||
/**
|
||||
* Converts a parsed `CsdlOperation` (from Dataverse `$metadata` XML) into the `.Schema.json`
|
||||
* file format consumed by the codegen pipeline.
|
||||
*
|
||||
* The output uses the connector schema format (`properties.swagger`) so the existing
|
||||
* `handleSwaggerSchema` codegen path processes it without a separate handler. All parameter
|
||||
* metadata — `in`, `type`, `x-ms-dataverse-type`, `x-ms-csdl-type` — is resolved at write-time
|
||||
* and baked into the swagger. Downstream processors read the swagger spec directly.
|
||||
*
|
||||
* The `x-ms-csdl-type` extension preserves the original CSDL type string (e.g.
|
||||
* `"mscrm.opportunity"`) so that `modelServiceGenerator` can resolve entity references to their
|
||||
* generated TypeScript `*Base` interfaces instead of falling back to plain `object`.
|
||||
*
|
||||
* @param operation - The parsed CSDL operation (kind, parameters, return type, binding info)
|
||||
* @param entitySetName - OData entity set name for the binding entity (e.g. `"opportunities"`),
|
||||
* or `undefined` for unbound operations
|
||||
* @param apiName - The user-supplied API name, used verbatim as `title` and lowercased as `name`
|
||||
*/
|
||||
export function buildDataverseOperationSchema(operation, entitySetName, apiName) {
|
||||
const { name, kind, isBound, parameters, returnType } = operation;
|
||||
// OData semantics: Functions are read-only (GET), Actions are write/side-effect (POST)
|
||||
const method = kind === 'Function' ? 'get' : 'post';
|
||||
// Bound operations target a specific entity record via its primary key in the path.
|
||||
// Unbound operations are called directly on the service root.
|
||||
const operationPath = isBound && entitySetName
|
||||
? `/${entitySetName}({id})/Microsoft.Dynamics.CRM.${name}`
|
||||
: `/${name}`;
|
||||
const swaggerParams = [];
|
||||
if (isBound && entitySetName) {
|
||||
// The binding parameter in CSDL describes the entity type, but on the wire it becomes a GUID
|
||||
// primary key in the URL path. We replace it with a synthetic `id` path parameter.
|
||||
swaggerParams.push({
|
||||
name: 'id',
|
||||
in: 'path',
|
||||
required: true,
|
||||
type: 'string',
|
||||
'x-ms-dataverse-type': 'UniqueidentifierType',
|
||||
'x-ms-csdl-type': 'Edm.Guid',
|
||||
});
|
||||
}
|
||||
// Skip index 0 for bound operations — that is the binding parameter (the entity type), which
|
||||
// was already replaced above with the synthetic `id` path parameter.
|
||||
// Gate on entitySetName for the same reason as the id-param injection above: if isBound=true
|
||||
// but entitySetName is undefined, we have no valid path to emit, so treat it as unbound.
|
||||
const startIdx = isBound && entitySetName ? 1 : 0;
|
||||
for (const param of parameters.slice(startIdx)) {
|
||||
swaggerParams.push({
|
||||
name: param.name,
|
||||
// OData Functions pass parameters via query string; Actions pass them in the request body
|
||||
in: kind === 'Function' ? 'query' : 'body',
|
||||
// CSDL `nullable: false` means the parameter is required on the wire
|
||||
required: !param.nullable,
|
||||
type: mapCsdlTypeToJsonSchemaType(param.type),
|
||||
'x-ms-dataverse-type': mapCsdlTypeToDataverseType(param.type),
|
||||
// Preserve the raw CSDL type so modelServiceGenerator can resolve entity interface names
|
||||
'x-ms-csdl-type': param.type,
|
||||
});
|
||||
}
|
||||
// Operations without a return type yield HTTP 204 No Content.
|
||||
// Operations with a return type yield HTTP 200 with a typed response schema.
|
||||
const responses = returnType
|
||||
? {
|
||||
'200': {
|
||||
description: 'Success',
|
||||
schema: {
|
||||
type: mapCsdlTypeToJsonSchemaType(returnType.type),
|
||||
'x-ms-dataverse-type': mapCsdlTypeToDataverseType(returnType.type),
|
||||
'x-ms-csdl-type': returnType.type,
|
||||
},
|
||||
},
|
||||
}
|
||||
: { '204': { description: 'No Content' } };
|
||||
// Aligns to the connector schema format (properties.swagger) so the existing
|
||||
// handleSwaggerSchema codegen path processes this without a separate handler.
|
||||
// `name` preserves casing (e.g. "WinOpportunity") so extractModelName produces
|
||||
// a clean service class name (WinOpportunityService, dataSourceName 'winopportunity').
|
||||
const schema = {
|
||||
name: apiName,
|
||||
title: apiName,
|
||||
type: 'Microsoft.PowerApps/dataverseOperation',
|
||||
properties: {
|
||||
displayName: apiName,
|
||||
swagger: {
|
||||
swagger: '2.0',
|
||||
info: { title: apiName, version: '1.0' },
|
||||
basePath: '/api/data/v9.2',
|
||||
paths: {
|
||||
[operationPath]: {
|
||||
[method]: {
|
||||
operationId: name,
|
||||
parameters: swaggerParams,
|
||||
responses,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
// entitySetName is only present for bound operations; omitting it for unbound keeps the schema
|
||||
// minimal and avoids ambiguity in downstream processors that check for its presence
|
||||
if (entitySetName !== undefined) {
|
||||
schema.entitySetName = entitySetName;
|
||||
}
|
||||
return schema;
|
||||
}
|
||||
/**
|
||||
* Adds a Dataverse API (custom action, custom function, SDK message, or first-party
|
||||
* action/function) to the app by fetching its definition from the $metadata CSDL,
|
||||
* persisting a schema file, and updating dataSourcesInfo.
|
||||
*
|
||||
* @param {AddDataverseApiContext} context
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
export async function addDataverseApiAsync(context) {
|
||||
const { apiName } = context.actionsParams;
|
||||
const { schemaPath } = context.localFilePaths;
|
||||
const { logger } = context;
|
||||
initializePlayerActions({
|
||||
vfs: context.vfs,
|
||||
authProvider: context.authProvider,
|
||||
region: context.region,
|
||||
environmentName: context.environmentName,
|
||||
logger,
|
||||
});
|
||||
const envMetadata = await getEnvironmentByName(context.environmentName);
|
||||
const orgUrl = envMetadata?.properties?.linkedEnvironmentMetadata?.instanceApiUrl;
|
||||
if (!orgUrl) {
|
||||
throw new Error('Unable to determine the Dataverse organization URL. Ensure the environment has a linked Dataverse instance.');
|
||||
}
|
||||
const { httpClient } = getPlayerServiceConfig();
|
||||
const metadataService = new DataverseMetadataService(httpClient);
|
||||
// searchCsdlOperations does a case-insensitive substring match; filter to exact name
|
||||
const allMatches = await metadataService.searchCsdlOperations(orgUrl, apiName, logger);
|
||||
const exactMatches = allMatches.filter((op) => op.name === apiName);
|
||||
if (exactMatches.length === 0) {
|
||||
throw new Error(`No Dataverse operation named '${apiName}' was found. Use 'find-dataverse-api' to search for available operations.`);
|
||||
}
|
||||
if (exactMatches.length > 1) {
|
||||
throw new Error(`Multiple Dataverse operations named '${apiName}' were found. This is unexpected — operation names should be unique.`);
|
||||
}
|
||||
const operation = exactMatches[0];
|
||||
// For bound operations, resolve the entity set name and display name from the binding parameter type
|
||||
let entitySetName;
|
||||
let bindingEntityInfo;
|
||||
if (operation.isBound && operation.parameters.length > 0) {
|
||||
const bindingType = operation.parameters[0].type; // e.g. "mscrm.account"
|
||||
const entityLogicalName = bindingType.replace(/^mscrm\./, '');
|
||||
const entityDef = await metadataService.getEntityDefinitionAsync(orgUrl, entityLogicalName, false, logger);
|
||||
entitySetName = entityDef.EntitySetName;
|
||||
if (!entitySetName) {
|
||||
throw new Error(`Could not resolve entity set name for binding type '${bindingType}' (entity '${entityLogicalName}').`);
|
||||
}
|
||||
bindingEntityInfo = {
|
||||
entitySetName,
|
||||
logicalName: entityLogicalName,
|
||||
displayName: entityDef.DisplayName?.UserLocalizedLabel?.Label ?? entityLogicalName,
|
||||
};
|
||||
}
|
||||
// Ensure each entity type referenced in non-binding params and return type has an entity data
|
||||
// source (schema file + config entry). This must happen before generateModelService so that
|
||||
// buildEntityModelMap finds the schema files and resolves the correct *Base interface names.
|
||||
const entitySchemaFilePaths = [];
|
||||
for (const logicalName of extractEntityLogicalNames(operation)) {
|
||||
if (logicalName === UNKNOWN_LOGICAL_NAME) {
|
||||
continue;
|
||||
}
|
||||
const entitySchemaFilePath = await ensureDataverseEntityDataSource({
|
||||
orgUrl,
|
||||
logicalName,
|
||||
localFilePaths: context.localFilePaths,
|
||||
metadataService,
|
||||
logger,
|
||||
});
|
||||
if (entitySchemaFilePath) {
|
||||
entitySchemaFilePaths.push(entitySchemaFilePath);
|
||||
}
|
||||
}
|
||||
// Write schema file to {schemaPath}/dataverse/{apiName}.Schema.json
|
||||
const schemaData = buildDataverseOperationSchema(operation, entitySetName, apiName);
|
||||
const vfs = getVfs();
|
||||
const dirPath = vfs.join(schemaPath, 'dataverse');
|
||||
const filePath = vfs.join(dirPath, `${apiName}.Schema.json`);
|
||||
await vfs.mkdir(dirPath, { recursive: true });
|
||||
await vfs.writeFile(filePath, JSON.stringify(schemaData, null, 2), { encoding: 'utf-8' });
|
||||
// Update power.config.json: ensure default.cds exists (for org URL resolution at runtime),
|
||||
// and for bound operations add the binding entity to dataSources if not already present.
|
||||
await updatePowerConfigForDataverseApi(context.localFilePaths.powerConfigPath, bindingEntityInfo);
|
||||
// Regenerate dataSourcesInfo.ts from all schema files (idempotent)
|
||||
await processDataSourceInfo(schemaPath, logger);
|
||||
// Generate TypeScript model and service for all new schema files in parallel.
|
||||
await Promise.all([...entitySchemaFilePaths, filePath].map((schemaFilePath) => generateModelService({
|
||||
schemaFolderPath: schemaPath,
|
||||
schemaFilePath,
|
||||
codeGenPath: context.localFilePaths.codeGenPath,
|
||||
logger,
|
||||
})));
|
||||
}
|
||||
/**
|
||||
* Updates `power.config.json` for a Dataverse API add:
|
||||
* - Ensures `databaseReferences["default.cds"]` exists (required for org URL resolution at runtime)
|
||||
* - For bound operations: adds the binding entity to `dataSources` if not already present.
|
||||
* Deduplicates by `logicalName` so that adding two operations bound to the same entity
|
||||
* does not create a second entry.
|
||||
* - For unbound operations: only the `default.cds` key is guaranteed to exist; no `dataSources`
|
||||
* entry is written because unbound operations are not associated with any entity table.
|
||||
*/
|
||||
async function updatePowerConfigForDataverseApi(configPath, entityInfo) {
|
||||
const config = await readRepoConfig(configPath);
|
||||
if (!config.databaseReferences) {
|
||||
config.databaseReferences = {};
|
||||
}
|
||||
// Ensure the default.cds key always exists
|
||||
if (!config.databaseReferences['default.cds']) {
|
||||
config.databaseReferences['default.cds'] = {
|
||||
dataSources: {},
|
||||
environmentVariableName: '',
|
||||
};
|
||||
}
|
||||
// Always ensure the canonical environment data source entry is present.
|
||||
// environment_39a902ba is the fixed logical name for the current-environment CDS data source
|
||||
// (CurrentEnvironmentLogicalName in Power Apps runtime). The host uses it to anchor unbound
|
||||
// Dataverse operations and returns a runtimeUrl for it via getAppCdsDataSourceConfigsAsync,
|
||||
// which the executor uses to resolve the org API URL at runtime.
|
||||
const ENV_DS_KEY = 'environment_39a902ba';
|
||||
if (!config.databaseReferences['default.cds'].dataSources[ENV_DS_KEY]) {
|
||||
config.databaseReferences['default.cds'].dataSources[ENV_DS_KEY] = {
|
||||
entitySetName: 'Environment',
|
||||
logicalName: 'environment_39a902ba',
|
||||
isHidden: false,
|
||||
};
|
||||
}
|
||||
if (entityInfo) {
|
||||
const existingDataSources = config.databaseReferences['default.cds'].dataSources ?? {};
|
||||
// Only add the entity if it is not already tracked (dedup by logicalName)
|
||||
const alreadyPresent = Object.values(existingDataSources).some((ds) => ds.logicalName === entityInfo.logicalName);
|
||||
if (!alreadyPresent) {
|
||||
upsertDatabaseReference(config.databaseReferences, {
|
||||
entitySetName: entityInfo.entitySetName,
|
||||
logicalName: entityInfo.logicalName,
|
||||
displayName: entityInfo.displayName,
|
||||
cdpDataSourceNames: getExistingCdpNames(config.connectionReferences ?? {}),
|
||||
});
|
||||
}
|
||||
}
|
||||
await writeRepoConfig(configPath, config);
|
||||
}
|
||||
//# sourceMappingURL=AddDataverseApi.js.map
|
||||
Generated
Vendored
+1
File diff suppressed because one or more lines are too long
Generated
Vendored
+17
@@ -0,0 +1,17 @@
|
||||
/*!
|
||||
* Copyright (C) Microsoft Corporation. All rights reserved.
|
||||
*/
|
||||
import type { AddFlowContext } from '../Types/ActionTypes.js';
|
||||
export type AddFlowResult = {
|
||||
success: boolean;
|
||||
error?: string;
|
||||
};
|
||||
/**
|
||||
* Adds a flow by retrieving its metadata and swagger, updating the power.config file with the
|
||||
* appropriate connection references, and generating models and services from the flow's swagger.
|
||||
*
|
||||
* @param {AddFlowContext} context
|
||||
* @returns {Promise<AddFlowResult>}
|
||||
*/
|
||||
export declare function addFlowAsync(context: AddFlowContext): Promise<AddFlowResult>;
|
||||
//# sourceMappingURL=AddFlow.d.ts.map
|
||||
Generated
Vendored
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"AddFlow.d.ts","sourceRoot":"","sources":["../../src/Actions/AddFlow.ts"],"names":[],"mappings":"AAAA;;GAEG;AAkBH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAI3D,MAAM,MAAM,aAAa,GAAG;IAC1B,OAAO,EAAE,OAAO,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB,CAAC;AAEF;;;;;;GAMG;AACH,wBAAsB,YAAY,CAAC,OAAO,EAAE,cAAc,GAAG,OAAO,CAAC,aAAa,CAAC,CAoFlF"}
|
||||
Generated
Vendored
+131
@@ -0,0 +1,131 @@
|
||||
/*!
|
||||
* Copyright (C) Microsoft Corporation. All rights reserved.
|
||||
*/
|
||||
import { processDataSourceInfo } from '../CodeGen/dataSourceInfoProcessor.js';
|
||||
import { generateModelService } from '../CodeGen/modelServiceGenerator.js';
|
||||
import { sanitizeName } from '../CodeGen/shared/nameUtils.js';
|
||||
import { readRepoConfig, writeRepoConfig } from '../Config/ConfigManager.js';
|
||||
import { initializePlayerActions } from '../initializePlayerActions.js';
|
||||
import { upsertFlowConnectionReferences, } from '../ReferenceStores/connectionReferences.js';
|
||||
import { getConnectionByNameAsync, getFlowMetadataAsync, getFlowSwaggerAsync, isConnectionShareable, } from '../services/index.js';
|
||||
import { getVfs } from '../VfsManager.js';
|
||||
import { FLOW_API_NAME, LOGIC_FLOWS_API_ID, SUPPORTED_TRIGGER_KINDS } from './flowConstants.js';
|
||||
/**
|
||||
* Adds a flow by retrieving its metadata and swagger, updating the power.config file with the
|
||||
* appropriate connection references, and generating models and services from the flow's swagger.
|
||||
*
|
||||
* @param {AddFlowContext} context
|
||||
* @returns {Promise<AddFlowResult>}
|
||||
*/
|
||||
export async function addFlowAsync(context) {
|
||||
const { flowId } = context.actionsParams;
|
||||
initializePlayerActions({
|
||||
vfs: context.vfs,
|
||||
authProvider: context.authProvider,
|
||||
region: context.region,
|
||||
environmentName: context.environmentName,
|
||||
logger: context.logger,
|
||||
});
|
||||
try {
|
||||
const { powerConfigPath, schemaPath, codeGenPath } = context.localFilePaths;
|
||||
const vfs = getVfs();
|
||||
if (!(await vfs.exists(powerConfigPath))) {
|
||||
throw new Error(`Configuration file not found at ${powerConfigPath}`);
|
||||
}
|
||||
// Fetch metadata and swagger in parallel.
|
||||
const [flowMetadata, flowSwagger] = await Promise.all([
|
||||
getFlowMetadataAsync(flowId, context.logger),
|
||||
getFlowSwaggerAsync(flowId, context.logger),
|
||||
]);
|
||||
// Validate that the flow has a supported trigger kind before proceeding.
|
||||
const triggers = flowMetadata.properties.definitionSummary?.triggers ?? [];
|
||||
const hasSupportedTrigger = triggers.some((t) => t.kind && SUPPORTED_TRIGGER_KINDS.has(t.kind));
|
||||
if (!hasSupportedTrigger) {
|
||||
const triggerKinds = triggers.map((t) => t.kind ?? t.type ?? 'unknown').join(', ') || 'unknown';
|
||||
throw new Error(`Flow '${flowMetadata.properties.displayName}' cannot be added to Code Apps. ` +
|
||||
`Only flows with a PowerApp or PowerAppV2 trigger are supported, ` +
|
||||
`but this flow has trigger kind: ${triggerKinds}.`);
|
||||
}
|
||||
const flowDisplayName = flowMetadata.properties.displayName;
|
||||
// Filter out only the dependencies with source "invoker", as those are the ones we need to add to the code repo config for authentication purposes.
|
||||
const invokerConnectionReferences = Object.fromEntries(Object.entries(flowMetadata.properties.connectionReferences).filter(([, connRef]) => connRef.source.toLowerCase() === 'invoker'));
|
||||
// Enrich each dependency with authenticationType and sharedConnectionId by querying
|
||||
// the connector and connection APIs. Throws if any connection cannot be accessed.
|
||||
const enrichedDependencies = await fetchEnrichedDependenciesAsync(invokerConnectionReferences, context.logger);
|
||||
// Update power.config with connection references for the flow and its dependencies.
|
||||
const config = await readRepoConfig(powerConfigPath);
|
||||
config.connectionReferences ??= {};
|
||||
upsertFlowConnectionReferences(config.connectionReferences, {
|
||||
flowDisplayName,
|
||||
workflowEntityId: flowMetadata.properties.workflowEntityId,
|
||||
flowDependencies: enrichedDependencies,
|
||||
workflowName: flowMetadata.name,
|
||||
});
|
||||
await writeRepoConfig(powerConfigPath, config);
|
||||
// Save the swagger as the flow's schema file.
|
||||
const schemaFilePath = await saveFlowSchema(flowSwagger, flowDisplayName, schemaPath);
|
||||
// Generate model services from the updated schema folder.
|
||||
await processDataSourceInfo(schemaPath, context.logger);
|
||||
await generateModelService({
|
||||
schemaFolderPath: schemaPath,
|
||||
codeGenPath,
|
||||
schemaFilePath,
|
||||
logger: context.logger,
|
||||
});
|
||||
return { success: true };
|
||||
}
|
||||
catch (error) {
|
||||
const errorMessage = error instanceof Error ? error.message : String(error);
|
||||
return { success: false, error: errorMessage };
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Fetches connector and connection details for each flow dependency in parallel,
|
||||
* returning an enriched record with `authenticationType` and `sharedConnectionId`.
|
||||
* Throws if any dependency connection cannot be retrieved (e.g. the user lacks access).
|
||||
*/
|
||||
async function fetchEnrichedDependenciesAsync(flowDependencies, logger) {
|
||||
const enrichedEntries = await Promise.all(Object.entries(flowDependencies).map(async ([connRefName, connRef]) => {
|
||||
const connector = connRef.apiDefinition;
|
||||
let connection;
|
||||
try {
|
||||
const maybeConnection = await getConnectionByNameAsync(connRef.connectionName, connector.name, logger);
|
||||
if (!maybeConnection) {
|
||||
throw new Error();
|
||||
}
|
||||
connection = maybeConnection;
|
||||
}
|
||||
catch (error) {
|
||||
throw new Error(`Unable to retrieve connection '${connRef.id}'. You may not have access to the underlying connections.`);
|
||||
}
|
||||
const authenticationType = connection.properties.connectionParametersSet?.name;
|
||||
const sharedConnectionId = authenticationType && isConnectionShareable(connector, authenticationType)
|
||||
? connection.id
|
||||
: undefined;
|
||||
return [connRefName, { ...connRef, authenticationType, sharedConnectionId }];
|
||||
}));
|
||||
return Object.fromEntries(enrichedEntries);
|
||||
}
|
||||
/**
|
||||
* Saves the flow's swagger as a schema JSON file under schemaPath/logicflows/.
|
||||
* Returns the path of the saved file.
|
||||
*/
|
||||
async function saveFlowSchema(swagger, flowDisplayName, schemaPath) {
|
||||
const vfs = getVfs();
|
||||
const directoryPath = vfs.join(schemaPath, FLOW_API_NAME);
|
||||
const schemaFilePath = vfs.join(directoryPath, `${sanitizeName(flowDisplayName)}.Schema.json`);
|
||||
const fullSchema = {
|
||||
name: flowDisplayName,
|
||||
id: LOGIC_FLOWS_API_ID,
|
||||
type: 'Microsoft.PowerApps/apis',
|
||||
properties: {
|
||||
displayName: flowDisplayName,
|
||||
isCustom: false,
|
||||
swagger: swagger,
|
||||
},
|
||||
};
|
||||
await vfs.mkdir(directoryPath, { recursive: true });
|
||||
await vfs.writeFile(schemaFilePath, JSON.stringify(fullSchema, null, 2), { encoding: 'utf-8' });
|
||||
return schemaFilePath;
|
||||
}
|
||||
//# sourceMappingURL=AddFlow.js.map
|
||||
Generated
Vendored
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"AddFlow.js","sourceRoot":"","sources":["../../src/Actions/AddFlow.ts"],"names":[],"mappings":"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"}
|
||||
Generated
Vendored
+26
@@ -0,0 +1,26 @@
|
||||
/*!
|
||||
* Copyright (C) Microsoft Corporation. All rights reserved.
|
||||
*/
|
||||
import type { Connection } from '../services/types/ApiTypes.js';
|
||||
import type { CreateConnectionContext } from '../Types/ActionTypes.js';
|
||||
export type CreateConnectionResult = Connection;
|
||||
/**
|
||||
* Creates and authenticates a new SSO connection for a first-party connector.
|
||||
*
|
||||
* Full 4-step flow:
|
||||
* 1. GET connector definition → validate SSO eligibility (no required user params)
|
||||
* 2. PUT new connection with $expand=ConsentLink → receives firstPartyLoginUri in response
|
||||
* 3. POST firstPartyLoginUri with OBO access token → authenticate the connection
|
||||
* 4. POST testConnection → verify the connection is working
|
||||
*
|
||||
* The OBO token is acquired for the apihub audience (consentServerResource),
|
||||
* which equals apiHubRuntimeAudience per PowerApps-Client region configs.
|
||||
*
|
||||
* If steps 3–4 fail, the newly-created connection is deleted before rethrowing,
|
||||
* so no orphaned connections are left in the environment.
|
||||
*
|
||||
* @param context - Shared action context (auth, region, logger, etc.).
|
||||
* @returns The authenticated and tested {@link Connection}.
|
||||
*/
|
||||
export declare function createConnectionAsync(context: CreateConnectionContext): Promise<Connection>;
|
||||
//# sourceMappingURL=CreateConnection.d.ts.map
|
||||
Generated
Vendored
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"CreateConnection.d.ts","sourceRoot":"","sources":["../../src/Actions/CreateConnection.ts"],"names":[],"mappings":"AAAA;;GAEG;AAcH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,4BAA4B,CAAC;AAC7D,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,sBAAsB,CAAC;AAEpE,MAAM,MAAM,sBAAsB,GAAG,UAAU,CAAC;AAEhD;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAsB,qBAAqB,CAAC,OAAO,EAAE,uBAAuB,GAAG,OAAO,CAAC,UAAU,CAAC,CAqDjG"}
|
||||
Generated
Vendored
+75
@@ -0,0 +1,75 @@
|
||||
/*!
|
||||
* Copyright (C) Microsoft Corporation. All rights reserved.
|
||||
*/
|
||||
import { generateGuid } from '@microsoft/power-apps-common/utils';
|
||||
import { getConnectorAsync } from '../services/index.js';
|
||||
import { connectionFirstPartyLoginAsync, testConnectionAsync, } from '../services/connectivity/ApimService.js';
|
||||
import { deleteConnectionAsync, putConnectionAsync, validateSsoEligible, } from '../services/connectivity/ConnectivityService.js';
|
||||
/**
|
||||
* Creates and authenticates a new SSO connection for a first-party connector.
|
||||
*
|
||||
* Full 4-step flow:
|
||||
* 1. GET connector definition → validate SSO eligibility (no required user params)
|
||||
* 2. PUT new connection with $expand=ConsentLink → receives firstPartyLoginUri in response
|
||||
* 3. POST firstPartyLoginUri with OBO access token → authenticate the connection
|
||||
* 4. POST testConnection → verify the connection is working
|
||||
*
|
||||
* The OBO token is acquired for the apihub audience (consentServerResource),
|
||||
* which equals apiHubRuntimeAudience per PowerApps-Client region configs.
|
||||
*
|
||||
* If steps 3–4 fail, the newly-created connection is deleted before rethrowing,
|
||||
* so no orphaned connections are left in the environment.
|
||||
*
|
||||
* @param context - Shared action context (auth, region, logger, etc.).
|
||||
* @returns The authenticated and tested {@link Connection}.
|
||||
*/
|
||||
export async function createConnectionAsync(context) {
|
||||
const { actionsParams, authProvider, logger } = context;
|
||||
const connectorId = normalizeConnectorId(actionsParams.connectorId);
|
||||
// 1. Fetch connector definition and validate SSO eligibility
|
||||
const connector = await getConnectorAsync(connectorId, logger);
|
||||
if (!connector) {
|
||||
throw new Error(`Failed to fetch connector '${connectorId}': connector not found`);
|
||||
}
|
||||
const ssoSetName = validateSsoEligible(connector, connectorId);
|
||||
// 2. PUT the new connection with $expand=ConsentLink to receive firstPartyLoginUri in response
|
||||
const connectionId = generateGuid();
|
||||
const displayName = actionsParams.displayName ?? connector.properties?.displayName ?? connectorId;
|
||||
const connection = await putConnectionAsync(connectorId, connectionId, displayName, logger, ssoSetName);
|
||||
// Steps 3–4 may fail — delete the connection if so to avoid orphans
|
||||
try {
|
||||
const firstPartyLoginUri = connection.properties.consentInfo?.firstPartyLoginUri;
|
||||
if (!firstPartyLoginUri) {
|
||||
throw new Error(`Connector '${connectorId}' did not return a firstPartyLoginUri — ` +
|
||||
`it may not support silent SSO. Use an interactive connection flow instead.`);
|
||||
}
|
||||
// 3. POST OBO token to firstPartyLoginUri → authenticate the connection
|
||||
await connectionFirstPartyLoginAsync(authProvider, firstPartyLoginUri, logger);
|
||||
// 4. POST testConnection → verify the connection works
|
||||
await testConnectionAsync(connection, logger);
|
||||
}
|
||||
catch (authOrTestError) {
|
||||
logger.trackActivityEvent('createConnection.cleanup', { connectorId, connectionId });
|
||||
try {
|
||||
await deleteConnectionAsync(connectorId, connectionId, logger);
|
||||
logger.trackActivityEvent('createConnection.cleanup.done', { connectorId, connectionId });
|
||||
}
|
||||
catch (deleteError) {
|
||||
logger.trackActivityEvent('createConnection.cleanup.failed', {
|
||||
connectorId,
|
||||
connectionId,
|
||||
error: String(deleteError),
|
||||
});
|
||||
}
|
||||
throw authOrTestError;
|
||||
}
|
||||
return connection;
|
||||
}
|
||||
/**
|
||||
* Normalizes a connector ID to the APIM shared form.
|
||||
* "office365" → "shared_office365"; "shared_office365" → "shared_office365".
|
||||
*/
|
||||
function normalizeConnectorId(connectorId) {
|
||||
return connectorId.startsWith('shared_') ? connectorId : `shared_${connectorId}`;
|
||||
}
|
||||
//# sourceMappingURL=CreateConnection.js.map
|
||||
Generated
Vendored
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"CreateConnection.js","sourceRoot":"","sources":["../../src/Actions/CreateConnection.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,YAAY,EAAE,MAAM,oCAAoC,CAAC;AAElE,OAAO,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAChD,OAAO,EACL,8BAA8B,EAC9B,mBAAmB,GACpB,MAAM,sCAAsC,CAAC;AAC9C,OAAO,EACL,qBAAqB,EACrB,kBAAkB,EAClB,mBAAmB,GACpB,MAAM,8CAA8C,CAAC;AAMtD;;;;;;;;;;;;;;;;;GAiBG;AACH,MAAM,CAAC,KAAK,UAAU,qBAAqB,CAAC,OAAgC;IAC1E,MAAM,EAAE,aAAa,EAAE,YAAY,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC;IACxD,MAAM,WAAW,GAAG,oBAAoB,CAAC,aAAa,CAAC,WAAW,CAAC,CAAC;IAEpE,6DAA6D;IAC7D,MAAM,SAAS,GAAG,MAAM,iBAAiB,CAAC,WAAW,EAAE,MAAM,CAAC,CAAC;IAC/D,IAAI,CAAC,SAAS,EAAE,CAAC;QACf,MAAM,IAAI,KAAK,CAAC,8BAA8B,WAAW,wBAAwB,CAAC,CAAC;IACrF,CAAC;IACD,MAAM,UAAU,GAAG,mBAAmB,CAAC,SAAS,EAAE,WAAW,CAAC,CAAC;IAE/D,+FAA+F;IAC/F,MAAM,YAAY,GAAG,YAAY,EAAE,CAAC;IACpC,MAAM,WAAW,GAAG,aAAa,CAAC,WAAW,IAAI,SAAS,CAAC,UAAU,EAAE,WAAW,IAAI,WAAW,CAAC;IAClG,MAAM,UAAU,GAAG,MAAM,kBAAkB,CACzC,WAAW,EACX,YAAY,EACZ,WAAW,EACX,MAAM,EACN,UAAU,CACX,CAAC;IAEF,oEAAoE;IACpE,IAAI,CAAC;QACH,MAAM,kBAAkB,GAAG,UAAU,CAAC,UAAU,CAAC,WAAW,EAAE,kBAAkB,CAAC;QACjF,IAAI,CAAC,kBAAkB,EAAE,CAAC;YACxB,MAAM,IAAI,KAAK,CACb,cAAc,WAAW,0CAA0C;gBACjE,4EAA4E,CAC/E,CAAC;QACJ,CAAC;QAED,wEAAwE;QACxE,MAAM,8BAA8B,CAAC,YAAY,EAAE,kBAAkB,EAAE,MAAM,CAAC,CAAC;QAE/E,uDAAuD;QACvD,MAAM,mBAAmB,CAAC,UAAU,EAAE,MAAM,CAAC,CAAC;IAChD,CAAC;IAAC,OAAO,eAAe,EAAE,CAAC;QACzB,MAAM,CAAC,kBAAkB,CAAC,0BAA0B,EAAE,EAAE,WAAW,EAAE,YAAY,EAAE,CAAC,CAAC;QACrF,IAAI,CAAC;YACH,MAAM,qBAAqB,CAAC,WAAW,EAAE,YAAY,EAAE,MAAM,CAAC,CAAC;YAC/D,MAAM,CAAC,kBAAkB,CAAC,+BAA+B,EAAE,EAAE,WAAW,EAAE,YAAY,EAAE,CAAC,CAAC;QAC5F,CAAC;QAAC,OAAO,WAAW,EAAE,CAAC;YACrB,MAAM,CAAC,kBAAkB,CAAC,iCAAiC,EAAE;gBAC3D,WAAW;gBACX,YAAY;gBACZ,KAAK,EAAE,MAAM,CAAC,WAAW,CAAC;aAC3B,CAAC,CAAC;QACL,CAAC;QACD,MAAM,eAAe,CAAC;IACxB,CAAC;IAED,OAAO,UAAU,CAAC;AACpB,CAAC;AAED;;;GAGG;AACH,SAAS,oBAAoB,CAAC,WAAmB;IAC/C,OAAO,WAAW,CAAC,UAAU,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,UAAU,WAAW,EAAE,CAAC;AACnF,CAAC"}
|
||||
Generated
Vendored
+29
@@ -0,0 +1,29 @@
|
||||
/*!
|
||||
* Copyright (C) Microsoft Corporation. All rights reserved.
|
||||
*/
|
||||
import type { IHttpClient, ILogger } from '../services/index.js';
|
||||
import type { LazyParam } from '../Types/ActionTypes.js';
|
||||
export declare function isEnvironmentVariable(parameter: string | undefined): boolean;
|
||||
export declare function getEnvironmentVariableName(parameter: string | undefined): string | undefined;
|
||||
export declare function resolveEnvironmentVariable(parameter: string | undefined, httpClient: IHttpClient, orgUrl: string | undefined, logger: ILogger): Promise<string | undefined>;
|
||||
/**
|
||||
* Retrieves the Dataverse organization URL for the current environment based on the linked environment metadata.
|
||||
* @param powerConfigPath The path to the power configuration file.
|
||||
* @returns The Dataverse organization URL or undefined if not found.
|
||||
*/
|
||||
export declare function getDataverseOrgUrl(powerConfigPath: string): Promise<string | undefined>;
|
||||
/**
|
||||
* Retrieves the Dataverse organization URL for the given environment name (environment ID GUID).
|
||||
* @param environmentName The environment ID GUID (from BaseActionContext.environmentName).
|
||||
* @returns The Dataverse organization URL.
|
||||
* @throws If the environment does not have a linked Dataverse instance.
|
||||
*/
|
||||
export declare function getDataverseOrgUrlByEnvironmentName(environmentName: string): Promise<string>;
|
||||
/**
|
||||
* Helper function to normalize lazy parameters.
|
||||
* Handles both direct string values and deferred functions that return Promise<string>.
|
||||
* @param value - The value to normalize (string, function, or undefined/null)
|
||||
* @returns The resolved value if it's not an empty string, otherwise undefined
|
||||
*/
|
||||
export declare function normalizeParam(value: LazyParam | undefined | null): Promise<string | undefined>;
|
||||
//# sourceMappingURL=DataUtils.d.ts.map
|
||||
Generated
Vendored
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"DataUtils.d.ts","sourceRoot":"","sources":["../../src/Actions/DataUtils.ts"],"names":[],"mappings":"AAAA;;GAEG;AAGH,OAAO,KAAK,EAAE,WAAW,EAAE,OAAO,EAAE,MAAM,aAAa,CAAC;AAExD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,sBAAsB,CAAC;AAItD,wBAAgB,qBAAqB,CAAC,SAAS,EAAE,MAAM,GAAG,SAAS,WAElE;AAED,wBAAgB,0BAA0B,CAAC,SAAS,EAAE,MAAM,GAAG,SAAS,sBAQvE;AAED,wBAAsB,0BAA0B,CAC9C,SAAS,EAAE,MAAM,GAAG,SAAS,EAC7B,UAAU,EAAE,WAAW,EACvB,MAAM,EAAE,MAAM,GAAG,SAAS,EAC1B,MAAM,EAAE,OAAO,+BAwBhB;AAED;;;;GAIG;AACH,wBAAsB,kBAAkB,CAAC,eAAe,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAgB7F;AAED;;;;;GAKG;AACH,wBAAsB,mCAAmC,CACvD,eAAe,EAAE,MAAM,GACtB,OAAO,CAAC,MAAM,CAAC,CASjB;AAED;;;;;GAKG;AACH,wBAAsB,cAAc,CAClC,KAAK,EAAE,SAAS,GAAG,SAAS,GAAG,IAAI,GAClC,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAS7B"}
|
||||
Generated
Vendored
+81
@@ -0,0 +1,81 @@
|
||||
/*!
|
||||
* Copyright (C) Microsoft Corporation. All rights reserved.
|
||||
*/
|
||||
import { readRepoConfig } from '../Config/ConfigManager.js';
|
||||
import { DataverseSystemDataService, getEnvironmentByName } from '../services/index.js';
|
||||
const ENV_VAR_PREFIX = '@envvar:';
|
||||
export function isEnvironmentVariable(parameter) {
|
||||
return !!parameter && parameter.startsWith(ENV_VAR_PREFIX);
|
||||
}
|
||||
export function getEnvironmentVariableName(parameter) {
|
||||
if (!parameter) {
|
||||
return parameter;
|
||||
}
|
||||
if (!isEnvironmentVariable(parameter)) {
|
||||
throw new Error(`Parameter '${parameter}' is not an environment variable.`);
|
||||
}
|
||||
return parameter.slice(ENV_VAR_PREFIX.length);
|
||||
}
|
||||
export async function resolveEnvironmentVariable(parameter, httpClient, orgUrl, logger) {
|
||||
if (!parameter || !orgUrl) {
|
||||
return parameter;
|
||||
}
|
||||
if (!isEnvironmentVariable(parameter)) {
|
||||
throw new Error(`Parameter '${parameter}' is not an environment variable.`);
|
||||
}
|
||||
const dataverseClient = new DataverseSystemDataService(httpClient);
|
||||
const variableDefinition = await dataverseClient.getEnvironmentVariable(orgUrl, parameter.slice(ENV_VAR_PREFIX.length), logger);
|
||||
if (!variableDefinition) {
|
||||
throw new Error(`Environment variable ${parameter} not found in current env.`);
|
||||
}
|
||||
return (variableDefinition.environmentvariabledefinition_environmentvariablevalue?.[0]?.value || '');
|
||||
}
|
||||
/**
|
||||
* Retrieves the Dataverse organization URL for the current environment based on the linked environment metadata.
|
||||
* @param powerConfigPath The path to the power configuration file.
|
||||
* @returns The Dataverse organization URL or undefined if not found.
|
||||
*/
|
||||
export async function getDataverseOrgUrl(powerConfigPath) {
|
||||
const config = await readRepoConfig(powerConfigPath);
|
||||
const environmentId = config.environmentId;
|
||||
if (environmentId) {
|
||||
const metadata = await getEnvironmentByName(environmentId);
|
||||
const dataverseOrgUrl = metadata?.properties?.linkedEnvironmentMetadata?.instanceApiUrl;
|
||||
if (!dataverseOrgUrl) {
|
||||
throw new Error('Unable to determine the dataverse org associated with the current environment.');
|
||||
}
|
||||
return dataverseOrgUrl;
|
||||
}
|
||||
else {
|
||||
throw new Error('Environment ID is not set in the configuration file.');
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Retrieves the Dataverse organization URL for the given environment name (environment ID GUID).
|
||||
* @param environmentName The environment ID GUID (from BaseActionContext.environmentName).
|
||||
* @returns The Dataverse organization URL.
|
||||
* @throws If the environment does not have a linked Dataverse instance.
|
||||
*/
|
||||
export async function getDataverseOrgUrlByEnvironmentName(environmentName) {
|
||||
const metadata = await getEnvironmentByName(environmentName);
|
||||
const orgUrl = metadata?.properties?.linkedEnvironmentMetadata?.instanceApiUrl;
|
||||
if (!orgUrl) {
|
||||
throw new Error(`Unable to determine the Dataverse organization URL for environment '${environmentName}'. Ensure the environment has a linked Dataverse instance.`);
|
||||
}
|
||||
return orgUrl;
|
||||
}
|
||||
/**
|
||||
* Helper function to normalize lazy parameters.
|
||||
* Handles both direct string values and deferred functions that return Promise<string>.
|
||||
* @param value - The value to normalize (string, function, or undefined/null)
|
||||
* @returns The resolved value if it's not an empty string, otherwise undefined
|
||||
*/
|
||||
export async function normalizeParam(value) {
|
||||
if (value === undefined || value === null) {
|
||||
return undefined;
|
||||
}
|
||||
// If it's a function, call it to get the promise and await it
|
||||
const resolvedValue = typeof value === 'function' ? await value() : value;
|
||||
return resolvedValue && resolvedValue.trim() !== '' ? resolvedValue.trim() : undefined;
|
||||
}
|
||||
//# sourceMappingURL=DataUtils.js.map
|
||||
Generated
Vendored
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"DataUtils.js","sourceRoot":"","sources":["../../src/Actions/DataUtils.ts"],"names":[],"mappings":"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"}
|
||||
Generated
Vendored
+6
@@ -0,0 +1,6 @@
|
||||
/*!
|
||||
* Copyright (C) Microsoft Corporation. All rights reserved.
|
||||
*/
|
||||
import type { RemoveDataSourceContext } from '../Types/ActionTypes.js';
|
||||
export declare function deleteDataSourceAsync(config: RemoveDataSourceContext): Promise<boolean>;
|
||||
//# sourceMappingURL=DeleteDataSource.d.ts.map
|
||||
Generated
Vendored
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"DeleteDataSource.d.ts","sourceRoot":"","sources":["../../src/Actions/DeleteDataSource.ts"],"names":[],"mappings":"AAAA;;GAEG;AAMH,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,sBAAsB,CAAC;AAIpE,wBAAsB,qBAAqB,CAAC,MAAM,EAAE,uBAAuB,GAAG,OAAO,CAAC,OAAO,CAAC,CAiH7F"}
|
||||
Generated
Vendored
+167
@@ -0,0 +1,167 @@
|
||||
/*!
|
||||
* Copyright (C) Microsoft Corporation. All rights reserved.
|
||||
*/
|
||||
import { processDataSourceInfo } from '../CodeGen/dataSourceInfoProcessor.js';
|
||||
import { clearAndRegenerateModelService } from '../CodeGen/modelServiceGenerator.js';
|
||||
import { DataSources } from '../Config/Config.types.js';
|
||||
import { readRepoConfig, writeRepoConfig } from '../Config/ConfigManager.js';
|
||||
import { getVfs, setVfs } from '../VfsManager.js';
|
||||
import { normalizeParam } from './DataUtils.js';
|
||||
export async function deleteDataSourceAsync(config) {
|
||||
let apiId = await normalizeParam(config.actionsParams.apiId);
|
||||
const { schemaPath: schemaFolder, codeGenPath: codeGenDirectory, powerConfigPath, } = config.localFilePaths;
|
||||
// validate parameters, cleanup, and throw errors where necessary
|
||||
if (!apiId) {
|
||||
throw new Error('Argument apiId is required. Use --apiId or -a to specify it.');
|
||||
}
|
||||
// remove the "shared_" prefix from the apiId if it exists
|
||||
if (apiId.startsWith('shared_')) {
|
||||
apiId = apiId.substring('shared_'.length);
|
||||
}
|
||||
apiId = apiId.toLowerCase();
|
||||
let dataSourceName;
|
||||
let sqlStoredProcedure;
|
||||
if (apiId === 'sql') {
|
||||
sqlStoredProcedure = await normalizeParam(config.actionsParams.sqlStoredProcedure); // optional
|
||||
}
|
||||
if (!sqlStoredProcedure) {
|
||||
dataSourceName = await normalizeParam(config.actionsParams.dataSourceName);
|
||||
}
|
||||
if (!dataSourceName && !sqlStoredProcedure) {
|
||||
throw new Error('Argument dataSourceName or sqlStoredProcedure is required for delete data sources. Use --dataSourceName or --sqlStoredProcedure to specify it.');
|
||||
}
|
||||
if (!schemaFolder || !codeGenDirectory) {
|
||||
throw new Error('Schema path and code generation path must be provided in the config');
|
||||
}
|
||||
setVfs(config.vfs);
|
||||
const vfs = getVfs();
|
||||
if (!powerConfigPath || !(await vfs.exists(powerConfigPath))) {
|
||||
throw new Error('Power configuration file must exist');
|
||||
}
|
||||
// Start of the delete operation
|
||||
{
|
||||
// Construct the directory path based on the schemaPath, apiId, and dataSourceName
|
||||
let directoryPath = vfs.join(schemaFolder, apiId);
|
||||
let updated = false;
|
||||
if (sqlStoredProcedure && sqlStoredProcedure.length > 0) {
|
||||
directoryPath = vfs.join(directoryPath, sqlStoredProcedure);
|
||||
try {
|
||||
await vfs.rmdir(directoryPath);
|
||||
}
|
||||
catch (error) {
|
||||
// Could not remove directory. Not an issue and can proceed
|
||||
}
|
||||
updated = true;
|
||||
}
|
||||
else {
|
||||
const schemaFilePath = vfs.join(directoryPath, `${dataSourceName}.Schema.json`);
|
||||
const schemaFileExists = await vfs.exists(schemaFilePath);
|
||||
let dataSourceDirectoryIsEmpty = false;
|
||||
if (schemaFileExists) {
|
||||
// delete the file
|
||||
await vfs.unlink(schemaFilePath);
|
||||
// check if the directory is empty after deleting the schema file
|
||||
if (await vfs.exists(directoryPath)) {
|
||||
const files = await vfs.readdir(directoryPath);
|
||||
dataSourceDirectoryIsEmpty = files.length === 0;
|
||||
if (dataSourceDirectoryIsEmpty) {
|
||||
try {
|
||||
await vfs.rmdir(directoryPath);
|
||||
}
|
||||
catch (error) {
|
||||
// Could not clean up empty directory. Not an issue and can proceed
|
||||
}
|
||||
}
|
||||
}
|
||||
updated = true;
|
||||
}
|
||||
}
|
||||
const configUpdated = await _cleanupPowerConfig(powerConfigPath, (sqlStoredProcedure ?? dataSourceName), apiId === DataSources.Dataverse.toLowerCase());
|
||||
// Always call processDataSourceInfo and ModelServiceGenerator for Dataverse or when files were updated
|
||||
if (updated || configUpdated) {
|
||||
// Ensure that the datasource info file is updated
|
||||
// Pass the deleted schema file so it can be excluded from processing
|
||||
await processDataSourceInfo(schemaFolder, config.logger);
|
||||
await clearAndRegenerateModelService({
|
||||
schemaFolderPath: schemaFolder,
|
||||
codeGenPath: codeGenDirectory,
|
||||
logger: config.logger,
|
||||
});
|
||||
}
|
||||
if (!updated && !configUpdated) {
|
||||
// There was nothing to delete or the data source was not found
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
async function _cleanupPowerConfig(powerConfigPath, dataSourceName, isDataverse = false) {
|
||||
// Read the power config file
|
||||
const powerConfig = await readRepoConfig(powerConfigPath);
|
||||
if (!powerConfig) {
|
||||
return false;
|
||||
}
|
||||
let updated = false;
|
||||
const keysToDelete = new Set();
|
||||
if (isDataverse) {
|
||||
// Handle Dataverse specific cleanup
|
||||
if (!powerConfig.databaseReferences || !powerConfig.databaseReferences['default.cds']) {
|
||||
return false;
|
||||
}
|
||||
const databaseReference = powerConfig.databaseReferences['default.cds'];
|
||||
if (databaseReference.dataSources && databaseReference.dataSources[dataSourceName]) {
|
||||
delete databaseReference.dataSources[dataSourceName];
|
||||
updated = true;
|
||||
}
|
||||
// If there are no more data sources, mark the database reference for removal
|
||||
const remainingDataSources = Object.keys(databaseReference.dataSources).length;
|
||||
if (remainingDataSources === 0) {
|
||||
delete powerConfig.databaseReferences['default.cds'];
|
||||
updated = true;
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (!powerConfig.connectionReferences) {
|
||||
return false;
|
||||
}
|
||||
for (const [connectionName, metadata] of Object.entries(powerConfig.connectionReferences)) {
|
||||
let dataSources = metadata.dataSources || [];
|
||||
if (dataSources &&
|
||||
dataSources.some((ds) => ds.toLowerCase() === dataSourceName.toLowerCase())) {
|
||||
// Remove the data source from the list (case-insensitive)
|
||||
dataSources = dataSources.filter((ds) => ds.toLowerCase() !== dataSourceName.toLowerCase());
|
||||
updated = true;
|
||||
if (metadata.dataSets) {
|
||||
for (const [dataSetName, dataSet] of Object.entries(metadata.dataSets)) {
|
||||
if (dataSet) {
|
||||
const dataSourceReferences = dataSet.dataSources;
|
||||
// if any in the dataSourceReferences matches datasource name case insensitve remove it
|
||||
dataSet.dataSources = Object.fromEntries(Object.entries(dataSourceReferences).filter(([key, _value]) => key.toLowerCase() !== dataSourceName.toLowerCase()));
|
||||
updated = true;
|
||||
metadata.dataSets[dataSetName] = dataSet; // Update the dataset reference
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// If there are no more data sources, mark the connection for removal
|
||||
if (dataSources.length === 0) {
|
||||
keysToDelete.add(connectionName);
|
||||
}
|
||||
else {
|
||||
metadata.dataSources = dataSources;
|
||||
powerConfig.connectionReferences[connectionName] = metadata; // Update the connection reference
|
||||
}
|
||||
}
|
||||
// Remove empty connections
|
||||
for (const key of keysToDelete) {
|
||||
delete powerConfig.connectionReferences[key];
|
||||
updated = true;
|
||||
}
|
||||
}
|
||||
// save the updated power config back to the file
|
||||
if (updated) {
|
||||
await writeRepoConfig(powerConfigPath, powerConfig);
|
||||
}
|
||||
return updated;
|
||||
}
|
||||
//# sourceMappingURL=DeleteDataSource.js.map
|
||||
Generated
Vendored
+1
File diff suppressed because one or more lines are too long
Generated
Vendored
+14
@@ -0,0 +1,14 @@
|
||||
/*!
|
||||
* Copyright (C) Microsoft Corporation. All rights reserved.
|
||||
*/
|
||||
import type { CsdlOperation } from '../services/index.js';
|
||||
import type { FindDataverseApiContext } from '../Types/ActionTypes.js';
|
||||
/**
|
||||
* Search Dataverse operations using the OData $metadata CSDL document.
|
||||
* The org URL is derived from the environment name in the context.
|
||||
*
|
||||
* @param {FindDataverseApiContext} context
|
||||
* @returns {Promise<CsdlOperation[]>}
|
||||
*/
|
||||
export declare function findDataverseApiAsync(context: FindDataverseApiContext): Promise<CsdlOperation[]>;
|
||||
//# sourceMappingURL=FindDataverseApi.d.ts.map
|
||||
Generated
Vendored
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"FindDataverseApi.d.ts","sourceRoot":"","sources":["../../src/Actions/FindDataverseApi.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAMjD,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,sBAAsB,CAAC;AAEpE;;;;;;GAMG;AACH,wBAAsB,qBAAqB,CACzC,OAAO,EAAE,uBAAuB,GAC/B,OAAO,CAAC,aAAa,EAAE,CAAC,CAc1B"}
|
||||
Generated
Vendored
+22
@@ -0,0 +1,22 @@
|
||||
/*!
|
||||
* Copyright (C) Microsoft Corporation. All rights reserved.
|
||||
*/
|
||||
import { DataverseMetadataService, getEnvironmentByName, getPlayerServiceConfig, } from '../services/index.js';
|
||||
/**
|
||||
* Search Dataverse operations using the OData $metadata CSDL document.
|
||||
* The org URL is derived from the environment name in the context.
|
||||
*
|
||||
* @param {FindDataverseApiContext} context
|
||||
* @returns {Promise<CsdlOperation[]>}
|
||||
*/
|
||||
export async function findDataverseApiAsync(context) {
|
||||
const metadata = await getEnvironmentByName(context.environmentName);
|
||||
const orgUrl = metadata?.properties?.linkedEnvironmentMetadata?.instanceApiUrl;
|
||||
if (!orgUrl) {
|
||||
throw new Error('Unable to determine the Dataverse organization URL. Ensure the environment has a linked Dataverse instance.');
|
||||
}
|
||||
const { httpClient } = getPlayerServiceConfig();
|
||||
const dataService = new DataverseMetadataService(httpClient);
|
||||
return dataService.searchCsdlOperations(orgUrl, context.actionsParams.searchTerm, context.logger);
|
||||
}
|
||||
//# sourceMappingURL=FindDataverseApi.js.map
|
||||
Generated
Vendored
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"FindDataverseApi.js","sourceRoot":"","sources":["../../src/Actions/FindDataverseApi.ts"],"names":[],"mappings":"AAAA;;GAEG;AAGH,OAAO,EACL,wBAAwB,EACxB,oBAAoB,EACpB,sBAAsB,GACvB,MAAM,aAAa,CAAC;AAGrB;;;;;;GAMG;AACH,MAAM,CAAC,KAAK,UAAU,qBAAqB,CACzC,OAAgC;IAEhC,MAAM,QAAQ,GAAG,MAAM,oBAAoB,CAAC,OAAO,CAAC,eAAe,CAAC,CAAC;IACrE,MAAM,MAAM,GAAG,QAAQ,EAAE,UAAU,EAAE,yBAAyB,EAAE,cAAc,CAAC;IAE/E,IAAI,CAAC,MAAM,EAAE,CAAC;QACZ,MAAM,IAAI,KAAK,CACb,6GAA6G,CAC9G,CAAC;IACJ,CAAC;IAED,MAAM,EAAE,UAAU,EAAE,GAAG,sBAAsB,EAAE,CAAC;IAChD,MAAM,WAAW,GAAG,IAAI,wBAAwB,CAAC,UAAU,CAAC,CAAC;IAE7D,OAAO,WAAW,CAAC,oBAAoB,CAAC,MAAM,EAAE,OAAO,CAAC,aAAa,CAAC,UAAU,EAAE,OAAO,CAAC,MAAM,CAAC,CAAC;AACpG,CAAC"}
|
||||
Generated
Vendored
+6
@@ -0,0 +1,6 @@
|
||||
/*!
|
||||
* Copyright (C) Microsoft Corporation. All rights reserved.
|
||||
*/
|
||||
import type { App } from '../services/index.js';
|
||||
export declare function getCodeAppsAsync(): Promise<App[]>;
|
||||
//# sourceMappingURL=GetCodeApps.d.ts.map
|
||||
Generated
Vendored
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"GetCodeApps.d.ts","sourceRoot":"","sources":["../../src/Actions/GetCodeApps.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,aAAa,CAAC;AAGvC,wBAAsB,gBAAgB,IAAI,OAAO,CAAC,GAAG,EAAE,CAAC,CAIvD"}
|
||||
Generated
Vendored
+7
@@ -0,0 +1,7 @@
|
||||
import { listAppsAsync } from '../services/index.js';
|
||||
export async function getCodeAppsAsync() {
|
||||
const codeApps = await listAppsAsync();
|
||||
const filteredCodeApps = codeApps.filter((app) => app.appType === 'CodeApp');
|
||||
return filteredCodeApps;
|
||||
}
|
||||
//# sourceMappingURL=GetCodeApps.js.map
|
||||
Generated
Vendored
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"GetCodeApps.js","sourceRoot":"","sources":["../../src/Actions/GetCodeApps.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAE5C,MAAM,CAAC,KAAK,UAAU,gBAAgB;IACpC,MAAM,QAAQ,GAAG,MAAM,aAAa,EAAE,CAAC;IACvC,MAAM,gBAAgB,GAAG,QAAQ,CAAC,MAAM,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,GAAG,CAAC,OAAO,KAAK,SAAS,CAAC,CAAC;IAC7E,OAAO,gBAAgB,CAAC;AAC1B,CAAC"}
|
||||
Generated
Vendored
+7
@@ -0,0 +1,7 @@
|
||||
/*!
|
||||
* Copyright (C) Microsoft Corporation. All rights reserved.
|
||||
*/
|
||||
import type { ConnectionReferenceSolutionComponent } from '../services/index.js';
|
||||
import type { ListConnectionReferencesContext } from '../Types/ActionTypes.js';
|
||||
export declare function listConnectionReferences(context: ListConnectionReferencesContext): Promise<ConnectionReferenceSolutionComponent[]>;
|
||||
//# sourceMappingURL=ListConnectionReferences.d.ts.map
|
||||
Generated
Vendored
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"ListConnectionReferences.d.ts","sourceRoot":"","sources":["../../src/Actions/ListConnectionReferences.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EAAE,oCAAoC,EAAE,MAAM,aAAa,CAAC;AAExE,OAAO,KAAK,EAAE,+BAA+B,EAAE,MAAM,sBAAsB,CAAC;AAG5E,wBAAsB,wBAAwB,CAC5C,OAAO,EAAE,+BAA+B,GACvC,OAAO,CAAC,oCAAoC,EAAE,CAAC,CA0BjD"}
|
||||
Generated
Vendored
+23
@@ -0,0 +1,23 @@
|
||||
/*!
|
||||
* Copyright (C) Microsoft Corporation. All rights reserved.
|
||||
*/
|
||||
import { DataverseSystemDataService, getPlayerServiceConfig } from '../services/index.js';
|
||||
import { getDataverseOrgUrlByEnvironmentName, normalizeParam } from './DataUtils.js';
|
||||
export async function listConnectionReferences(context) {
|
||||
const solutionId = context.actionsParams.solutionId;
|
||||
let orgUrl;
|
||||
if (context.environmentName && context.environmentName !== 'default') {
|
||||
orgUrl = await getDataverseOrgUrlByEnvironmentName(context.environmentName);
|
||||
}
|
||||
if (!orgUrl) {
|
||||
orgUrl = await normalizeParam(context.actionsParams.envUrl);
|
||||
}
|
||||
if (!orgUrl) {
|
||||
throw new Error('Unable to determine the Dataverse organization URL. Run from an app directory or provide --org-url.');
|
||||
}
|
||||
const { httpClient } = getPlayerServiceConfig();
|
||||
const dataverseAPI = new DataverseSystemDataService(httpClient);
|
||||
const connectionReferences = await dataverseAPI.getConnectionReferencesInSolution(solutionId, orgUrl, context.logger);
|
||||
return connectionReferences;
|
||||
}
|
||||
//# sourceMappingURL=ListConnectionReferences.js.map
|
||||
Generated
Vendored
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"ListConnectionReferences.js","sourceRoot":"","sources":["../../src/Actions/ListConnectionReferences.ts"],"names":[],"mappings":"AAAA;;GAEG;AAGH,OAAO,EAAE,0BAA0B,EAAE,sBAAsB,EAAE,MAAM,aAAa,CAAC;AAEjF,OAAO,EAAE,mCAAmC,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAElF,MAAM,CAAC,KAAK,UAAU,wBAAwB,CAC5C,OAAwC;IAExC,MAAM,UAAU,GAAG,OAAO,CAAC,aAAa,CAAC,UAAU,CAAC;IAEpD,IAAI,MAA0B,CAAC;IAC/B,IAAI,OAAO,CAAC,eAAe,IAAI,OAAO,CAAC,eAAe,KAAK,SAAS,EAAE,CAAC;QACrE,MAAM,GAAG,MAAM,mCAAmC,CAAC,OAAO,CAAC,eAAe,CAAC,CAAC;IAC9E,CAAC;IACD,IAAI,CAAC,MAAM,EAAE,CAAC;QACZ,MAAM,GAAG,MAAM,cAAc,CAAC,OAAO,CAAC,aAAa,CAAC,MAAM,CAAC,CAAC;IAC9D,CAAC;IACD,IAAI,CAAC,MAAM,EAAE,CAAC;QACZ,MAAM,IAAI,KAAK,CACb,qGAAqG,CACtG,CAAC;IACJ,CAAC;IAED,MAAM,EAAE,UAAU,EAAE,GAAG,sBAAsB,EAAE,CAAC;IAEhD,MAAM,YAAY,GAAG,IAAI,0BAA0B,CAAC,UAAU,CAAC,CAAC;IAChE,MAAM,oBAAoB,GAAG,MAAM,YAAY,CAAC,iCAAiC,CAC/E,UAAU,EACV,MAAM,EACN,OAAO,CAAC,MAAM,CACf,CAAC;IAEF,OAAO,oBAAoB,CAAC;AAC9B,CAAC"}
|
||||
Generated
Vendored
+7
@@ -0,0 +1,7 @@
|
||||
/*!
|
||||
* Copyright (C) Microsoft Corporation. All rights reserved.
|
||||
*/
|
||||
import type { Connection } from '../services/index.js';
|
||||
import type { ListConnectionsContext } from '../Types/ActionTypes.js';
|
||||
export declare function listConnectionsAsync(context: ListConnectionsContext): Promise<Connection[]>;
|
||||
//# sourceMappingURL=ListConnections.d.ts.map
|
||||
CodeApp_NeonReactor/node_modules/@microsoft/power-apps-actions/dist/Actions/ListConnections.d.ts.map
Generated
Vendored
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"ListConnections.d.ts","sourceRoot":"","sources":["../../src/Actions/ListConnections.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAE9C,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,sBAAsB,CAAC;AAEnE,wBAAsB,oBAAoB,CAAC,OAAO,EAAE,sBAAsB,GAAG,OAAO,CAAC,UAAU,EAAE,CAAC,CAEjG"}
|
||||
Generated
Vendored
+8
@@ -0,0 +1,8 @@
|
||||
/*!
|
||||
* Copyright (C) Microsoft Corporation. All rights reserved.
|
||||
*/
|
||||
import { listAllConnectionsAsync } from '../services/index.js';
|
||||
export async function listConnectionsAsync(context) {
|
||||
return listAllConnectionsAsync(context.actionsParams.search, context.logger);
|
||||
}
|
||||
//# sourceMappingURL=ListConnections.js.map
|
||||
Generated
Vendored
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"ListConnections.js","sourceRoot":"","sources":["../../src/Actions/ListConnections.ts"],"names":[],"mappings":"AAAA;;GAEG;AAGH,OAAO,EAAE,uBAAuB,EAAE,MAAM,aAAa,CAAC;AAGtD,MAAM,CAAC,KAAK,UAAU,oBAAoB,CAAC,OAA+B;IACxE,OAAO,uBAAuB,CAAC,OAAO,CAAC,aAAa,CAAC,MAAM,EAAE,OAAO,CAAC,MAAM,CAAC,CAAC;AAC/E,CAAC"}
|
||||
Generated
Vendored
+7
@@ -0,0 +1,7 @@
|
||||
/*!
|
||||
* Copyright (C) Microsoft Corporation. All rights reserved.
|
||||
*/
|
||||
import type { IConnectedDatasetListMetadata } from '../services/index.js';
|
||||
import type { ListDataSetsContext } from '../Types/ActionTypes.js';
|
||||
export declare function listDatasetsAsync(context: ListDataSetsContext): Promise<IConnectedDatasetListMetadata>;
|
||||
//# sourceMappingURL=ListDatasets.d.ts.map
|
||||
Generated
Vendored
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"ListDatasets.d.ts","sourceRoot":"","sources":["../../src/Actions/ListDatasets.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EAAE,6BAA6B,EAAE,MAAM,aAAa,CAAC;AAEjE,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,sBAAsB,CAAC;AAEhE,wBAAsB,iBAAiB,CACrC,OAAO,EAAE,mBAAmB,GAC3B,OAAO,CAAC,6BAA6B,CAAC,CAuBxC"}
|
||||
Generated
Vendored
+21
@@ -0,0 +1,21 @@
|
||||
/*!
|
||||
* Copyright (C) Microsoft Corporation. All rights reserved.
|
||||
*/
|
||||
import { getConnectionByNameAsync, getConnectorAsync, getDatasetsAsync } from '../services/index.js';
|
||||
export async function listDatasetsAsync(context) {
|
||||
const connectorName = context.actionsParams.apiId;
|
||||
const connectionName = context.actionsParams.connectionId;
|
||||
if (!connectorName || !connectionName) {
|
||||
throw new Error('Both Argument apiId and Argument connectionId are required to list datasets. Use --apiId or -a and --connectionId or -c to specify them.');
|
||||
}
|
||||
const connector = await getConnectorAsync(connectorName);
|
||||
if (!connector) {
|
||||
throw new Error(`Connector with name ${connectorName} not found.`);
|
||||
}
|
||||
const connection = await getConnectionByNameAsync(connectionName, connectorName);
|
||||
if (!connection) {
|
||||
throw new Error(`Connection with name ${connectionName} not found for connector ${connectorName}.`);
|
||||
}
|
||||
return await getDatasetsAsync(connection, connector, context.logger);
|
||||
}
|
||||
//# sourceMappingURL=ListDatasets.js.map
|
||||
Generated
Vendored
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"ListDatasets.js","sourceRoot":"","sources":["../../src/Actions/ListDatasets.ts"],"names":[],"mappings":"AAAA;;GAEG;AAGH,OAAO,EAAE,wBAAwB,EAAE,iBAAiB,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAG5F,MAAM,CAAC,KAAK,UAAU,iBAAiB,CACrC,OAA4B;IAE5B,MAAM,aAAa,GAAG,OAAO,CAAC,aAAa,CAAC,KAAK,CAAC;IAClD,MAAM,cAAc,GAAG,OAAO,CAAC,aAAa,CAAC,YAAY,CAAC;IAE1D,IAAI,CAAC,aAAa,IAAI,CAAC,cAAc,EAAE,CAAC;QACtC,MAAM,IAAI,KAAK,CACb,0IAA0I,CAC3I,CAAC;IACJ,CAAC;IAED,MAAM,SAAS,GAAG,MAAM,iBAAiB,CAAC,aAAa,CAAC,CAAC;IACzD,IAAI,CAAC,SAAS,EAAE,CAAC;QACf,MAAM,IAAI,KAAK,CAAC,uBAAuB,aAAa,aAAa,CAAC,CAAC;IACrE,CAAC;IAED,MAAM,UAAU,GAAG,MAAM,wBAAwB,CAAC,cAAc,EAAE,aAAa,CAAC,CAAC;IACjF,IAAI,CAAC,UAAU,EAAE,CAAC;QAChB,MAAM,IAAI,KAAK,CACb,wBAAwB,cAAc,4BAA4B,aAAa,GAAG,CACnF,CAAC;IACJ,CAAC;IAED,OAAO,MAAM,gBAAgB,CAAC,UAAU,EAAE,SAAS,EAAE,OAAO,CAAC,MAAM,CAAC,CAAC;AACvE,CAAC"}
|
||||
Generated
Vendored
+15
@@ -0,0 +1,15 @@
|
||||
/*!
|
||||
* Copyright (C) Microsoft Corporation. All rights reserved.
|
||||
*/
|
||||
import type { EnvironmentVariableDefinition } from '../services/index.js';
|
||||
import type { ListAllEnvironmentVariablesContext } from '../Types/ActionTypes.js';
|
||||
/**
|
||||
* Get environment variables from a Dataverse environment.
|
||||
* Expects the following arguments:
|
||||
* - envUrl: The environment URL for Dataverse data sources
|
||||
*
|
||||
* @param {ArgumentProvider} argumentProvider
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
export declare function listAllEnvironmentVariablesAsync(context: ListAllEnvironmentVariablesContext): Promise<EnvironmentVariableDefinition[]>;
|
||||
//# sourceMappingURL=ListEnvironmentVariables.d.ts.map
|
||||
Generated
Vendored
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"ListEnvironmentVariables.d.ts","sourceRoot":"","sources":["../../src/Actions/ListEnvironmentVariables.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EAAE,6BAA6B,EAAE,MAAM,aAAa,CAAC;AAEjE,OAAO,KAAK,EAAE,kCAAkC,EAAE,MAAM,sBAAsB,CAAC;AAG/E;;;;;;;GAOG;AACH,wBAAsB,gCAAgC,CACpD,OAAO,EAAE,kCAAkC,GAC1C,OAAO,CAAC,6BAA6B,EAAE,CAAC,CAkB1C"}
|
||||
Generated
Vendored
+29
@@ -0,0 +1,29 @@
|
||||
/*!
|
||||
* Copyright (C) Microsoft Corporation. All rights reserved.
|
||||
*/
|
||||
import { DataverseSystemDataService, getPlayerServiceConfig } from '../services/index.js';
|
||||
import { getDataverseOrgUrlByEnvironmentName, normalizeParam } from './DataUtils.js';
|
||||
/**
|
||||
* Get environment variables from a Dataverse environment.
|
||||
* Expects the following arguments:
|
||||
* - envUrl: The environment URL for Dataverse data sources
|
||||
*
|
||||
* @param {ArgumentProvider} argumentProvider
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
export async function listAllEnvironmentVariablesAsync(context) {
|
||||
let orgUrl;
|
||||
if (context.environmentName && context.environmentName !== 'default') {
|
||||
orgUrl = await getDataverseOrgUrlByEnvironmentName(context.environmentName);
|
||||
}
|
||||
if (!orgUrl) {
|
||||
orgUrl = await normalizeParam(context.actionsParams.envUrl);
|
||||
}
|
||||
if (!orgUrl) {
|
||||
throw new Error('Unable to determine the Dataverse organization URL. Run from an app directory or provide --org-url.');
|
||||
}
|
||||
const { httpClient } = getPlayerServiceConfig();
|
||||
const dataService = new DataverseSystemDataService(httpClient);
|
||||
return (await dataService.getEnvironmentVariables(orgUrl, context.logger)) || [];
|
||||
}
|
||||
//# sourceMappingURL=ListEnvironmentVariables.js.map
|
||||
Generated
Vendored
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"ListEnvironmentVariables.js","sourceRoot":"","sources":["../../src/Actions/ListEnvironmentVariables.ts"],"names":[],"mappings":"AAAA;;GAEG;AAGH,OAAO,EAAE,0BAA0B,EAAE,sBAAsB,EAAE,MAAM,aAAa,CAAC;AAEjF,OAAO,EAAE,mCAAmC,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAElF;;;;;;;GAOG;AACH,MAAM,CAAC,KAAK,UAAU,gCAAgC,CACpD,OAA2C;IAE3C,IAAI,MAA0B,CAAC;IAC/B,IAAI,OAAO,CAAC,eAAe,IAAI,OAAO,CAAC,eAAe,KAAK,SAAS,EAAE,CAAC;QACrE,MAAM,GAAG,MAAM,mCAAmC,CAAC,OAAO,CAAC,eAAe,CAAC,CAAC;IAC9E,CAAC;IACD,IAAI,CAAC,MAAM,EAAE,CAAC;QACZ,MAAM,GAAG,MAAM,cAAc,CAAC,OAAO,CAAC,aAAa,CAAC,MAAM,CAAC,CAAC;IAC9D,CAAC;IACD,IAAI,CAAC,MAAM,EAAE,CAAC;QACZ,MAAM,IAAI,KAAK,CACb,qGAAqG,CACtG,CAAC;IACJ,CAAC;IAED,MAAM,EAAE,UAAU,EAAE,GAAG,sBAAsB,EAAE,CAAC;IAChD,MAAM,WAAW,GAAG,IAAI,0BAA0B,CAAC,UAAU,CAAC,CAAC;IAE/D,OAAO,CAAC,MAAM,WAAW,CAAC,uBAAuB,CAAC,MAAM,EAAE,OAAO,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,CAAC;AACnF,CAAC"}
|
||||
Generated
Vendored
+24
@@ -0,0 +1,24 @@
|
||||
/*!
|
||||
* Copyright (C) Microsoft Corporation. All rights reserved.
|
||||
*/
|
||||
import type { DataverseWorkflow } from '../services/index.js';
|
||||
import type { ListFlowsContext } from '../Types/ActionTypes.js';
|
||||
import type { Workflow } from '../Types/Workflow.types.js';
|
||||
/**
|
||||
* Determines whether a flow is supported by Code Apps by inspecting its trigger kind.
|
||||
* Only flows with a PowerApp or PowerAppV2 manual trigger are supported.
|
||||
*
|
||||
* @param dataverseWorkflow The Dataverse workflow to check
|
||||
* @returns true if the flow has a supported trigger kind
|
||||
*/
|
||||
export declare function isSupportedCodeAppFlow(dataverseWorkflow: DataverseWorkflow): boolean;
|
||||
/**
|
||||
* Get all cloud flows from a Dataverse environment that are supported by Code Apps.
|
||||
* Only flows with a PowerApp or PowerAppV2 trigger are returned.
|
||||
* The org URL is derived from the environment ID in the context.
|
||||
*
|
||||
* @param {ListFlowsContext} context
|
||||
* @returns {Promise<Workflow[]>}
|
||||
*/
|
||||
export declare function listFlowsAsync(context: ListFlowsContext): Promise<Workflow[]>;
|
||||
//# sourceMappingURL=ListFlows.d.ts.map
|
||||
Generated
Vendored
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"ListFlows.d.ts","sourceRoot":"","sources":["../../src/Actions/ListFlows.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAMrD,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAC;AAC7D,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,yBAAyB,CAAC;AAGxD;;;;;;GAMG;AACH,wBAAgB,sBAAsB,CAAC,iBAAiB,EAAE,iBAAiB,GAAG,OAAO,CAiBpF;AAgBD;;;;;;;GAOG;AACH,wBAAsB,cAAc,CAAC,OAAO,EAAE,gBAAgB,GAAG,OAAO,CAAC,QAAQ,EAAE,CAAC,CAkBnF"}
|
||||
Generated
Vendored
+58
@@ -0,0 +1,58 @@
|
||||
/*!
|
||||
* Copyright (C) Microsoft Corporation. All rights reserved.
|
||||
*/
|
||||
import { DataverseSystemDataService, getEnvironmentByName, getPlayerServiceConfig, } from '../services/index.js';
|
||||
import { SUPPORTED_TRIGGER_KINDS } from './flowConstants.js';
|
||||
/**
|
||||
* Determines whether a flow is supported by Code Apps by inspecting its trigger kind.
|
||||
* Only flows with a PowerApp or PowerAppV2 manual trigger are supported.
|
||||
*
|
||||
* @param dataverseWorkflow The Dataverse workflow to check
|
||||
* @returns true if the flow has a supported trigger kind
|
||||
*/
|
||||
export function isSupportedCodeAppFlow(dataverseWorkflow) {
|
||||
try {
|
||||
const clientData = JSON.parse(dataverseWorkflow.clientdata || '{}');
|
||||
const triggers = clientData?.properties?.definition?.triggers;
|
||||
if (!triggers)
|
||||
return false;
|
||||
return Object.values(triggers).some((trigger) => trigger?.kind && SUPPORTED_TRIGGER_KINDS.has(trigger.kind));
|
||||
}
|
||||
catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Maps a DataverseWorkflow to the public Workflow interface
|
||||
* @param dataverseWorkflow The internal Dataverse workflow object
|
||||
* @returns A Workflow object with cleaned up field names
|
||||
*/
|
||||
function mapToWorkflow(dataverseWorkflow) {
|
||||
return {
|
||||
name: dataverseWorkflow.name,
|
||||
modifiedOn: dataverseWorkflow.modifiedon,
|
||||
workflowId: dataverseWorkflow.resourceid,
|
||||
statecode: dataverseWorkflow.statecode,
|
||||
};
|
||||
}
|
||||
/**
|
||||
* Get all cloud flows from a Dataverse environment that are supported by Code Apps.
|
||||
* Only flows with a PowerApp or PowerAppV2 trigger are returned.
|
||||
* The org URL is derived from the environment ID in the context.
|
||||
*
|
||||
* @param {ListFlowsContext} context
|
||||
* @returns {Promise<Workflow[]>}
|
||||
*/
|
||||
export async function listFlowsAsync(context) {
|
||||
// Get environment metadata to retrieve the Dataverse org URL
|
||||
const metadata = await getEnvironmentByName(context.environmentName);
|
||||
const orgUrl = metadata?.properties?.linkedEnvironmentMetadata?.instanceApiUrl;
|
||||
if (!orgUrl) {
|
||||
throw new Error('Unable to determine the Dataverse organization URL. Ensure the environment has a linked Dataverse instance.');
|
||||
}
|
||||
const { httpClient } = getPlayerServiceConfig();
|
||||
const dataService = new DataverseSystemDataService(httpClient);
|
||||
const dataverseWorkflows = (await dataService.getWorkflows(orgUrl, context.logger, context.actionsParams.search)) || [];
|
||||
return dataverseWorkflows.filter(isSupportedCodeAppFlow).map(mapToWorkflow);
|
||||
}
|
||||
//# sourceMappingURL=ListFlows.js.map
|
||||
Generated
Vendored
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"ListFlows.js","sourceRoot":"","sources":["../../src/Actions/ListFlows.ts"],"names":[],"mappings":"AAAA;;GAEG;AAGH,OAAO,EACL,0BAA0B,EAC1B,oBAAoB,EACpB,sBAAsB,GACvB,MAAM,aAAa,CAAC;AAGrB,OAAO,EAAE,uBAAuB,EAAE,MAAM,iBAAiB,CAAC;AAE1D;;;;;;GAMG;AACH,MAAM,UAAU,sBAAsB,CAAC,iBAAoC;IACzE,IAAI,CAAC;QACH,MAAM,UAAU,GAAG,IAAI,CAAC,KAAK,CAAC,iBAAiB,CAAC,UAAU,IAAI,IAAI,CAMjE,CAAC;QACF,MAAM,QAAQ,GAAG,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,QAAQ,CAAC;QAC9D,IAAI,CAAC,QAAQ;YAAE,OAAO,KAAK,CAAC;QAC5B,OAAO,MAAM,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,IAAI,CACjC,CAAC,OAAO,EAAE,EAAE,CAAC,OAAO,EAAE,IAAI,IAAI,uBAAuB,CAAC,GAAG,CAAC,OAAO,CAAC,IAAI,CAAC,CACxE,CAAC;IACJ,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,KAAK,CAAC;IACf,CAAC;AACH,CAAC;AAED;;;;GAIG;AACH,SAAS,aAAa,CAAC,iBAAoC;IACzD,OAAO;QACL,IAAI,EAAE,iBAAiB,CAAC,IAAI;QAC5B,UAAU,EAAE,iBAAiB,CAAC,UAAU;QACxC,UAAU,EAAE,iBAAiB,CAAC,UAAU;QACxC,SAAS,EAAE,iBAAiB,CAAC,SAAS;KACvC,CAAC;AACJ,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,CAAC,KAAK,UAAU,cAAc,CAAC,OAAyB;IAC5D,6DAA6D;IAC7D,MAAM,QAAQ,GAAG,MAAM,oBAAoB,CAAC,OAAO,CAAC,eAAe,CAAC,CAAC;IACrE,MAAM,MAAM,GAAG,QAAQ,EAAE,UAAU,EAAE,yBAAyB,EAAE,cAAc,CAAC;IAE/E,IAAI,CAAC,MAAM,EAAE,CAAC;QACZ,MAAM,IAAI,KAAK,CACb,6GAA6G,CAC9G,CAAC;IACJ,CAAC;IAED,MAAM,EAAE,UAAU,EAAE,GAAG,sBAAsB,EAAE,CAAC;IAChD,MAAM,WAAW,GAAG,IAAI,0BAA0B,CAAC,UAAU,CAAC,CAAC;IAE/D,MAAM,kBAAkB,GACtB,CAAC,MAAM,WAAW,CAAC,YAAY,CAAC,MAAM,EAAE,OAAO,CAAC,MAAM,EAAE,OAAO,CAAC,aAAa,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,CAAC;IAE/F,OAAO,kBAAkB,CAAC,MAAM,CAAC,sBAAsB,CAAC,CAAC,GAAG,CAAC,aAAa,CAAC,CAAC;AAC9E,CAAC"}
|
||||
Generated
Vendored
+7
@@ -0,0 +1,7 @@
|
||||
/*!
|
||||
* Copyright (C) Microsoft Corporation. All rights reserved.
|
||||
*/
|
||||
import type { IConnectedDataTableListMetadata } from '../services/index.js';
|
||||
import type { ListSQLStoredProceduresContext } from '../Types/ActionTypes.js';
|
||||
export declare function listSqlStoredProceduresAsync(config: ListSQLStoredProceduresContext): Promise<IConnectedDataTableListMetadata>;
|
||||
//# sourceMappingURL=ListSqlStoredProcedures.d.ts.map
|
||||
Generated
Vendored
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"ListSqlStoredProcedures.d.ts","sourceRoot":"","sources":["../../src/Actions/ListSqlStoredProcedures.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EAAE,+BAA+B,EAAE,MAAM,aAAa,CAAC;AAMnE,OAAO,KAAK,EAAE,8BAA8B,EAAE,MAAM,sBAAsB,CAAC;AAE3E,wBAAsB,4BAA4B,CAChD,MAAM,EAAE,8BAA8B,GACrC,OAAO,CAAC,+BAA+B,CAAC,CAyB1C"}
|
||||
Generated
Vendored
+20
@@ -0,0 +1,20 @@
|
||||
/*!
|
||||
* Copyright (C) Microsoft Corporation. All rights reserved.
|
||||
*/
|
||||
import { getConnectionByNameAsync, getConnectorAsync, getSqlStoredProceduresAsync, } from '../services/index.js';
|
||||
export async function listSqlStoredProceduresAsync(config) {
|
||||
const connectorName = 'shared_sql';
|
||||
const connectionName = config.actionsParams.connectionId;
|
||||
const dataset = config.actionsParams.dataset;
|
||||
const connector = await getConnectorAsync(connectorName);
|
||||
if (!connector) {
|
||||
throw new Error(`Connector with ID ${connectorName} not found.`);
|
||||
}
|
||||
const connection = await getConnectionByNameAsync(connectionName, connectorName);
|
||||
if (!connection) {
|
||||
throw new Error(`Connection with name ${connectionName} not found for connector ${connectorName}.`);
|
||||
}
|
||||
const storedProcedures = await getSqlStoredProceduresAsync(connection, connector, dataset, config.logger);
|
||||
return storedProcedures;
|
||||
}
|
||||
//# sourceMappingURL=ListSqlStoredProcedures.js.map
|
||||
Generated
Vendored
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"ListSqlStoredProcedures.js","sourceRoot":"","sources":["../../src/Actions/ListSqlStoredProcedures.ts"],"names":[],"mappings":"AAAA;;GAEG;AAGH,OAAO,EACL,wBAAwB,EACxB,iBAAiB,EACjB,2BAA2B,GAC5B,MAAM,aAAa,CAAC;AAGrB,MAAM,CAAC,KAAK,UAAU,4BAA4B,CAChD,MAAsC;IAEtC,MAAM,aAAa,GAAG,YAAY,CAAC;IACnC,MAAM,cAAc,GAAG,MAAM,CAAC,aAAa,CAAC,YAAY,CAAC;IACzD,MAAM,OAAO,GAAG,MAAM,CAAC,aAAa,CAAC,OAAO,CAAC;IAE7C,MAAM,SAAS,GAAG,MAAM,iBAAiB,CAAC,aAAa,CAAC,CAAC;IACzD,IAAI,CAAC,SAAS,EAAE,CAAC;QACf,MAAM,IAAI,KAAK,CAAC,qBAAqB,aAAa,aAAa,CAAC,CAAC;IACnE,CAAC;IAED,MAAM,UAAU,GAAG,MAAM,wBAAwB,CAAC,cAAc,EAAE,aAAa,CAAC,CAAC;IACjF,IAAI,CAAC,UAAU,EAAE,CAAC;QAChB,MAAM,IAAI,KAAK,CACb,wBAAwB,cAAc,4BAA4B,aAAa,GAAG,CACnF,CAAC;IACJ,CAAC;IAED,MAAM,gBAAgB,GAAG,MAAM,2BAA2B,CACxD,UAAU,EACV,SAAS,EACT,OAAO,EACP,MAAM,CAAC,MAAM,CACd,CAAC;IAEF,OAAO,gBAAgB,CAAC;AAC1B,CAAC"}
|
||||
Generated
Vendored
+7
@@ -0,0 +1,7 @@
|
||||
/*!
|
||||
* Copyright (C) Microsoft Corporation. All rights reserved.
|
||||
*/
|
||||
import type { IConnectedDataTableListMetadata } from '../services/index.js';
|
||||
import type { ListTablesContext } from '../Types/ActionTypes.js';
|
||||
export declare function listTablesAsync(context: ListTablesContext): Promise<IConnectedDataTableListMetadata>;
|
||||
//# sourceMappingURL=ListTables.d.ts.map
|
||||
Generated
Vendored
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"ListTables.d.ts","sourceRoot":"","sources":["../../src/Actions/ListTables.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EAAE,+BAA+B,EAAE,MAAM,aAAa,CAAC;AAEnE,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,sBAAsB,CAAC;AAE9D,wBAAsB,eAAe,CACnC,OAAO,EAAE,iBAAiB,GACzB,OAAO,CAAC,+BAA+B,CAAC,CAkC1C"}
|
||||
Generated
Vendored
+29
@@ -0,0 +1,29 @@
|
||||
/*!
|
||||
* Copyright (C) Microsoft Corporation. All rights reserved.
|
||||
*/
|
||||
import { getConnectionByNameAsync, getConnectorAsync, getTablesAsync } from '../services/index.js';
|
||||
export async function listTablesAsync(context) {
|
||||
const connectorName = context.actionsParams.apiId;
|
||||
const connectionName = context.actionsParams.connectionId;
|
||||
const dataset = context.actionsParams.dataset;
|
||||
if (!dataset) {
|
||||
throw new Error('Argument dataset is required to list tables. Use --dataset or -d to specify it.');
|
||||
}
|
||||
if (!connectorName) {
|
||||
throw new Error('Argument apiId is required to list tables. Use --apiId or -a to specify it.');
|
||||
}
|
||||
if (!connectionName) {
|
||||
throw new Error('Argument connectionId is required to list tables. Use --connectionId or -c to specify it.');
|
||||
}
|
||||
const connector = await getConnectorAsync(connectorName);
|
||||
if (!connector) {
|
||||
throw new Error(`Connector with ID ${connectorName} not found.`);
|
||||
}
|
||||
const connection = await getConnectionByNameAsync(connectionName, connectorName);
|
||||
if (!connection) {
|
||||
throw new Error(`Connection with name ${connectionName} not found for connector ${connectorName}.`);
|
||||
}
|
||||
const tables = await getTablesAsync(connection, connector, dataset, context.logger);
|
||||
return tables;
|
||||
}
|
||||
//# sourceMappingURL=ListTables.js.map
|
||||
Generated
Vendored
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"ListTables.js","sourceRoot":"","sources":["../../src/Actions/ListTables.ts"],"names":[],"mappings":"AAAA;;GAEG;AAGH,OAAO,EAAE,wBAAwB,EAAE,iBAAiB,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAG1F,MAAM,CAAC,KAAK,UAAU,eAAe,CACnC,OAA0B;IAE1B,MAAM,aAAa,GAAG,OAAO,CAAC,aAAa,CAAC,KAAK,CAAC;IAClD,MAAM,cAAc,GAAG,OAAO,CAAC,aAAa,CAAC,YAAY,CAAC;IAC1D,MAAM,OAAO,GAAG,OAAO,CAAC,aAAa,CAAC,OAAO,CAAC;IAE9C,IAAI,CAAC,OAAO,EAAE,CAAC;QACb,MAAM,IAAI,KAAK,CACb,iFAAiF,CAClF,CAAC;IACJ,CAAC;IACD,IAAI,CAAC,aAAa,EAAE,CAAC;QACnB,MAAM,IAAI,KAAK,CAAC,6EAA6E,CAAC,CAAC;IACjG,CAAC;IACD,IAAI,CAAC,cAAc,EAAE,CAAC;QACpB,MAAM,IAAI,KAAK,CACb,2FAA2F,CAC5F,CAAC;IACJ,CAAC;IAED,MAAM,SAAS,GAAG,MAAM,iBAAiB,CAAC,aAAa,CAAC,CAAC;IACzD,IAAI,CAAC,SAAS,EAAE,CAAC;QACf,MAAM,IAAI,KAAK,CAAC,qBAAqB,aAAa,aAAa,CAAC,CAAC;IACnE,CAAC;IAED,MAAM,UAAU,GAAG,MAAM,wBAAwB,CAAC,cAAc,EAAE,aAAa,CAAC,CAAC;IACjF,IAAI,CAAC,UAAU,EAAE,CAAC;QAChB,MAAM,IAAI,KAAK,CACb,wBAAwB,cAAc,4BAA4B,aAAa,GAAG,CACnF,CAAC;IACJ,CAAC;IAED,MAAM,MAAM,GAAG,MAAM,cAAc,CAAC,UAAU,EAAE,SAAS,EAAE,OAAO,EAAE,OAAO,CAAC,MAAM,CAAC,CAAC;IAEpF,OAAO,MAAM,CAAC;AAChB,CAAC"}
|
||||
Generated
Vendored
+22
@@ -0,0 +1,22 @@
|
||||
/*!
|
||||
* Copyright (C) Microsoft Corporation. All rights reserved.
|
||||
*/
|
||||
import type { ConnectionReference as SchemaConnectionReference } from '../Config/Config.types.js';
|
||||
import type { App, ConnectionReference } from '../services/index.js';
|
||||
import type { PushAppContext } from '../Types/ActionTypes.js';
|
||||
export declare function pushApp(context: PushAppContext): Promise<App>;
|
||||
/**
|
||||
* Converts a full file path returned by the VFS (which may use OS-native separators,
|
||||
* e.g. backslashes on Windows) into a relative URL segment suitable for blob storage.
|
||||
*
|
||||
* Steps:
|
||||
* 1. Normalise `buildPath` to forward slashes and strip the leading `./` and any
|
||||
* trailing slash, so the prefix comparison is separator-agnostic.
|
||||
* 2. Normalise `fullFilePath` to forward slashes.
|
||||
* 3. Remove the build-directory prefix and any remaining leading slash.
|
||||
*
|
||||
* @internal Exported for unit-testing only.
|
||||
*/
|
||||
export declare function computeRelativeBlobPath(fullFilePath: string, buildPath: string): string;
|
||||
export declare function constructConnectionReferences(configConnectionReferences: Record<string, SchemaConnectionReference> | undefined): Record<string, ConnectionReference>;
|
||||
//# sourceMappingURL=PushApp.d.ts.map
|
||||
Generated
Vendored
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"PushApp.d.ts","sourceRoot":"","sources":["../../src/Actions/PushApp.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EACV,mBAAmB,IAAI,yBAAyB,EAGjD,MAAM,wBAAwB,CAAC;AAIhC,OAAO,KAAK,EACV,GAAG,EAEH,mBAAmB,EAGpB,MAAM,aAAa,CAAC;AAWrB,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAG3D,wBAAsB,OAAO,CAAC,OAAO,EAAE,cAAc,GAAG,OAAO,CAAC,GAAG,CAAC,CAyFnE;AAqDD;;;;;;;;;;;GAWG;AACH,wBAAgB,uBAAuB,CAAC,YAAY,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,MAAM,CAGvF;AA+ED,wBAAgB,6BAA6B,CAC3C,0BAA0B,EAAE,MAAM,CAAC,MAAM,EAAE,yBAAyB,CAAC,GAAG,SAAS,GAChF,MAAM,CAAC,MAAM,EAAE,mBAAmB,CAAC,CAkErC"}
|
||||
Generated
Vendored
+278
@@ -0,0 +1,278 @@
|
||||
/*!
|
||||
* Copyright (C) Microsoft Corporation. All rights reserved.
|
||||
*/
|
||||
import { readRepoConfig, writeRepoConfig } from '../Config/ConfigManager.js';
|
||||
import { stringifyError } from '../ErrorUtils.js';
|
||||
import { initializePlayerActions } from '../initializePlayerActions.js';
|
||||
import { createApp, endSession, generateResourceStorage, getPlayerServiceConfig, makeAppSolutionAware, publishApp, saveApp, startSession, } from '../services/index.js';
|
||||
export async function pushApp(context) {
|
||||
const { actionsParams, authProvider, environmentName, region, vfs, localFilePaths, logger, httpClient, } = context;
|
||||
const { appSubtype, solutionId } = actionsParams;
|
||||
const { powerConfigPath } = localFilePaths;
|
||||
initializePlayerActions({
|
||||
environmentName,
|
||||
vfs,
|
||||
authProvider,
|
||||
logger,
|
||||
region,
|
||||
httpClient,
|
||||
});
|
||||
const pushAppScenario = logger.trackScenario('PushApp');
|
||||
try {
|
||||
const powerConfig = await readRepoConfig(powerConfigPath);
|
||||
const buildPath = powerConfig.buildPath;
|
||||
if (!buildPath || !(await vfs.exists(buildPath))) {
|
||||
throw new Error(`Build path ${buildPath} does not exist`);
|
||||
}
|
||||
let attempts = 0;
|
||||
const maxAttempts = 5;
|
||||
let appName = actionsParams.appName;
|
||||
let codeAppResponse;
|
||||
const saveAppScenario = logger.trackScenario('PushApp.SaveApp');
|
||||
while (attempts < maxAttempts) {
|
||||
try {
|
||||
const blobUri = await generateResourceStorage();
|
||||
const documentUri = await uploadFilesToBlob(powerConfig, blobUri, vfs, logger);
|
||||
const codeAppMetadata = createAppMetadata(powerConfig, environmentName, documentUri, appSubtype);
|
||||
// Create app in a solution
|
||||
if (!appName) {
|
||||
codeAppResponse = await createApp(codeAppMetadata);
|
||||
appName = codeAppResponse.name;
|
||||
if (solutionId) {
|
||||
await makeAppSolutionAware(appName, solutionId);
|
||||
}
|
||||
}
|
||||
else {
|
||||
const session = await startSession(appName);
|
||||
codeAppResponse = await saveApp(appName, codeAppMetadata);
|
||||
await endSession(appName, session.currentSession.sessionId);
|
||||
}
|
||||
break;
|
||||
}
|
||||
catch (error) {
|
||||
attempts++;
|
||||
if (attempts >= maxAttempts) {
|
||||
saveAppScenario.failure({ error: stringifyError(error) });
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!appName || !codeAppResponse) {
|
||||
throw new Error('Failed to upload and save app');
|
||||
}
|
||||
await publishApp(appName);
|
||||
await writeRepoConfig(powerConfigPath, {
|
||||
...powerConfig,
|
||||
appId: codeAppResponse.name,
|
||||
});
|
||||
saveAppScenario.complete();
|
||||
return codeAppResponse;
|
||||
}
|
||||
catch (error) {
|
||||
pushAppScenario.failure({
|
||||
error: stringifyError(error),
|
||||
});
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
async function uploadFilesToBlob(powerConfig, blobUri, vfs, logger) {
|
||||
const httpClient = getPlayerServiceConfig().httpClient;
|
||||
// The blob URL has query params. So we need to ensure those are at the end.
|
||||
const pos = blobUri.indexOf('?');
|
||||
const baseUrl = blobUri.substring(0, pos);
|
||||
const restUrl = blobUri.substring(pos);
|
||||
const { buildEntryPoint, buildPath } = powerConfig;
|
||||
if (!buildEntryPoint || !buildPath) {
|
||||
throw new Error('Invalid power config');
|
||||
}
|
||||
const uploadFilesScenario = logger.trackScenario('PushApp.SaveApp.UploadFiles', {
|
||||
buildPath,
|
||||
buildEntryPoint,
|
||||
});
|
||||
try {
|
||||
// Recursively collect all files from buildPath, handling nested directories
|
||||
const allFiles = await collectAllFiles(buildPath, vfs);
|
||||
let totalBytes = 0;
|
||||
for (const fullFilePath of allFiles) {
|
||||
const fileContent = await vfs.readFile(fullFilePath, 'utf-8');
|
||||
totalBytes += Buffer.byteLength(fileContent, 'utf-8');
|
||||
// Compute the blob-relative URL segment, normalising OS path separators cross-platform.
|
||||
const relativePath = computeRelativeBlobPath(fullFilePath, buildPath);
|
||||
const url = `${baseUrl}/${relativePath}${restUrl}`;
|
||||
await httpClient.put(url, {
|
||||
headers: {
|
||||
'Content-Type': getMimeType(fullFilePath),
|
||||
'x-ms-blob-type': 'BlockBlob',
|
||||
},
|
||||
body: fileContent,
|
||||
});
|
||||
}
|
||||
logger.trackActivityEvent('PushApp.SaveApp.UploadFiles.FileSize', {
|
||||
fileCount: allFiles.length,
|
||||
totalBytes,
|
||||
});
|
||||
uploadFilesScenario.complete();
|
||||
return `${baseUrl}/${computeRelativeBlobPath(buildEntryPoint, buildPath)}${restUrl}`;
|
||||
}
|
||||
catch (error) {
|
||||
uploadFilesScenario.failure({ error: stringifyError(error) });
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Converts a full file path returned by the VFS (which may use OS-native separators,
|
||||
* e.g. backslashes on Windows) into a relative URL segment suitable for blob storage.
|
||||
*
|
||||
* Steps:
|
||||
* 1. Normalise `buildPath` to forward slashes and strip the leading `./` and any
|
||||
* trailing slash, so the prefix comparison is separator-agnostic.
|
||||
* 2. Normalise `fullFilePath` to forward slashes.
|
||||
* 3. Remove the build-directory prefix and any remaining leading slash.
|
||||
*
|
||||
* @internal Exported for unit-testing only.
|
||||
*/
|
||||
export function computeRelativeBlobPath(fullFilePath, buildPath) {
|
||||
const normalizedBuildPath = buildPath.replace(/\\/g, '/').replace(/^\.\//, '').replace(/\/$/, '');
|
||||
return fullFilePath.replace(/\\/g, '/').replace(normalizedBuildPath, '').replace(/^\//, '');
|
||||
}
|
||||
function getMimeType(filePath) {
|
||||
if (filePath.endsWith('.html')) {
|
||||
return 'text/html';
|
||||
}
|
||||
if (filePath.endsWith('.js')) {
|
||||
return 'application/javascript';
|
||||
}
|
||||
if (filePath.endsWith('.css')) {
|
||||
return 'text/css';
|
||||
}
|
||||
if (filePath.endsWith('.json')) {
|
||||
return 'application/json';
|
||||
}
|
||||
return 'application/octet-stream';
|
||||
}
|
||||
async function collectAllFiles(dirPath, vfs) {
|
||||
const allFiles = [];
|
||||
const entries = await vfs.readdir(dirPath);
|
||||
for (const entry of entries) {
|
||||
const fullPath = vfs.join(dirPath, entry);
|
||||
const isDir = await vfs.isDirectory(fullPath);
|
||||
if (isDir) {
|
||||
// Recursively collect files from subdirectories
|
||||
const nestedFiles = await collectAllFiles(fullPath, vfs);
|
||||
allFiles.push(...nestedFiles);
|
||||
}
|
||||
else {
|
||||
// Add file to collection
|
||||
allFiles.push(fullPath);
|
||||
}
|
||||
}
|
||||
return allFiles;
|
||||
}
|
||||
function createAppMetadata(powerConfig, environmentName, documentUri, appSubtype) {
|
||||
const appMetadata = {
|
||||
appType: powerConfig.appType || 'CodeApp',
|
||||
appSubtype: 'BYOCApp',
|
||||
properties: {
|
||||
lifeCycleId: 'Draft',
|
||||
displayName: powerConfig.appDisplayName || 'New App' + Date.now(),
|
||||
description: powerConfig.description || '',
|
||||
backgroundColor: 'RGBA(255,255,255,1)',
|
||||
backgroundImageUri: '',
|
||||
createdByClientVersion: {
|
||||
major: 1,
|
||||
},
|
||||
appUris: {
|
||||
codeAppPackageUri: {
|
||||
value: documentUri,
|
||||
readonlyValue: documentUri,
|
||||
},
|
||||
},
|
||||
environment: {
|
||||
name: environmentName,
|
||||
id: `/providers/Microsoft.PowerApps/environments/${environmentName}`,
|
||||
},
|
||||
connectionReferences: constructConnectionReferences(powerConfig.connectionReferences),
|
||||
databaseReferences: constructDatabaseReferences(powerConfig.databaseReferences),
|
||||
},
|
||||
};
|
||||
if (appSubtype) {
|
||||
appMetadata.appSubtype = appSubtype;
|
||||
}
|
||||
return appMetadata;
|
||||
}
|
||||
export function constructConnectionReferences(configConnectionReferences) {
|
||||
const result = {};
|
||||
if (!configConnectionReferences) {
|
||||
return result;
|
||||
}
|
||||
// First pass: build base RP connection references
|
||||
for (const [key, ref] of Object.entries(configConnectionReferences)) {
|
||||
result[key] = {
|
||||
id: ref.id,
|
||||
displayName: ref.displayName,
|
||||
dataSources: ref.dataSources ?? [],
|
||||
nestedActions: [],
|
||||
actions: [],
|
||||
parameterHints: {},
|
||||
parameterHintsV2: {},
|
||||
dependents: [],
|
||||
dependencies: [],
|
||||
dataSets: ref.dataSets ?? {},
|
||||
sharedConnectionId: ref.sharedConnectionId ?? null,
|
||||
authenticationType: ref.authenticationType ?? null,
|
||||
};
|
||||
}
|
||||
// Second pass: process workflowDetails to populate parameterHintsV2, dependencies, and dependents
|
||||
for (const [key, ref] of Object.entries(configConnectionReferences)) {
|
||||
if (!ref.workflowDetails)
|
||||
continue;
|
||||
const { workflowEntityId, workflowDisplayName, dependencies, workflowName } = ref.workflowDetails;
|
||||
const parameterHintsV2 = {
|
||||
workflowEntityId: { value: workflowEntityId },
|
||||
workflowDisplayName: { value: workflowDisplayName },
|
||||
workflowName: { value: workflowName },
|
||||
};
|
||||
const parameterHints = {
|
||||
workflowEntityId: { value: workflowEntityId },
|
||||
workflowDisplayName: { value: workflowDisplayName },
|
||||
workflowName: { value: workflowName },
|
||||
};
|
||||
const dependencyUuids = [];
|
||||
if (dependencies) {
|
||||
for (const [flowConnRefKey, dependencyUuid] of Object.entries(dependencies)) {
|
||||
parameterHintsV2[flowConnRefKey] = { value: dependencyUuid };
|
||||
dependencyUuids.push(dependencyUuid);
|
||||
// Add this flow connection reference as a dependent on the dependency
|
||||
const depRef = result[dependencyUuid];
|
||||
if (depRef) {
|
||||
depRef.dependents.push(key);
|
||||
}
|
||||
}
|
||||
}
|
||||
result[key].parameterHintsV2 = parameterHintsV2;
|
||||
// Set parameterHints. Disambiguation still uses this.
|
||||
result[key].parameterHints = parameterHints;
|
||||
result[key].dependencies = dependencyUuids;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
function constructDatabaseReferences(databaseReferences) {
|
||||
const constructedDatabaseReferences = {};
|
||||
if (!databaseReferences) {
|
||||
return constructedDatabaseReferences;
|
||||
}
|
||||
for (const key of Object.keys(databaseReferences)) {
|
||||
const value = databaseReferences[key];
|
||||
constructedDatabaseReferences[key] = {
|
||||
databaseDetails: {
|
||||
environmentName: key,
|
||||
overrideValues: {
|
||||
environmentVariableName: value?.environmentVariableName ?? '',
|
||||
},
|
||||
},
|
||||
dataSources: value?.dataSources,
|
||||
};
|
||||
}
|
||||
return constructedDatabaseReferences;
|
||||
}
|
||||
//# sourceMappingURL=PushApp.js.map
|
||||
Generated
Vendored
+1
File diff suppressed because one or more lines are too long
Generated
Vendored
+14
@@ -0,0 +1,14 @@
|
||||
/*!
|
||||
* Copyright (C) Microsoft Corporation. All rights reserved.
|
||||
*/
|
||||
import type { RefreshDataSourceContext, RefreshDataSourceResult } from '../Types/ActionTypes.js';
|
||||
/**
|
||||
* Refreshes one or all data sources by re-adding them, which regenerates models, services, and schemas.
|
||||
* Reads the power.config.json to determine which data sources exist.
|
||||
* If `config.actionsParams.dataSourceName` is specified, only that data source is refreshed.
|
||||
* Failures for individual data sources are collected and returned rather than aborting the entire operation.
|
||||
* @param config - Context including VFS, auth, file paths, and optional data source name filter.
|
||||
* @returns A result object with `succeeded` and `failed` arrays.
|
||||
*/
|
||||
export declare function refreshDataSourceAsync(config: RefreshDataSourceContext): Promise<RefreshDataSourceResult>;
|
||||
//# sourceMappingURL=RefreshDataSource.d.ts.map
|
||||
Generated
Vendored
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"RefreshDataSource.d.ts","sourceRoot":"","sources":["../../src/Actions/RefreshDataSource.ts"],"names":[],"mappings":"AAAA;;GAEG;AAWH,OAAO,KAAK,EAEV,wBAAwB,EACxB,uBAAuB,EACxB,MAAM,sBAAsB,CAAC;AAoB9B;;;;;;;GAOG;AACH,wBAAsB,sBAAsB,CAC1C,MAAM,EAAE,wBAAwB,GAC/B,OAAO,CAAC,uBAAuB,CAAC,CA6LlC"}
|
||||
Generated
Vendored
+296
@@ -0,0 +1,296 @@
|
||||
/*!
|
||||
* Copyright (C) Microsoft Corporation. All rights reserved.
|
||||
*/
|
||||
import { DataSources } from '../Config/Config.types.js';
|
||||
import { readRepoConfig } from '../Config/ConfigManager.js';
|
||||
import { initializePlayerActions } from '../initializePlayerActions.js';
|
||||
import { getConnectionByNameAsync, getFlowMetadataAsync, listConnectionsForConnectorAsync, } from '../services/index.js';
|
||||
import { getVfs, setVfs } from '../VfsManager.js';
|
||||
import { addDataSourceAsync } from './AddDataSource.js';
|
||||
import { addFlowAsync } from './AddFlow.js';
|
||||
import { removeFlowAsync } from './RemoveFlow.js';
|
||||
/**
|
||||
* Refreshes one or all data sources by re-adding them, which regenerates models, services, and schemas.
|
||||
* Reads the power.config.json to determine which data sources exist.
|
||||
* If `config.actionsParams.dataSourceName` is specified, only that data source is refreshed.
|
||||
* Failures for individual data sources are collected and returned rather than aborting the entire operation.
|
||||
* @param config - Context including VFS, auth, file paths, and optional data source name filter.
|
||||
* @returns A result object with `succeeded` and `failed` arrays.
|
||||
*/
|
||||
export async function refreshDataSourceAsync(config) {
|
||||
setVfs(config.vfs);
|
||||
const vfs = getVfs();
|
||||
const { powerConfigPath } = config.localFilePaths;
|
||||
if (!(await vfs.exists(powerConfigPath))) {
|
||||
throw new Error(`Configuration file not found at ${powerConfigPath}`);
|
||||
}
|
||||
// Initialize services so listConnectionsForConnectorAsync can be used during collection
|
||||
initializePlayerActions({
|
||||
vfs: config.vfs,
|
||||
authProvider: config.authProvider,
|
||||
region: config.region,
|
||||
environmentName: config.environmentName,
|
||||
logger: config.logger,
|
||||
});
|
||||
const repoConfig = await readRepoConfig(powerConfigPath);
|
||||
const targetDataSourceName = config.actionsParams.dataSourceName;
|
||||
const { logger } = config;
|
||||
const toRefresh = [];
|
||||
// Collect Dataverse data sources from databaseReferences
|
||||
if (repoConfig.databaseReferences) {
|
||||
for (const dbRef of Object.values(repoConfig.databaseReferences)) {
|
||||
for (const [dsName, dsInfo] of Object.entries(dbRef.dataSources)) {
|
||||
if (!targetDataSourceName || dsName === targetDataSourceName) {
|
||||
toRefresh.push({
|
||||
dsName,
|
||||
addParams: {
|
||||
apiId: DataSources.Dataverse,
|
||||
tableName: dsInfo.logicalName,
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// Collect connector data sources from connectionReferences
|
||||
if (repoConfig.connectionReferences) {
|
||||
for (const [, connRef] of Object.entries(repoConfig.connectionReferences)) {
|
||||
// The map key is a random UUID used internally — it must NOT be used as apiId.
|
||||
//
|
||||
// connRef.id stores the full connector path returned by the API, e.g.
|
||||
// "/providers/Microsoft.PowerApps/apis/shared_sharepointonline".
|
||||
// getConnectorAsync expects only the short name ("shared_sharepointonline"), so we
|
||||
// extract the last path segment. If connRef.id is already a short name (no slashes)
|
||||
// it is returned unchanged.
|
||||
//
|
||||
// Connection identity is recovered in priority order:
|
||||
// 1. xrmConnectionReferenceLogicalName — set when originally added via --connection-ref.
|
||||
// Pass it as connectionRef so addCdpDataSourceAsync resolves the connection ID from
|
||||
// Dataverse the same way it did originally.
|
||||
// 2. sharedConnectionId — set for shareable connectors added via --connection-id.
|
||||
// Stored as the full resource path (e.g. "/providers/.../connections/abc123");
|
||||
// extract the last segment since getConnectionByNameAsync expects just the name.
|
||||
// 3. Neither stored → look up a matching connection via the API. This handles SSO
|
||||
// connectors (SPO, Teams, Office 365) which are not shareable and have no
|
||||
// connection reference logical name.
|
||||
const apiId = connRef.id.split('/').pop() ?? connRef.id;
|
||||
const connectionRef = connRef.xrmConnectionReferenceLogicalName ?? undefined;
|
||||
const rawSharedConnId = connRef.sharedConnectionId ?? undefined;
|
||||
let connectionId = connectionRef
|
||||
? undefined
|
||||
: (rawSharedConnId?.split('/').pop() ?? undefined);
|
||||
if (!connectionRef && !connectionId) {
|
||||
connectionId = await findConnectionIdForRefAsync(connRef.id, connRef.authenticationType ?? undefined, logger);
|
||||
}
|
||||
if (connRef.dataSets && Object.keys(connRef.dataSets).length > 0) {
|
||||
// Tabular connector: each (dataset, dsName) pair is a separate data source
|
||||
for (const [dataset, dataSet] of Object.entries(connRef.dataSets)) {
|
||||
for (const [dsName, dsInfo] of Object.entries(dataSet.dataSources)) {
|
||||
if (!targetDataSourceName || dsName === targetDataSourceName) {
|
||||
if (dsInfo.tableName === undefined && apiId === 'shared_sql') {
|
||||
// SQL entries with no tableName are stored procedures; recover the procedure
|
||||
// name from the schema file written by the original add-data-source call.
|
||||
const procName = await tryReadStoredProcedureNameAsync(vfs, config.localFilePaths.schemaPath, dsName);
|
||||
toRefresh.push({
|
||||
dsName,
|
||||
addParams: {
|
||||
apiId,
|
||||
connectionId,
|
||||
connectionRef,
|
||||
dataset,
|
||||
...(procName !== undefined
|
||||
? { sqlStoredProcedure: procName }
|
||||
: { tableName: dsName }),
|
||||
},
|
||||
});
|
||||
}
|
||||
else {
|
||||
toRefresh.push({
|
||||
dsName,
|
||||
addParams: {
|
||||
apiId,
|
||||
connectionId,
|
||||
connectionRef,
|
||||
dataset,
|
||||
tableName: dsInfo.tableName ?? dsName,
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// Non-tabular data sources: entries in dataSources not covered by any dataSets entry.
|
||||
// For pure non-tabular connectors (no dataSets) this is all of dataSources. For connectors
|
||||
// that support both tabular and non-tabular, this handles the non-tabular remainder.
|
||||
const tabularDsNames = new Set(Object.values(connRef.dataSets ?? {}).flatMap((ds) => Object.keys(ds.dataSources)));
|
||||
const nonTabularSources = (connRef.dataSources ?? []).filter((ds) => !tabularDsNames.has(ds));
|
||||
if (nonTabularSources.length > 0) {
|
||||
// One addDataSource call per connection reference refreshes all its non-tabular data sources
|
||||
const isMatch = !targetDataSourceName || nonTabularSources.some((ds) => ds === targetDataSourceName);
|
||||
if (isMatch) {
|
||||
toRefresh.push({
|
||||
dsName: nonTabularSources[0],
|
||||
addParams: {
|
||||
apiId,
|
||||
connectionId,
|
||||
connectionRef,
|
||||
workflowName: connRef.workflowDetails?.workflowName,
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (targetDataSourceName && toRefresh.length === 0) {
|
||||
throw new Error(`Data source '${targetDataSourceName}' not found in power.config.json`);
|
||||
}
|
||||
const succeeded = [];
|
||||
const failed = [];
|
||||
for (const entry of toRefresh) {
|
||||
try {
|
||||
// For flows, we need to remove and re-add to refresh the swagger and trigger model regeneration. For other connectors, re-adding is sufficient.
|
||||
if (entry.addParams.workflowName) {
|
||||
await refreshFlowAsync(entry.addParams.workflowName, config);
|
||||
}
|
||||
else {
|
||||
const addConfig = {
|
||||
vfs: config.vfs,
|
||||
authProvider: config.authProvider,
|
||||
region: config.region,
|
||||
environmentName: config.environmentName,
|
||||
localFilePaths: config.localFilePaths,
|
||||
logger: config.logger,
|
||||
actionsParams: {
|
||||
apiId: entry.addParams.apiId,
|
||||
connectionId: entry.addParams.connectionId,
|
||||
connectionRef: entry.addParams.connectionRef,
|
||||
tableName: entry.addParams.tableName,
|
||||
dataset: entry.addParams.dataset,
|
||||
sqlStoredProcedure: entry.addParams.sqlStoredProcedure,
|
||||
},
|
||||
};
|
||||
await addDataSourceAsync(addConfig);
|
||||
}
|
||||
succeeded.push(entry.dsName);
|
||||
}
|
||||
catch (err) {
|
||||
failed.push({
|
||||
dataSourceName: entry.dsName,
|
||||
error: err instanceof Error ? err.message : String(err),
|
||||
});
|
||||
}
|
||||
}
|
||||
return { succeeded, failed };
|
||||
}
|
||||
/**
|
||||
* Refreshes a single flow by verifying access to all dependent connections, removing, and re-adding it.
|
||||
* Access is checked before any removal is attempted so we don't remove a flow we can't re-add.
|
||||
*/
|
||||
async function refreshFlowAsync(workflowName, config) {
|
||||
const flowMetadata = await getFlowMetadataAsync(workflowName, config.logger);
|
||||
// Check access to all invoker connections before making any destructive changes.
|
||||
const invokerConnectionReferences = Object.fromEntries(Object.entries(flowMetadata.properties.connectionReferences).filter(([, connRef]) => connRef.source.toLowerCase() === 'invoker'));
|
||||
await verifyConnectionAccessAsync(invokerConnectionReferences, config.logger);
|
||||
const flowContext = {
|
||||
vfs: config.vfs,
|
||||
authProvider: config.authProvider,
|
||||
region: config.region,
|
||||
environmentName: config.environmentName,
|
||||
localFilePaths: config.localFilePaths,
|
||||
logger: config.logger,
|
||||
actionsParams: { flowId: workflowName },
|
||||
};
|
||||
await removeFlowAsync(flowContext);
|
||||
const addResult = await addFlowAsync(flowContext);
|
||||
if (!addResult.success) {
|
||||
throw new Error(addResult.error ?? 'addFlowAsync failed');
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Verifies that all invoker connection references are accessible.
|
||||
* Mirrors the access check in AddFlow's fetchEnrichedDependenciesAsync.
|
||||
* Throws if any connection cannot be retrieved.
|
||||
*/
|
||||
async function verifyConnectionAccessAsync(connectionReferences, logger) {
|
||||
await Promise.all(Object.values(connectionReferences).map(async (connRef) => {
|
||||
const connector = connRef.apiDefinition;
|
||||
try {
|
||||
const connection = await getConnectionByNameAsync(connRef.connectionName, connector.name, logger);
|
||||
if (!connection) {
|
||||
throw new Error();
|
||||
}
|
||||
}
|
||||
catch {
|
||||
throw new Error(`Unable to retrieve connection '${connRef.id}'. You may not have access to the underlying connections.`);
|
||||
}
|
||||
}));
|
||||
}
|
||||
/**
|
||||
* Finds a connection ID for a connector reference by listing connections and selecting the best match.
|
||||
* Used as a fallback for SSO connectors (e.g., SharePoint, Teams, Office 365) that are not shareable
|
||||
* and have no stored sharedConnectionId or xrmConnectionReferenceLogicalName.
|
||||
*
|
||||
* Matching criteria (mirrors the PowerApps client's ConnectionOperations logic):
|
||||
* - Skips on-premises gateway connections (no stored gateway info to match against).
|
||||
* - If `authenticationType` is specified, only considers connections with a matching
|
||||
* `connectionParametersSet.name`.
|
||||
* @returns The connection name (used as connection ID) of the first match, or undefined if none found.
|
||||
*/
|
||||
async function findConnectionIdForRefAsync(connectorFullId, authenticationType, logger) {
|
||||
const connectorId = connectorFullId.split('/').pop() ?? connectorFullId;
|
||||
let connections;
|
||||
try {
|
||||
connections = await listConnectionsForConnectorAsync(connectorId, logger);
|
||||
}
|
||||
catch {
|
||||
return undefined;
|
||||
}
|
||||
const match = connections.find((conn) => {
|
||||
// Skip on-premises gateway connections; we have no stored gateway info to match against
|
||||
if (conn.properties.gatewayObject) {
|
||||
return false;
|
||||
}
|
||||
// Match authenticationType if the stored connection reference specifies one
|
||||
if (authenticationType) {
|
||||
return conn.properties.connectionParametersSet?.name === authenticationType;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
return match?.name;
|
||||
}
|
||||
/**
|
||||
* Recovers the SQL stored procedure name from its saved schema file.
|
||||
*
|
||||
* SQL stored procedures are stored as `{}` in `dataSets.dataSources` — the procedure name
|
||||
* is not persisted in the config. `addDataSourceAsync` always writes a schema file at:
|
||||
* `schemaPath/sql/{dsName}/<friendlyName>.Schema.json`
|
||||
* where `dsName` is the key in `dataSets.dataSources`. The schema JSON contains a top-level
|
||||
* `name` field with the original SQL procedure name (e.g. `"[dbo].[GetContacts]"`), which is
|
||||
* what `addDataSourceAsync` needs as `sqlStoredProcedure` to re-fetch the metadata.
|
||||
*
|
||||
* @returns The stored procedure name, or `undefined` if the schema file cannot be found/parsed.
|
||||
*/
|
||||
async function tryReadStoredProcedureNameAsync(vfs, schemaPath, dsName) {
|
||||
try {
|
||||
const procDir = vfs.join(schemaPath, 'sql', dsName);
|
||||
const files = await vfs.readdir(procDir);
|
||||
const schemaFileName = files.find((f) => f.endsWith('.Schema.json'));
|
||||
if (!schemaFileName) {
|
||||
return undefined;
|
||||
}
|
||||
const content = await vfs.readFile(vfs.join(procDir, schemaFileName), 'utf-8');
|
||||
const parsed = JSON.parse(content);
|
||||
if (parsed &&
|
||||
typeof parsed === 'object' &&
|
||||
'name' in parsed &&
|
||||
typeof parsed.name === 'string') {
|
||||
return parsed.name;
|
||||
}
|
||||
}
|
||||
catch {
|
||||
// Schema file missing or unreadable — fall through
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
//# sourceMappingURL=RefreshDataSource.js.map
|
||||
CodeApp_NeonReactor/node_modules/@microsoft/power-apps-actions/dist/Actions/RefreshDataSource.js.map
Generated
Vendored
+1
File diff suppressed because one or more lines are too long
Generated
Vendored
+16
@@ -0,0 +1,16 @@
|
||||
/*!
|
||||
* Copyright (C) Microsoft Corporation. All rights reserved.
|
||||
*/
|
||||
import type { RegenDataSourceTsContext, RegenDataSourceTsResult } from '../Types/ActionTypes.js';
|
||||
/**
|
||||
* Regenerates TypeScript code for data sources from existing schema files.
|
||||
* Does not re-fetch schemas from the API — uses the local .Schema.json files already on disk.
|
||||
*
|
||||
* If `config.actionsParams.dataSource` is specified, only that data source is regenerated.
|
||||
* If the name does not match any known data source, an error is thrown.
|
||||
*
|
||||
* @param config - Context including VFS, file paths, and optional data source name filter.
|
||||
* @returns A result object with `succeeded` and `failed` arrays.
|
||||
*/
|
||||
export declare function regenDataSourceTsAsync(config: RegenDataSourceTsContext): Promise<RegenDataSourceTsResult>;
|
||||
//# sourceMappingURL=RegenDataSourceTs.d.ts.map
|
||||
Generated
Vendored
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"RegenDataSourceTs.d.ts","sourceRoot":"","sources":["../../src/Actions/RegenDataSourceTs.ts"],"names":[],"mappings":"AAAA;;GAEG;AAUH,OAAO,KAAK,EAAE,wBAAwB,EAAE,uBAAuB,EAAE,MAAM,sBAAsB,CAAC;AAsD9F;;;;;;;;;GASG;AACH,wBAAsB,sBAAsB,CAC1C,MAAM,EAAE,wBAAwB,GAC/B,OAAO,CAAC,uBAAuB,CAAC,CAoElC"}
|
||||
Generated
Vendored
+118
@@ -0,0 +1,118 @@
|
||||
/*!
|
||||
* Copyright (C) Microsoft Corporation. All rights reserved.
|
||||
*/
|
||||
import { processDataSourceInfo } from '../CodeGen/dataSourceInfoProcessor.js';
|
||||
import { clearAndRegenerateModelService, generateModelService, } from '../CodeGen/modelServiceGenerator.js';
|
||||
import { getAllJsonFiles } from '../CodeGen/shared/schemaUtils.js';
|
||||
import { readRepoConfig } from '../Config/ConfigManager.js';
|
||||
import { getVfs, setVfs } from '../VfsManager.js';
|
||||
/**
|
||||
* Collects all data source names from power.config.json.
|
||||
*/
|
||||
function collectAllDataSourceNames(repoConfig) {
|
||||
const names = [];
|
||||
if (repoConfig.databaseReferences) {
|
||||
for (const dbRef of Object.values(repoConfig.databaseReferences)) {
|
||||
for (const dsName of Object.keys(dbRef.dataSources)) {
|
||||
names.push(dsName);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (repoConfig.connectionReferences) {
|
||||
for (const connRef of Object.values(repoConfig.connectionReferences)) {
|
||||
if (connRef.dataSets && Object.keys(connRef.dataSets).length > 0) {
|
||||
for (const dataSet of Object.values(connRef.dataSets)) {
|
||||
for (const dsName of Object.keys(dataSet.dataSources)) {
|
||||
names.push(dsName);
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (connRef.dataSources) {
|
||||
for (const ds of connRef.dataSources) {
|
||||
names.push(ds);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return names;
|
||||
}
|
||||
/**
|
||||
* Finds schema files matching a data source name.
|
||||
* Schema files are named {dsName}.Schema.json (case-insensitive match on dsName).
|
||||
*/
|
||||
async function findSchemaFilesForDataSource(schemaFolderPath, dataSourceName) {
|
||||
const vfs = getVfs();
|
||||
const allJsonFiles = await getAllJsonFiles(schemaFolderPath);
|
||||
const lowerName = dataSourceName.toLowerCase();
|
||||
return allJsonFiles.filter((filePath) => {
|
||||
const fileName = vfs.basename(filePath).toLowerCase();
|
||||
return fileName === `${lowerName}.schema.json`;
|
||||
});
|
||||
}
|
||||
/**
|
||||
* Regenerates TypeScript code for data sources from existing schema files.
|
||||
* Does not re-fetch schemas from the API — uses the local .Schema.json files already on disk.
|
||||
*
|
||||
* If `config.actionsParams.dataSource` is specified, only that data source is regenerated.
|
||||
* If the name does not match any known data source, an error is thrown.
|
||||
*
|
||||
* @param config - Context including VFS, file paths, and optional data source name filter.
|
||||
* @returns A result object with `succeeded` and `failed` arrays.
|
||||
*/
|
||||
export async function regenDataSourceTsAsync(config) {
|
||||
setVfs(config.vfs);
|
||||
const vfs = getVfs();
|
||||
const { powerConfigPath, schemaPath, codeGenPath } = config.localFilePaths;
|
||||
const { logger } = config;
|
||||
if (!(await vfs.exists(powerConfigPath))) {
|
||||
throw new Error(`Configuration file not found at ${powerConfigPath}`);
|
||||
}
|
||||
const repoConfig = await readRepoConfig(powerConfigPath);
|
||||
const allDataSourceNames = collectAllDataSourceNames(repoConfig);
|
||||
const targetDataSource = config.actionsParams.dataSource;
|
||||
if (targetDataSource) {
|
||||
const exists = allDataSourceNames.some((name) => name.toLowerCase() === targetDataSource.toLowerCase());
|
||||
if (!exists) {
|
||||
throw new Error(`'${targetDataSource}' is not a valid data source name. ` +
|
||||
`Available data sources: ${allDataSourceNames.join(', ')}`);
|
||||
}
|
||||
}
|
||||
const succeeded = [];
|
||||
const failed = [];
|
||||
try {
|
||||
await processDataSourceInfo(schemaPath, logger);
|
||||
if (targetDataSource) {
|
||||
const schemaFiles = await findSchemaFilesForDataSource(schemaPath, targetDataSource);
|
||||
if (schemaFiles.length === 0) {
|
||||
throw new Error(`No schema file found for data source '${targetDataSource}'. ` +
|
||||
`Expected a file named '${targetDataSource}.Schema.json' under '${schemaPath}'.`);
|
||||
}
|
||||
for (const schemaFile of schemaFiles) {
|
||||
await generateModelService({
|
||||
schemaFolderPath: schemaPath,
|
||||
schemaFilePath: schemaFile,
|
||||
codeGenPath,
|
||||
logger,
|
||||
});
|
||||
}
|
||||
succeeded.push(targetDataSource);
|
||||
}
|
||||
else {
|
||||
await clearAndRegenerateModelService({
|
||||
schemaFolderPath: schemaPath,
|
||||
codeGenPath,
|
||||
logger,
|
||||
});
|
||||
succeeded.push(...allDataSourceNames);
|
||||
}
|
||||
}
|
||||
catch (err) {
|
||||
const dsName = targetDataSource ?? 'all';
|
||||
failed.push({
|
||||
dataSourceName: dsName,
|
||||
error: err instanceof Error ? err.message : String(err),
|
||||
});
|
||||
}
|
||||
return { succeeded, failed };
|
||||
}
|
||||
//# sourceMappingURL=RegenDataSourceTs.js.map
|
||||
CodeApp_NeonReactor/node_modules/@microsoft/power-apps-actions/dist/Actions/RegenDataSourceTs.js.map
Generated
Vendored
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"RegenDataSourceTs.js","sourceRoot":"","sources":["../../src/Actions/RegenDataSourceTs.ts"],"names":[],"mappings":"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"}
|
||||
Generated
Vendored
+21
@@ -0,0 +1,21 @@
|
||||
/*!
|
||||
* Copyright (C) Microsoft Corporation. All rights reserved.
|
||||
*/
|
||||
import type { RemoveFlowContext } from '../Types/ActionTypes.js';
|
||||
export type RemoveFlowResult = {
|
||||
success: boolean;
|
||||
found: boolean;
|
||||
error?: string;
|
||||
};
|
||||
/**
|
||||
* Removes a flow from the current Power Apps code app by undoing what addFlowAsync performed:
|
||||
* - Removes the flow's connection reference from power.config
|
||||
* - Removes stub dependency connection references no longer referenced by any other flow
|
||||
* - Deletes the flow's schema file and cleans up the empty directory
|
||||
* - Regenerates models and services
|
||||
*
|
||||
* @param {RemoveFlowContext} context
|
||||
* @returns {Promise<RemoveFlowResult>}
|
||||
*/
|
||||
export declare function removeFlowAsync(context: RemoveFlowContext): Promise<RemoveFlowResult>;
|
||||
//# sourceMappingURL=RemoveFlow.d.ts.map
|
||||
Generated
Vendored
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"RemoveFlow.d.ts","sourceRoot":"","sources":["../../src/Actions/RemoveFlow.ts"],"names":[],"mappings":"AAAA;;GAEG;AAUH,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,sBAAsB,CAAC;AAK9D,MAAM,MAAM,gBAAgB,GAAG;IAC7B,OAAO,EAAE,OAAO,CAAC;IACjB,KAAK,EAAE,OAAO,CAAC;IACf,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB,CAAC;AAEF;;;;;;;;;GASG;AACH,wBAAsB,eAAe,CAAC,OAAO,EAAE,iBAAiB,GAAG,OAAO,CAAC,gBAAgB,CAAC,CAoG3F"}
|
||||
Generated
Vendored
+142
@@ -0,0 +1,142 @@
|
||||
/*!
|
||||
* Copyright (C) Microsoft Corporation. All rights reserved.
|
||||
*/
|
||||
import { equalsIgnoreCase } from '@microsoft/power-apps-common/utils';
|
||||
import { processDataSourceInfo } from '../CodeGen/dataSourceInfoProcessor.js';
|
||||
import { clearAndRegenerateModelService } from '../CodeGen/modelServiceGenerator.js';
|
||||
import { sanitizeName } from '../CodeGen/shared/nameUtils.js';
|
||||
import { readRepoConfig, writeRepoConfig } from '../Config/ConfigManager.js';
|
||||
import { initializePlayerActions } from '../initializePlayerActions.js';
|
||||
import { getVfs } from '../VfsManager.js';
|
||||
import { normalizeParam } from './DataUtils.js';
|
||||
import { FLOW_API_NAME, LOGIC_FLOWS_API_ID } from './flowConstants.js';
|
||||
/**
|
||||
* Removes a flow from the current Power Apps code app by undoing what addFlowAsync performed:
|
||||
* - Removes the flow's connection reference from power.config
|
||||
* - Removes stub dependency connection references no longer referenced by any other flow
|
||||
* - Deletes the flow's schema file and cleans up the empty directory
|
||||
* - Regenerates models and services
|
||||
*
|
||||
* @param {RemoveFlowContext} context
|
||||
* @returns {Promise<RemoveFlowResult>}
|
||||
*/
|
||||
export async function removeFlowAsync(context) {
|
||||
const flowDataSourceName = await normalizeParam(context.actionsParams.flowDataSourceName);
|
||||
const flowId = await normalizeParam(context.actionsParams.flowId);
|
||||
if (!flowDataSourceName && !flowId) {
|
||||
return {
|
||||
success: false,
|
||||
found: false,
|
||||
error: 'Either --flow-name or --flow-id must be provided.',
|
||||
};
|
||||
}
|
||||
initializePlayerActions({
|
||||
vfs: context.vfs,
|
||||
authProvider: context.authProvider,
|
||||
region: context.region,
|
||||
environmentName: context.environmentName,
|
||||
logger: context.logger,
|
||||
});
|
||||
try {
|
||||
const { powerConfigPath, schemaPath, codeGenPath } = context.localFilePaths;
|
||||
const vfs = getVfs();
|
||||
if (!(await vfs.exists(powerConfigPath))) {
|
||||
throw new Error(`Configuration file not found at ${powerConfigPath}`);
|
||||
}
|
||||
const config = await readRepoConfig(powerConfigPath);
|
||||
if (!config.connectionReferences) {
|
||||
return { success: true, found: false };
|
||||
}
|
||||
const flowEntryId = flowId
|
||||
? findFlowById(config.connectionReferences, flowId)
|
||||
: findFlowByDataSourceName(config.connectionReferences, flowDataSourceName);
|
||||
if (!flowEntryId) {
|
||||
return { success: true, found: false };
|
||||
}
|
||||
const flowEntry = config.connectionReferences[flowEntryId];
|
||||
// Validate required fields before making any mutations.
|
||||
const flowDisplayName = flowEntry.workflowDetails?.workflowDisplayName;
|
||||
if (!flowDisplayName) {
|
||||
throw new Error(`Flow entry ${flowEntryId} is missing workflowDetails.workflowDisplayName`);
|
||||
}
|
||||
const dependencyUuids = Object.values(flowEntry.workflowDetails?.dependencies ?? {});
|
||||
// Remove the flow's own connection reference.
|
||||
delete config.connectionReferences[flowEntryId];
|
||||
// Remove stub dependency connection references that are no longer referenced by any flow.
|
||||
for (const depUuid of dependencyUuids) {
|
||||
const dep = config.connectionReferences[depUuid];
|
||||
if (!dep)
|
||||
continue;
|
||||
const isStub = !dep.dataSources || dep.dataSources.length === 0;
|
||||
const isReferencedByOtherFlow = isFlowDependency(config.connectionReferences, depUuid);
|
||||
if (isStub && !isReferencedByOtherFlow) {
|
||||
delete config.connectionReferences[depUuid];
|
||||
}
|
||||
}
|
||||
await writeRepoConfig(powerConfigPath, config);
|
||||
const directoryPath = vfs.join(schemaPath, FLOW_API_NAME);
|
||||
const schemaFilePath = vfs.join(directoryPath, `${sanitizeName(flowDisplayName)}.Schema.json`);
|
||||
if (await vfs.exists(schemaFilePath)) {
|
||||
await vfs.unlink(schemaFilePath);
|
||||
// Remove the directory if it is now empty.
|
||||
if (await vfs.exists(directoryPath)) {
|
||||
const files = await vfs.readdir(directoryPath);
|
||||
if (files.length === 0) {
|
||||
try {
|
||||
await vfs.rmdir(directoryPath);
|
||||
}
|
||||
catch {
|
||||
// Not critical if cleanup fails.
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
await processDataSourceInfo(schemaPath, context.logger);
|
||||
await clearAndRegenerateModelService({
|
||||
schemaFolderPath: schemaPath,
|
||||
codeGenPath,
|
||||
logger: context.logger,
|
||||
});
|
||||
return { success: true, found: true };
|
||||
}
|
||||
catch (error) {
|
||||
const errorMessage = error instanceof Error ? error.message : String(error);
|
||||
return { success: false, found: false, error: errorMessage };
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Finds the UUID of a flow's connection reference whose dataSources array contains
|
||||
* the given flow data source name (case-insensitive).
|
||||
*/
|
||||
function findFlowByDataSourceName(connectionReferences, flowDataSourceName) {
|
||||
const normalizedName = flowDataSourceName.toLowerCase();
|
||||
for (const [uuid, ref] of Object.entries(connectionReferences)) {
|
||||
if (equalsIgnoreCase(ref.id, LOGIC_FLOWS_API_ID) &&
|
||||
ref.dataSources?.some((ds) => ds.toLowerCase() === normalizedName)) {
|
||||
return uuid;
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
/**
|
||||
* Finds the UUID of a flow's connection reference whose workflowDetails.workflowName matches
|
||||
* the given flow resource ID (case-insensitive).
|
||||
*/
|
||||
function findFlowById(connectionReferences, flowId) {
|
||||
const normalizedId = flowId.toLowerCase();
|
||||
for (const [uuid, ref] of Object.entries(connectionReferences)) {
|
||||
if (equalsIgnoreCase(ref.id, LOGIC_FLOWS_API_ID) &&
|
||||
ref.workflowDetails?.workflowName?.toLowerCase() === normalizedId) {
|
||||
return uuid;
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
/**
|
||||
* Returns true if the given UUID appears as a dependency value in any remaining flow entry.
|
||||
*/
|
||||
function isFlowDependency(connectionReferences, uuid) {
|
||||
return Object.values(connectionReferences).some((ref) => ref.workflowDetails?.dependencies !== undefined &&
|
||||
Object.values(ref.workflowDetails.dependencies).includes(uuid));
|
||||
}
|
||||
//# sourceMappingURL=RemoveFlow.js.map
|
||||
Generated
Vendored
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"RemoveFlow.js","sourceRoot":"","sources":["../../src/Actions/RemoveFlow.ts"],"names":[],"mappings":"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"}
|
||||
Generated
Vendored
+14
@@ -0,0 +1,14 @@
|
||||
/*!
|
||||
* Copyright (C) Microsoft Corporation. All rights reserved.
|
||||
*/
|
||||
/** The API name used for Power Automate flows in schema paths and directory structure. */
|
||||
export declare const FLOW_API_NAME = "logicflows";
|
||||
/** The full API ID for Power Automate flows used in connection references. */
|
||||
export declare const LOGIC_FLOWS_API_ID = "/providers/Microsoft.PowerApps/apis/shared_logicflows";
|
||||
/**
|
||||
* The set of flow trigger kinds supported by Code Apps.
|
||||
* Only flows with a PowerApp or PowerAppV2 trigger can be invoked from a Code App.
|
||||
* Mirrors canvas apps' isPowerAppsInstantFlow check in FlowHelpers.ts.
|
||||
*/
|
||||
export declare const SUPPORTED_TRIGGER_KINDS: Set<string>;
|
||||
//# sourceMappingURL=flowConstants.d.ts.map
|
||||
Generated
Vendored
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"flowConstants.d.ts","sourceRoot":"","sources":["../../src/Actions/flowConstants.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,0FAA0F;AAC1F,eAAO,MAAM,aAAa,eAAe,CAAC;AAE1C,8EAA8E;AAC9E,eAAO,MAAM,kBAAkB,0DAA0D,CAAC;AAE1F;;;;GAIG;AACH,eAAO,MAAM,uBAAuB,aAAsC,CAAC"}
|
||||
Generated
Vendored
+14
@@ -0,0 +1,14 @@
|
||||
/*!
|
||||
* Copyright (C) Microsoft Corporation. All rights reserved.
|
||||
*/
|
||||
/** The API name used for Power Automate flows in schema paths and directory structure. */
|
||||
export const FLOW_API_NAME = 'logicflows';
|
||||
/** The full API ID for Power Automate flows used in connection references. */
|
||||
export const LOGIC_FLOWS_API_ID = '/providers/Microsoft.PowerApps/apis/shared_logicflows';
|
||||
/**
|
||||
* The set of flow trigger kinds supported by Code Apps.
|
||||
* Only flows with a PowerApp or PowerAppV2 trigger can be invoked from a Code App.
|
||||
* Mirrors canvas apps' isPowerAppsInstantFlow check in FlowHelpers.ts.
|
||||
*/
|
||||
export const SUPPORTED_TRIGGER_KINDS = new Set(['PowerApp', 'PowerAppV2']);
|
||||
//# sourceMappingURL=flowConstants.js.map
|
||||
Generated
Vendored
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"flowConstants.js","sourceRoot":"","sources":["../../src/Actions/flowConstants.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,0FAA0F;AAC1F,MAAM,CAAC,MAAM,aAAa,GAAG,YAAY,CAAC;AAE1C,8EAA8E;AAC9E,MAAM,CAAC,MAAM,kBAAkB,GAAG,uDAAuD,CAAC;AAE1F;;;;GAIG;AACH,MAAM,CAAC,MAAM,uBAAuB,GAAG,IAAI,GAAG,CAAC,CAAC,UAAU,EAAE,YAAY,CAAC,CAAC,CAAC"}
|
||||
Generated
Vendored
+29
@@ -0,0 +1,29 @@
|
||||
/*!
|
||||
* Copyright (C) Microsoft Corporation. All rights reserved.
|
||||
*/
|
||||
export interface ApiResponseInfo {
|
||||
type?: string;
|
||||
format?: string;
|
||||
}
|
||||
export interface ApiParameter {
|
||||
name: string;
|
||||
in: string;
|
||||
required: boolean;
|
||||
type: string;
|
||||
format?: string;
|
||||
}
|
||||
export interface ApiDefinition {
|
||||
path: string;
|
||||
method: string;
|
||||
parameters?: ApiParameter[];
|
||||
responseInfo?: Record<string, ApiResponseInfo>;
|
||||
}
|
||||
export interface DataSourceInfo {
|
||||
tableId: string;
|
||||
version?: string;
|
||||
primaryKey?: string;
|
||||
dataSourceType?: string;
|
||||
apis: Record<string, ApiDefinition>;
|
||||
}
|
||||
export type DataSourcesInfo = Record<string, DataSourceInfo>;
|
||||
//# sourceMappingURL=dataSourceInfo.d.ts.map
|
||||
Generated
Vendored
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"dataSourceInfo.d.ts","sourceRoot":"","sources":["../../src/CodeGen/dataSourceInfo.ts"],"names":[],"mappings":"AAAA;;GAEG;AAMH,MAAM,WAAW,eAAe;IAC9B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAMD,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,MAAM,CAAC;IACb,EAAE,EAAE,MAAM,CAAC;IACX,QAAQ,EAAE,OAAO,CAAC;IAClB,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAMD,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,CAAC,EAAE,YAAY,EAAE,CAAC;IAC5B,YAAY,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,eAAe,CAAC,CAAC;CAChD;AAMD,MAAM,WAAW,cAAc;IAC7B,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,aAAa,CAAC,CAAC;CACrC;AAKD,MAAM,MAAM,eAAe,GAAG,MAAM,CAAC,MAAM,EAAE,cAAc,CAAC,CAAC"}
|
||||
Generated
Vendored
+5
@@ -0,0 +1,5 @@
|
||||
/*!
|
||||
* Copyright (C) Microsoft Corporation. All rights reserved.
|
||||
*/
|
||||
export {};
|
||||
//# sourceMappingURL=dataSourceInfo.js.map
|
||||
Generated
Vendored
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"dataSourceInfo.js","sourceRoot":"","sources":["../../src/CodeGen/dataSourceInfo.ts"],"names":[],"mappings":"AAAA;;GAEG"}
|
||||
Generated
Vendored
+10
@@ -0,0 +1,10 @@
|
||||
/*!
|
||||
* Copyright (C) Microsoft Corporation. All rights reserved.
|
||||
*/
|
||||
import type { ILogger } from '../services/index.js';
|
||||
/**
|
||||
* Processes all JSON files in the specified folder and generates a TypeScript file with data source information.
|
||||
* @param schemaFolderPath The path to the folder containing JSON files.
|
||||
*/
|
||||
export declare function processDataSourceInfo(schemaFolderPath: string, logger: ILogger): Promise<void>;
|
||||
//# sourceMappingURL=dataSourceInfoProcessor.d.ts.map
|
||||
Generated
Vendored
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"dataSourceInfoProcessor.d.ts","sourceRoot":"","sources":["../../src/CodeGen/dataSourceInfoProcessor.ts"],"names":[],"mappings":"AAAA;;GAEG;AAGH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,aAAa,CAAC;AAiB3C;;;GAGG;AACH,wBAAsB,qBAAqB,CACzC,gBAAgB,EAAE,MAAM,EACxB,MAAM,EAAE,OAAO,GACd,OAAO,CAAC,IAAI,CAAC,CAmDf"}
|
||||
Generated
Vendored
+555
@@ -0,0 +1,555 @@
|
||||
/*!
|
||||
* Copyright (C) Microsoft Corporation. All rights reserved.
|
||||
*/
|
||||
import { getVfs } from '../VfsManager.js';
|
||||
import { isDataverseEntity } from './shared/dataverseUtils.js';
|
||||
import { extractModelName } from './shared/modelUtils.js';
|
||||
import { getAllJsonFiles, isJsonObject } from './shared/schemaUtils.js';
|
||||
import { getSqlStoredProcApiKey } from './shared/sqlUtils.js';
|
||||
/** Regex to match file extension (e.g., ".json", ".ts") */
|
||||
const FILE_EXTENSION_PATTERN = /\.[^/.]+$/;
|
||||
/**
|
||||
* Processes all JSON files in the specified folder and generates a TypeScript file with data source information.
|
||||
* @param schemaFolderPath The path to the folder containing JSON files.
|
||||
*/
|
||||
export async function processDataSourceInfo(schemaFolderPath, logger) {
|
||||
const scenario = logger.trackScenario('ProcessDataSourceInfo', { schemaFolderPath });
|
||||
const vfs = getVfs();
|
||||
try {
|
||||
const jsonFiles = await getAllJsonFiles(schemaFolderPath);
|
||||
const resultJson = {};
|
||||
for (const filePath of jsonFiles) {
|
||||
try {
|
||||
const jsonContent = await vfs.readFile(filePath, 'utf-8');
|
||||
const jsonDocument = JSON.parse(jsonContent);
|
||||
const dataSourceInfo = processJson(jsonDocument);
|
||||
// note: this should use the same function as used in modelServiceGenerator to avoid mismatches
|
||||
const propertyKey = determinePropertyKey(jsonDocument, filePath, vfs, logger);
|
||||
mergeDataSourceInfo(resultJson, propertyKey, dataSourceInfo);
|
||||
}
|
||||
catch (err) {
|
||||
const error = err;
|
||||
throw new Error(`Error processing file '${filePath}': ${error.message}`);
|
||||
}
|
||||
}
|
||||
const outputJson = JSON.stringify(resultJson, null, 2); // pretty print
|
||||
const finalOutput = `/*!
|
||||
* Copyright (C) Microsoft Corporation. All rights reserved.
|
||||
* This file is auto-generated. Do not modify it manually.
|
||||
* Changes to this file may be overwritten.
|
||||
*/
|
||||
|
||||
export const dataSourcesInfo = ${outputJson};
|
||||
`;
|
||||
const outputDir = vfs.join(schemaFolderPath, 'appschemas');
|
||||
const outputFilePath = vfs.join(outputDir, 'dataSourcesInfo.ts');
|
||||
// Create the appschemas directory if it doesn't exist
|
||||
if (!(await vfs.exists(outputDir))) {
|
||||
await vfs.mkdir(outputDir, { recursive: true });
|
||||
}
|
||||
await vfs.writeFile(outputFilePath, finalOutput);
|
||||
scenario.complete();
|
||||
}
|
||||
catch (error) {
|
||||
scenario.failure({ error });
|
||||
throw new Error(`An unexpected error occurred: ${error.message}`);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Merges new data source information into the result JSON object.
|
||||
* If the property key already exists, it merges the APIs, avoiding duplicates.
|
||||
* @param resultJson The result JSON object to merge into.
|
||||
* @param propertyKey The key under which to store the data source information.
|
||||
* @param newDataSourceInfo The new data source information to merge.
|
||||
*/
|
||||
function mergeDataSourceInfo(resultJson, propertyKey, newDataSourceInfo) {
|
||||
if (Object.prototype.hasOwnProperty.call(resultJson, propertyKey)) {
|
||||
const existingDataSource = resultJson[propertyKey];
|
||||
if (existingDataSource && existingDataSource.apis) {
|
||||
for (const apiKey in newDataSourceInfo.apis) {
|
||||
if (!Object.prototype.hasOwnProperty.call(existingDataSource.apis, apiKey)) {
|
||||
existingDataSource.apis[apiKey] = newDataSourceInfo.apis[apiKey];
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
throw new Error(`The existing data source for key '${propertyKey}' is not of type DataSourceInfo.`);
|
||||
}
|
||||
}
|
||||
else {
|
||||
resultJson[propertyKey] = newDataSourceInfo;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Determines the property key for a data source, falling back to filename if extraction fails.
|
||||
* @param jsonDocument The parsed JSON document.
|
||||
* @param filePath The path to the JSON file.
|
||||
* @param vfs The virtual file system instance.
|
||||
* @param logger The logger instance for telemetry.
|
||||
* @returns The property key to use for the data source.
|
||||
*/
|
||||
function determinePropertyKey(jsonDocument, filePath, vfs, logger) {
|
||||
try {
|
||||
return extractModelName(jsonDocument, filePath, vfs).dataSourceName.toLowerCase();
|
||||
}
|
||||
catch (error) {
|
||||
logger.trackActivityEvent('DataSourceInfoProcessor_FallbackName', {
|
||||
filePath,
|
||||
error: error instanceof Error ? error.message : String(error),
|
||||
});
|
||||
// fall back to using the file name without extension
|
||||
const fileName = vfs.basename(filePath);
|
||||
return fileName.replace(FILE_EXTENSION_PATTERN, '').toLowerCase();
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Processes the JSON document and extracts relevant data source information.
|
||||
* @param json The JSON document to process.
|
||||
* @param existingDataSourceInfo Optional existing data source information to merge with.
|
||||
* @returns The processed data source information.
|
||||
*/
|
||||
function processJson(json, existingDataSourceInfo) {
|
||||
const root = json;
|
||||
if (typeof root.type === 'string' && root.type === 'Microsoft.PowerApps/apis') {
|
||||
const dataSourceInfo = {
|
||||
tableId: '',
|
||||
version: '',
|
||||
primaryKey: '',
|
||||
dataSourceType: 'Connector',
|
||||
apis: {},
|
||||
};
|
||||
const properties = isJsonObject(root.properties) ? root.properties : null;
|
||||
const swagger = properties && isJsonObject(properties.swagger) ? properties.swagger : null;
|
||||
const paths = swagger && isJsonObject(swagger.paths) ? swagger.paths : null;
|
||||
if (paths) {
|
||||
for (const [pathKey, pathValue] of Object.entries(paths)) {
|
||||
if (isJsonObject(pathValue)) {
|
||||
for (const [methodKey, methodValue] of Object.entries(pathValue)) {
|
||||
if (isJsonObject(methodValue)) {
|
||||
const operation = methodValue;
|
||||
const operationId = operation.operationId;
|
||||
if (typeof operationId === 'string') {
|
||||
const apiDefinition = {
|
||||
path: pathKey,
|
||||
method: methodKey.toUpperCase(),
|
||||
};
|
||||
apiDefinition.parameters =
|
||||
operation.parameters && Array.isArray(operation.parameters)
|
||||
? processParameters(operation.parameters, swagger)
|
||||
: [];
|
||||
if (operation.responses) {
|
||||
apiDefinition.responseInfo = processResponse(operation.responses, swagger);
|
||||
}
|
||||
else {
|
||||
apiDefinition.responseInfo = {
|
||||
default: { type: 'void', format: undefined },
|
||||
};
|
||||
}
|
||||
dataSourceInfo.apis[operationId] = apiDefinition;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return dataSourceInfo;
|
||||
}
|
||||
else if (isJsonObject(root.schema)) {
|
||||
const schema = root.schema;
|
||||
if (isDataverseEntity(root)) {
|
||||
const items = schema.items;
|
||||
// This is a Dataverse entity schema
|
||||
const primaryKey = typeof items['x-ms-dataverse-primary-id'] === 'string'
|
||||
? items['x-ms-dataverse-primary-id']
|
||||
: '';
|
||||
return {
|
||||
tableId: '',
|
||||
version: '',
|
||||
primaryKey,
|
||||
dataSourceType: 'Dataverse',
|
||||
apis: {},
|
||||
};
|
||||
}
|
||||
if (typeof schema.procedureresultschema !== 'undefined') {
|
||||
const dataSourceInfo = existingDataSourceInfo ?? {
|
||||
tableId: '',
|
||||
version: 'v2',
|
||||
primaryKey: '',
|
||||
dataSourceType: 'Connector',
|
||||
apis: {},
|
||||
};
|
||||
const procedureName = typeof root.name === 'string' ? root.name : '';
|
||||
const cleanedProcedureName = '/' + procedureName.replace(/\[|\]/g, '');
|
||||
const cleanedProcedureNameWithoutSchema = getSqlStoredProcApiKey(procedureName);
|
||||
let parameters = [];
|
||||
if (isJsonObject(schema.inputparameters) && isJsonObject(schema.inputparameters.properties)) {
|
||||
// Stored procedure parameters are wrapped under 'inputParameters' by the codegen.
|
||||
// Use a single body param with that name so _buildOperationBodyParam can find the value.
|
||||
parameters = [{ name: 'inputParameters', in: 'body', required: false, type: 'object' }];
|
||||
}
|
||||
const apiDefinition = {
|
||||
path: cleanedProcedureName,
|
||||
method: 'POST',
|
||||
parameters,
|
||||
};
|
||||
if (!dataSourceInfo.apis[cleanedProcedureNameWithoutSchema]) {
|
||||
dataSourceInfo.apis[cleanedProcedureNameWithoutSchema] = apiDefinition;
|
||||
}
|
||||
else {
|
||||
throw new Error(`Duplicate stored procedure detected: ${cleanedProcedureName}`);
|
||||
}
|
||||
return dataSourceInfo;
|
||||
}
|
||||
else if (typeof root.name === 'string' && root.name.match(/^\[[^\]]+\]\./)) {
|
||||
// Use the explicit version annotation injected at add-time when available.
|
||||
// Fall back to 'v2' for backward compatibility with schema files created before this annotation was added.
|
||||
const version = typeof root['x-ms-connection-version'] === 'string'
|
||||
? root['x-ms-connection-version']
|
||||
: 'v2';
|
||||
return {
|
||||
tableId: root.name,
|
||||
version,
|
||||
primaryKey: getPrimaryKeyFromSchema(schema),
|
||||
dataSourceType: 'Connector',
|
||||
apis: {},
|
||||
};
|
||||
}
|
||||
else {
|
||||
// Sharepoint List
|
||||
const tableId = typeof root.name === 'string' && root.name !== null ? root.name : '';
|
||||
const dataSourceInfo = {
|
||||
tableId,
|
||||
version: '',
|
||||
primaryKey: getPrimaryKeyFromSchema(schema),
|
||||
dataSourceType: 'Connector',
|
||||
apis: {},
|
||||
};
|
||||
const referencedEntities = isJsonObject(root.referencedEntities)
|
||||
? root.referencedEntities
|
||||
: null;
|
||||
if (referencedEntities) {
|
||||
for (const referencedEntityKey in referencedEntities) {
|
||||
if (!Object.prototype.hasOwnProperty.call(referencedEntities, referencedEntityKey)) {
|
||||
continue;
|
||||
}
|
||||
const referencedEntityValue = referencedEntities[referencedEntityKey];
|
||||
if (isJsonObject(referencedEntityValue) &&
|
||||
referencedEntityValue.lookupEndpoint !== null) {
|
||||
const lookupEndpointProps = referencedEntityValue.lookupEndpoint;
|
||||
if (isJsonObject(lookupEndpointProps) && lookupEndpointProps.path !== null) {
|
||||
const lookupEndpointPath = lookupEndpointProps.path;
|
||||
if (typeof lookupEndpointPath === 'string') {
|
||||
const segments = lookupEndpointPath.split('/');
|
||||
// Process the segments as needed
|
||||
segments[segments.length - 1] = referencedEntityKey; // Replace last segment
|
||||
// in the updated segment remove hyphens in the GUIDs
|
||||
// This might be an SPO connector issue, but we need to work around it here.
|
||||
// ex: "/tables/05bfc50c-b51b-4c17-aac4-45bd5dbb9532/entities/Author",
|
||||
// becomes "/tables/05bfc50cb51b4c17aac445bd5dbb9532/entities/Author"
|
||||
const guidRegex = /[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}/g;
|
||||
const updatedSegment = segments
|
||||
.join('/')
|
||||
.replace(guidRegex, (match) => match.replace(/-/g, ''));
|
||||
const finalPath = `/{connectionId}/datasets/{dataset}${updatedSegment}`;
|
||||
const apiParameterList = new Array();
|
||||
const defaultApiParameters = ['connectionId', 'dataset', 'table'];
|
||||
const defaultQueryParameters = ['search'];
|
||||
for (const param of defaultApiParameters) {
|
||||
const apiParameter = {
|
||||
name: param,
|
||||
in: 'path',
|
||||
required: true,
|
||||
type: 'string',
|
||||
};
|
||||
apiParameterList.push(apiParameter);
|
||||
}
|
||||
for (const param of defaultQueryParameters) {
|
||||
const apiParameter = {
|
||||
name: param,
|
||||
in: 'query',
|
||||
required: false,
|
||||
type: 'string',
|
||||
};
|
||||
apiParameterList.push(apiParameter);
|
||||
}
|
||||
const apiResponseInfo = {
|
||||
'200': { type: 'array' },
|
||||
};
|
||||
const apiDefinition = {
|
||||
path: finalPath,
|
||||
method: 'GET',
|
||||
parameters: apiParameterList,
|
||||
responseInfo: apiResponseInfo,
|
||||
};
|
||||
const referencedEntityKeyNormalized = normalizeHyphenatedName(referencedEntityKey);
|
||||
const operationId = `Get${referencedEntityKeyNormalized}`;
|
||||
if (dataSourceInfo.apis[operationId]) {
|
||||
throw new Error(`Duplicate operationId detected: ${operationId}`);
|
||||
}
|
||||
dataSourceInfo.apis[operationId] = apiDefinition;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return dataSourceInfo;
|
||||
}
|
||||
}
|
||||
if (typeof root.type === 'string' && root.type === 'Microsoft.PowerApps/dataverseOperation') {
|
||||
return processDataverseOperation(root);
|
||||
}
|
||||
throw new Error('The JSON does not represent a valid data source.');
|
||||
}
|
||||
/**
|
||||
* Processes the responses node and extracts response information.
|
||||
* @param responsesNode The responses node from the Swagger document.
|
||||
* @param root The root of the Swagger document for reference.
|
||||
* @returns A record of response information keyed by status code.
|
||||
*/
|
||||
function processResponse(responses, root) {
|
||||
const result = {};
|
||||
// Type guard: ensure responsesNode is an object
|
||||
if (!isJsonObject(responses)) {
|
||||
return result;
|
||||
}
|
||||
for (const statusCode in responses) {
|
||||
if (!Object.prototype.hasOwnProperty.call(responses, statusCode)) {
|
||||
continue;
|
||||
}
|
||||
const respObj = responses[statusCode];
|
||||
// Type guard: ensure resp is an object
|
||||
if (isJsonObject(respObj)) {
|
||||
if (isJsonObject(respObj.schema)) {
|
||||
const schemaNode = respObj.schema;
|
||||
const type = typeof schemaNode.type === 'string' ? schemaNode.type : null;
|
||||
const format = typeof schemaNode.format === 'string' ? schemaNode.format : null;
|
||||
// If type is array, always return "array" as type
|
||||
if (type === 'array') {
|
||||
result[statusCode] = { type: 'array', format: format || undefined };
|
||||
continue;
|
||||
}
|
||||
// $ref (single object)
|
||||
// eslint-disable-next-line dot-notation
|
||||
if (schemaNode['$ref']) {
|
||||
// eslint-disable-next-line dot-notation
|
||||
const refValue = schemaNode['$ref'];
|
||||
if (typeof refValue === 'string' && refValue.startsWith('#/definitions/')) {
|
||||
const defName = refValue.substring('#/definitions/'.length);
|
||||
let refType = 'object';
|
||||
// Type guard for root to access definitions
|
||||
if (isJsonObject(root)) {
|
||||
if (isJsonObject(root.definitions)) {
|
||||
const definitions = root.definitions;
|
||||
const defNode = definitions[defName];
|
||||
if (isJsonObject(defNode)) {
|
||||
refType = typeof defNode.type === 'string' ? defNode.type : 'object';
|
||||
}
|
||||
}
|
||||
}
|
||||
result[statusCode] = { type: refType, format: format || undefined };
|
||||
continue;
|
||||
}
|
||||
}
|
||||
// Primitive or object
|
||||
result[statusCode] = { type: type || 'object', format: format || undefined };
|
||||
}
|
||||
else {
|
||||
// No schema, but description exists
|
||||
result[statusCode] = { type: 'void', format: undefined };
|
||||
}
|
||||
}
|
||||
else {
|
||||
// resp is not an object, treat as void
|
||||
result[statusCode] = { type: 'void', format: undefined };
|
||||
}
|
||||
}
|
||||
// If no responses at all, return a default void
|
||||
if (Object.keys(result).length === 0) {
|
||||
result.default = { type: 'void', format: undefined };
|
||||
}
|
||||
return result;
|
||||
}
|
||||
/**
|
||||
* Retrieves the primary key from the schema node.
|
||||
* @param schemaNode The schema node to extract the primary key from.
|
||||
* @returns The primary key as a string, or an empty string if not found.
|
||||
*/
|
||||
function getPrimaryKeyFromSchema(schema) {
|
||||
// Type guard: ensure schemaNode. items exists and is an object. properties exists and is an object
|
||||
if (!isJsonObject(schema) ||
|
||||
!isJsonObject(schema.items) ||
|
||||
!isJsonObject(schema.items.properties)) {
|
||||
return '';
|
||||
}
|
||||
const propertiesNode = schema.items.properties;
|
||||
for (const propertyName in propertiesNode) {
|
||||
if (!Object.prototype.hasOwnProperty.call(propertiesNode, propertyName)) {
|
||||
continue;
|
||||
}
|
||||
const propertyValue = propertiesNode[propertyName];
|
||||
// Type guard: ensure propertyValue is an object
|
||||
if (isJsonObject(propertyValue)) {
|
||||
if (propertyValue['x-ms-keyType'] === 'primary') {
|
||||
return propertyName;
|
||||
}
|
||||
}
|
||||
}
|
||||
return '';
|
||||
}
|
||||
/**
|
||||
* Processes the parameters node and extracts parameter information.
|
||||
* @param parametersNode The parameters node from the Swagger document.
|
||||
* @param root The root of the Swagger document for reference.
|
||||
* @returns An array of ApiParameter objects representing the parameters.
|
||||
*/
|
||||
function processParameters(parametersNode, root) {
|
||||
const parameters = [];
|
||||
// Type guard: ensure parametersNode is an array
|
||||
if (!Array.isArray(parametersNode)) {
|
||||
return parameters;
|
||||
}
|
||||
for (const parameter of parametersNode) {
|
||||
// Type guard: ensure parameter is an object
|
||||
if (isJsonObject(parameter)) {
|
||||
if (parameter.$ref) {
|
||||
const refPath = parameter.$ref;
|
||||
if (typeof refPath === 'string') {
|
||||
const resolvedParameter = resolveRef(refPath, root);
|
||||
if (resolvedParameter) {
|
||||
parameters.push(parseParameter(resolvedParameter));
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
parameters.push(parseParameter(parameter));
|
||||
}
|
||||
}
|
||||
}
|
||||
// Stable sort: required=true first, then required=false
|
||||
return parameters.sort((a, b) => Number(b.required) - Number(a.required));
|
||||
}
|
||||
/**
|
||||
* Resolves a $ref reference in the Swagger document.
|
||||
* @param refPath The $ref path to resolve.
|
||||
* @param root The root of the Swagger document for reference.
|
||||
* @returns The resolved object or null if not found.
|
||||
*/
|
||||
function resolveRef(refPath, root) {
|
||||
// Type guard: ensure root is an object
|
||||
if (!isJsonObject(root)) {
|
||||
return null;
|
||||
}
|
||||
// Resolve $ref (e.g., "#/swagger/parameters/ParameterName")
|
||||
if (refPath.startsWith('#/')) {
|
||||
const parts = refPath.substring(2).split('/');
|
||||
let currentElement = root;
|
||||
for (const part of parts) {
|
||||
// Type guard: ensure currentElement is an object
|
||||
if (isJsonObject(currentElement)) {
|
||||
if (part in currentElement) {
|
||||
currentElement = currentElement[part];
|
||||
}
|
||||
else {
|
||||
return null; // Reference not found
|
||||
}
|
||||
}
|
||||
else {
|
||||
return null; // Current element is not an object
|
||||
}
|
||||
}
|
||||
// Type guard: ensure final result is an object
|
||||
if (isJsonObject(currentElement)) {
|
||||
return currentElement;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
/**
|
||||
* Parses a parameter object and extracts relevant information.
|
||||
* @param parameter The parameter object to parse.
|
||||
* @returns An ApiParameter object representing the parsed parameter.
|
||||
*/
|
||||
function parseParameter(parameter) {
|
||||
// In OpenAPI 2.0, body parameters encode type/format inside a "schema" sub-object.
|
||||
// Path/query parameters use top-level "type"/"format" fields. Check both locations.
|
||||
const schema = isJsonObject(parameter.schema) ? parameter.schema : null;
|
||||
const type = typeof parameter.type === 'string'
|
||||
? parameter.type
|
||||
: typeof schema?.type === 'string'
|
||||
? schema.type
|
||||
: 'object';
|
||||
const format = typeof parameter.format === 'string'
|
||||
? parameter.format
|
||||
: typeof schema?.format === 'string'
|
||||
? schema.format
|
||||
: undefined;
|
||||
return {
|
||||
name: typeof parameter.name === 'string' ? parameter.name : '',
|
||||
in: typeof parameter.in === 'string' ? parameter.in : '',
|
||||
required: parameter.required === true,
|
||||
type,
|
||||
format,
|
||||
};
|
||||
}
|
||||
/*
|
||||
* The normalizeHyphenatedName method normalizes hyphenated names.
|
||||
* It removes all hyphens from the name and returns the normalized name.
|
||||
*/
|
||||
function normalizeHyphenatedName(name) {
|
||||
return name.replace(/-/g, '');
|
||||
}
|
||||
/**
|
||||
* Processes a Microsoft.PowerApps/dataverseOperation schema into a DataSourceInfo entry.
|
||||
* Each operation is keyed by its lowercased operation name (e.g. `winopportunity`).
|
||||
*
|
||||
* Reads from the embedded Swagger 2.0 spec produced by `buildDataverseOperationSchema`.
|
||||
* The swagger is nested under `properties.swagger` (connector schema format) so that the
|
||||
* existing `handleSwaggerSchema` codegen path processes it without a separate handler.
|
||||
* All type mappings are already resolved in the swagger at write-time.
|
||||
*/
|
||||
function processDataverseOperation(root) {
|
||||
const props = isJsonObject(root.properties) ? root.properties : null;
|
||||
const swagger = props && isJsonObject(props.swagger) ? props.swagger : null;
|
||||
if (!swagger) {
|
||||
throw new Error('Dataverse operation schema is missing the required "properties.swagger" field.');
|
||||
}
|
||||
const basePath = typeof swagger.basePath === 'string' ? swagger.basePath : '';
|
||||
const paths = isJsonObject(swagger.paths) ? swagger.paths : null;
|
||||
if (!paths) {
|
||||
throw new Error('Dataverse operation schema swagger is missing "paths".');
|
||||
}
|
||||
const apis = {};
|
||||
for (const [pathKey, pathValue] of Object.entries(paths)) {
|
||||
if (!isJsonObject(pathValue))
|
||||
continue;
|
||||
for (const [methodKey, methodValue] of Object.entries(pathValue)) {
|
||||
if (!isJsonObject(methodValue))
|
||||
continue;
|
||||
const operationId = typeof methodValue.operationId === 'string' ? methodValue.operationId : '';
|
||||
if (!operationId)
|
||||
continue;
|
||||
const parameters = Array.isArray(methodValue.parameters)
|
||||
? methodValue.parameters.filter(isJsonObject).map((param) => ({
|
||||
name: typeof param.name === 'string' ? param.name : '',
|
||||
in: typeof param.in === 'string' ? param.in : 'body',
|
||||
required: param.required === true,
|
||||
type: typeof param.type === 'string' ? param.type : 'object',
|
||||
}))
|
||||
: [];
|
||||
apis[operationId] = {
|
||||
path: basePath + pathKey,
|
||||
method: methodKey.toUpperCase(),
|
||||
parameters,
|
||||
responseInfo: processResponse(methodValue.responses, swagger),
|
||||
};
|
||||
}
|
||||
}
|
||||
return {
|
||||
tableId: '',
|
||||
version: '',
|
||||
primaryKey: '',
|
||||
dataSourceType: 'Dataverse',
|
||||
apis,
|
||||
};
|
||||
}
|
||||
//# sourceMappingURL=dataSourceInfoProcessor.js.map
|
||||
Generated
Vendored
+1
File diff suppressed because one or more lines are too long
Generated
Vendored
+17
@@ -0,0 +1,17 @@
|
||||
/*!
|
||||
* Copyright (C) Microsoft Corporation. All rights reserved.
|
||||
*/
|
||||
import type { ILogger } from '../services/index.js';
|
||||
export interface IGenerateModelServiceOptions {
|
||||
schemaFolderPath: string;
|
||||
schemaFilePath?: string;
|
||||
codeGenPath: string;
|
||||
logger: ILogger;
|
||||
useV2Codegen?: boolean;
|
||||
}
|
||||
export declare function generateModelService(options: IGenerateModelServiceOptions): Promise<void>;
|
||||
/**
|
||||
* Clears and regenerates the model and service files.
|
||||
*/
|
||||
export declare function clearAndRegenerateModelService(options: IGenerateModelServiceOptions): Promise<void>;
|
||||
//# sourceMappingURL=modelServiceGenerator.d.ts.map
|
||||
Generated
Vendored
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"modelServiceGenerator.d.ts","sourceRoot":"","sources":["../../src/CodeGen/modelServiceGenerator.ts"],"names":[],"mappings":"AAAA;;GAEG;AAQH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,aAAa,CAAC;AAuD3C,MAAM,WAAW,4BAA4B;IAC3C,gBAAgB,EAAE,MAAM,CAAC;IACzB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,WAAW,EAAE,MAAM,CAAC;IACpB,MAAM,EAAE,OAAO,CAAC;IAChB,YAAY,CAAC,EAAE,OAAO,CAAC;CACxB;AAYD,wBAAsB,oBAAoB,CAAC,OAAO,EAAE,4BAA4B,GAAG,OAAO,CAAC,IAAI,CAAC,CAoD/F;AAED;;GAEG;AACH,wBAAsB,8BAA8B,CAClD,OAAO,EAAE,4BAA4B,GACpC,OAAO,CAAC,IAAI,CAAC,CAiBf"}
|
||||
CodeApp_NeonReactor/node_modules/@microsoft/power-apps-actions/dist/CodeGen/modelServiceGenerator.js
Generated
Vendored
+2830
File diff suppressed because it is too large
Load Diff
Generated
Vendored
+1
File diff suppressed because one or more lines are too long
Generated
Vendored
+6
@@ -0,0 +1,6 @@
|
||||
/*!
|
||||
* Copyright (C) Microsoft Corporation. All rights reserved.
|
||||
*/
|
||||
/** Extracts a human-readable display name from CDP connector metadata, for use as the data source label. */
|
||||
export declare function tryExtractDisplayNameFromCdpMetadata(metadata: unknown): string | undefined;
|
||||
//# sourceMappingURL=cdpUtils.d.ts.map
|
||||
CodeApp_NeonReactor/node_modules/@microsoft/power-apps-actions/dist/CodeGen/shared/cdpUtils.d.ts.map
Generated
Vendored
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"cdpUtils.d.ts","sourceRoot":"","sources":["../../../src/CodeGen/shared/cdpUtils.ts"],"names":[],"mappings":"AAAA;;GAEG;AAIH,4GAA4G;AAC5G,wBAAgB,oCAAoC,CAAC,QAAQ,EAAE,OAAO,GAAG,MAAM,GAAG,SAAS,CA2B1F"}
|
||||
Generated
Vendored
+32
@@ -0,0 +1,32 @@
|
||||
/*!
|
||||
* Copyright (C) Microsoft Corporation. All rights reserved.
|
||||
*/
|
||||
import { isJsonObject } from './schemaUtils.js';
|
||||
/** Extracts a human-readable display name from CDP connector metadata, for use as the data source label. */
|
||||
export function tryExtractDisplayNameFromCdpMetadata(metadata) {
|
||||
// Assuming metadata contains a 'name' or similar field for the stored procedure
|
||||
try {
|
||||
// If metadata is a string, try to parse it as JSON
|
||||
let json = metadata;
|
||||
if (typeof metadata === 'string') {
|
||||
json = JSON.parse(metadata);
|
||||
}
|
||||
// Use type guards to safely access properties
|
||||
if (isJsonObject(json)) {
|
||||
// Try to get the title first, as it's often more user-friendly
|
||||
if ('title' in json && typeof json.title === 'string') {
|
||||
return json.title;
|
||||
}
|
||||
// Fall back to name if title isn't available
|
||||
if ('name' in json && typeof json.name === 'string') {
|
||||
return json.name;
|
||||
}
|
||||
}
|
||||
// If we can't find a suitable property, use the default name
|
||||
return undefined;
|
||||
}
|
||||
catch (ex) {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
//# sourceMappingURL=cdpUtils.js.map
|
||||
Generated
Vendored
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"cdpUtils.js","sourceRoot":"","sources":["../../../src/CodeGen/shared/cdpUtils.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AAE7C,4GAA4G;AAC5G,MAAM,UAAU,oCAAoC,CAAC,QAAiB;IACpE,gFAAgF;IAChF,IAAI,CAAC;QACH,mDAAmD;QACnD,IAAI,IAAI,GAAY,QAAQ,CAAC;QAC7B,IAAI,OAAO,QAAQ,KAAK,QAAQ,EAAE,CAAC;YACjC,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC;QAC9B,CAAC;QAED,8CAA8C;QAC9C,IAAI,YAAY,CAAC,IAAI,CAAC,EAAE,CAAC;YACvB,+DAA+D;YAC/D,IAAI,OAAO,IAAI,IAAI,IAAI,OAAO,IAAI,CAAC,KAAK,KAAK,QAAQ,EAAE,CAAC;gBACtD,OAAO,IAAI,CAAC,KAAK,CAAC;YACpB,CAAC;YAED,6CAA6C;YAC7C,IAAI,MAAM,IAAI,IAAI,IAAI,OAAO,IAAI,CAAC,IAAI,KAAK,QAAQ,EAAE,CAAC;gBACpD,OAAO,IAAI,CAAC,IAAI,CAAC;YACnB,CAAC;QACH,CAAC;QAED,6DAA6D;QAC7D,OAAO,SAAS,CAAC;IACnB,CAAC;IAAC,OAAO,EAAE,EAAE,CAAC;QACZ,OAAO,SAAS,CAAC;IACnB,CAAC;AACH,CAAC"}
|
||||
Generated
Vendored
+65
@@ -0,0 +1,65 @@
|
||||
/*!
|
||||
* Copyright (C) Microsoft Corporation. All rights reserved.
|
||||
*/
|
||||
import type { JsonObject } from '../../Common/jsonTypes.js';
|
||||
/** Returns true if the schema root represents a Dataverse entity table. */
|
||||
export declare function isDataverseEntity(root: unknown): boolean;
|
||||
/** Returns the Dataverse entity set name for a schema (used as the OData API path), or null if not defined. */
|
||||
export declare function getDataverseEntitySetName(root: unknown): string | null;
|
||||
/** Returns true if the property is a Dataverse lookup relationship to another table. */
|
||||
export declare function isDataverseLookupProperty(propValue: JsonObject): boolean;
|
||||
/** Collects all lookup relationship columns from a properties node, keyed by property name. */
|
||||
export declare function getDataverseLookupColumns(propertiesNode: JsonObject): Map<string, JsonObject>;
|
||||
/** Returns true if the property is an auto-generated name field for a lookup column (e.g. `owneridname` for `ownerid`). */
|
||||
export declare function isLookupRelatedProperty(propName: string, lookupColumns: Map<string, unknown>): boolean;
|
||||
/** Returns the write-form property name for a lookup column, used when creating or updating Dataverse records. */
|
||||
export declare function generateODataBindPropertyName(schemaName: string): string;
|
||||
/** Returns the read-form property name for a lookup's resolved GUID value. */
|
||||
export declare function generateLookupValuePropertyName(lookupPropertyName: string): string;
|
||||
/**
|
||||
* Returns true if the property is a Dataverse boolean (yes/no) field.
|
||||
* These fields have enum/x-ms-enum-values in the schema but must be typed as `boolean`,
|
||||
* not as a numeric optionset, because the Dataverse Web API expects true/false.
|
||||
*/
|
||||
export declare function isDataverseBooleanProperty(propValue: JsonObject): boolean;
|
||||
/**
|
||||
* Returns true if the property is a Dataverse numeric field (integer, decimal, double, money, bigint).
|
||||
* These fields have `"type": "string"` in the schema but must be typed as `number` because
|
||||
* the Dataverse Web API sends and receives them as JSON numbers.
|
||||
*/
|
||||
export declare function isDataverseNumericProperty(propValue: JsonObject): boolean;
|
||||
/**
|
||||
* Returns true if the property is a Dataverse multi-select picklist (multi-choice) field.
|
||||
* These fields allow multiple selections and should be typed as an array of the enum type.
|
||||
*/
|
||||
export declare function isDataverseMultiSelectPicklistProperty(propValue: JsonObject): boolean;
|
||||
/**
|
||||
* Returns the names of all MultiSelectPicklist (multi-choice) properties in a Dataverse entity
|
||||
* properties node — i.e., properties that have optionset data AND are typed as MultiSelectPicklistType.
|
||||
* These fields require serialization (array → comma-separated string) when writing to the Dataverse API
|
||||
* and deserialization (comma-separated string → array) when reading back from it.
|
||||
*/
|
||||
export declare function getDataverseMultiSelectPicklistFields(propertiesNode: JsonObject): string[];
|
||||
/** Returns true if the property represents an optionset (choice) column with enumerable values. */
|
||||
export declare function hasOptionsetData(propValue: JsonObject): boolean;
|
||||
/** Produces the TypeScript identifier used for the enum or const generated from an optionset property. */
|
||||
export declare function generateOptionsetEnumName(modelName: string, propName: string): string;
|
||||
/** Formats an optionset display label as a TypeScript string literal for use in union types and const declarations. */
|
||||
export declare function formatEnumLabel(label: unknown): string;
|
||||
type GenericColumn = {
|
||||
type: 'lookupRelated' | 'normal';
|
||||
propName: string;
|
||||
propValue: JsonObject;
|
||||
readOnly: boolean;
|
||||
};
|
||||
type LookupColumn = {
|
||||
type: 'lookup';
|
||||
propName: string;
|
||||
propValue: JsonObject;
|
||||
readOnly: boolean;
|
||||
schemaName: string;
|
||||
};
|
||||
/** Yields each property in a Dataverse entity's properties node, tagged by its role and mutability, for use in interface generation. */
|
||||
export declare function iterateDataverseProperties(propertiesNode: JsonObject, lookupColumns: Map<string, JsonObject>): Generator<GenericColumn | LookupColumn>;
|
||||
export {};
|
||||
//# sourceMappingURL=dataverseUtils.d.ts.map
|
||||
Generated
Vendored
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"dataverseUtils.d.ts","sourceRoot":"","sources":["../../../src/CodeGen/shared/dataverseUtils.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,wBAAwB,CAAC;AAIzD,2EAA2E;AAC3E,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,OAAO,GAAG,OAAO,CAOxD;AAED,+GAA+G;AAC/G,wBAAgB,yBAAyB,CAAC,IAAI,EAAE,OAAO,GAAG,MAAM,GAAG,IAAI,CAQtE;AAED,wFAAwF;AACxF,wBAAgB,yBAAyB,CAAC,SAAS,EAAE,UAAU,GAAG,OAAO,CAExE;AAED,+FAA+F;AAC/F,wBAAgB,yBAAyB,CAAC,cAAc,EAAE,UAAU,GAAG,GAAG,CAAC,MAAM,EAAE,UAAU,CAAC,CAQ7F;AAED,2HAA2H;AAC3H,wBAAgB,uBAAuB,CACrC,QAAQ,EAAE,MAAM,EAChB,aAAa,EAAE,GAAG,CAAC,MAAM,EAAE,OAAO,CAAC,GAClC,OAAO,CAOT;AAED,kHAAkH;AAClH,wBAAgB,6BAA6B,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,CAExE;AAED,8EAA8E;AAC9E,wBAAgB,+BAA+B,CAAC,kBAAkB,EAAE,MAAM,GAAG,MAAM,CAElF;AAED;;;;GAIG;AACH,wBAAgB,0BAA0B,CAAC,SAAS,EAAE,UAAU,GAAG,OAAO,CAKzE;AAED;;;;GAIG;AACH,wBAAgB,0BAA0B,CAAC,SAAS,EAAE,UAAU,GAAG,OAAO,CAoBzE;AAED;;;GAGG;AACH,wBAAgB,sCAAsC,CAAC,SAAS,EAAE,UAAU,GAAG,OAAO,CAErF;AAED;;;;;GAKG;AACH,wBAAgB,qCAAqC,CAAC,cAAc,EAAE,UAAU,GAAG,MAAM,EAAE,CAa1F;AAED,mGAAmG;AACnG,wBAAgB,gBAAgB,CAAC,SAAS,EAAE,UAAU,GAAG,OAAO,CAK/D;AAED,0GAA0G;AAC1G,wBAAgB,yBAAyB,CAAC,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,CAErF;AAED,uHAAuH;AACvH,wBAAgB,eAAe,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,CAStD;AAED,KAAK,aAAa,GAAG;IACnB,IAAI,EAAE,eAAe,GAAG,QAAQ,CAAC;IACjC,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,UAAU,CAAC;IACtB,QAAQ,EAAE,OAAO,CAAC;CACnB,CAAC;AACF,KAAK,YAAY,GAAG;IAClB,IAAI,EAAE,QAAQ,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,UAAU,CAAC;IACtB,QAAQ,EAAE,OAAO,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;CACpB,CAAC;AAEF,wIAAwI;AACxI,wBAAiB,0BAA0B,CACzC,cAAc,EAAE,UAAU,EAC1B,aAAa,EAAE,GAAG,CAAC,MAAM,EAAE,UAAU,CAAC,GACrC,SAAS,CAAC,aAAa,GAAG,YAAY,CAAC,CAezC"}
|
||||
CodeApp_NeonReactor/node_modules/@microsoft/power-apps-actions/dist/CodeGen/shared/dataverseUtils.js
Generated
Vendored
+154
@@ -0,0 +1,154 @@
|
||||
/*!
|
||||
* Copyright (C) Microsoft Corporation. All rights reserved.
|
||||
*/
|
||||
import { convertToValidIdentifier } from './nameUtils.js';
|
||||
import { isJsonObject } from './schemaUtils.js';
|
||||
/** Returns true if the schema root represents a Dataverse entity table. */
|
||||
export function isDataverseEntity(root) {
|
||||
return (isJsonObject(root) &&
|
||||
isJsonObject(root.schema) &&
|
||||
isJsonObject(root.schema.items) &&
|
||||
root.schema.items['x-ms-dataverse-entity'] === true);
|
||||
}
|
||||
/** Returns the Dataverse entity set name for a schema (used as the OData API path), or null if not defined. */
|
||||
export function getDataverseEntitySetName(root) {
|
||||
if (isJsonObject(root) && isJsonObject(root.schema) && isJsonObject(root.schema.items)) {
|
||||
const entitySetName = root.schema.items['x-ms-dataverse-entityset'];
|
||||
if (typeof entitySetName === 'string' && entitySetName.trim()) {
|
||||
return entitySetName;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
/** Returns true if the property is a Dataverse lookup relationship to another table. */
|
||||
export function isDataverseLookupProperty(propValue) {
|
||||
return propValue['x-ms-dataverse-type'] === 'LookupType';
|
||||
}
|
||||
/** Collects all lookup relationship columns from a properties node, keyed by property name. */
|
||||
export function getDataverseLookupColumns(propertiesNode) {
|
||||
const lookupColumns = new Map();
|
||||
for (const [propName, propValue] of Object.entries(propertiesNode)) {
|
||||
if (isJsonObject(propValue) && isDataverseLookupProperty(propValue)) {
|
||||
lookupColumns.set(propName, propValue);
|
||||
}
|
||||
}
|
||||
return lookupColumns;
|
||||
}
|
||||
/** Returns true if the property is an auto-generated name field for a lookup column (e.g. `owneridname` for `ownerid`). */
|
||||
export function isLookupRelatedProperty(propName, lookupColumns) {
|
||||
for (const lookupPropName of lookupColumns.keys()) {
|
||||
if (propName === `${lookupPropName}name`) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
/** Returns the write-form property name for a lookup column, used when creating or updating Dataverse records. */
|
||||
export function generateODataBindPropertyName(schemaName) {
|
||||
return `${schemaName}@odata.bind`;
|
||||
}
|
||||
/** Returns the read-form property name for a lookup's resolved GUID value. */
|
||||
export function generateLookupValuePropertyName(lookupPropertyName) {
|
||||
return `_${lookupPropertyName}_value`;
|
||||
}
|
||||
/**
|
||||
* Returns true if the property is a Dataverse boolean (yes/no) field.
|
||||
* These fields have enum/x-ms-enum-values in the schema but must be typed as `boolean`,
|
||||
* not as a numeric optionset, because the Dataverse Web API expects true/false.
|
||||
*/
|
||||
export function isDataverseBooleanProperty(propValue) {
|
||||
return (propValue['x-ms-dataverse-type'] === 'BooleanType' ||
|
||||
propValue['x-ms-optionSetType'] === 'Boolean');
|
||||
}
|
||||
/**
|
||||
* Returns true if the property is a Dataverse numeric field (integer, decimal, double, money, bigint).
|
||||
* These fields have `"type": "string"` in the schema but must be typed as `number` because
|
||||
* the Dataverse Web API sends and receives them as JSON numbers.
|
||||
*/
|
||||
export function isDataverseNumericProperty(propValue) {
|
||||
const dvType = typeof propValue['x-ms-dataverse-type'] === 'string'
|
||||
? propValue['x-ms-dataverse-type'].toLowerCase()
|
||||
: undefined;
|
||||
switch (dvType) {
|
||||
case 'integer':
|
||||
case 'integertype':
|
||||
case 'bigint':
|
||||
case 'biginttype':
|
||||
case 'decimal':
|
||||
case 'decimaltype':
|
||||
case 'double':
|
||||
case 'doubletype':
|
||||
case 'money':
|
||||
case 'moneytype':
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Returns true if the property is a Dataverse multi-select picklist (multi-choice) field.
|
||||
* These fields allow multiple selections and should be typed as an array of the enum type.
|
||||
*/
|
||||
export function isDataverseMultiSelectPicklistProperty(propValue) {
|
||||
return propValue['x-ms-dataverse-type'] === 'MultiSelectPicklistType';
|
||||
}
|
||||
/**
|
||||
* Returns the names of all MultiSelectPicklist (multi-choice) properties in a Dataverse entity
|
||||
* properties node — i.e., properties that have optionset data AND are typed as MultiSelectPicklistType.
|
||||
* These fields require serialization (array → comma-separated string) when writing to the Dataverse API
|
||||
* and deserialization (comma-separated string → array) when reading back from it.
|
||||
*/
|
||||
export function getDataverseMultiSelectPicklistFields(propertiesNode) {
|
||||
const fields = [];
|
||||
for (const [propName, propValue] of Object.entries(propertiesNode)) {
|
||||
if (isJsonObject(propValue) &&
|
||||
isDataverseMultiSelectPicklistProperty(propValue) &&
|
||||
Array.isArray(propValue.enum) &&
|
||||
propValue.enum.length > 0) {
|
||||
fields.push(propName);
|
||||
}
|
||||
}
|
||||
return fields;
|
||||
}
|
||||
/** Returns true if the property represents an optionset (choice) column with enumerable values. */
|
||||
export function hasOptionsetData(propValue) {
|
||||
if (isDataverseBooleanProperty(propValue)) {
|
||||
return false;
|
||||
}
|
||||
return Array.isArray(propValue.enum) && propValue.enum.length > 0;
|
||||
}
|
||||
/** Produces the TypeScript identifier used for the enum or const generated from an optionset property. */
|
||||
export function generateOptionsetEnumName(modelName, propName) {
|
||||
return convertToValidIdentifier(`${modelName}${propName}`);
|
||||
}
|
||||
/** Formats an optionset display label as a TypeScript string literal for use in union types and const declarations. */
|
||||
export function formatEnumLabel(label) {
|
||||
if (label === null || label === undefined) {
|
||||
return "''";
|
||||
}
|
||||
const str = String(label);
|
||||
if (str.trim() === '') {
|
||||
return "''";
|
||||
}
|
||||
return `'${str.replace(/'/g, "\\'")}'`;
|
||||
}
|
||||
/** Yields each property in a Dataverse entity's properties node, tagged by its role and mutability, for use in interface generation. */
|
||||
export function* iterateDataverseProperties(propertiesNode, lookupColumns) {
|
||||
for (const [propName, propValue] of Object.entries(propertiesNode)) {
|
||||
if (isJsonObject(propValue)) {
|
||||
const readOnly = !!propValue['x-ms-read-only'];
|
||||
if (lookupColumns.has(propName)) {
|
||||
const schemaNameRaw = propValue['x-ms-schema-name'];
|
||||
const schemaName = typeof schemaNameRaw === 'string' ? schemaNameRaw : propName;
|
||||
yield { type: 'lookup', readOnly, propName, propValue, schemaName };
|
||||
}
|
||||
else if (isLookupRelatedProperty(propName, lookupColumns)) {
|
||||
yield { type: 'lookupRelated', readOnly, propName, propValue };
|
||||
}
|
||||
else {
|
||||
yield { type: 'normal', readOnly, propName, propValue };
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
//# sourceMappingURL=dataverseUtils.js.map
|
||||
Generated
Vendored
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"dataverseUtils.js","sourceRoot":"","sources":["../../../src/CodeGen/shared/dataverseUtils.ts"],"names":[],"mappings":"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"}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user