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