push neon reactor

This commit is contained in:
2026-07-12 15:11:38 +02:00
parent 172e72dbcd
commit aeab5f7820
9597 changed files with 2407488 additions and 0 deletions
+153
View File
@@ -0,0 +1,153 @@
MICROSOFT SOFTWARE LICENSE TERMS
MICROSOFT POWER PLATFORM @MICROSOFT/POWER-APPS-ACTIONS
August 2025
These license terms are an agreement between you and Microsoft Corporation (or one of its affiliates). They
apply to the software named above and any Microsoft services or software updates (except to the extent such
services or updates are accompanied by new or additional terms, in which case those different terms apply
prospectively and do not alter your or Microsoft's rights relating to pre-updated software or services). IF YOU
COMPLY WITH THESE LICENSE TERMS, YOU HAVE THE RIGHTS BELOW. BY USING THE SOFTWARE, YOU
ACCEPT THESE TERMS.
1. INSTALLATION AND USE RIGHTS.
a) General. You may install and use any number of copies of the software to develop and test your
applications.
b) Third Party Components. The software may include third party components with separate legal
notices or governed by other agreements, as may be described in the ThirdPartyNotices file(s)
accompanying the software.
c) Work or School Accounts. You can sign into the software with a work or school email address. If
you do, you agree that the owner of the domain associated with your email address may control and
administer your account, and access and process your data, including the contents of your
communications and files. You further agree that your use of the software may be subject to: i) your
organization's guidelines and policies regarding the use of the software; and ii) the agreements
Microsoft has with you or your organization, and in such case these terms may not apply. If you
already have a Microsoft account and you use a separate work or school email address to access the
software, you may be prompted to update the email address associated with your Microsoft account to
continue accessing the software.
2. SCOPE OF LICENSE. The software is licensed, not sold. Microsoft reserves all other rights. Unless
applicable law gives you more rights despite this limitation, you will not (and have no right to):
a) work around any technical limitations in the software that only allow you to use it in certain ways;
b) reverse engineer, decompile or disassemble the software, or otherwise attempt to derive the source
code for the software, except and to the extent required by third party licensing terms governing use
of certain open source components that may be included in the software;
c) remove, minimize, block, or modify any notices of Microsoft or its suppliers in the software;
d) use the software in any way that is against the law or to create or propagate malware; or
e) share, publish, distribute, or lease the software (except for any distributable code, subject to the
terms above), provide the software as a stand-alone offering for others to use, or transfer the
software or this agreement to any third party.
3. DATA.
a) Data Collection. The software may collect information about you and your use of the software, and
send that to Microsoft. Microsoft may use this information to provide services and improve our
products and services. You may opt-out of many of these scenarios, but not all, as described in the
product documentation. There are also some features in the software that may enable you to collect
data from users of your applications. If you use these features to enable data collection in your
applications, you must comply with applicable law, including providing appropriate notices to users of
your applications. You can learn more about data collection and use in the help documentation and
the privacy statement at https://go.microsoft.com/fwlink/?linkid=521839. Your use of the software
operates as your consent to these practices.
b) Processing of Personal Data. To the extent Microsoft is a processor or subprocessor of personal
data in connection with the software, Microsoft makes the commitments in the European Union
General Data Protection Regulation Terms of the Microsoft Products and Services Data Protection
Addendum to all customers effective May 25, 2018, at
https://go.microsoft.com/fwlink/?linkid=2135030.
4. COMPLIANCE WITH TRADE LAWS. Microsoft products, software, technology, and services ("Items")
may be subject to U.S. and other countries' export jurisdictions. Each party will comply with all laws and
regulations applicable to the import or export of the Items, including, without limitation, trade laws of the
U.S., EU, and UK, such as the U.S. Export Administration Regulations, sanctions regulations administered
by the U.S. Office of Foreign Assets Control, the EU Dual Use Regulation 2021/821, and/or other end-user,
end use, and destination restrictions ("Trade Laws") as well as the global legal compliance standards
detailed in the Microsoft Standards of Business Conduct. Customer will not, and will ensure its Affiliates
will not, take any action that causes Microsoft to violate applicable Trade Laws. Microsoft may suspend or
terminate this agreement immediately without notice to the extent that Microsoft reasonably believes that
performance would cause it to violate Trade Laws or put it at risk of becoming subject to sanctions and
penalties under such laws. Customer remains responsible for its and for its Affiliates' compliance with this
section and, to the extent applicable, the Regional Trade Compliance Addendum incorporated herein by
reference.
5. SUPPORT SERVICES. Microsoft is not obligated under this agreement to provide any support services for
the software. Any support provided is "as is", "with all faults", and without warranty of any kind.
6. ENTIRE AGREEMENT. This agreement, and any other terms Microsoft may provide for supplements,
updates, or third-party applications, is the entire agreement for the software.
7. APPLICABLE LAW AND PLACE TO RESOLVE DISPUTES. If you acquired the software in the United
States or Canada, the laws of the state or province where you live (or, if a business, where your principal
place of business is located) govern the interpretation of this agreement, claims for its breach, and all
other claims (including consumer protection, unfair competition, and tort claims), regardless of conflict of
laws principles. If you acquired the software in any other country, its laws apply. If U.S. federal jurisdiction
exists, you and Microsoft consent to exclusive jurisdiction and venue in the federal court in King County,
Washington for all disputes heard in court. If not, you and Microsoft consent to exclusive jurisdiction and
venue in the Superior Court of King County, Washington for all disputes heard in court.
8. CONSUMER RIGHTS; REGIONAL VARIATIONS. This agreement describes certain legal rights. You
may have other rights, including consumer rights, under the laws of your state or country. Separate and
apart from your relationship with Microsoft, you may also have rights with respect to the party from which
you acquired the software. This agreement does not change those other rights if the laws of your state or
country do not permit it to do so. For example, if you acquired the software in one of the below regions, or
mandatory country law applies, then the following provisions apply to you:
a) Australia. You have statutory guarantees under the Australian Consumer Law and nothing in this
agreement is intended to affect those rights.
b) Canada. If you acquired this software in Canada, you may stop receiving updates by turning off the
automatic update feature, disconnecting your device from the Internet (if and when you re-connect to
the Internet, however, the software will resume checking for and installing updates), or uninstalling
the software. The product documentation, if any, may also specify how to turn off updates for your
specific device or software.
c) Germany and Austria.
i. Warranty. The properly licensed software will perform substantially as described in any Microsoft
materials that accompany the software. However, Microsoft gives no contractual guarantee in
relation to the licensed software.
ii. Limitation of Liability. In case of intentional conduct, gross negligence, claims based on the
Product Liability Act, as well as, in case of death or personal or physical injury, Microsoft is liable
according to the statutory law.
Subject to the foregoing clause ii., Microsoft will only be liable for slight negligence if Microsoft is in
breach of such material contractual obligations, the fulfillment of which facilitate the due performance
of this agreement, the breach of which would endanger the purpose of this agreement and the
compliance with which a party may constantly trust in (so-called "cardinal obligations"). In other cases
of slight negligence, Microsoft will not be liable for slight negligence.
9. DISCLAIMER OF WARRANTY. THE SOFTWARE IS LICENSED "AS IS." YOU BEAR THE RISK OF
USING IT. MICROSOFT GIVES NO EXPRESS WARRANTIES, GUARANTEES, OR CONDITIONS.
TO THE EXTENT PERMITTED UNDER APPLICABLE LAWS, MICROSOFT EXCLUDES ALL IMPLIED
WARRANTIES, INCLUDING MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, AND
NON-INFRINGEMENT.
10. LIMITATION ON AND EXCLUSION OF DAMAGES. IF YOU HAVE ANY BASIS FOR RECOVERING
DAMAGES DESPITE THE PRECEDING DISCLAIMER OF WARRANTY, YOU CAN RECOVER FROM
MICROSOFT AND ITS SUPPLIERS ONLY DIRECT DAMAGES UP TO U.S. $5.00. YOU CANNOT
RECOVER ANY OTHER DAMAGES, INCLUDING CONSEQUENTIAL, LOST PROFITS, SPECIAL,
INDIRECT, OR INCIDENTAL DAMAGES.
This limitation applies to (a) anything related to the software, services, content (including
code) on third party Internet sites, or third party applications; and (b) claims for breach of
contract, warranty, guarantee, or condition; strict liability, negligence, or other tort; or any
other claim; in each case to the extent permitted by applicable law.
It also applies even if Microsoft knew or should have known about the possibility of the
damages. The above limitation or exclusion may not apply to you because your state,
province, or country may not allow the exclusion or limitation of incidental, consequential, or
other damages.
Please note: As this software is distributed in Canada, some of the clauses in this agreement are
provided below in French.
Remarque: Ce logiciel étant distribué au Canada, certaines des clauses dans ce contrat sont
fournies ci-dessous en français.
EXONÉRATION DE GARANTIE. Le logiciel visé par une licence est offert « tel quel ». Toute
utilisation de ce logiciel est à votre seule risque et péril. Microsoft n'accorde aucune autre
garantie expresse. Vous pouvez bénéficier de droits additionnels en vertu du droit local sur la
protection des consommateurs, que ce contrat ne peut modifier. La ou elles sont permises par le
droit locale, les garanties implicites de qualité marchande, d'adéquation à un usage particulier et
d'absence de contrefaçon sont exclues.
LIMITATION DES DOMMAGES-INTÉRÊTS ET EXCLUSION DE RESPONSABILITÉ POUR LES
DOMMAGES. Vous pouvez obtenir de Microsoft et de ses fournisseurs une indemnisation en cas de
dommages directs uniquement à hauteur de 5,00 $ US. Vous ne pouvez prétendre à aucune
indemnisation pour les autres dommages, y compris les dommages spéciaux, indirects ou
accessoires et pertes de bénéfices.
Cette limitation concerne:
• tout ce qui est relié au logiciel, aux services ou au contenu (y compris le code) figurant sur
des sites Internet tiers ou dans des programmes tiers; et
• les réclamations au titre de violation de contrat ou de garantie, ou au titre de responsabilité
stricte, de négligence ou d'une autre faute dans la limite autorisée par la loi en vigueur.
Elle s'applique également, même si Microsoft connaissait ou devrait connaître l'éventualité d'un
tel dommage. Si votre pays n'autorise pas l'exclusion ou la limitation de responsabilité pour les
dommages indirects, accessoires ou de quelque nature que ce soit, il se peut que la limitation ou
l'exclusion ci-dessus ne s'appliquera pas à votre égard.
EFFET JURIDIQUE. Le présent contrat décrit certains droits juridiques. Vous pourriez avoir
d'autres droits prévus par les lois de votre pays. Le présent contrat ne modifie pas les droits que
vous confèrent les lois de votre pays si celles-ci ne le permettent pas.
DWT 28909173v1 0085000-001126
DWT 28909173v1 0085000-001126
@@ -0,0 +1,3 @@
# Power Apps Player Actions
Base package for the actions required for code apps
@@ -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
@@ -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"}
@@ -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
File diff suppressed because one or more lines are too long
@@ -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
@@ -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"}
@@ -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
File diff suppressed because one or more lines are too long
@@ -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
@@ -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"}
@@ -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
@@ -0,0 +1 @@
{"version":3,"file":"AddFlow.js","sourceRoot":"","sources":["../../src/Actions/AddFlow.ts"],"names":[],"mappings":"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"}
@@ -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 34 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
@@ -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"}
@@ -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 34 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 34 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
@@ -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"}
@@ -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
@@ -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"}
@@ -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
@@ -0,0 +1 @@
{"version":3,"file":"DataUtils.js","sourceRoot":"","sources":["../../src/Actions/DataUtils.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,cAAc,EAAE,MAAM,yBAAyB,CAAC;AAEzD,OAAO,EAAE,0BAA0B,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAC;AAG/E,MAAM,cAAc,GAAG,UAAU,CAAC;AAElC,MAAM,UAAU,qBAAqB,CAAC,SAA6B;IACjE,OAAO,CAAC,CAAC,SAAS,IAAI,SAAS,CAAC,UAAU,CAAC,cAAc,CAAC,CAAC;AAC7D,CAAC;AAED,MAAM,UAAU,0BAA0B,CAAC,SAA6B;IACtE,IAAI,CAAC,SAAS,EAAE,CAAC;QACf,OAAO,SAAS,CAAC;IACnB,CAAC;IACD,IAAI,CAAC,qBAAqB,CAAC,SAAS,CAAC,EAAE,CAAC;QACtC,MAAM,IAAI,KAAK,CAAC,cAAc,SAAS,mCAAmC,CAAC,CAAC;IAC9E,CAAC;IACD,OAAO,SAAS,CAAC,KAAK,CAAC,cAAc,CAAC,MAAM,CAAC,CAAC;AAChD,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,0BAA0B,CAC9C,SAA6B,EAC7B,UAAuB,EACvB,MAA0B,EAC1B,MAAe;IAEf,IAAI,CAAC,SAAS,IAAI,CAAC,MAAM,EAAE,CAAC;QAC1B,OAAO,SAAS,CAAC;IACnB,CAAC;IACD,IAAI,CAAC,qBAAqB,CAAC,SAAS,CAAC,EAAE,CAAC;QACtC,MAAM,IAAI,KAAK,CAAC,cAAc,SAAS,mCAAmC,CAAC,CAAC;IAC9E,CAAC;IAED,MAAM,eAAe,GAAG,IAAI,0BAA0B,CAAC,UAAU,CAAC,CAAC;IAEnE,MAAM,kBAAkB,GAAG,MAAM,eAAe,CAAC,sBAAsB,CACrE,MAAM,EACN,SAAS,CAAC,KAAK,CAAC,cAAc,CAAC,MAAM,CAAC,EACtC,MAAM,CACP,CAAC;IAEF,IAAI,CAAC,kBAAkB,EAAE,CAAC;QACxB,MAAM,IAAI,KAAK,CAAC,wBAAwB,SAAS,4BAA4B,CAAC,CAAC;IACjF,CAAC;IAED,OAAO,CACL,kBAAkB,CAAC,sDAAsD,EAAE,CAAC,CAAC,CAAC,EAAE,KAAK,IAAI,EAAE,CAC5F,CAAC;AACJ,CAAC;AAED;;;;GAIG;AACH,MAAM,CAAC,KAAK,UAAU,kBAAkB,CAAC,eAAuB;IAC9D,MAAM,MAAM,GAAG,MAAM,cAAc,CAAC,eAAe,CAAC,CAAC;IAErD,MAAM,aAAa,GAAG,MAAM,CAAC,aAAa,CAAC;IAC3C,IAAI,aAAa,EAAE,CAAC;QAClB,MAAM,QAAQ,GAAG,MAAM,oBAAoB,CAAC,aAAa,CAAC,CAAC;QAC3D,MAAM,eAAe,GAAG,QAAQ,EAAE,UAAU,EAAE,yBAAyB,EAAE,cAAc,CAAC;QACxF,IAAI,CAAC,eAAe,EAAE,CAAC;YACrB,MAAM,IAAI,KAAK,CACb,gFAAgF,CACjF,CAAC;QACJ,CAAC;QACD,OAAO,eAAe,CAAC;IACzB,CAAC;SAAM,CAAC;QACN,MAAM,IAAI,KAAK,CAAC,sDAAsD,CAAC,CAAC;IAC1E,CAAC;AACH,CAAC;AAED;;;;;GAKG;AACH,MAAM,CAAC,KAAK,UAAU,mCAAmC,CACvD,eAAuB;IAEvB,MAAM,QAAQ,GAAG,MAAM,oBAAoB,CAAC,eAAe,CAAC,CAAC;IAC7D,MAAM,MAAM,GAAG,QAAQ,EAAE,UAAU,EAAE,yBAAyB,EAAE,cAAc,CAAC;IAC/E,IAAI,CAAC,MAAM,EAAE,CAAC;QACZ,MAAM,IAAI,KAAK,CACb,uEAAuE,eAAe,4DAA4D,CACnJ,CAAC;IACJ,CAAC;IACD,OAAO,MAAM,CAAC;AAChB,CAAC;AAED;;;;;GAKG;AACH,MAAM,CAAC,KAAK,UAAU,cAAc,CAClC,KAAmC;IAEnC,IAAI,KAAK,KAAK,SAAS,IAAI,KAAK,KAAK,IAAI,EAAE,CAAC;QAC1C,OAAO,SAAS,CAAC;IACnB,CAAC;IAED,8DAA8D;IAC9D,MAAM,aAAa,GAAG,OAAO,KAAK,KAAK,UAAU,CAAC,CAAC,CAAC,MAAM,KAAK,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC;IAE1E,OAAO,aAAa,IAAI,aAAa,CAAC,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC,CAAC,aAAa,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,SAAS,CAAC;AACzF,CAAC"}
@@ -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
@@ -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"}
@@ -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
File diff suppressed because one or more lines are too long
@@ -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
@@ -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"}
@@ -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
@@ -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"}
@@ -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
@@ -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"}
@@ -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
@@ -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"}
@@ -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
@@ -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"}
@@ -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
@@ -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"}
@@ -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
@@ -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"}
@@ -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
@@ -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"}
@@ -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
@@ -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"}
@@ -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
@@ -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"}
@@ -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
@@ -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"}
@@ -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
@@ -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"}
@@ -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
@@ -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"}
@@ -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
@@ -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"}
@@ -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
@@ -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"}
@@ -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
@@ -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"}
@@ -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
@@ -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"}
@@ -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
@@ -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"}
@@ -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
@@ -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"}
@@ -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
File diff suppressed because one or more lines are too long
@@ -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
@@ -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"}
@@ -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
File diff suppressed because one or more lines are too long
@@ -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
@@ -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"}
@@ -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
@@ -0,0 +1 @@
{"version":3,"file":"RegenDataSourceTs.js","sourceRoot":"","sources":["../../src/Actions/RegenDataSourceTs.ts"],"names":[],"mappings":"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"}
@@ -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
@@ -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"}
@@ -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
@@ -0,0 +1 @@
{"version":3,"file":"RemoveFlow.js","sourceRoot":"","sources":["../../src/Actions/RemoveFlow.ts"],"names":[],"mappings":"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"}
@@ -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
@@ -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"}
@@ -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
@@ -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"}
@@ -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
@@ -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"}
@@ -0,0 +1,5 @@
/*!
* Copyright (C) Microsoft Corporation. All rights reserved.
*/
export {};
//# sourceMappingURL=dataSourceInfo.js.map
@@ -0,0 +1 @@
{"version":3,"file":"dataSourceInfo.js","sourceRoot":"","sources":["../../src/CodeGen/dataSourceInfo.ts"],"names":[],"mappings":"AAAA;;GAEG"}
@@ -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
@@ -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"}
@@ -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
File diff suppressed because one or more lines are too long
@@ -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
@@ -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"}
File diff suppressed because it is too large Load Diff
File diff suppressed because one or more lines are too long
@@ -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
@@ -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"}
@@ -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
@@ -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"}
@@ -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
@@ -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"}

Some files were not shown because too many files have changed in this diff Show More