diff --git a/src/StrawberryShake/MetaPackages/Blazor/MSBuild/StrawberryShake.Blazor.props b/src/StrawberryShake/MetaPackages/Blazor/MSBuild/StrawberryShake.Blazor.props
index 79162641d54..ae8c28b5ec1 100644
--- a/src/StrawberryShake/MetaPackages/Blazor/MSBuild/StrawberryShake.Blazor.props
+++ b/src/StrawberryShake/MetaPackages/Blazor/MSBuild/StrawberryShake.Blazor.props
@@ -10,6 +10,11 @@
md5
intermediate
+
+
+
+ default
+
disable
diff --git a/src/StrawberryShake/MetaPackages/Common/MSBuild/StrawberryShake.targets b/src/StrawberryShake/MetaPackages/Common/MSBuild/StrawberryShake.targets
index 0660063b0c4..190f40581b3 100644
--- a/src/StrawberryShake/MetaPackages/Common/MSBuild/StrawberryShake.targets
+++ b/src/StrawberryShake/MetaPackages/Common/MSBuild/StrawberryShake.targets
@@ -22,16 +22,19 @@
Condition="'@(GraphQL)' != ''">
$(MSBuildProjectDirectory)\$(IntermediateOutputPath)berry\
+ $(MSBuildProjectDirectory)\$(GraphQLPersistedQueryFormat)
dotnet $(GenTool) generate "$(MSBuildProjectDirectory)"
$(GenCommand) -o "$(GraphQLCodeGenerationRoot)"
$(GenCommand) -n "$(RootNamespace)"
+ $(GenCommand) -q "$(GraphQLQueryGenerationRoot)"
$(GenCommand) -a "$(GraphQLRequestHash)"
$(GenCommand) -s
$(GenCommand) -t
$(GenCommand) -r
+ $(GenCommand) --relayFormat
diff --git a/src/StrawberryShake/MetaPackages/Maui/MSBuild/StrawberryShake.Maui.props b/src/StrawberryShake/MetaPackages/Maui/MSBuild/StrawberryShake.Maui.props
index 5932725030c..131d5c3c86e 100644
--- a/src/StrawberryShake/MetaPackages/Maui/MSBuild/StrawberryShake.Maui.props
+++ b/src/StrawberryShake/MetaPackages/Maui/MSBuild/StrawberryShake.Maui.props
@@ -10,6 +10,11 @@
md5
intermediate
+
+
+
+ default
+
disable
diff --git a/src/StrawberryShake/MetaPackages/Server/MSBuild/StrawberryShake.Server.props b/src/StrawberryShake/MetaPackages/Server/MSBuild/StrawberryShake.Server.props
index 7958f055eb1..5f2c69f2d7b 100644
--- a/src/StrawberryShake/MetaPackages/Server/MSBuild/StrawberryShake.Server.props
+++ b/src/StrawberryShake/MetaPackages/Server/MSBuild/StrawberryShake.Server.props
@@ -10,6 +10,11 @@
md5
intermediate
+
+
+
+ default
+
disable
diff --git a/src/StrawberryShake/Tooling/.vscode/launch.json b/src/StrawberryShake/Tooling/.vscode/launch.json
index 267277223e0..767264f96ce 100644
--- a/src/StrawberryShake/Tooling/.vscode/launch.json
+++ b/src/StrawberryShake/Tooling/.vscode/launch.json
@@ -13,11 +13,13 @@
"generate",
"/Users/michael/local/play/StrawberryBuildTests",
"-o /Users/michael/local/play/StrawberryBuildTests/obj/Debug/net7.0/berry/",
+ "-q /Users/michael/local/play/StrawberryBuildTests/obj/Debug/net7.0/berry/q",
"-n StrawberryBuildTests",
"-a md5",
"-s",
"-t",
- "-r"
+ "-r",
+ "--relayFormat"
],
"cwd": "${workspaceFolder}/src/dotnet-graphql",
"console": "internalConsole",
diff --git a/src/StrawberryShake/Tooling/src/dotnet-graphql/ExportCommand.cs b/src/StrawberryShake/Tooling/src/dotnet-graphql/ExportCommand.cs
deleted file mode 100644
index e24361e2ebd..00000000000
--- a/src/StrawberryShake/Tooling/src/dotnet-graphql/ExportCommand.cs
+++ /dev/null
@@ -1,121 +0,0 @@
-using McMaster.Extensions.CommandLineUtils;
-using StrawberryShake.CodeGeneration.CSharp;
-using StrawberryShake.Tools.Configuration;
-using static System.Environment;
-using static StrawberryShake.Tools.GeneratorHelpers;
-
-namespace StrawberryShake.Tools;
-
-public static class ExportCommand
-{
- public static void Build(CommandLineApplication generate)
- {
- generate.Description = "Exports Persisted Queries for Strawberry Shake Clients";
-
- var pathArg = generate.Argument(
- "path",
- "The project directory.");
-
- var razorArg = generate.Option(
- "-o|--outputPath",
- "Output Directory.",
- CommandOptionType.SingleValue);
-
- var relayFormatArg = generate.Option(
- "-r|--relayFormat",
- "Export Persisted Queries as Relay Format.",
- CommandOptionType.NoValue);
-
- var jsonArg = generate.Option(
- "-j|--json",
- "Console output as JSON.",
- CommandOptionType.NoValue);
-
- generate.OnExecuteAsync(ct =>
- {
- var arguments = new ExportCommandArguments(
- pathArg.Value ?? CurrentDirectory,
- razorArg.Value()!,
- relayFormatArg.HasValue());
- var handler = CommandTools.CreateHandler(jsonArg);
- return handler.ExecuteAsync(arguments, ct);
- });
- }
-
- private sealed class ExportCommandHandler : CommandHandler
- {
- public ExportCommandHandler(IConsoleOutput output)
- {
- Output = output;
- }
-
- public IConsoleOutput Output { get; }
-
- public override async Task ExecuteAsync(
- ExportCommandArguments arguments,
- CancellationToken cancellationToken)
- {
- /*
- using var activity = Output.WriteActivity("Export Persisted Queries");
-
- if (string.IsNullOrEmpty(arguments.OutputPath))
- {
- activity.WriteError(new HotChocolate.Error(
- "The Output Directory `-o` must be set!"));
- }
-
- var generator = new CSharpGeneratorClient(GetCodeGenServerLocation());
- var documents = GetDocuments(arguments.Path);
- var configFiles = GetConfigFiles(arguments.Path);
-
- foreach (var configFileName in configFiles)
- {
- var config = await LoadConfigAsync(configFileName);
-
- var persistedDir = configFiles.Length == 1
- ? arguments.OutputPath
- : Path.Combine(arguments.OutputPath, config.Extensions.StrawberryShake.Name);
-
- var request = new GeneratorRequest(
- configFileName,
- documents,
- persistedQueryDirectory: persistedDir,
- option: arguments.RelayFormat
- ? RequestOptions.ExportPersistedQueriesJson
- : RequestOptions.ExportPersistedQueries);
-
- var response = generator.Execute(request);
-
- if (response.TryLogErrors(activity))
- {
- return 1;
- }
- }
- */
-
- return 0;
- }
-
- private static async Task LoadConfigAsync(string configFileName)
- {
- var json = await File.ReadAllTextAsync(configFileName);
- return GraphQLConfig.FromJson(json);
- }
- }
-
- private sealed class ExportCommandArguments
- {
- public ExportCommandArguments(string path, string outputPath, bool relayFormat)
- {
- Path = path;
- OutputPath = outputPath;
- RelayFormat = relayFormat;
- }
-
- public string Path { get; }
-
- public string OutputPath { get; }
-
- public bool RelayFormat { get; }
- }
-}
diff --git a/src/StrawberryShake/Tooling/src/dotnet-graphql/GenerateCommand.cs b/src/StrawberryShake/Tooling/src/dotnet-graphql/GenerateCommand.cs
index 86de3d81baa..52d63dcae6a 100644
--- a/src/StrawberryShake/Tooling/src/dotnet-graphql/GenerateCommand.cs
+++ b/src/StrawberryShake/Tooling/src/dotnet-graphql/GenerateCommand.cs
@@ -1,3 +1,4 @@
+using System.Text.Json;
using McMaster.Extensions.CommandLineUtils;
using StrawberryShake.CodeGeneration.CSharp;
using StrawberryShake.Tools.Configuration;
@@ -46,11 +47,29 @@ public static void Build(CommandLineApplication generate)
"The output directory.",
CommandOptionType.SingleValue);
+ var queryOutputDirArg = generate.Option(
+ "-q|--queryOutputDirectory",
+ "The output directory for persisted query files.",
+ CommandOptionType.SingleValue);
+
+ var relayFormatArg = generate.Option(
+ "--relayFormat",
+ "Export persisted queries in the relay format.",
+ CommandOptionType.NoValue);
+
var jsonArg = generate.Option(
"-j|--json",
"Console output as JSON.",
CommandOptionType.NoValue);
+ var strategy = RequestStrategy.Default;
+ var queryOutputDir = queryOutputDirArg.Value();
+
+ if (!string.IsNullOrEmpty(queryOutputDir) || relayFormatArg.HasValue())
+ {
+ strategy = RequestStrategy.PersistedQuery;
+ }
+
generate.OnExecuteAsync(ct =>
{
var arguments = new GenerateCommandArguments(
@@ -61,7 +80,10 @@ public static void Build(CommandLineApplication generate)
true,
disableStoreArg.HasValue(),
razorComponentsArg.HasValue(),
- outputDirArg.Value());
+ outputDirArg.Value(),
+ strategy,
+ queryOutputDir,
+ relayFormatArg.HasValue());
var handler = CommandTools.CreateHandler(jsonArg);
return handler.ExecuteAsync(arguments, ct);
});
@@ -91,10 +113,14 @@ public GenerateCommandHandler(IConsoleOutput output)
var rootNamespace = args.RootNamespace ?? $"{clientName}NS";
var documents = GetGraphQLDocuments(args.Path, config.Documents);
var settings = CreateSettings(config, args, rootNamespace);
- var result = CSharpGenerator.Generate(documents, settings);
+ var result = GenerateClient(settings.ClientName, documents, settings);
var outputDir = args.OutputDir ?? Path.Combine(
Path.GetDirectoryName(configFileName)!,
config.Extensions.StrawberryShake.OutputDirectoryName);
+ var queryOutputDir = args.QueryOutputDir ?? Path.Combine(
+ Path.GetDirectoryName(configFileName)!,
+ config.Extensions.StrawberryShake.OutputDirectoryName,
+ "Queries");
if (result.HasErrors())
{
@@ -103,30 +129,15 @@ public GenerateCommandHandler(IConsoleOutput output)
}
else
{
- foreach (var doc in result.Documents)
+ await WriteCodeFilesAsync(result, outputDir, cancellationToken);
+
+ if (args.Strategy is RequestStrategy.PersistedQuery)
{
- if (doc.Kind is SourceDocumentKind.CSharp or SourceDocumentKind.Razor)
- {
- var fileName = CreateFileName(outputDir, doc.Path, doc.Name, doc.Kind);
- var dir = Path.GetDirectoryName(fileName)!;
-
- if (!Directory.Exists(dir))
- {
- Directory.CreateDirectory(dir);
- }
-
- if (File.Exists(fileName))
- {
- File.Delete(fileName);
- }
-
- await File.WriteAllTextAsync(
- fileName,
- doc.SourceText,
- cancellationToken);
-
- Output.WriteFileCreated(fileName);
- }
+ await WritePersistedQueriesAsync(
+ result,
+ queryOutputDir,
+ args.RelayFormat,
+ cancellationToken);
}
}
}
@@ -134,7 +145,79 @@ public GenerateCommandHandler(IConsoleOutput output)
return statusCode;
}
- private static string CreateFileName(
+ private CSharpGeneratorResult GenerateClient(
+ string clientName,
+ string[] documents,
+ CSharpGeneratorSettings settings)
+ {
+ using var activity = Output.WriteActivity($"Generate {clientName}");
+ return CSharpGenerator.Generate(documents, settings);
+ }
+
+ private async Task WriteCodeFilesAsync(
+ CSharpGeneratorResult result,
+ string outputDir,
+ CancellationToken cancellationToken)
+ {
+ foreach (var doc in result.Documents)
+ {
+ if (doc.Kind is SourceDocumentKind.CSharp or SourceDocumentKind.Razor)
+ {
+ var fileName = CreateCodeFileName(outputDir, doc.Path, doc.Name, doc.Kind);
+
+ EnsureWeCanWriteTheFile(fileName);
+
+ await File.WriteAllTextAsync(
+ fileName,
+ doc.SourceText,
+ cancellationToken);
+
+ Output.WriteFileCreated(fileName);
+ }
+ }
+ }
+
+ private static async Task WritePersistedQueriesAsync(
+ CSharpGeneratorResult result,
+ string outputDir,
+ bool relayFormat,
+ CancellationToken cancellationToken)
+ {
+ if (relayFormat)
+ {
+ var map = new Dictionary();
+
+ foreach (var doc in result.Documents)
+ {
+ map[doc.Hash!] = doc.SourceText;
+ }
+
+ var fileName = Path.Combine(outputDir, "queries.json");
+
+ EnsureWeCanWriteTheFile(fileName);
+
+ await File.WriteAllTextAsync(
+ fileName,
+ JsonSerializer.Serialize(map),
+ cancellationToken);
+ }
+ else
+ {
+ foreach (var doc in result.Documents)
+ {
+ var fileName = Path.Combine(outputDir, $"{doc.Hash}.graphql");
+
+ EnsureWeCanWriteTheFile(fileName);
+
+ await File.WriteAllTextAsync(
+ fileName,
+ doc.SourceText,
+ cancellationToken);
+ }
+ }
+ }
+
+ private static string CreateCodeFileName(
string outputDir,
string? path,
string name,
@@ -149,6 +232,21 @@ public GenerateCommandHandler(IConsoleOutput output)
? Path.Combine(outputDir, $"{name}.{kindName}.cs")
: Path.Combine(outputDir, path, $"{name}.{kindName}.cs");
}
+
+ private static void EnsureWeCanWriteTheFile(string fileName)
+ {
+ var dir = Path.GetDirectoryName(fileName)!;
+
+ if (!Directory.Exists(dir))
+ {
+ Directory.CreateDirectory(dir);
+ }
+
+ if (File.Exists(fileName))
+ {
+ File.Delete(fileName);
+ }
+ }
}
internal sealed class GenerateCommandArguments
@@ -161,7 +259,10 @@ internal sealed class GenerateCommandArguments
bool useSingleFile,
bool noStore,
bool razorComponents,
- string? outputDir)
+ string? outputDir,
+ RequestStrategy strategy,
+ string? queryOutputDir,
+ bool relayFormat)
{
Path = path;
RootNamespace = rootNamespace;
@@ -171,6 +272,9 @@ internal sealed class GenerateCommandArguments
NoStore = noStore;
RazorComponents = razorComponents;
OutputDir = outputDir;
+ Strategy = strategy;
+ QueryOutputDir = queryOutputDir ?? outputDir;
+ RelayFormat = relayFormat;
}
public string Path { get; }
@@ -188,5 +292,11 @@ internal sealed class GenerateCommandArguments
public bool RazorComponents { get; }
public string? OutputDir { get; }
+
+ public RequestStrategy Strategy { get; }
+
+ public string? QueryOutputDir { get; }
+
+ public bool RelayFormat { get; }
}
}
diff --git a/src/StrawberryShake/Tooling/src/dotnet-graphql/Program.cs b/src/StrawberryShake/Tooling/src/dotnet-graphql/Program.cs
index c91620f3cbf..df43c402156 100644
--- a/src/StrawberryShake/Tooling/src/dotnet-graphql/Program.cs
+++ b/src/StrawberryShake/Tooling/src/dotnet-graphql/Program.cs
@@ -15,7 +15,6 @@ internal static Task Main(string[] args)
root.Command("update", UpdateCommand.Build);
root.Command("download", DownloadCommand.Build);
root.Command("generate", GenerateCommand.Build);
- root.Command("export", ExportCommand.Build);
root.Command("where", WhereCommand.Build);
root.OnExecute(() =>
diff --git a/src/StrawberryShake/Tooling/src/dotnet-graphql/UpdateCommandHandler.cs b/src/StrawberryShake/Tooling/src/dotnet-graphql/UpdateCommandHandler.cs
index c6d757a7b85..c62121d4137 100644
--- a/src/StrawberryShake/Tooling/src/dotnet-graphql/UpdateCommandHandler.cs
+++ b/src/StrawberryShake/Tooling/src/dotnet-graphql/UpdateCommandHandler.cs
@@ -1,16 +1,9 @@
-using System;
-using System.IO;
-using System.Net.Http;
-using System.Threading.Tasks;
-using System.Threading;
-using StrawberryShake.Tools.OAuth;
using System.Text;
using StrawberryShake.Tools.Configuration;
namespace StrawberryShake.Tools
{
- public class UpdateCommandHandler
- : CommandHandler
+ public class UpdateCommandHandler : CommandHandler
{
public UpdateCommandHandler(
IFileSystem fileSystem,
@@ -107,17 +100,61 @@ await UpdateSchemaAsync(context, clientDirectory, configuration, cancellationTok
{
var uri = new Uri(configuration.Extensions.StrawberryShake.Url);
var schemaFilePath = Path.Combine(clientDirectory, configuration.Schema);
+ var tempFile = CreateTempFileName();
- if (!await DownloadSchemaAsync(context, uri, schemaFilePath, cancellationToken)
+ // we first attempt to download the new schema into a temp file.
+ // if that should fail we still have the original schema file and
+ // the user can still work.
+ if (!await DownloadSchemaAsync(context, uri, tempFile, cancellationToken)
.ConfigureAwait(false))
{
+ // if the schema download succeeded we will replace the old schema with the
+ // new one.
+ if (File.Exists(schemaFilePath))
+ {
+ File.Delete(schemaFilePath);
+ }
+
+ File.Move(tempFile, schemaFilePath);
+
hasErrors = true;
}
+
+ // in any case we will make sure the temp file is removed at the end.
+ if (File.Exists(tempFile))
+ {
+ File.Delete(tempFile);
+ }
}
return !hasErrors;
}
+ private static string CreateTempFileName()
+ {
+ var pathSegment = Random.Shared.Next(9999).ToString();
+ string tempFile;
+
+ for (var i = 0; i < 100; i++)
+ {
+ tempFile = Path.Combine(Path.GetTempPath(), pathSegment, Path.GetRandomFileName());
+
+ if (!File.Exists(tempFile))
+ {
+ var tempDir = Path.GetDirectoryName(tempFile)!;
+
+ if (!Directory.Exists(tempDir))
+ {
+ Directory.CreateDirectory(tempDir);
+ }
+
+ return tempFile;
+ }
+ }
+
+ throw new InvalidOperationException("Could not acquire a temp file.");
+ }
+
private async Task DownloadSchemaAsync(
UpdateCommandContext context,
Uri serviceUri,