Skip to content

Commit

Permalink
Merge pull request #105 from CommunityToolkit/aaronpowell/issue95
Browse files Browse the repository at this point in the history
npm ci support
  • Loading branch information
aaronpowell authored Oct 18, 2024
2 parents 2d7ab1d + 6e3b661 commit 9e8da7f
Show file tree
Hide file tree
Showing 6 changed files with 24 additions and 28 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
using Aspire.Hosting.Utils;
using Aspire.CommunityToolkit.Hosting.NodeJS.Extensions;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.DependencyInjection;

namespace Aspire.Hosting;

Expand Down Expand Up @@ -96,10 +97,12 @@ public static IResourceBuilder<NodeAppResource> AddPnpmApp(this IDistributedAppl
/// Ensures the Node.js packages are installed before the application starts using npm as the package manager.
/// </summary>
/// <param name="resource">The Node.js app resource.</param>
/// <param name="useCI">When true use <code>npm ci</code> otherwise use <code>npm install</code> when installing packages.</param>
/// <returns>A reference to the <see cref="IResourceBuilder{T}"/>.</returns>
public static IResourceBuilder<NodeAppResource> WithNpmPackageInstallation(this IResourceBuilder<NodeAppResource> resource)
public static IResourceBuilder<NodeAppResource> WithNpmPackageInstallation(this IResourceBuilder<NodeAppResource> resource, bool useCI = false)
{
resource.ApplicationBuilder.Services.TryAddLifecycleHook<NpmPackageInstallerLifecycleHook>();
resource.ApplicationBuilder.Services.TryAddLifecycleHook<NpmPackageInstallerLifecycleHook>(sp =>
new(useCI, sp.GetRequiredService<ResourceLoggerService>(), sp.GetRequiredService<ResourceNotificationService>(), sp.GetRequiredService<DistributedApplicationExecutionContext>()));
return resource;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,10 @@ namespace Aspire.CommunityToolkit.Hosting.NodeJS.Extensions;
/// <param name="packageManager">The package manager to use.</param>
/// <param name="loggerService">The logger service to use.</param>
/// <param name="notificationService">The notification service to use.</param>
internal class NodePackageInstaller(string packageManager, ResourceLoggerService loggerService, ResourceNotificationService notificationService)
internal class NodePackageInstaller(string packageManager, string installCommand, string lockfile, ResourceLoggerService loggerService, ResourceNotificationService notificationService)
{
private readonly bool isWindows = RuntimeInformation.IsOSPlatform(OSPlatform.Windows);

/// <summary>
/// Finds the Node.js resources using the specified package manager and installs the packages.
/// </summary>
Expand Down Expand Up @@ -42,16 +44,16 @@ private async Task PerformInstall(NodeAppResource resource, CancellationToken ca
{
var logger = loggerService.GetLogger(resource);

var packageJsonPath = Path.Combine(resource.WorkingDirectory, "package.json");
var packageJsonPath = Path.Combine(resource.WorkingDirectory, lockfile);

if (!File.Exists(packageJsonPath))
{
await notificationService.PublishUpdateAsync(resource, state => state with
{
State = new($"No package.json file found in {resource.WorkingDirectory}", KnownResourceStates.FailedToStart)
State = new($"No {lockfile} file found in {resource.WorkingDirectory}", KnownResourceStates.FailedToStart)
}).ConfigureAwait(false);

throw new InvalidOperationException($"No package.json file found in {resource.WorkingDirectory}");
throw new InvalidOperationException($"No {lockfile} file found in {resource.WorkingDirectory}");
}

await notificationService.PublishUpdateAsync(resource, state => state with
Expand All @@ -61,25 +63,12 @@ await notificationService.PublishUpdateAsync(resource, state => state with

logger.LogInformation("Installing {PackageManager} packages in {WorkingDirectory}", packageManager, resource.WorkingDirectory);

var packageInstaller = !RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? new Process
{
StartInfo = new ProcessStartInfo
{
FileName = packageManager,
Arguments = "install",
WorkingDirectory = resource.WorkingDirectory,
RedirectStandardOutput = true,
RedirectStandardError = true,
CreateNoWindow = true,
UseShellExecute = false,
}
}
: new Process
var packageInstaller = new Process
{
StartInfo = new ProcessStartInfo
{
FileName = "cmd",
Arguments = $"/c {packageManager} install",
FileName = isWindows ? "cmd" : packageManager,
Arguments = isWindows ? $"/c {packageManager} {installCommand}" : installCommand,
WorkingDirectory = resource.WorkingDirectory,
RedirectStandardOutput = true,
RedirectStandardError = true,
Expand Down Expand Up @@ -118,7 +107,7 @@ await notificationService.PublishUpdateAsync(resource, state => state with
packageInstaller.BeginOutputReadLine();
packageInstaller.BeginErrorReadLine();

packageInstaller.WaitForExit();
await packageInstaller.WaitForExitAsync(cancellationToken).ConfigureAwait(false);

if (packageInstaller.ExitCode != 0)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,13 @@ namespace Aspire.CommunityToolkit.Hosting.NodeJS.Extensions;
/// <param name="loggerService">The logger service used for logging.</param>
/// <param name="notificationService">The notification service used for sending notifications.</param>
/// <param name="context">The execution context of the distributed application.</param>
internal class NpmPackageInstallerLifecycleHook(ResourceLoggerService loggerService, ResourceNotificationService notificationService, DistributedApplicationExecutionContext context) : IDistributedApplicationLifecycleHook
internal class NpmPackageInstallerLifecycleHook(
bool useCI,
ResourceLoggerService loggerService,
ResourceNotificationService notificationService,
DistributedApplicationExecutionContext context) : IDistributedApplicationLifecycleHook
{
private readonly NodePackageInstaller _installer = new("npm", loggerService, notificationService);
private readonly NodePackageInstaller _installer = new("npm", useCI ? "ci" : "install", "package-lock.json", loggerService, notificationService);

/// <inheritdoc />
public Task BeforeStartAsync(DistributedApplicationModel appModel, CancellationToken cancellationToken = default)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ internal class PnpmPackageInstallerLifecycleHook(
ResourceNotificationService notificationService,
DistributedApplicationExecutionContext context) : IDistributedApplicationLifecycleHook
{
private readonly NodePackageInstaller _installer = new("pnpm", loggerService, notificationService);
private readonly NodePackageInstaller _installer = new("pnpm", "install", "pnpm-lock.yaml", loggerService, notificationService);

/// <inheritdoc />
public Task BeforeStartAsync(DistributedApplicationModel appModel, CancellationToken cancellationToken = default)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,6 @@
static Aspire.Hosting.NodeJSHostingExtensions.AddPnpmApp(this Aspire.Hosting.IDistributedApplicationBuilder! builder, string! name, string! workingDirectory, string! scriptName = "start", string![]? args = null) -> Aspire.Hosting.ApplicationModel.IResourceBuilder<Aspire.Hosting.NodeAppResource!>!
static Aspire.Hosting.NodeJSHostingExtensions.AddViteApp(this Aspire.Hosting.IDistributedApplicationBuilder! builder, string! name, string? workingDirectory = null, string! packageManager = "npm") -> Aspire.Hosting.ApplicationModel.IResourceBuilder<Aspire.Hosting.NodeAppResource!>!
static Aspire.Hosting.NodeJSHostingExtensions.AddYarnApp(this Aspire.Hosting.IDistributedApplicationBuilder! builder, string! name, string! workingDirectory, string! scriptName = "start", string![]? args = null) -> Aspire.Hosting.ApplicationModel.IResourceBuilder<Aspire.Hosting.NodeAppResource!>!
static Aspire.Hosting.NodeJSHostingExtensions.WithNpmPackageInstallation(this Aspire.Hosting.ApplicationModel.IResourceBuilder<Aspire.Hosting.NodeAppResource!>! resource) -> Aspire.Hosting.ApplicationModel.IResourceBuilder<Aspire.Hosting.NodeAppResource!>!
static Aspire.Hosting.NodeJSHostingExtensions.WithNpmPackageInstallation(this Aspire.Hosting.ApplicationModel.IResourceBuilder<Aspire.Hosting.NodeAppResource!>! resource, bool useCI = false) -> Aspire.Hosting.ApplicationModel.IResourceBuilder<Aspire.Hosting.NodeAppResource!>!
static Aspire.Hosting.NodeJSHostingExtensions.WithPnpmPackageInstallation(this Aspire.Hosting.ApplicationModel.IResourceBuilder<Aspire.Hosting.NodeAppResource!>! resource) -> Aspire.Hosting.ApplicationModel.IResourceBuilder<Aspire.Hosting.NodeAppResource!>!
static Aspire.Hosting.NodeJSHostingExtensions.WithYarnPackageInstallation(this Aspire.Hosting.ApplicationModel.IResourceBuilder<Aspire.Hosting.NodeAppResource!>! resource) -> Aspire.Hosting.ApplicationModel.IResourceBuilder<Aspire.Hosting.NodeAppResource!>!
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ internal class YarnPackageInstallerLifecycleHook(
ResourceNotificationService notificationService,
DistributedApplicationExecutionContext context) : IDistributedApplicationLifecycleHook
{
private readonly NodePackageInstaller _installer = new("yarn", loggerService, notificationService);
private readonly NodePackageInstaller _installer = new("yarn", "install", "yarn.lock", loggerService, notificationService);

/// <inheritdoc />
public Task BeforeStartAsync(DistributedApplicationModel appModel, CancellationToken cancellationToken = default)
Expand Down

0 comments on commit 9e8da7f

Please sign in to comment.