diff --git a/apps/vs-code-designer/src/app/commands/__test__/commandWebviewWrappers.test.ts b/apps/vs-code-designer/src/app/commands/__test__/commandWebviewWrappers.test.ts index fd01cc35899..f4872d93f9d 100644 --- a/apps/vs-code-designer/src/app/commands/__test__/commandWebviewWrappers.test.ts +++ b/apps/vs-code-designer/src/app/commands/__test__/commandWebviewWrappers.test.ts @@ -3,7 +3,7 @@ import * as path from 'path'; import * as vscode from 'vscode'; import { beforeEach, describe, expect, it, type Mock, vi } from 'vitest'; import { ext } from '../../../extensionVariables'; -import { isCodefulProject } from '../../utils/codeful'; +import { hasCodefulWorkflowSetting } from '../../utils/codeful'; import { getLogicAppWithoutCustomCode, getWorkspaceRoot } from '../../utils/workspace'; import { tryGetLogicAppProjectRoot } from '../../utils/verifyIsProject'; import { cloudToLocal } from '../cloudToLocal/cloudToLocal'; @@ -47,7 +47,7 @@ vi.mock('../../utils/workspace', () => ({ })); vi.mock('../../utils/codeful', () => ({ - isCodefulProject: vi.fn(), + hasCodefulWorkflowSetting: vi.fn(), })); vi.mock('../../utils/verifyIsProject', () => ({ @@ -70,7 +70,7 @@ describe('workspace webview command wrappers', () => { (vscode.workspace.fs.readFile as Mock).mockReset(); (getWorkspaceRoot as Mock).mockResolvedValue(workspaceRoot); (tryGetLogicAppProjectRoot as Mock).mockResolvedValue(logicAppRoot); - (isCodefulProject as Mock).mockResolvedValue(false); + (hasCodefulWorkflowSetting as Mock).mockResolvedValue(false); (getLogicAppWithoutCustomCode as Mock).mockResolvedValue([]); }); @@ -192,12 +192,12 @@ describe('workspace webview command wrappers', () => { } as vscode.WorkspaceFolder; (vscode.workspace as any).workspaceFolders = [folder]; (tryGetLogicAppProjectRoot as Mock).mockResolvedValue(projectRoot); - (isCodefulProject as Mock).mockResolvedValue(true); + (hasCodefulWorkflowSetting as Mock).mockResolvedValue(true); await createWorkflow(context); expect(tryGetLogicAppProjectRoot).toHaveBeenCalledWith(context, workspaceRoot, true); - expect(isCodefulProject).toHaveBeenCalledWith(projectRoot); + expect(hasCodefulWorkflowSetting).toHaveBeenCalledWith(projectRoot); const config = getLastWebviewConfig(); expect(config).toMatchObject({ diff --git a/apps/vs-code-designer/src/app/commands/__test__/pickFuncProcess.test.ts b/apps/vs-code-designer/src/app/commands/__test__/pickFuncProcess.test.ts index ad250ff8926..7afba7768e1 100644 --- a/apps/vs-code-designer/src/app/commands/__test__/pickFuncProcess.test.ts +++ b/apps/vs-code-designer/src/app/commands/__test__/pickFuncProcess.test.ts @@ -60,7 +60,7 @@ vi.mock('../../utils/vsCodeConfig/settings', () => ({ })); vi.mock('../../utils/codeful', () => ({ - isCodefulProject: vi.fn(), + hasCodefulWorkflowSetting: vi.fn(), })); vi.mock('../buildCustomCodeFunctionsProject', () => ({ @@ -77,7 +77,7 @@ import { getProjFiles } from '../../utils/dotnet/dotnet'; import { getFuncPortFromTaskOrProject, runningFuncTaskMap } from '../../utils/funcCoreTools/funcHostTask'; import { executeIfNotActive } from '../../utils/taskUtils'; import { delay } from '../../utils/delay'; -import { isCodefulProject } from '../../utils/codeful'; +import { hasCodefulWorkflowSetting } from '../../utils/codeful'; import { tryGetLogicAppProjectRoot } from '../../utils/verifyIsProject'; import { getWorkspaceSetting } from '../../utils/vsCodeConfig/settings'; import { tryBuildCustomCodeFunctionsProject } from '../buildCustomCodeFunctionsProject'; @@ -121,7 +121,7 @@ describe('pickFuncProcessInternal', () => { context.errorHandling = {}; runningFuncTaskMap.clear(); (preDebugValidate as any).mockResolvedValue(true); - (isCodefulProject as any).mockResolvedValue(true); + (hasCodefulWorkflowSetting as any).mockResolvedValue(true); (tryBuildCustomCodeFunctionsProject as any).mockResolvedValue(true); (publishCodefulProject as any).mockResolvedValue(undefined); (delay as any).mockResolvedValue(undefined); @@ -158,14 +158,14 @@ describe('pickFuncProcessInternal', () => { ) ).rejects.toThrow('Failed to find "func: host start" task.'); - expect(isCodefulProject).toHaveBeenCalledWith(projectPath); + expect(hasCodefulWorkflowSetting).toHaveBeenCalledWith(projectPath); expect(tryBuildCustomCodeFunctionsProject).not.toHaveBeenCalled(); expect(publishCodefulProject).toHaveBeenCalledWith(context, workspaceFolder.uri, { skipIfBuildPopulatesCodeful: true }); expect(executeIfNotActive).not.toHaveBeenCalled(); }); it('custom code project skips codeful publish', async () => { - (isCodefulProject as any).mockResolvedValue(false); + (hasCodefulWorkflowSetting as any).mockResolvedValue(false); (vscode.tasks.fetchTasks as any).mockResolvedValue([]); await expect( @@ -177,7 +177,7 @@ describe('pickFuncProcessInternal', () => { ) ).rejects.toThrow('Failed to find "func: host start" task.'); - expect(isCodefulProject).toHaveBeenCalledWith(projectPath); + expect(hasCodefulWorkflowSetting).toHaveBeenCalledWith(projectPath); expect(tryBuildCustomCodeFunctionsProject).toHaveBeenCalledWith(context, workspaceFolder.uri); expect(publishCodefulProject).not.toHaveBeenCalled(); expect(executeIfNotActive).not.toHaveBeenCalled(); @@ -210,7 +210,7 @@ describe('pickFuncProcessInternal', () => { it('waits for a previous func task to stop before custom code build', async () => { const events: string[] = []; - (isCodefulProject as any).mockResolvedValue(false); + (hasCodefulWorkflowSetting as any).mockResolvedValue(false); runningFuncTaskMap.set(workspaceFolder, { startTime: Date.now(), processId: 5678 }); (delay as any).mockImplementationOnce(async () => { expect(tryBuildCustomCodeFunctionsProject).not.toHaveBeenCalled(); diff --git a/apps/vs-code-designer/src/app/commands/__test__/publishCodefulProject.test.ts b/apps/vs-code-designer/src/app/commands/__test__/publishCodefulProject.test.ts index 34dcc2331c7..a5e9511be66 100644 --- a/apps/vs-code-designer/src/app/commands/__test__/publishCodefulProject.test.ts +++ b/apps/vs-code-designer/src/app/commands/__test__/publishCodefulProject.test.ts @@ -1,7 +1,7 @@ import * as vscode from 'vscode'; import { beforeEach, describe, expect, it, type Mock, vi } from 'vitest'; import { ext } from '../../../extensionVariables'; -import { isCodefulProject } from '../../utils/codeful'; +import { hasCodefulWorkflowSetting } from '../../utils/codeful'; import { getWorkspaceRoot } from '../../utils/workspace'; import { publishCodefulProject } from '../publishCodefulProject'; @@ -15,7 +15,7 @@ vi.mock('../../utils/workspace', () => ({ })); vi.mock('../../utils/codeful', () => ({ - isCodefulProject: vi.fn(), + hasCodefulWorkflowSetting: vi.fn(), inspectCodefulCsprojBuildHooks: vi.fn(), invalidateCodefulSdkCacheIfNeeded: vi.fn(), })); @@ -44,7 +44,7 @@ describe('publishCodefulProject', () => { }), }; (getWorkspaceRoot as Mock).mockResolvedValue(projectPath); - (isCodefulProject as Mock).mockResolvedValue(true); + (hasCodefulWorkflowSetting as Mock).mockResolvedValue(true); (invalidateCodefulSdkCacheIfNeeded as Mock).mockResolvedValue(false); (inspectCodefulCsprojBuildHooks as Mock).mockResolvedValue({ copyAfterTargets: 'Build;Publish', @@ -63,11 +63,11 @@ describe('publishCodefulProject', () => { errorMessage: 'No project path found to publish custom code functions project.', }); expect(ext.outputChannel.appendLog).toHaveBeenCalledWith('No project path found to publish custom code functions project.'); - expect(isCodefulProject).not.toHaveBeenCalled(); + expect(hasCodefulWorkflowSetting).not.toHaveBeenCalled(); }); it('skips publishing when the selected path is not codeful', async () => { - (isCodefulProject as Mock).mockResolvedValue(false); + (hasCodefulWorkflowSetting as Mock).mockResolvedValue(false); await publishCodefulProject(context, { fsPath: projectPath } as vscode.Uri); diff --git a/apps/vs-code-designer/src/app/commands/createNewCodeProject/CodeProjectBase/CreateLogicAppWorkspace.ts b/apps/vs-code-designer/src/app/commands/createNewCodeProject/CodeProjectBase/CreateLogicAppWorkspace.ts index 8e4d1bf1b7d..07dca6b0144 100644 --- a/apps/vs-code-designer/src/app/commands/createNewCodeProject/CodeProjectBase/CreateLogicAppWorkspace.ts +++ b/apps/vs-code-designer/src/app/commands/createNewCodeProject/CodeProjectBase/CreateLogicAppWorkspace.ts @@ -26,7 +26,7 @@ import { testsDirectoryName, vscodeFolderName, workerRuntimeKey, - workflowCodefulEnabled, + workflowCodefulEnabledKey, workflowFileName, } from '../../../../constants'; import { localize } from '../../../../localize'; @@ -106,7 +106,6 @@ export async function createLogicAppAndWorkflow( context.telemetry.properties.logicAppType = logicAppType || 'logicApp'; context.telemetry.properties.workflowType = workflowType || 'unknown'; - context.telemetry.properties.isCodefulWorkflow = String(logicAppType === ProjectType.codeful); await fse.ensureDir(logicAppFolderPath); if (logicAppType === ProjectType.codeful) { @@ -230,10 +229,8 @@ export async function createLocalConfigurationFiles( localSettingsJson.Values[azureWebJobsFeatureFlagsKey] = multiLanguageWorkerSetting; } - // TODO(aeldridge): Update to point to codeful private bundle once it's published. if (logicAppType === ProjectType.codeful) { - localSettingsJson.Values[workflowCodefulEnabled] = 'true'; - localSettingsJson.Values['AzureFunctionsJobHost__extensionBundle__id'] = 'Microsoft.Azure.Functions.ExtensionBundle.Workflows'; + localSettingsJson.Values[workflowCodefulEnabledKey] = 'true'; } const hostJsonPath: string = path.join(logicAppFolderPath, hostFileName); diff --git a/apps/vs-code-designer/src/app/commands/createWorkflow/__test__/createLogicAppWorkflow.test.ts b/apps/vs-code-designer/src/app/commands/createWorkflow/__test__/createLogicAppWorkflow.test.ts index cdcaa04437d..cd289778bf0 100644 --- a/apps/vs-code-designer/src/app/commands/createWorkflow/__test__/createLogicAppWorkflow.test.ts +++ b/apps/vs-code-designer/src/app/commands/createWorkflow/__test__/createLogicAppWorkflow.test.ts @@ -1,7 +1,7 @@ import { ProjectType } from '@microsoft/vscode-extension-logic-apps'; import * as vscode from 'vscode'; import { beforeEach, describe, expect, it, type Mock, vi } from 'vitest'; -import { isCodefulProject } from '../../../utils/codeful'; +import { hasCodefulWorkflowSetting } from '../../../utils/codeful'; import { addLocalFuncTelemetry } from '../../../utils/funcCoreTools/funcVersion'; import { createLogicAppAndWorkflow } from '../../createNewCodeProject/CodeProjectBase/CreateLogicAppWorkspace'; import { createLogicAppWorkflow } from '../createLogicAppWorkflow'; @@ -16,7 +16,7 @@ vi.mock('../../../utils/funcCoreTools/funcVersion', () => ({ })); vi.mock('../../../utils/codeful', () => ({ - isCodefulProject: vi.fn(), + hasCodefulWorkflowSetting: vi.fn(), })); vi.mock('../../createNewCodeProject/CodeProjectBase/CreateLogicAppWorkspace', () => ({ @@ -32,7 +32,7 @@ describe('createLogicAppWorkflow', () => { vi.clearAllMocks(); context = { telemetry: { properties: {}, measurements: {} } }; (vscode.workspace as any).workspaceFile = { fsPath: workspaceFilePath }; - (isCodefulProject as Mock).mockResolvedValue(true); + (hasCodefulWorkflowSetting as Mock).mockResolvedValue(true); }); it('creates a workflow in the open workspace and infers codeful project metadata', async () => { @@ -45,7 +45,7 @@ describe('createLogicAppWorkflow', () => { await createLogicAppWorkflow(context, options, logicAppFolderPath); expect(addLocalFuncTelemetry).toHaveBeenCalledWith(context); - expect(isCodefulProject).toHaveBeenCalledWith(logicAppFolderPath); + expect(hasCodefulWorkflowSetting).toHaveBeenCalledWith(logicAppFolderPath); expect(options).toMatchObject({ logicAppType: ProjectType.codeful, workspaceFilePath, diff --git a/apps/vs-code-designer/src/app/commands/createWorkflow/__test__/createWorkflow.test.ts b/apps/vs-code-designer/src/app/commands/createWorkflow/__test__/createWorkflow.test.ts index 5852df9b88c..73f52a9cc8a 100644 --- a/apps/vs-code-designer/src/app/commands/createWorkflow/__test__/createWorkflow.test.ts +++ b/apps/vs-code-designer/src/app/commands/createWorkflow/__test__/createWorkflow.test.ts @@ -12,7 +12,7 @@ vi.mock('../../shared/workspaceWebviewCommandHandler', () => ({ })); vi.mock('../../../utils/codeful', () => ({ - isCodefulProject: vi.fn(), + hasCodefulWorkflowSetting: vi.fn(), })); vi.mock('../../../utils/verifyIsProject', () => ({ @@ -28,7 +28,7 @@ vi.mock('../createLogicAppWorkflow', () => ({ })); import { createWorkspaceWebviewCommandHandler } from '../../shared/workspaceWebviewCommandHandler'; -import { isCodefulProject } from '../../../utils/codeful'; +import { hasCodefulWorkflowSetting } from '../../../utils/codeful'; import { tryGetLogicAppProjectRoot } from '../../../utils/verifyIsProject'; import { createLogicAppWorkflow } from '../createLogicAppWorkflow'; import { createWorkflow } from '../createWorkflow'; @@ -45,7 +45,7 @@ describe('createWorkflow', () => { vi.clearAllMocks(); (vscode.workspace as any).workspaceFolders = undefined; (vscode.workspace.getWorkspaceFolder as any) = vi.fn(); - vi.mocked(isCodefulProject).mockResolvedValue(false); + vi.mocked(hasCodefulWorkflowSetting).mockResolvedValue(false); }); describe('project collection and selection', () => { @@ -62,7 +62,7 @@ describe('createWorkflow', () => { } return undefined; }); - vi.mocked(isCodefulProject).mockImplementation(async (projectPath) => { + vi.mocked(hasCodefulWorkflowSetting).mockImplementation(async (projectPath) => { return projectPath === 'D:\\workspace\\ProjectB'; }); @@ -84,7 +84,7 @@ describe('createWorkflow', () => { const folder = { name: 'OnlyProject', uri: { fsPath: 'D:\\workspace\\OnlyProject' }, index: 0 } as vscode.WorkspaceFolder; (vscode.workspace as any).workspaceFolders = [folder]; vi.mocked(tryGetLogicAppProjectRoot).mockResolvedValue('D:\\workspace\\OnlyProject'); - vi.mocked(isCodefulProject).mockResolvedValue(true); + vi.mocked(hasCodefulWorkflowSetting).mockResolvedValue(true); await createWorkflow(context); @@ -142,7 +142,7 @@ describe('createWorkflow', () => { } return undefined; }); - vi.mocked(isCodefulProject).mockResolvedValue(true); + vi.mocked(hasCodefulWorkflowSetting).mockResolvedValue(true); await createWorkflow(context, clickedUri); diff --git a/apps/vs-code-designer/src/app/commands/createWorkflow/createLogicAppWorkflow.ts b/apps/vs-code-designer/src/app/commands/createWorkflow/createLogicAppWorkflow.ts index 813326a8384..45526e345c5 100644 --- a/apps/vs-code-designer/src/app/commands/createWorkflow/createLogicAppWorkflow.ts +++ b/apps/vs-code-designer/src/app/commands/createWorkflow/createLogicAppWorkflow.ts @@ -4,7 +4,7 @@ import { addLocalFuncTelemetry } from '../../utils/funcCoreTools/funcVersion'; import * as vscode from 'vscode'; import { createLogicAppAndWorkflow } from '../createNewCodeProject/CodeProjectBase/CreateLogicAppWorkspace'; import { localize } from '../../../localize'; -import { isCodefulProject } from '../../utils/codeful'; +import { hasCodefulWorkflowSetting } from '../../utils/codeful'; export async function createLogicAppWorkflow(context: IActionContext, options: any, logicAppFolderPath: string) { addLocalFuncTelemetry(context); @@ -13,7 +13,7 @@ export async function createLogicAppWorkflow(context: IActionContext, options: a // If logicAppType is not set in options, check if this is a codeful project if (!webviewProjectContext.logicAppType) { - const isCodeful = await isCodefulProject(logicAppFolderPath); + const isCodeful = await hasCodefulWorkflowSetting(logicAppFolderPath); if (isCodeful) { webviewProjectContext.logicAppType = ProjectType.codeful; } diff --git a/apps/vs-code-designer/src/app/commands/createWorkflow/createWorkflow.ts b/apps/vs-code-designer/src/app/commands/createWorkflow/createWorkflow.ts index 0c2d6655537..fde7d82c361 100644 --- a/apps/vs-code-designer/src/app/commands/createWorkflow/createWorkflow.ts +++ b/apps/vs-code-designer/src/app/commands/createWorkflow/createWorkflow.ts @@ -9,7 +9,7 @@ import { createWorkspaceWebviewCommandHandler } from '../shared/workspaceWebview import { localize } from '../../../localize'; import * as vscode from 'vscode'; import { createLogicAppWorkflow } from './createLogicAppWorkflow'; -import { isCodefulProject } from '../../utils/codeful'; +import { hasCodefulWorkflowSetting } from '../../utils/codeful'; import { tryGetLogicAppProjectRoot } from '../../utils/verifyIsProject'; import { getWorkflowsInLocalProject } from '../../utils/codeless/common'; import * as path from 'path'; @@ -106,7 +106,7 @@ async function collectAvailableProjects(context: IActionContext): Promise { const projectPath = 'D:\\workspace\\CodefulLogicApp'; const runtimeDependenciesPath = 'D:\\runtime-dependencies'; const lspDirectoryPath = path.join(runtimeDependenciesPath, lspDirectory); - const csprojPath = path.join(projectPath, 'CodefulLogicApp.csproj'); const nugetConfigPath = path.join(projectPath, 'nuget.config'); const installedSdkHashMarkerPath = path.join(runtimeDependenciesPath, '.lspsdk-hash'); const projectSdkHashMarkerPath = path.join(projectPath, '.nuget', '.logicapps-lspsdk-hash'); const projectSdkPackagePath = path.join(projectPath, '.nuget', 'packages', 'microsoft.azure.workflows.sdk', '1.0.0-preview.1'); const projectAssetsPath = path.join(projectPath, 'obj', 'project.assets.json'); const projectNugetCachePath = path.join(projectPath, 'obj', 'project.nuget.cache'); + const localSettingsPath = path.join(projectPath, 'local.settings.json'); + const codefulLocalSettings = JSON.stringify({ IsEncrypted: false, Values: { WORKFLOW_CODEFUL_ENABLED: 'true' } }); const currentSdkHash = 'current-sdk-hash'; - const csprojContent = ` - - - net8 - - - - -`; const codefulNugetConfig = ` @@ -87,7 +79,7 @@ describe('invalidateCodefulSdkCacheIfNeeded', () => { mocks.readdir.mockResolvedValue(['CodefulLogicApp.csproj']); mocks.statSync.mockReturnValue({ isDirectory: () => true }); setExistingPaths([ - csprojPath, + localSettingsPath, nugetConfigPath, installedSdkHashMarkerPath, projectSdkPackagePath, @@ -95,8 +87,8 @@ describe('invalidateCodefulSdkCacheIfNeeded', () => { projectNugetCachePath, ]); mocks.readFile.mockImplementation(async (filePath: string) => { - if (filePath === csprojPath) { - return csprojContent; + if (filePath === localSettingsPath) { + return codefulLocalSettings; } if (filePath === nugetConfigPath) { return codefulNugetConfig; @@ -125,10 +117,10 @@ describe('invalidateCodefulSdkCacheIfNeeded', () => { }); it('keeps the project cache when its marker already matches the installed SDK hash', async () => { - setExistingPaths([csprojPath, nugetConfigPath, installedSdkHashMarkerPath, projectSdkHashMarkerPath, projectSdkPackagePath]); + setExistingPaths([localSettingsPath, nugetConfigPath, installedSdkHashMarkerPath, projectSdkHashMarkerPath, projectSdkPackagePath]); mocks.readFile.mockImplementation(async (filePath: string) => { - if (filePath === csprojPath) { - return csprojContent; + if (filePath === localSettingsPath) { + return codefulLocalSettings; } if (filePath === nugetConfigPath) { return codefulNugetConfig; @@ -148,8 +140,8 @@ describe('invalidateCodefulSdkCacheIfNeeded', () => { it('does not touch caches for projects that do not use the extension local SDK source and project-local packages folder', async () => { mocks.readFile.mockImplementation(async (filePath: string) => { - if (filePath === csprojPath) { - return csprojContent; + if (filePath === localSettingsPath) { + return codefulLocalSettings; } if (filePath === nugetConfigPath) { return codefulNugetConfig.replace(lspDirectoryPath, 'https://api.nuget.org/v3/index.json'); diff --git a/apps/vs-code-designer/src/app/utils/__test__/extension.test.ts b/apps/vs-code-designer/src/app/utils/__test__/extension.test.ts index d5cec14ecb7..06413f4a3e2 100644 --- a/apps/vs-code-designer/src/app/utils/__test__/extension.test.ts +++ b/apps/vs-code-designer/src/app/utils/__test__/extension.test.ts @@ -1,13 +1,9 @@ -import { customExtensionContext, extensionCommand, logicAppsStandardExtensionId } from '../../../constants'; +import { extensionCommand, logicAppsStandardExtensionId } from '../../../constants'; import * as vscode from 'vscode'; import { beforeEach, describe, expect, it, vi } from 'vitest'; -import { detectCodefulWorkflow, hasCodefulWorkflowSetting } from '../codeful'; import { getExtensionVersion, initializeCustomExtensionContext, - scanWorkspaceForCodefulWorkflows, - updateCodefulWorkflowContext, - updateCodefulWorkflowFilesContext, updateLogicAppsContext, } from '../extension'; import { getWorkspaceFolderWithoutPrompting } from '../workspace'; @@ -21,11 +17,6 @@ vi.mock('../verifyIsProject', () => ({ isLogicAppProjectInRoot: vi.fn(), })); -vi.mock('../codeful', () => ({ - detectCodefulWorkflow: vi.fn(), - hasCodefulWorkflowSetting: vi.fn(), -})); - describe('extension utilities', () => { beforeEach(() => { vi.clearAllMocks(); @@ -71,42 +62,4 @@ describe('extension utilities', () => { expect(isLogicAppProjectInRoot).toHaveBeenCalledWith(workspaceFolder); expect(vscode.commands.executeCommand).toHaveBeenCalledWith('setContext', 'logicApps.hasProject', true); }); - - it('scans only codeful projects for workflow C# files', async () => { - const settingsUri = { fsPath: '/workspace/logicapp/local.settings.json' }; - const workflowUri = { fsPath: '/workspace/logicapp/Workflow.cs' }; - const unrelatedUri = { fsPath: '/workspace/other/Workflow.cs' }; - (vscode.workspace.findFiles as any).mockResolvedValueOnce([settingsUri]).mockResolvedValueOnce([workflowUri, unrelatedUri]); - (hasCodefulWorkflowSetting as any).mockResolvedValue(true); - (vscode.workspace.openTextDocument as any).mockResolvedValue({ - getText: () => 'workflow source', - }); - (detectCodefulWorkflow as any).mockReturnValue({ workflowName: 'workflow' }); - - await expect(scanWorkspaceForCodefulWorkflows()).resolves.toEqual([workflowUri.fsPath]); - expect(vscode.workspace.openTextDocument).toHaveBeenCalledWith(workflowUri); - expect(vscode.workspace.openTextDocument).not.toHaveBeenCalledWith(unrelatedUri); - }); - - it('updates codeful workflow file context from the workspace scan', async () => { - (vscode.workspace.findFiles as any).mockResolvedValue([]); - - await updateCodefulWorkflowFilesContext(); - - expect(vscode.commands.executeCommand).toHaveBeenCalledWith('setContext', customExtensionContext.codefulWorkflowFiles, []); - }); - - it('updates active editor context for C# workflow files', () => { - (detectCodefulWorkflow as any).mockReturnValue({ workflowName: 'workflow' }); - - updateCodefulWorkflowContext({ - document: { - fileName: 'D:\\workspace\\logicapp\\Workflow.cs', - getText: () => 'workflow source', - languageId: 'csharp', - }, - } as any); - - expect(vscode.commands.executeCommand).toHaveBeenCalledWith('setContext', customExtensionContext.isCodefulWorkflowFile, true); - }); }); diff --git a/apps/vs-code-designer/src/app/utils/appSettings/localSettings.ts b/apps/vs-code-designer/src/app/utils/appSettings/localSettings.ts index 87a8336de46..f6cc82c683b 100644 --- a/apps/vs-code-designer/src/app/utils/appSettings/localSettings.ts +++ b/apps/vs-code-designer/src/app/utils/appSettings/localSettings.ts @@ -14,6 +14,7 @@ import { azureStorageTypeSetting, functionsInprocNet8Enabled, functionsInprocNet8EnabledTrue, + workflowCodefulEnabledKey, } from '../../../constants'; import { localize } from '../../../localize'; import { decryptLocalSettings } from '../../commands/appSettings/decryptLocalSettings'; @@ -99,6 +100,8 @@ export async function getLocalSettingsJson( try { const localSettings = parseJson(data) as ILocalSettingsJson; const decryptedlocalSettings = await getDecryptedLocalSettings(context, localSettings, localSettingsUri, localSettingsPath); + decryptedlocalSettings.Values ??= {}; + if (isDesignTime) { decryptedlocalSettings.Values[azureWebJobsSecretStorageTypeKey] = azureStorageTypeSetting; delete decryptedlocalSettings.Values[azureWebJobsStorageKey]; @@ -184,24 +187,22 @@ export const getLocalSettingsSchema = (isDesignTime: boolean, projectPath?: stri // Add project path if provided if (projectPath) { - baseSettings.Values[ProjectDirectoryPathKey] = projectPath; + baseSettings.Values![ProjectDirectoryPathKey] = projectPath; } // Add runtime-specific settings if (isDesignTime) { - baseSettings.Values[workerRuntimeKey] = WorkerRuntime.Node; - baseSettings.Values[azureWebJobsSecretStorageTypeKey] = azureStorageTypeSetting; + baseSettings.Values![workerRuntimeKey] = WorkerRuntime.Node; + baseSettings.Values![azureWebJobsSecretStorageTypeKey] = azureStorageTypeSetting; } else { - baseSettings.Values[workerRuntimeKey] = WorkerRuntime.Dotnet; - baseSettings.Values[azureWebJobsStorageKey] = localEmulatorConnectionString; - baseSettings.Values[functionsInprocNet8Enabled] = functionsInprocNet8EnabledTrue; + baseSettings.Values![workerRuntimeKey] = WorkerRuntime.Dotnet; + baseSettings.Values![azureWebJobsStorageKey] = localEmulatorConnectionString; + baseSettings.Values![functionsInprocNet8Enabled] = functionsInprocNet8EnabledTrue; } // Add codeful-specific settings if (isCodeful) { - Object.assign(baseSettings.Values, { - WORKFLOW_CODEFUL_ENABLED: 'true', - }); + baseSettings.Values![workflowCodefulEnabledKey] = 'true'; } return baseSettings; diff --git a/apps/vs-code-designer/src/app/utils/codeful.ts b/apps/vs-code-designer/src/app/utils/codeful.ts index 5c8eea70d26..ddd5b30154f 100644 --- a/apps/vs-code-designer/src/app/utils/codeful.ts +++ b/apps/vs-code-designer/src/app/utils/codeful.ts @@ -1,7 +1,7 @@ import path from 'path'; import * as fse from 'fs-extra'; import * as vscode from 'vscode'; -import { autoRuntimeDependenciesPathSettingKey, localSettingsFileName, lspDirectory } from '../../constants'; +import { autoRuntimeDependenciesPathSettingKey, defaultDependencyPathValue, localSettingsFileName, lspDirectory, workflowCodefulEnabledKey } from '../../constants'; import { ext } from '../../extensionVariables'; import { getGlobalSetting } from './vsCodeConfig/settings'; @@ -31,10 +31,6 @@ export async function codefulProjectsExist(): Promise { * Checks if the codeful agent is enabled for a given folder by examining the local settings file. * @param folderPath - The path to the folder containing the local settings file * @returns A promise that resolves to true if the codeful agent is enabled, false otherwise - * @remarks - * This function reads the local settings file (typically local.settings.json) from the specified - * folder path and checks for the WORKFLOW_CODEFUL_ENABLED flag in the Values section. - * Returns false if the file doesn't exist, cannot be read, or doesn't contain valid JSON. */ export const hasCodefulWorkflowSetting = async (folderPath: string): Promise => { const localSettingsFilePath = path.join(folderPath, localSettingsFileName); @@ -45,18 +41,18 @@ export const hasCodefulWorkflowSetting = async (folderPath: string): Promise} Returns true if the folder is a custom code functions project, otherwise false. + * @returns {Promise} Returns true if the folder contains a .NET 8 project with a reference to the codeful SDK, otherwise false. */ -export const isCodefulProject = async (folderPath: string): Promise => { +export const hasCodefulSdkReference = async (folderPath: string): Promise => { try { if (!fse.statSync(folderPath).isDirectory()) { return false; @@ -79,11 +75,11 @@ export const isCodefulProject = async (folderPath: string): Promise => * when the VSIX ships changed nupkg bits with the same package ID/version. */ export const invalidateCodefulSdkCacheIfNeeded = async (projectPath: string): Promise => { - if (!(await isCodefulProject(projectPath))) { + if (!(await hasCodefulWorkflowSetting(projectPath))) { return false; } - const targetDirectory = getGlobalSetting(autoRuntimeDependenciesPathSettingKey); + const targetDirectory = getGlobalSetting(autoRuntimeDependenciesPathSettingKey) || defaultDependencyPathValue; const lspDirectoryPath = path.join(targetDirectory, lspDirectory); const nugetConfigPath = path.join(projectPath, 'nuget.config'); const installedSdkHashMarkerPath = path.join(targetDirectory, lspSdkHashMarkerName); diff --git a/apps/vs-code-designer/src/app/utils/codeless/startDesignTimeApi.ts b/apps/vs-code-designer/src/app/utils/codeless/startDesignTimeApi.ts index a6a47a24d49..c1e4994343d 100644 --- a/apps/vs-code-designer/src/app/utils/codeless/startDesignTimeApi.ts +++ b/apps/vs-code-designer/src/app/utils/codeless/startDesignTimeApi.ts @@ -53,7 +53,7 @@ import { Uri, window, workspace, type MessageItem } from 'vscode'; import { findChildProcess } from '../../commands/pickFuncProcess'; import find_process from 'find-process'; import { getChildProcessesWithScript } from '../findChildProcess/findChildProcess'; -import { isCodefulProject } from '../codeful'; +import { hasCodefulSdkReference } from '../codeful'; import { ensureExtensionBundleHealthy, isExtensionBundleDownloadInFlight, @@ -255,7 +255,7 @@ export async function startDesignTimeApi(projectPath: string): Promise { ext.outputChannel.appendLog(localize('startingDesignTimeApi', 'Starting Design Time Api for project: {0}', projectPath)); const designTimeDirectory: Uri | undefined = await getOrCreateDesignTimeDirectory(designTimeDirectoryName, projectPath); - const isCodeful = (await isCodefulProject(projectPath)) ?? false; + const isCodeful = (await hasCodefulSdkReference(projectPath)) ?? false; const settingsFileContent = getLocalSettingsSchema(true, projectPath, isCodeful); const hostFileContent: any = { @@ -767,7 +767,8 @@ export async function promptStartDesignTimeOption(context: IActionContext) { for (const projectPath of logicAppFolders) { if (!fs.existsSync(path.join(projectPath, localSettingsFileName))) { - const settingsFileContent = getLocalSettingsSchema(false, projectPath); + const isCodeful = (await hasCodefulSdkReference(projectPath)) ?? false; + const settingsFileContent = getLocalSettingsSchema(false, projectPath, isCodeful); const projectUri: Uri = Uri.file(projectPath); await createJsonFile(projectUri, localSettingsFileName, settingsFileContent); } diff --git a/apps/vs-code-designer/src/app/utils/extension.ts b/apps/vs-code-designer/src/app/utils/extension.ts index fa87858db6a..f8adbe9a644 100644 --- a/apps/vs-code-designer/src/app/utils/extension.ts +++ b/apps/vs-code-designer/src/app/utils/extension.ts @@ -2,7 +2,7 @@ * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { extensionCommand, logicAppsStandardExtensionId, customExtensionContext } from '../../constants'; +import { extensionCommand, logicAppsStandardExtensionId } from '../../constants'; import * as vscode from 'vscode'; import { supportedDataMapDefinitionFileExts, @@ -11,8 +11,6 @@ import { } from '../commands/dataMapper/extensionConfig'; import { getWorkspaceFolderWithoutPrompting } from './workspace'; import { isLogicAppProjectInRoot } from './verifyIsProject'; -import { detectCodefulWorkflow, hasCodefulWorkflowSetting } from './codeful'; -import * as path from 'path'; /** * Gets extension version from the package.json version. @@ -57,133 +55,3 @@ export async function updateLogicAppsContext() { await vscode.commands.executeCommand('setContext', 'logicApps.hasProject', logicAppOpened); } } - -/** - * Scans workspace for .cs files that are codeful workflows and returns their paths. - * Only scans projects that have WORKFLOW_CODEFUL_ENABLED set to true in local.settings.json. - * @returns Array of file paths that contain codeful workflow definitions - */ -export async function scanWorkspaceForCodefulWorkflows(): Promise { - const codefulWorkflowFiles: string[] = []; - - // First, find all local.settings.json files - const settingsFiles = await vscode.workspace.findFiles('**/local.settings.json', '**/node_modules/**'); - - // Check which projects have codeful workflows enabled - const codefulProjectPaths: string[] = []; - for (const settingsUri of settingsFiles) { - const projectPath = path.dirname(settingsUri.fsPath); - const isCodeful = await hasCodefulWorkflowSetting(projectPath); - if (isCodeful) { - codefulProjectPaths.push(projectPath); - } - } - - // If no codeful projects found, return empty array - if (codefulProjectPaths.length === 0) { - return codefulWorkflowFiles; - } - - // Now scan .cs files only in codeful project directories - const csFiles = await vscode.workspace.findFiles('**/*.cs', '**/node_modules/**'); - - for (const fileUri of csFiles) { - // Check if this .cs file is within a codeful project - const filePath = fileUri.fsPath; - const isInCodefulProject = codefulProjectPaths.some((projectPath) => filePath.startsWith(projectPath)); - - if (!isInCodefulProject) { - continue; - } - - try { - const document = await vscode.workspace.openTextDocument(fileUri); - const fileContent = document.getText(); - const workflowInfo = detectCodefulWorkflow(fileContent); - - if (workflowInfo) { - codefulWorkflowFiles.push(fileUri.fsPath); - } - } catch { - // Skip files that can't be read - } - } - - return codefulWorkflowFiles; -} - -/** - * Updates context with the list of codeful workflow file paths. - */ -export async function updateCodefulWorkflowFilesContext(): Promise { - const codefulWorkflowFiles = await scanWorkspaceForCodefulWorkflows(); - await vscode.commands.executeCommand('setContext', customExtensionContext.codefulWorkflowFiles, codefulWorkflowFiles); -} - -/** - * Updates the context to indicate if the current file is a codeful workflow file. - * @param editor The active text editor - */ -export function updateCodefulWorkflowContext(editor: vscode.TextEditor | undefined): void { - if (!editor) { - vscode.commands.executeCommand('setContext', customExtensionContext.isCodefulWorkflowFile, false); - return; - } - - const document = editor.document; - - // Only check .cs files - if (document.languageId !== 'csharp' || !document.fileName.endsWith('.cs')) { - vscode.commands.executeCommand('setContext', customExtensionContext.isCodefulWorkflowFile, false); - return; - } - - try { - const fileContent = document.getText(); - const workflowInfo = detectCodefulWorkflow(fileContent); - - vscode.commands.executeCommand('setContext', customExtensionContext.isCodefulWorkflowFile, !!workflowInfo); - } catch { - vscode.commands.executeCommand('setContext', customExtensionContext.isCodefulWorkflowFile, false); - } -} - -/** - * Registers a listener for active editor changes to update codeful workflow context. - * Also scans workspace for codeful workflow files and watches for file changes. - * @param context The extension context - */ -export function registerCodefulWorkflowContextListener(context: vscode.ExtensionContext): void { - // Update context for the currently active editor - updateCodefulWorkflowContext(vscode.window.activeTextEditor); - - // Initial scan of workspace for codeful workflow files - updateCodefulWorkflowFilesContext(); - - // Register listener for active editor changes - context.subscriptions.push( - vscode.window.onDidChangeActiveTextEditor((editor) => { - updateCodefulWorkflowContext(editor); - }) - ); - - // Also listen to document changes for the active editor - context.subscriptions.push( - vscode.workspace.onDidChangeTextDocument((event) => { - if (vscode.window.activeTextEditor && event.document === vscode.window.activeTextEditor.document) { - updateCodefulWorkflowContext(vscode.window.activeTextEditor); - } - }) - ); - - // Watch for .cs file changes and updates - const fileWatcher = vscode.workspace.createFileSystemWatcher('**/*.cs'); - - context.subscriptions.push(fileWatcher.onDidCreate(() => updateCodefulWorkflowFilesContext())); - - context.subscriptions.push(fileWatcher.onDidChange(() => updateCodefulWorkflowFilesContext())); - - context.subscriptions.push(fileWatcher.onDidDelete(() => updateCodefulWorkflowFilesContext())); - - context.subscriptions.push(fileWatcher); -} diff --git a/apps/vs-code-designer/src/app/utils/verifyIsProject.ts b/apps/vs-code-designer/src/app/utils/verifyIsProject.ts index a2b035c01ad..28a33cfcaba 100644 --- a/apps/vs-code-designer/src/app/utils/verifyIsProject.ts +++ b/apps/vs-code-designer/src/app/utils/verifyIsProject.ts @@ -96,21 +96,21 @@ export async function isLogicAppProject(folderPath: string): Promise { const hasValidCodefulWorkflow = validCodefulWorkflowChecks.some((valid) => valid); const hasValidCodelessWorkflow = validWorkflowChecks.some((valid) => valid); - const isCodefulAgent = await hasCodefulWorkflowSetting(folderPath); + const isCodeful = await hasCodefulWorkflowSetting(folderPath); - if (isCodefulAgent) { + if (isCodeful) { vscode.commands.executeCommand('setContext', customExtensionContext.isCodeful, true); } // Only return false if none of the possible validation mechanisms are present - if (!(hasValidCodelessWorkflow || hasValidCodefulWorkflow || isCodefulAgent)) { + if (!(hasValidCodelessWorkflow || hasValidCodefulWorkflow || isCodeful)) { return false; } try { const hostJsonData = await fse.readFile(hostFilePath, 'utf-8'); const hostJson = JSON.parse(hostJsonData); - return hostJson?.extensionBundle?.id === extensionBundleId || hasValidCodefulWorkflow || isCodefulAgent; + return hostJson?.extensionBundle?.id === extensionBundleId || hasValidCodefulWorkflow || isCodeful; } catch { return false; } diff --git a/apps/vs-code-designer/src/constants.ts b/apps/vs-code-designer/src/constants.ts index 5d8eb04c2cc..0742f38efe1 100644 --- a/apps/vs-code-designer/src/constants.ts +++ b/apps/vs-code-designer/src/constants.ts @@ -98,6 +98,7 @@ export const customConnectorResourceGroupNameKey = 'CUSTOM_CONNECTOR_RESOURCE_GR export const workflowSubscriptionIdKey = 'WORKFLOWS_SUBSCRIPTION_ID'; export const workflowTenantIdKey = 'WORKFLOWS_TENANT_ID'; export const workflowManagementBaseURIKey = 'WORKFLOWS_MANAGEMENT_BASE_URI'; +export const workflowCodefulEnabledKey = 'WORKFLOW_CODEFUL_ENABLED'; export const workflowAppApiVersion = '2018-11-01'; export const hybridAppApiVersion = '2024-02-02-preview'; export const azureWebJobsStorageKey = 'AzureWebJobsStorage'; @@ -112,8 +113,6 @@ export const workflowAppAADObjectId = 'WORKFLOWAPP_AAD_OBJECTID'; export const workflowAppAADTenantId = 'WORKFLOWAPP_AAD_TENANTID'; export const workflowAppAADClientSecret = 'WORKFLOWAPP_AAD_CLIENTSECRET'; export const debugSymbolDll = 'Microsoft.Azure.Workflows.BuildTasks.DebugSymbolGenerator.dll'; -// Codeful settings -export const workflowCodefulEnabled = 'WORKFLOW_CODEFUL_ENABLED'; export const WorkflowKind = { stateful: 'Stateful', @@ -222,8 +221,6 @@ export type extensionCommand = (typeof extensionCommand)[keyof typeof extensionC // Extension context export const customExtensionContext = { isCodeful: 'azureLogicAppsStandard.isCodeful', - isCodefulWorkflowFile: 'azureLogicAppsStandard.isCodefulWorkflowFile', - codefulWorkflowFiles: 'azureLogicAppsStandard.codefulWorkflowFiles', } as const; export type customExtensionContext = (typeof customExtensionContext)[keyof typeof customExtensionContext]; diff --git a/apps/vs-code-designer/src/main.ts b/apps/vs-code-designer/src/main.ts index 6d6ea19b880..50d29237e57 100644 --- a/apps/vs-code-designer/src/main.ts +++ b/apps/vs-code-designer/src/main.ts @@ -7,12 +7,7 @@ import type { AzureAccountTreeItemWithProjects } from './app/tree/AzureAccountTr import { downloadExtensionBundle } from './app/utils/bundleFeed'; import { stopAllDesignTimeApis } from './app/utils/codeless/startDesignTimeApi'; import { UriHandler } from './app/utils/codeless/urihandler'; -import { - getExtensionVersion, - initializeCustomExtensionContext, - registerCodefulWorkflowContextListener, - updateLogicAppsContext, -} from './app/utils/extension'; +import { getExtensionVersion, initializeCustomExtensionContext, updateLogicAppsContext } from './app/utils/extension'; import { registerFuncHostTaskEvents } from './app/utils/funcCoreTools/funcHostTask'; import { shouldRequireStrictDependencyValidation } from './app/utils/strictDependencyValidation'; import { verifyVSCodeConfigOnActivate } from './app/utils/vsCodeConfig/verifyVSCodeConfigOnActivate'; @@ -43,7 +38,7 @@ import { codefulProjectsExist } from './app/utils/codeful'; const perfStats = { loadStartTime: Date.now(), - loadEndTime: undefined, + loadEndTime: undefined as number | undefined, }; const telemetryString = 'setInGitHubBuild'; @@ -198,10 +193,7 @@ export async function activate(context: vscode.ExtensionContext) { activateContext.telemetry.properties.lastStep = 'registerFuncHostTaskEvents'; registerFuncHostTaskEvents(); - // Register codeful workflow context listener - registerCodefulWorkflowContextListener(context); - - ext.rgApi.registerApplicationResourceResolver(getAzExtResourceType(logicAppFilter), new LogicAppResolver()); + ext.rgApi.registerApplicationResourceResolver(getAzExtResourceType(logicAppFilter)!, new LogicAppResolver()); const azureResourcesApi = await getAzureResourcesExtensionApi(context, '2.0.0'); ext.rgApiV2 = azureResourcesApi; diff --git a/apps/vs-code-react/src/app/designer/appV2.tsx b/apps/vs-code-react/src/app/designer/appV2.tsx index 8e670e9e8b4..a041b31525c 100644 --- a/apps/vs-code-react/src/app/designer/appV2.tsx +++ b/apps/vs-code-react/src/app/designer/appV2.tsx @@ -27,6 +27,7 @@ import { commonMessages, useIntlMessages } from '../../intl'; import { useAppStyles } from './appStyles'; import { DesignerViewType } from './constants'; import CodeViewEditor from './CodeViewEditor'; +import { workflowCodefulEnabledKey } from '../../../../vs-code-designer/src/constants'; export const DesignerApp = () => { const vscode = useContext(VSCodeContext); @@ -61,7 +62,7 @@ export const DesignerApp = () => { const [initialWorkflow, setInitialWorkflow] = useState(panelMetaData?.standardApp); const [workflow, setWorkflow] = useState(panelMetaData?.standardApp); const [customCode, setCustomCode] = useState | undefined>(panelMetaData?.customCodeData); - const isCodefulWorkflow = panelMetaData?.localSettings?.WORKFLOW_CODEFUL_ENABLED === 'true'; + const isCodefulWorkflow = panelMetaData?.localSettings?.[workflowCodefulEnabledKey] === 'true'; const [designerID, setDesignerID] = useState(guid()); const [workflowDefinitionId, setWorkflowDefinitionId] = useState(guid());