diff --git a/README.md b/README.md index f48161a..996190b 100644 --- a/README.md +++ b/README.md @@ -22,21 +22,44 @@ dotnet add {project} package ecoAPM.StatiqPipelines ## Usage -This package currently contains one pipeline and one module. +This package currently contains one pipeline and two modules. ### CopyFromNPM This pipeline copies files from the `node_modules` directory to a set location in your output. ```c# -bootstrapper.AddPipeline("NPM", new CopyFromNPM(new [] { +var files = new [] { "bootstrap/dist/css/bootstrap.min.css", - "bootstrap/dist/js/bootstrap.min.js", - "jquery/dist/jquery.min.js", - "marked/marked.min.js", - "notosans/*", - "vue/dist/vue.global.prod.js" -}); + "jquery/dist/jquery.min.js" +}; +bootstrapper.AddPipeline("NPM", new CopyFromNPM(files, "assets"); +``` + +The copied files can then be referenced from markup: + +```html + + +``` + +A dictionary can be used to specify the output path for a given input. An empty string value flattens output with the input filename, as above. + +```c# +var files = new Dictionary { + { "bootstrap/dist/css/bootstrap.min.css", "" }, + { "jquery/dist/jquery.min.js", "" }, + { "@fontsource/noto-sans/*", "fonts" } +}; +bootstrapper.AddPipeline("NPM", new CopyFromNPM(files); +``` + +Note that the output path is optional and defaults to `lib`. + +```html + + + ``` ### NiceURL @@ -50,6 +73,14 @@ instead of the default `output/category/page.html` bootstrapper.ModifyPipeline("Content", p => p.ProcessModules.Add(new NiceURL())); ``` +### NodeRestore + +This module simply runs `npm`/`yarn` install as part of the build pipeline. + +```c# +bootstrapper.ModifyPipeline("Content", p => p.InputModules.Add(new NodeRestore())); +``` + ## Contributing Please be sure to read and follow ecoAPM's [Contribution Guidelines](CONTRIBUTING.md) when submitting issues or pull requests. \ No newline at end of file diff --git a/StatiqPipelines.Tests/CopyFromNPMTests.cs b/StatiqPipelines.Tests/CopyFromNPMTests.cs index 2afd2ea..12d5acd 100644 --- a/StatiqPipelines.Tests/CopyFromNPMTests.cs +++ b/StatiqPipelines.Tests/CopyFromNPMTests.cs @@ -11,32 +11,31 @@ public class CopyFromNPMTests public async Task NodeModulesAreTranslated() { //arrange - var context = new TestExecutionContext(); - context.FileSystem.GetInputFile("/node_modules/x/1.js").OpenWrite(); - context.FileSystem.GetInputFile("/node_modules/y/2.js").OpenWrite(); + var context = new TestExecutionContext { FileSystem = { RootPath = "/code/app" } }; + context.FileSystem.GetInputFile("/code/app/node_modules/x/1.js").OpenWrite(); + context.FileSystem.GetInputFile("/code/app/node_modules/y/2.js").OpenWrite(); var pipeline = new CopyFromNPM(new[] { "x/1.js", "y/2.js" }, "assets/js"); - var input = pipeline.InputModules.Where(m => m is ReadFiles); + var input = pipeline.InputModules.First(m => m is ReadFiles); //act - var tasks = input.Select(async i => await i.ExecuteAsync(context)); - var output = await Task.WhenAll(tasks); + var output = await input.ExecuteAsync(context); //assert - var files = output.SelectMany(d => d).Select(d => d.Source.FileName.ToString()).ToArray(); + var files = output.Select(d => d.Source.FileName.ToString()).ToArray(); Assert.Equal(2, files.Length); Assert.Contains("1.js", files); Assert.Contains("2.js", files); } [Fact] - public async Task CopyToFlattensOutputPathForFiles() + public async Task CopyToFlattensOutputByDefault() { //arrange var docs = new List { - new TestDocument(new NormalizedPath("/node_modules/x/1.js")), - new TestDocument(new NormalizedPath("/node_modules/y/2.js")) + new TestDocument(new NormalizedPath("/code/app/node_modules/x/1.js")), + new TestDocument(new NormalizedPath("/code/app/node_modules/y/2.js")) }; var context = new TestExecutionContext(); context.SetInputs(docs); @@ -54,18 +53,80 @@ public async Task CopyToFlattensOutputPathForFiles() } [Fact] - public async Task CopyToDoesNotFlattenOutputPathForDirectories() + public async Task CopyToFlattensOutputForEmptyValues() + { + //arrange + var docs = new List + { + new TestDocument(new NormalizedPath("/code/app/node_modules/x/1.js")), + new TestDocument(new NormalizedPath("/code/app/node_modules/y/2.js")) + }; + var context = new TestExecutionContext(); + context.SetInputs(docs); + + var files = new Dictionary + { + { "x/1.js", "" }, + { "y/2.js", " " } + }; + var pipeline = new CopyFromNPM(files, "assets/js"); + var copy = pipeline.ProcessModules.First(m => m is SetDestination); + + //act + var output = await copy.ExecuteAsync(context); + + //assert + var outputDocs = output.ToArray(); + Assert.Equal("assets/js/1.js", outputDocs[0].Destination); + Assert.Equal("assets/js/2.js", outputDocs[1].Destination); + } + + [Fact] + public async Task CopyToUsesSpecifiedValues() + { + //arrange + var docs = new List + { + new TestDocument(new NormalizedPath("/code/app/node_modules/x/y/1.js")), + new TestDocument(new NormalizedPath("/code/app/node_modules/x/y/z/2.js")) + }; + var context = new TestExecutionContext(); + context.SetInputs(docs); + + var files = new Dictionary + { + { "x/y/1.js", "y/1.js" }, + { "x/y/z/2.js", "y/z/2.js" } + }; + var pipeline = new CopyFromNPM(files, "assets/js"); + var copy = pipeline.ProcessModules.First(m => m is SetDestination); + + //act + var output = await copy.ExecuteAsync(context); + + //assert + var outputDocs = output.ToArray(); + Assert.Equal("assets/js/y/1.js", outputDocs[0].Destination); + Assert.Equal("assets/js/y/z/2.js", outputDocs[1].Destination); + } + + [Fact] + public async Task CanCopyToOutputUsingWildcardKeys() { //arrange var docs = new List { - new TestDocument(new NormalizedPath("/node_modules/x/1.js")), - new TestDocument(new NormalizedPath("/node_modules/x/y/2.js")) + new TestDocument(new NormalizedPath("/code/app/node_modules/x/1.js")), + new TestDocument(new NormalizedPath("/code/app/node_modules/x/2.js")) }; var context = new TestExecutionContext(); context.SetInputs(docs); - var pipeline = new CopyFromNPM(new[] { "x" }, "assets/js"); + var files = new Dictionary + { + { "x/*", "y" } + }; + var pipeline = new CopyFromNPM(files, "assets/js"); var copy = pipeline.ProcessModules.First(m => m is SetDestination); //act @@ -73,7 +134,7 @@ public async Task CopyToDoesNotFlattenOutputPathForDirectories() //assert var outputDocs = output.ToArray(); - Assert.Equal("assets/js/x/1.js", outputDocs[0].Destination); - Assert.Equal("assets/js/x/y/2.js", outputDocs[1].Destination); + Assert.Equal("assets/js/y/1.js", outputDocs[0].Destination); + Assert.Equal("assets/js/y/2.js", outputDocs[1].Destination); } } \ No newline at end of file diff --git a/StatiqPipelines/CopyFromNPM.cs b/StatiqPipelines/CopyFromNPM.cs index b9438e5..71af1ff 100644 --- a/StatiqPipelines/CopyFromNPM.cs +++ b/StatiqPipelines/CopyFromNPM.cs @@ -6,27 +6,49 @@ namespace ecoAPM.StatiqPipelines; public class CopyFromNPM : Pipeline { private const string NodeModulesDirectory = "node_modules/"; - private Dictionary _files; + + private readonly IDictionary _paths; /// /// Copies specific files from a `node_modules` directory to the output /// /// The file paths (relative to `node_modules`) to copy /// The path (relative to the output root) where the files will be copied - /// Flatten all files into the output directory public CopyFromNPM(IEnumerable paths, string output = "lib") + : this(Flatten(paths), output) { - _files = paths.ToDictionary(p => p, p => new ReadFiles(npmPath(p))); + } + + /// + /// Copies specific files from a `node_modules` directory to the output + /// + /// The file paths (relative to `node_modules`) to copy + /// The path (relative to the output root) where the files will be copied + public CopyFromNPM(Dictionary paths, string output = "lib") + { + _paths = paths; Isolated = true; - InputModules = new ModuleList { new NodeRestore() }; - InputModules.AddRange(_files.Values); + InputModules = new ModuleList + { + new NodeRestore(), + new ReadFiles(_paths.Keys.Select(npmPath)) + }; - ProcessModules = new ModuleList { CopyTo(output) }; + ProcessModules = new ModuleList + { + CopyTo(output) + }; - OutputModules = new ModuleList { new WriteFiles() }; + OutputModules = new ModuleList + { + new WriteFiles() + }; } + private static Dictionary Flatten(IEnumerable paths) + => paths.ToDictionary(p => p, path => new NormalizedPath(path).FileName.ToString()); + private static string npmPath(string path) => IExecutionContext.Current.FileSystem .GetRootDirectory(NodeModulesDirectory).GetFile(path) @@ -38,17 +60,27 @@ private SetDestination CopyTo(string output) private Config SetPath(string output) => Config.FromDocument(d => NewPath(output, d)); - private NormalizedPath NewPath(string output, IDocument d) - => new(OutputPath(output, d)); + private NormalizedPath NewPath(string output, IDocument doc) + => new(OutputPath(output, doc)); - private string OutputPath(string output, IDocument d) - => Path.Combine(output, RelativeOutputPath(d)); + private string OutputPath(string output, IDocument doc) + => Path.Combine(output, RelativeOutputPath(doc)); - private string RelativeOutputPath(IDocument d) - => _files.ContainsKey(RelativePath(d)) - ? d.Source.FileName.ToString() - : RelativePath(d); + private string RelativeOutputPath(IDocument doc) + => _paths.TryGetValue(RelativePath(doc.Source), out var path) + ? !path.IsNullOrWhiteSpace() ? path : doc.Source.FileName.ToString() + : HandleWildcard(doc); + + private string HandleWildcard(IDocument doc) + { + var paths = _paths.ToDictionary(p => p.Key.Split("*")[0], p => p.Value); + var match = paths.FirstOrDefault(p => doc.Source.FullPath.Contains(p.Key)); + var value = !match.Value.IsNullOrWhiteSpace() + ? Path.Combine(match.Value, doc.Source.FileName.ToString()).Replace("\\", "/") + : doc.Source.FileName.ToString(); + return value; + } - private static string RelativePath(IDocument d) - => d.Source.RootRelative.ToString().RemoveStart(NodeModulesDirectory); + private static string RelativePath(NormalizedPath p) + => p.RootRelative.ToString().Split(NodeModulesDirectory)[1]; } \ No newline at end of file diff --git a/StatiqPipelines/StatiqPipelines.csproj b/StatiqPipelines/StatiqPipelines.csproj index 9971286..b536bac 100644 --- a/StatiqPipelines/StatiqPipelines.csproj +++ b/StatiqPipelines/StatiqPipelines.csproj @@ -2,7 +2,7 @@ net6.0 - 1.1.0 + 1.2.0 ecoAPM.StatiqPipelines ecoAPM.StatiqPipelines Pipelines and helpers used in ecoAPM's static sites