Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
1d5f155
test(vscode): characterize local.settings.json + creation-path conten…
lambrianmsft Jul 8, 2026
9b03c75
feat(vscode): regenerate local.settings.json and workflow-designtime …
lambrianmsft Jul 8, 2026
4c15360
fix(vscode): align regenerated root local.settings.json key order wit…
lambrianmsft Jul 8, 2026
3842845
test(vscode): cover codeful regeneration of an existing-but-invalid d…
lambrianmsft Jul 8, 2026
1204623
feat(vscode): regenerate project-level host.json for source-controlle…
lambrianmsft Jul 8, 2026
e033fd0
chore(vscode): add diagnostic logging for project artifact regeneration
lambrianmsft Jul 8, 2026
4e5569e
test(vscode): cover validateAndRegenerateProjectArtifacts orchestrato…
lambrianmsft Jul 8, 2026
876fe04
test(vscode): characterize pre-change regen local.settings baseline p…
lambrianmsft Jul 9, 2026
7e37fb8
refactor(vscode): share one root local.settings builder between creat…
lambrianmsft Jul 9, 2026
ad86c92
feat(vscode): detect logic app project type when regenerating local.s…
lambrianmsft Jul 9, 2026
eea1b32
fix(vscode): tighten host.json validation for bundle version and desi…
lambrianmsft Jul 9, 2026
dc7f4b4
fix(vscode): use path-segment boundary when detecting the design-time…
lambrianmsft Jul 9, 2026
8454705
refactor(vscode): prune redundant inline comments around artifact reg…
lambrianmsft Jul 9, 2026
1f87218
chore(vscode): align regeneration code with upstream codeful API renames
lambrianmsft Jul 9, 2026
4454479
refactor(vscode): merge getRootLocalSettings into getLocalSettingsSchema
lambrianmsft Jul 10, 2026
79eb773
Recognize logic app projects by workflow signal, not host.json
lambrianmsft Jul 10, 2026
312ef7f
feat(vscode): detect codeful Logic App projects structurally via .csp…
lambrianmsft Jul 10, 2026
cdad122
refactor(vscode): drop redundant codeful-setting check from isLogicAp…
lambrianmsft Jul 10, 2026
e57b58a
test(vscode): rename 'golden' characterization tests to 'expected'
lambrianmsft Jul 10, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,32 +1,21 @@
import {
appKindSetting,
artifactsDirectory,
assetsFolderName,
autoRuntimeDependenciesPathSettingKey,
azureWebJobsFeatureFlagsKey,
azureWebJobsStorageKey,
defaultVersionRange,
devContainerFolderName,
extensionBundleId,
extensionCommand,
funcIgnoreFileName,
functionsInprocNet8Enabled,
functionsInprocNet8EnabledTrue,
gitignoreFileName,
hostFileName,
libDirectory,
localEmulatorConnectionString,
localSettingsFileName,
logicAppKind,
lspDirectory,
multiLanguageWorkerSetting,
ProjectDirectoryPathKey,
rulesDirectory,
schemasDirectory,
testsDirectoryName,
vscodeFolderName,
workerRuntimeKey,
workflowCodefulEnabledKey,
workflowFileName,
} from '../../../../constants';
import { localize } from '../../../../localize';
Expand All @@ -43,14 +32,9 @@ import { gitInit, isGitInstalled, isInsideRepo } from '../../../utils/git';
import { writeFormattedJson } from '../../../utils/fs';
import { getCodelessWorkflowTemplate } from '../../../utils/codeless/templates';
import { CreateFunctionAppFiles } from './CreateFunctionAppFiles';
import type {
IFunctionWizardContext,
IHostJsonV2,
ILocalSettingsJson,
IWebviewProjectContext,
StandardApp,
} from '@microsoft/vscode-extension-logic-apps';
import { WorkerRuntime, ProjectType, WorkflowType } from '@microsoft/vscode-extension-logic-apps';
import type { IFunctionWizardContext, IHostJsonV2, IWebviewProjectContext, StandardApp } from '@microsoft/vscode-extension-logic-apps';
import { ProjectType, WorkflowType } from '@microsoft/vscode-extension-logic-apps';
import { getLocalSettingsSchema } from '../../../utils/appSettings/localSettings';
import { createDevContainerContents, createLogicAppVsCodeContents } from './CreateLogicAppVSCodeContents';
import { logicAppPackageProcessing, unzipLogicAppPackageIntoWorkspace } from '../../../utils/cloudToLocalUtils';
import { isLogicAppProject } from '../../../utils/verifyIsProject';
Expand Down Expand Up @@ -213,24 +197,10 @@ export async function createLocalConfigurationFiles(
'.debug',
'workflow-designtime/',
];
const localSettingsJson: ILocalSettingsJson = {
IsEncrypted: false,
Values: {
[azureWebJobsStorageKey]: localEmulatorConnectionString,
[functionsInprocNet8Enabled]: functionsInprocNet8EnabledTrue,
[workerRuntimeKey]: WorkerRuntime.Dotnet,
[appKindSetting]: logicAppKind,
[ProjectDirectoryPathKey]: logicAppFolderPath,
},
};
const localSettingsJson = getLocalSettingsSchema(false, logicAppFolderPath, logicAppType);

if (logicAppType !== ProjectType.logicApp) {
funcignore.push('global.json');
localSettingsJson.Values[azureWebJobsFeatureFlagsKey] = multiLanguageWorkerSetting;
}

if (logicAppType === ProjectType.codeful) {
localSettingsJson.Values[workflowCodefulEnabledKey] = 'true';
}

const hostJsonPath: string = path.join(logicAppFolderPath, hostFileName);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1308,6 +1308,13 @@ describe('createLocalConfigurationFiles', () => {
workflowName: 'TestWorkflow',
} as any;

const mockContextCodeful: IWebviewProjectContext = {
workspaceName: 'TestWorkspace',
logicAppName: 'TestLogicApp',
logicAppType: ProjectType.codeful,
workflowName: 'TestWorkflow',
} as any;

const logicAppFolderPath = actualPath.join('test', 'workspace', 'TestLogicApp');

beforeEach(() => {
Expand Down Expand Up @@ -1506,6 +1513,31 @@ describe('createLocalConfigurationFiles', () => {
expect(Object.keys(localSettingsData.Values)).toHaveLength(6);
});

it('should create local.settings.json with exact required values for codeful projects', async () => {
await CreateLogicAppWorkspaceModule.createLocalConfigurationFiles(mockContextCodeful, logicAppFolderPath);

const localSettingsCall = vi
.mocked(fsUtils.writeFormattedJson)
.mock.calls.find((call) => call[0].toString().includes('local.settings.json'));
expect(localSettingsCall).toBeDefined();
const localSettingsData = localSettingsCall![1] as any;

// Codeful is not a plain logic app (so it gets the multi-language worker flag) and additionally
// gets the codeful-enabled flag.
expect(localSettingsData.Values).toEqual({
AzureWebJobsStorage: 'UseDevelopmentStorage=true',
FUNCTIONS_INPROC_NET8_ENABLED: '1',
FUNCTIONS_WORKER_RUNTIME: 'dotnet',
APP_KIND: 'workflowapp',
ProjectDirectoryPath: path.join('test', 'workspace', 'TestLogicApp'),
AzureWebJobsFeatureFlags: 'EnableMultiLanguageWorker',
WORKFLOW_CODEFUL_ENABLED: 'true',
});

// Verify exactly 7 properties exist (5 standard + feature flag + codeful-enabled).
expect(Object.keys(localSettingsData.Values)).toHaveLength(7);
});

it('should include extension bundle configuration in host.json', async () => {
await CreateLogicAppWorkspaceModule.createLocalConfigurationFiles(mockContext, logicAppFolderPath);

Expand Down
143 changes: 139 additions & 4 deletions apps/vs-code-designer/src/app/utils/__test__/verifyIsProject.test.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
import { describe, it, expect, vi, afterEach } from 'vitest';
import type { WorkspaceFolder } from 'vscode';
import * as fse from 'fs-extra';
import * as path from 'path';
import * as verifyIsProject from '../verifyIsProject';
import { hostFileName, workflowFileName } from '../../../constants';
import { hostFileName, localSettingsFileName, workflowFileName } from '../../../constants';

describe('tryGetAllLogicAppProjectRoots', () => {
const testWorkspaceFolderPath = path.join('test', 'workspace', 'LogicApp1');
Expand Down Expand Up @@ -102,7 +102,40 @@ describe('tryGetAllLogicAppProjectRoots', () => {
return [];
});
vi.spyOn(fse, 'readFile').mockImplementation(async (filePath: fse.PathLike) => {
// A workflow.json that is not a Microsoft.Logic workflow definition is not a Logic Apps signal.
if (filePath === path.join(testWorkspaceFolderPath, 'sub1', workflowFileName)) {
return JSON.stringify({ definition: { $schema: 'https://example.com/not-a-logic-workflow.json#' } });
}
return '';
});

const result = await verifyIsProject.tryGetAllLogicAppProjectRoots(testWorkspaceFolder);
expect(result).toEqual([]);
});
});

describe('isLogicAppProject', () => {
const projectPath = path.join('test', 'LogicApp');

afterEach(() => {
vi.clearAllMocks();
});

it('returns false when the folder does not exist', async () => {
vi.spyOn(fse, 'pathExists').mockResolvedValue(false);
expect(await verifyIsProject.isLogicAppProject(projectPath)).toBe(false);
});

it('detects a codeless project via workflow.json even when host.json is missing', async () => {
const workflowJsonPath = path.join(projectPath, 'stateful1', workflowFileName);
// No host.json, no local.settings.json, no .csproj on disk — only the workflow folder exists.
vi.spyOn(fse, 'pathExists').mockImplementation(async (p: fse.PathLike) => {
const s = String(p);
return s === projectPath || s === workflowJsonPath;
});
vi.spyOn(fse, 'readdir').mockImplementation(async (p: fse.PathLike) => (String(p) === projectPath ? ['stateful1'] : []));
vi.spyOn(fse, 'readFile').mockImplementation(async (p: fse.PathLike) => {
if (String(p) === workflowJsonPath) {
return JSON.stringify({
definition: {
$schema: 'https://schema.management.azure.com/providers/Microsoft.Logic/schemas/2016-06-01/workflowdefinition.json#',
Expand All @@ -112,7 +145,109 @@ describe('tryGetAllLogicAppProjectRoots', () => {
return '';
});

const result = await verifyIsProject.tryGetAllLogicAppProjectRoots(testWorkspaceFolder);
expect(result).toEqual([]);
expect(await verifyIsProject.isLogicAppProject(projectPath)).toBe(true);
});

it('detects a codeless project even when a present host.json is corrupted', async () => {
const workflowJsonPath = path.join(projectPath, 'stateful1', workflowFileName);
vi.spyOn(fse, 'pathExists').mockImplementation(async (p: fse.PathLike) => {
const s = String(p);
return s === projectPath || s === path.join(projectPath, hostFileName) || s === workflowJsonPath;
});
vi.spyOn(fse, 'readdir').mockImplementation(async (p: fse.PathLike) => (String(p) === projectPath ? [hostFileName, 'stateful1'] : []));
vi.spyOn(fse, 'readFile').mockImplementation(async (p: fse.PathLike) => {
if (String(p) === path.join(projectPath, hostFileName)) {
return 'this is not valid json {';
}
if (String(p) === workflowJsonPath) {
return JSON.stringify({
definition: {
$schema: 'https://schema.management.azure.com/providers/Microsoft.Logic/schemas/2016-06-01/workflowdefinition.json#',
},
});
}
return '';
});

expect(await verifyIsProject.isLogicAppProject(projectPath)).toBe(true);
});

it('detects a codeful project via its .csproj SDK reference regardless of the workflow .cs file name', async () => {
const csprojPath = path.join(projectPath, 'MyLogicApp.csproj');
// A codeful project is a .NET 8 project referencing the Logic Apps SDK; the workflow C# file
// can be named anything (here, MyCustomWorkflow.cs — not workflow.cs). Detection is structural
// (via the .csproj), so a literal workflow.cs is never required.
vi.spyOn(fse, 'statSync').mockReturnValue({ isDirectory: () => true } as unknown as fse.Stats);
vi.spyOn(fse, 'pathExists').mockImplementation(async (p: fse.PathLike) => String(p) === projectPath);
vi.spyOn(fse, 'readdir').mockImplementation(async (p: fse.PathLike) =>
String(p) === projectPath ? ['MyLogicApp.csproj', 'MyCustomWorkflow.cs', 'Program.cs'] : []
);
vi.spyOn(fse, 'readFile').mockImplementation(async (p: fse.PathLike) => {
if (String(p) === csprojPath) {
return [
'<Project Sdk="Microsoft.NET.Sdk">',
' <PropertyGroup><TargetFramework>net8</TargetFramework></PropertyGroup>',
' <ItemGroup><PackageReference Include="Microsoft.Azure.Workflows.Sdk" Version="1.0.0-preview.1" /></ItemGroup>',
'</Project>',
].join('\n');
}
return '';
});

expect(await verifyIsProject.isLogicAppProject(projectPath)).toBe(true);
});

it('does not tag a plain Functions project (host.json present, no workflow signal)', async () => {
const localSettingsPath = path.join(projectPath, localSettingsFileName);
vi.spyOn(fse, 'pathExists').mockImplementation(async (p: fse.PathLike) => {
const s = String(p);
return s === projectPath || s === path.join(projectPath, hostFileName) || s === localSettingsPath;
});
vi.spyOn(fse, 'readdir').mockImplementation(async (p: fse.PathLike) =>
String(p) === projectPath ? [hostFileName, localSettingsFileName, 'MyHttpFunction'] : []
);
vi.spyOn(fse, 'readFile').mockImplementation(async (p: fse.PathLike) => {
if (String(p) === localSettingsPath) {
return JSON.stringify({ Values: { FUNCTIONS_WORKER_RUNTIME: 'dotnet' } });
}
return '';
});

expect(await verifyIsProject.isLogicAppProject(projectPath)).toBe(false);
});

it('does not tag a folder that only has the codeful workspace setting but no .csproj', async () => {
// WORKFLOW_CODEFUL_ENABLED lives in the gitignored local.settings.json and is no longer a
// detection signal — without the authoritative .csproj SDK reference the folder is not a project.
const localSettingsPath = path.join(projectPath, localSettingsFileName);
vi.spyOn(fse, 'pathExists').mockImplementation(async (p: fse.PathLike) => {
const s = String(p);
return s === projectPath || s === localSettingsPath;
});
vi.spyOn(fse, 'readdir').mockImplementation(async (p: fse.PathLike) =>
String(p) === projectPath ? [localSettingsFileName, 'Program.cs'] : []
);
vi.spyOn(fse, 'readFile').mockImplementation(async (p: fse.PathLike) => {
if (String(p) === localSettingsPath) {
return JSON.stringify({ Values: { WORKFLOW_CODEFUL_ENABLED: 'true' } });
}
return '';
});

expect(await verifyIsProject.isLogicAppProject(projectPath)).toBe(false);
});

it('does not tag a folder whose workflow.json is not a Microsoft.Logic workflow', async () => {
const workflowJsonPath = path.join(projectPath, 'wf', workflowFileName);
vi.spyOn(fse, 'pathExists').mockImplementation(async (p: fse.PathLike) => {
const s = String(p);
return s === projectPath || s === workflowJsonPath;
});
vi.spyOn(fse, 'readdir').mockImplementation(async (p: fse.PathLike) => (String(p) === projectPath ? ['wf'] : []));
vi.spyOn(fse, 'readFile').mockImplementation(async (p: fse.PathLike) =>
String(p) === workflowJsonPath ? JSON.stringify({ definition: { $schema: 'https://example.com/not-logic.json#' } }) : ''
);

expect(await verifyIsProject.isLogicAppProject(projectPath)).toBe(false);
});
});
Loading
Loading