Ship two json schema files.. One that reference others, and a CMS one (#13123)

* Ship two json schema files.. One that reference others, and a CMS one

* git ignore

* Build schema files sepearately
This commit is contained in:
Bjarke Berg
2022-10-05 12:42:21 +02:00
committed by GitHub
parent 6dc874147f
commit 46da0b07a8
10 changed files with 250 additions and 147 deletions

4
.gitignore vendored
View File

@@ -106,4 +106,8 @@ cypress.env.json
# Ignore auto-generated schema # Ignore auto-generated schema
/src/Umbraco.Cms.Targets/appsettings-schema.json /src/Umbraco.Cms.Targets/appsettings-schema.json
/src/Umbraco.Web.UI/appsettings-schema.json /src/Umbraco.Web.UI/appsettings-schema.json
/src/Umbraco.Web.UI/appsettings-schema.umbraco.json
/tests/Umbraco.Tests.Integration/appsettings-schema.json /tests/Umbraco.Tests.Integration/appsettings-schema.json
/src/Umbraco.Cms.Targets/appsettings-schema.json
/src/Umbraco.Cms.Targets/appsettings-schema.umbraco.json

View File

@@ -3,133 +3,74 @@
using Umbraco.Cms.Core.Configuration; using Umbraco.Cms.Core.Configuration;
using Umbraco.Cms.Core.Configuration.Models; using Umbraco.Cms.Core.Configuration.Models;
using Umbraco.Deploy.Core.Configuration.DebugConfiguration;
using Umbraco.Deploy.Core.Configuration.DeployConfiguration;
using Umbraco.Deploy.Core.Configuration.DeployProjectConfiguration;
using Umbraco.Forms.Core.Configuration;
using SecuritySettings = Umbraco.Cms.Core.Configuration.Models.SecuritySettings;
namespace JsonSchema namespace JsonSchema;
internal class AppSettings
{ {
internal class AppSettings // ReSharper disable once InconsistentNaming
public CmsDefinition? CMS { get; set; }
/// <summary>
/// Configurations for the Umbraco CMS
/// </summary>
public class CmsDefinition
{ {
/// <summary> public ContentSettings? Content { get; set; }
/// Gets or sets the Umbraco public CoreDebugSettings? Debug { get; set; }
/// </summary>
public UmbracoDefinition? Umbraco { get; set; }
/// <summary> public ExceptionFilterSettings? ExceptionFilter { get; set; }
/// Configuration of Umbraco CMS and packages
/// </summary>
internal class UmbracoDefinition
{
// ReSharper disable once InconsistentNaming
public CmsDefinition? CMS { get; set; }
public FormsDefinition? Forms { get; set; } public ModelsBuilderSettings? ModelsBuilder { get; set; }
public DeployDefinition? Deploy { get; set; } public GlobalSettings? Global { get; set; }
/// <summary> public HealthChecksSettings? HealthChecks { get; set; }
/// Configurations for the Umbraco CMS
/// </summary>
public class CmsDefinition
{
public ContentSettings? Content { get; set; }
public CoreDebugSettings? Debug { get; set; }
public ExceptionFilterSettings? ExceptionFilter { get; set; } public HostingSettings? Hosting { get; set; }
public ModelsBuilderSettings? ModelsBuilder { get; set; } public ImagingSettings? Imaging { get; set; }
public GlobalSettings? Global { get; set; } public IndexCreatorSettings? Examine { get; set; }
public HealthChecksSettings? HealthChecks { get; set; } public KeepAliveSettings? KeepAlive { get; set; }
public HostingSettings? Hosting { get; set; } public LoggingSettings? Logging { get; set; }
public ImagingSettings? Imaging { get; set; }
public IndexCreatorSettings? Examine { get; set; }
public KeepAliveSettings? KeepAlive { get; set; }
public LoggingSettings? Logging { get; set; }
public NuCacheSettings? NuCache { get; set; } public NuCacheSettings? NuCache { get; set; }
public RequestHandlerSettings? RequestHandler { get; set; } public RequestHandlerSettings? RequestHandler { get; set; }
public RuntimeSettings? Runtime { get; set; } public RuntimeSettings? Runtime { get; set; }
public SecuritySettings? Security { get; set; } public SecuritySettings? Security { get; set; }
public TourSettings? Tours { get; set; } public TourSettings? Tours { get; set; }
public TypeFinderSettings? TypeFinder { get; set; } public TypeFinderSettings? TypeFinder { get; set; }
public WebRoutingSettings? WebRouting { get; set; } public WebRoutingSettings? WebRouting { get; set; }
public UmbracoPluginSettings? Plugins { get; set; } public UmbracoPluginSettings? Plugins { get; set; }
public UnattendedSettings? Unattended { get; set; } public UnattendedSettings? Unattended { get; set; }
public RichTextEditorSettings? RichTextEditor { get; set; } public RichTextEditorSettings? RichTextEditor { get; set; }
public RuntimeMinificationSettings? RuntimeMinification { get; set; } public RuntimeMinificationSettings? RuntimeMinification { get; set; }
public BasicAuthSettings? BasicAuth { get; set; } public BasicAuthSettings? BasicAuth { get; set; }
public PackageMigrationSettings? PackageMigration { get; set; } public PackageMigrationSettings? PackageMigration { get; set; }
public LegacyPasswordMigrationSettings? LegacyPasswordMigration { get; set; } public LegacyPasswordMigrationSettings? LegacyPasswordMigration { get; set; }
public ContentDashboardSettings? ContentDashboard { get; set; } public ContentDashboardSettings? ContentDashboard { get; set; }
public HelpPageSettings? HelpPage { get; set; } public HelpPageSettings? HelpPage { get; set; }
public InstallDefaultDataSettings? DefaultDataCreation { get; set; } public InstallDefaultDataSettings? DefaultDataCreation { get; set; }
public DataTypesSettings? DataTypes { get; set; } public DataTypesSettings? DataTypes { get; set; }
}
/// <summary>
/// Configurations for the Umbraco Forms package to Umbraco CMS
/// </summary>
public class FormsDefinition
{
public FormDesignSettings? FormDesign { get; set; }
public PackageOptionSettings? Options { get; set; }
public Umbraco.Forms.Core.Configuration.SecuritySettings? Security { get; set; }
public FieldTypesDefinition? FieldTypes { get; set; }
/// <summary>
/// Configurations for the Umbraco Forms Field Types
/// </summary>
public class FieldTypesDefinition
{
public DatePickerSettings? DatePicker { get; set; }
public Recaptcha2Settings? Recaptcha2 { get; set; }
public Recaptcha3Settings? Recaptcha3 { get; set; }
}
}
/// <summary>
/// Configurations for the Umbraco Deploy package to Umbraco CMS
/// </summary>
public class DeployDefinition
{
public DeploySettings? Settings { get; set; }
public DeployProjectConfig? Project { get; set; }
public DebugSettings? Debug { get; set; }
}
}
} }
} }

View File

@@ -8,6 +8,7 @@
<ItemGroup> <ItemGroup>
<PackageReference Include="CommandLineParser" Version="2.9.1" /> <PackageReference Include="CommandLineParser" Version="2.9.1" />
<PackageReference Include="NJsonSchema" Version="10.8.0" /> <PackageReference Include="NJsonSchema" Version="10.8.0" />
<PackageReference Include="System.Xml.XPath.XmlDocument" Version="4.3.0" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
@@ -15,4 +16,21 @@
<PackageReference Include="Umbraco.Deploy.Core" Version="10.1.0" /> <PackageReference Include="Umbraco.Deploy.Core" Version="10.1.0" />
<PackageReference Include="Umbraco.Forms.Core" Version="10.1.0" /> <PackageReference Include="Umbraco.Forms.Core" Version="10.1.0" />
</ItemGroup> </ItemGroup>
<ItemGroup>
<None Remove="appsettings-schema.json" />
<EmbeddedResource Include="appsettings-schema.json" />
</ItemGroup>
<!-- Copy Forms XML docs-->
<PropertyGroup>
<NuGetPackageRoot Condition=" '$(NuGetPackageRoot)' == '' ">$(UserProfile)\.nuget\packages\</NuGetPackageRoot>
</PropertyGroup>
<Target Name="CopyPackagesXml" BeforeTargets="Build">
<ItemGroup>
<PackageReferenceFiles Include="$(NugetPackageRoot)%(PackageReference.Identity)\%(PackageReference.Version)%(PackageReference.CopyToOutputDirectory)\lib\**\*.xml" />
</ItemGroup>
<Copy SourceFiles="@(PackageReferenceFiles)" DestinationFolder="$(OutDir)" />
</Target>
</Project> </Project>

View File

@@ -7,7 +7,10 @@ namespace JsonSchema
{ {
internal class Options internal class Options
{ {
[Option('o', "outputFile", Required = false, HelpText = "Set path of the output file.", Default = "appsettings-schema.json")] [Option('m', "mainOutputFile", Required = false, HelpText = "Set path of the main output file.", Default = "../../../../Umbraco.Web.UI/appsettings-schema.json")]
public string OutputFile { get; set; } = null!; public string MainOutputFile { get; set; } = null!;
[Option('f', "cmsOutputFile", Required = false, HelpText = "Set path of the cms output file.", Default = "../../../../Umbraco.Web.UI/appsettings-schema.umbraco.json")]
public string CmsOutputFile { get; set; } = null!;
} }
} }

View File

@@ -27,15 +27,23 @@ namespace JsonSchema
private static async Task Execute(Options options) private static async Task Execute(Options options)
{ {
var generator = new UmbracoJsonSchemaGenerator(); var generator = new UmbracoJsonSchemaGenerator();
var schema = await generator.Generate();
var path = Path.GetFullPath(Path.Combine(Environment.CurrentDirectory, options.OutputFile)); var cmsSchema = await generator.GenerateCmsFile();
Console.WriteLine("Path to use {0}", path); await WriteSchemaToFile(cmsSchema, options.CmsOutputFile);
Directory.CreateDirectory(Path.GetDirectoryName(path)!);
var schema = await generator.GenerateMainFile();
await WriteSchemaToFile(schema, options.MainOutputFile);
}
private static async Task WriteSchemaToFile(string schema, string filePath)
{
var mainPath = Path.GetFullPath(Path.Combine(Environment.CurrentDirectory, filePath));
Console.WriteLine("Path to use {0}", mainPath);
Directory.CreateDirectory(Path.GetDirectoryName(mainPath)!);
Console.WriteLine("Ensured directory exists"); Console.WriteLine("Ensured directory exists");
await File.WriteAllTextAsync(path, schema); await File.WriteAllTextAsync(mainPath, schema);
Console.WriteLine("File written at {0}", path); Console.WriteLine("File written at {0}", mainPath);
} }
} }
} }

View File

@@ -1,58 +1,83 @@
// Copyright (c) Umbraco. // Copyright (c) Umbraco.
// See LICENSE for more details. // See LICENSE for more details.
using Microsoft.Extensions.FileProviders;
using Newtonsoft.Json; using Newtonsoft.Json;
using Newtonsoft.Json.Linq; using Newtonsoft.Json.Linq;
using NJsonSchema.Generation; using NJsonSchema.Generation;
using Umbraco.Cms.Core.Configuration.Models; using Umbraco.Cms.Core.Configuration.Models;
namespace JsonSchema namespace JsonSchema;
/// <summary>
/// Generator of the JsonSchema for AppSettings.json including A specific Umbraco version.
/// </summary>
public class UmbracoJsonSchemaGenerator
{ {
private static readonly HttpClient s_client = new();
private readonly JsonSchemaGenerator _innerGenerator;
/// <summary> /// <summary>
/// Generator of the JsonSchema for AppSettings.json including A specific Umbraco version. /// Initializes a new instance of the <see cref="UmbracoJsonSchemaGenerator" /> class.
/// </summary> /// </summary>
public class UmbracoJsonSchemaGenerator public UmbracoJsonSchemaGenerator()
=> _innerGenerator = new JsonSchemaGenerator(new UmbracoJsonSchemaGeneratorSettings());
/// <summary>
/// Generates a json representing the JsonSchema for AppSettings.json including A specific Umbraco version..
/// </summary>
public async Task<string> GenerateMainFile()
{ {
private static readonly HttpClient s_client = new (); JObject officialSchema = await GetOfficialAppSettingsSchema();
private readonly JsonSchemaGenerator _innerGenerator; JObject externalFilePoints = GenerateSchemaWithExternalDefinitions();
/// <summary> officialSchema.Merge(externalFilePoints);
/// Initializes a new instance of the <see cref="UmbracoJsonSchemaGenerator" /> class.
/// </summary>
public UmbracoJsonSchemaGenerator()
=> _innerGenerator = new JsonSchemaGenerator(new UmbracoJsonSchemaGeneratorSettings());
/// <summary> return officialSchema.ToString();
/// Generates a json representing the JsonSchema for AppSettings.json including A specific Umbraco version.. }
/// </summary>
public async Task<string> Generate()
/// <summary>
/// Generates the CMS file
/// </summary>
/// <returns></returns>
public Task<string> GenerateCmsFile()
{
JObject cmsSchema = GenerateUmbracoSchema();
return Task.FromResult(cmsSchema.ToString());
}
private JObject GenerateSchemaWithExternalDefinitions()
{
var fileProvider = new EmbeddedFileProvider(GetType().Assembly);
IFileInfo schema = fileProvider.GetFileInfo("appsettings-schema.json");
using (Stream? stream = schema.CreateReadStream())
using (var reader = new StreamReader(stream))
{ {
JObject umbracoSchema = GenerateUmbracoSchema(); return JsonConvert.DeserializeObject<JObject>(reader.ReadToEnd())!;
JObject officialSchema = await GetOfficialAppSettingsSchema();
officialSchema.Merge(umbracoSchema);
return officialSchema.ToString();
}
private async Task<JObject> GetOfficialAppSettingsSchema()
{
HttpResponseMessage response = await s_client.GetAsync("https://json.schemastore.org/appsettings.json")
.ConfigureAwait(false);
var result = await response.Content.ReadAsStringAsync();
return JsonConvert.DeserializeObject<JObject>(result)!;
}
private JObject GenerateUmbracoSchema()
{
NJsonSchema.JsonSchema schema = _innerGenerator.Generate(typeof(AppSettings));
// TODO: when the "UmbracoPath" setter is removed from "GlobalSettings" (scheduled for V12), remove this line as well
schema.Definitions["UmbracoCmsCoreConfigurationModelsGlobalSettings"]?.Properties?.Remove(nameof(GlobalSettings.UmbracoPath));
return JsonConvert.DeserializeObject<JObject>(schema.ToJson())!;
} }
} }
private async Task<JObject> GetOfficialAppSettingsSchema()
{
HttpResponseMessage response = await s_client.GetAsync("https://json.schemastore.org/appsettings.json")
.ConfigureAwait(false);
var result = await response.Content.ReadAsStringAsync();
return JsonConvert.DeserializeObject<JObject>(result)!;
}
private JObject GenerateUmbracoSchema()
{
NJsonSchema.JsonSchema schema = _innerGenerator.Generate(typeof(AppSettings));
// TODO: when the "UmbracoPath" setter is removed from "GlobalSettings" (scheduled for V12), remove this line as well
schema.Definitions["UmbracoCmsCoreConfigurationModelsGlobalSettings"]?.Properties?.Remove(nameof(GlobalSettings.UmbracoPath));return JsonConvert.DeserializeObject<JObject>(schema.ToJson())!;
}
} }

View File

@@ -0,0 +1,50 @@
{
"$schema": "http://json-schema.org/draft-04/schema#",
"properties": {
"Umbraco": {
"description": "The container of all Umbraco content",
"oneOf": [
{
"type": "null"
},
{
"properties": {
"CMS": {
"description": "Configuration of Umbraco CMS",
"oneOf": [
{
"type": "null"
},
{
"$ref": "appsettings-schema.umbraco.json#/definitions/JsonSchemaCmsDefinition"
}
]
},
"Forms": {
"description": "Configuration of Umbraco Forms",
"oneOf": [
{
"type": "null"
},
{
"$ref": "appsettings-schema.umbraco.forms.json#/definitions/JsonSchemaFormsDefinition"
}
]
},
"Deploy": {
"description": "Configuration of Umbraco Deploy",
"oneOf": [
{
"type": "null"
},
{
"$ref": "appsettings-schema.umbraco.deploy.json#/definitions/JsonSchemaDeployDefinition"
}
]
}
}
}
]
}
}
}

View File

@@ -14,18 +14,35 @@
<ProjectReference Include="..\Umbraco.Web.Website\Umbraco.Web.Website.csproj" /> <ProjectReference Include="..\Umbraco.Web.Website\Umbraco.Web.Website.csproj" />
</ItemGroup> </ItemGroup>
<PropertyGroup>
<JsonSchemaPath>$(ProjectDir)appsettings-schema.json</JsonSchemaPath>
<JsonSchemaCmsPath>$(ProjectDir)appsettings-schema.umbraco.json</JsonSchemaCmsPath>
<JsonSchemaProjectPath>$(ProjectDir)../JsonSchema/</JsonSchemaProjectPath>
</PropertyGroup>
<ItemGroup> <ItemGroup>
<Content Include="buildTransitive\**" PackagePath="buildTransitive" /> <Content Include="buildTransitive\**" PackagePath="buildTransitive" />
<Content Include="$(JsonSchemaPath)" PackagePath="." /> <Content Include="$(JsonSchemaPath)" PackagePath="." />
<Content Include="$(JsonSchemaCmsPath)" PackagePath="." />
</ItemGroup> </ItemGroup>
<Target Name="JsonSchemaBuild">
<Exec WorkingDirectory="$(JsonSchemaProjectPath)" Command="dotnet run -c Release --mainOutputFile &quot;../Umbraco.Cms.Targets/appsettings-schema.json&quot; --cmsOutputFile &quot;../Umbraco.Cms.Targets/appsettings-schema.umbraco.json&quot;" />
</Target>
<!-- Generate appsettings.json schema on build (and before copying to project) --> <!-- Generate appsettings.json schema on build (and before copying to project) -->
<PropertyGroup> <PropertyGroup>
<JsonSchemaPath>$(MSBuildThisFileDirectory)appsettings-schema.json</JsonSchemaPath> <JsonSchemaPath>$(MSBuildThisFileDirectory)appsettings-schema.json</JsonSchemaPath>
<JsonSchemaProjectPath>$(MSBuildThisFileDirectory)..\JsonSchema\</JsonSchemaProjectPath> <JsonSchemaProjectPath>$(MSBuildThisFileDirectory)..\JsonSchema\</JsonSchemaProjectPath>
</PropertyGroup> </PropertyGroup>
<Target Name="GenerateAppsettingsSchema" BeforeTargets="Build;CopyAppsettingsSchema" Condition="!Exists('$(JsonSchemaPath)')"> <Target Name="GenerateAppsettingsSchema" BeforeTargets="Build;CopyAppsettingsSchema">
<Message Text="Generating appsettings-schema.json because it doesn't exist" Importance="high" /> <Message Text="JsonSchemaPath: $(JsonSchemaPath)" Importance="high" />
<Exec WorkingDirectory="$(JsonSchemaProjectPath)" Command="dotnet run -c Release -o &quot;$(JsonSchemaPath)&quot;" /> <Message Text="JsonSchemaCmsPath: $(JsonSchemaCmsPath)" Importance="high" />
<Message Text="Skip JsonSchema generation because $(JsonSchemaPath) and $(JsonSchemaCmsPath) exists." Condition="Exists('$(JsonSchemaPath)') AND Exists('$(JsonSchemaCmsPath)')" />
<Message Text="Generate the appsettings json schema." Importance="high" Condition="!(Exists('$(JsonSchemaPath)') AND Exists('$(JsonSchemaCmsPath)'))" />
<CallTarget Targets="JsonSchemaBuild" Condition="!(Exists('$(JsonSchemaPath)') AND Exists('$(JsonSchemaCmsPath)'))" />
</Target> </Target>
</Project> </Project>

View File

@@ -11,5 +11,12 @@
<ProjectReference Include="..\Umbraco.Cms.Imaging.ImageSharp\Umbraco.Cms.Imaging.ImageSharp.csproj" /> <ProjectReference Include="..\Umbraco.Cms.Imaging.ImageSharp\Umbraco.Cms.Imaging.ImageSharp.csproj" />
<ProjectReference Include="..\Umbraco.Cms.Persistence.Sqlite\Umbraco.Cms.Persistence.Sqlite.csproj" /> <ProjectReference Include="..\Umbraco.Cms.Persistence.Sqlite\Umbraco.Cms.Persistence.Sqlite.csproj" />
<ProjectReference Include="..\Umbraco.Cms.Persistence.SqlServer\Umbraco.Cms.Persistence.SqlServer.csproj" /> <ProjectReference Include="..\Umbraco.Cms.Persistence.SqlServer\Umbraco.Cms.Persistence.SqlServer.csproj" />
<ProjectReference Include="..\Umbraco.Cms.StaticAssets\Umbraco.Cms.StaticAssets.csproj" />
<ProjectReference Include="..\Umbraco.Web.BackOffice\Umbraco.Web.BackOffice.csproj" />
<ProjectReference Include="..\Umbraco.Web.Website\Umbraco.Web.Website.csproj" />
</ItemGroup>
<ItemGroup Condition=" '$(Configuration)' == 'Debug' ">
<ProjectReference Include="..\Umbraco.Cms.ManagementApi\Umbraco.Cms.ManagementApi.csproj" />
</ItemGroup> </ItemGroup>
</Project> </Project>

View File

@@ -17,6 +17,15 @@
<RuntimeHostConfigurationOption Include="System.Globalization.AppLocalIcu" Value="68.2.0.9" Condition="$(RuntimeIdentifier.StartsWith('linux')) or $(RuntimeIdentifier.StartsWith('win')) or ('$(RuntimeIdentifier)' == '' and !$([MSBuild]::IsOSPlatform('osx')))" /> <RuntimeHostConfigurationOption Include="System.Globalization.AppLocalIcu" Value="68.2.0.9" Condition="$(RuntimeIdentifier.StartsWith('linux')) or $(RuntimeIdentifier.StartsWith('win')) or ('$(RuntimeIdentifier)' == '' and !$([MSBuild]::IsOSPlatform('osx')))" />
</ItemGroup> </ItemGroup>
<ItemGroup>
<Content Update="appsettings.*.json">
<DependentUpon>appsettings.json</DependentUpon>
</Content>
<Content Update="appsettings-schema.*.json">
<DependentUpon>appsettings-schema.json</DependentUpon>
</Content>
</ItemGroup>
<PropertyGroup> <PropertyGroup>
<!-- Razor files are needed for the backoffice to work correctly --> <!-- Razor files are needed for the backoffice to work correctly -->
<CopyRazorGenerateFilesToPublishDirectory>true</CopyRazorGenerateFilesToPublishDirectory> <CopyRazorGenerateFilesToPublishDirectory>true</CopyRazorGenerateFilesToPublishDirectory>
@@ -37,4 +46,25 @@
<Message Text="Copying appsettings.Development.template.json to appsettings.Development.json because it doesn't exist" Importance="high" /> <Message Text="Copying appsettings.Development.template.json to appsettings.Development.json because it doesn't exist" Importance="high" />
<Copy SourceFiles="appsettings.Development.template.json" DestinationFiles="appsettings.Development.json" /> <Copy SourceFiles="appsettings.Development.template.json" DestinationFiles="appsettings.Development.json" />
</Target> </Target>
<PropertyGroup>
<JsonSchemaPath>$(ProjectDir)appsettings-schema.json</JsonSchemaPath>
<JsonSchemaCmsPath>$(ProjectDir)appsettings-schema.umbraco.json</JsonSchemaCmsPath>
<JsonSchemaProjectPath>$(ProjectDir)../JsonSchema/</JsonSchemaProjectPath>
</PropertyGroup>
<Target Name="CheckPreconditions" BeforeTargets="Build">
<Message Text="-CheckPreconditions-" Importance="high" />
<Message Text="JsonSchemaPath: $(JsonSchemaPath)" Importance="high" />
<Message Text="JsonSchemaCmsPath: $(JsonSchemaCmsPath)" Importance="high" />
<CallTarget Targets="JsonSchemaBuild" Condition="!(Exists('$(JsonSchemaPath)') AND Exists('$(JsonSchemaCmsPath)')) and '$(UmbracoBuild)' == ''" />
<CallTarget Targets="AppsettingsBuild" Condition="!Exists('appsettings.json')" />
<CallTarget Targets="AppsettingsDevBuild" Condition="!Exists('appsettings.Development.json')" />
</Target>
<Target Name="JsonSchemaBuild">
<Exec Command="dotnet run -c Release --project $(JsonSchemaProjectPath) --mainOutputFile &quot;$(ProjectDir)appsettings-schema.json&quot; --cmsOutputFile &quot;$(ProjectDir)appsettings-schema.umbraco.json&quot;" />
</Target>
</Project> </Project>