Content Developer II at Microsoft, working remotely in PA, TechBash conference organizer, former Microsoft MVP, Husband, Dad and Geek.
129718 stories
·
29 followers

Boost Your .NET Projects: Unleashing the Power of Spargine’s List Extension Methods

1 Share
Spargine is an open-source collection of .NET 8 assemblies and NuGet packages developed for enhancing List collections in .NET. It features various performance-optimized methods for List manipulation, including adding, converting, and checking items.



Read the whole story
alvinashcraft
11 hours ago
reply
West Grove, PA
Share this story
Delete

How to work on a large codebase

1 Share

Join my free Modern C# course https://www.productivecsharp.com/modern-csharp







Download audio: https://anchor.fm/s/991d954/podcast/play/94191695/https%3A%2F%2Fd3ctxlq1ktw2nl.cloudfront.net%2Fstaging%2F2024-10-9%2F389529038-44100-2-43335c6e224c2.mp3
Read the whole story
alvinashcraft
16 hours ago
reply
West Grove, PA
Share this story
Delete

Build a Command-Line Application With Deno 2.0

1 Share

Command-line interfaces (CLI) are often used for automating tasks, such as building reports, synchronizing data between systems, migrating data, deploying applications, and so on and on. Over the years, I have built countless CLI apps to save time. If I ever find myself doing something more than once, I try to find a way to automate it!

Deno 2.0 is an excellent solution for writing CLI apps. It supports TypeScript and JavaScript, it's cross-platform (runs on Windows, macOS, and Linux), has dozens of powerful tools in its standard library, and can also tap into most Node.js modules. The only limit is your imagination!

In this tutorial, you will learn how to:

  • Create a command-line interface with Deno 2.0
  • Parse command-line arguments
  • Print help and version information
  • Prompt for additional information
  • Compile your app into a standalone executable

Set Up Your CLI Project

First, let's make sure you have the tools you need!

Open your computer's terminal (or command prompt). Change the current directory to the folder where you normally save projects.

Note: If you don't already have a folder where you store software projects, I like creating a folder at the root of my home directory named projects. More than likely, when you open your computer's terminal/console app, you are automatically placed in your "user home" folder. Use mkdir projects (or md projects if you're on Windows) to create the folder. Then, use cd projects to change to that new folder.

Verify you have Deno 2.0 (or higher) installed using the following command.

deno --version

You should see something like:

deno 2.0.5 (stable, release, aarch64-apple-darwin)
v8 12.9.202.13-rusty
typescript 5.6.2

If you receive an error, or if your version of Deno is 1.x, follow the installation.

Next, enter the following commands to initialize a new Deno project.

deno init deno-cli-demo
cd deno-cli-demo

We're going to use Deno's @std/cli standard library, so add that to the project using the following command.

deno add jsr:@std/cli

Create Your First CLI App

Open up your new project using your preferred editor. Create a new file named hello.ts and add the following code.

const now = new Date();
const message = "The current time is: " + now.toTimeString();

console.log("Welcome to Deno 🦕 Land!");
console.log(message);

From your terminal, enter the following command to run the script.

deno run hello.ts

You've built your first Deno CLI application! Feel free to play around with writing other things to the console.

Using Command-Line Arguments

Arguments? No, we're not talking about getting into a heated debate with your terminal. Although that can certainly happen. Computers can be rather obstinate.

Command-line arguments are options and values you might provide the CLI when you run the app. When you enter deno run hello.ts, deno is the CLI, and run hello.ts are two arguments you provide to the CLI.

Create a new file named add.ts and add the following code.

import { parseArgs } from "@std/cli/parse-args";

const args = parseArgs(Deno.args);
console.log("Arguments:", args);
const a = args._[0];
const b = args._[1];
console.log(`${a} + ${b} = ` + (a + b));

The idea is to take two numbers and add them together. Try it out!

deno run add.ts 1 2

Experiment with additional arguments. Or none at all. The parseArgs function can also handle arguments traditionally called switches and flags. Try the following and observe the output.

deno run add.ts 3 4 --what=up -t -y no

Advanced Command-Line Arguments

We've only just scratched the surface of what you can do with command-line arguments. Let's try a more advanced example!

Create a new file named sum.ts and add the following code.

import { parseArgs, ParseOptions } from "@std/cli/parse-args";
import meta from "./deno.json" with { type: "json" };

function printUsage() {
    console.log("");
    console.log("Usage: sum <number1> <number2> ... <numberN>");
    console.log("Options:");
    console.log("  -h, --help        Show this help message");
    console.log("  -v, --version     Show the version number");
}

const options: ParseOptions = {
    boolean: ["help", "version"],
    alias: { "help": "h", "version": "v" },
};
const args = parseArgs(Deno.args, options);

if (args.help || (args._.length === 0 && !args.version)) {
    printUsage();
    Deno.exit(0);
} else if (args.version) {
    // Pro tip: add a version to your deno.json file
    console.log(meta.version ? meta.version : "1.0.0");
    Deno.exit(0);
}

// validate all arguments are numbers
const numbers: number[] = args._.filter((arg) => typeof arg === "number");
if (numbers.length !== args._.length) {
    console.error("ERROR: All arguments must be numbers");
    printUsage();
    Deno.exit(1);
}
// sum up the number arguments
const sum = numbers.reduce((sum, val) => sum + val);

// print the numbers and the total
console.log(`${numbers.join(" + ")} = ${sum}`);

Whoa, there's a lot going on here 😬 Let's try it out first, and then we'll cover some of the highlights. Try the following commands and see how the output changes.

deno run sum.ts 1 2 3 4 5
deno run sum.ts --help
deno run sum.ts --version
deno run sum.ts -h
deno run sum.ts 1 2 three

Now, if you go back and look through the code in sum.ts, you can probably figure out some of the logic involved in handling the different arguments. The main thing I want to point out is this block of code.

const options: ParseOptions = {
    boolean: ["help", "version"],
    alias: { "help": "h", "version": "v" },
};
const args = parseArgs(Deno.args, options);

The parseArgs function supports quite a few options to support a wide variety of arguments.

  • boolean: ["help", "version"]: defines the --help and --version flags.
  • alias: { "help": "h", "version": "v" }: defines alternate -h and -v flags.

As you may have guessed, Deno.exit(0); causes Deno to immediately stop the current script.

More Input Required (Prompts)

Imagine you're creating a CLI app to automate a report. Your app might connect to a system that requires authentication, such as a database or API.

Never embed secrets (sensitive information such as user names, passwords, API keys, connection strings, etc.) in your code. You don't want your secrets ending up in the hands of the wrong people!

In this case, the CLI should prompt for the sensitive information when it runs. Let's create another example to demonstrate how this is done.

First, add a new dependency using the following command.

deno add jsr:@std/dotenv

Create a new file named taskRunner.ts and add the following code.

import { Spinner } from "@std/cli/unstable-spinner";

function sleep(ms: number): Promise<void> {
    return new Promise((resolve) => setTimeout(resolve, ms));
}

export async function simulateTask(ms: number, message: string) {
    const spinner = new Spinner({ message, color: "yellow" });
    const start = performance.now();
    spinner.start();
    await sleep(ms);
    spinner.stop();
    const finish = performance.now();
    const duration = Math.round((finish - start) / 100) / 10;
    console.log(`${message} (${duration.toFixed(1)}s).`);
}

This is a module that exports a function named simulateTask that we'll use from the CLI app. The simulateTask function includes a few tricks, such as keeping track of how long the task runs and displaying a cool spinning animation while the task is running 🤓

Create a new file named updater.ts and add the following code.

import "@std/dotenv/load";
import { parseArgs, ParseOptions } from "@std/cli/parse-args";
import { promptSecret } from "@std/cli/prompt-secret";

import meta from "./deno.json" with { type: "json" };
import { simulateTask } from "./taskRunner.ts";

function printUsage() {
    console.log("Usage: ");
    console.log("  updater --input <input file> --output <output file>");
    console.log("Options:");
    console.log("  -h, --help        Show this help message");
    console.log("  -v, --version     Show the version number");
    console.log("  -i, --input       Input file");
    console.log("  -o, --output      Output file");
}

const options: ParseOptions = {
    boolean: ["help", "version"],
    string: ["input", "output"],
    default: { "input": "data.csv", "output": "report.pdf" },
    alias: { "help": "h", "version": "v", "input": "i", "output": "o" },
};
const args = parseArgs(Deno.args, options);

if (args.help) {
    printUsage();
    Deno.exit(0);
} else if (args.version) {
    // Pro tip: add a version to your deno.json file
    console.log(meta.version ? meta.version : "1.0.0");
    Deno.exit(0);
}

// validate the input and output arguments
if (!args.input || !args.output) {
    console.log("You must specify both an input and output file");
    printUsage();
    Deno.exit(1);
}

// attempt to get the username and password from environment variables
let user = Deno.env.get("MY_APP_USER");
let password = Deno.env.get("MY_APP_PASSWORD");

if (user === undefined) {
    const userPrompt = prompt("Please enter the username:");
    user = userPrompt ?? "";
}
if (password === undefined) {
    const passPrompt = promptSecret("Please enter the password:");
    password = passPrompt ?? "";
}

// simulating a few long-running tasks
await simulateTask(1000, `Reading input file [${args.input}]`);
await simulateTask(1500, `Connecting with user [${user}]`);
await simulateTask(5000, `Reading data from external system`);
await simulateTask(3200, `Writing output file [${args.output}]`);

console.log("Done!");

Some of this code may look familiar to the previous sum.ts CLI example. Try running the app and see what happens!

deno run updater.ts --help

Did you get a strange message like ⚠️ Deno requests read access to...? What's going on here?

Deno security and permissions

Deno is secure by default. It won't be able to read/write files, access your local environment variables, connect to your network, and a number of other potentially risky operations unless you explicitly give it permission.

Now run it using the following command. Don't worry. It's not doing anything; it's just simulating what a real app might look like.

deno run --allow-read --allow-env updater.ts

Wasn't that cool??

Updater Demo

Deno tasks

However, including those permissions is a lot to type every time you want to run the CLI app. Let's add a task to make it easier. Open up your deno.json file and update the "tasks" with the following.

"tasks": {
    "updater": "deno run --allow-read --allow-env updater.ts"
},

Now you can use the following command.

deno task updater --input data.csv --output report.pdf

Prompts and environment variables

Let's revisit the code in updater.ts. Specifically, this block of code.

// attempt to get the username and password from environment variables
let user = Deno.env.get("MY_APP_USER");
let password = Deno.env.get("MY_APP_PASSWORD");

if (user === undefined) {
    const userPrompt = prompt("Please enter the username:");
    user = userPrompt ?? "";
}
if (password === undefined) {
    const passPrompt = promptSecret("Please enter the password:");
    password = passPrompt ?? "";
}

It's common practice to store app configuration as environment variables. Deno can access environment variables (with permission) using Deno.env.get(). If MY_APP_USER or MY_APP_PASSWORD are not set as environment variables, the app will use prompt() or promptSecret() to ask for those values. As you've already seen, promptSecret() hides the characters you type.

Remember the dependency we added for jsr:@std/dotenv? This standard library reads a file named .env and adds any values as environment variables.

Create a new file in the project named .env and add the following text.

MY_APP_USER=loluser
MY_APP_PASSWORD=p@ssw0rd1

Now run the updater task again. It should run without prompting you for a username or password.

Compile Your CLI App to an Executable

Want to share your CLI app with others or run the app on another computer? You can compile a Deno-powered CLI app into a standalone executable! Standalone means it can run without installing Deno or any of the libraries. Everything is bundled 📦

Pro tip: Use a task scheduler to run the executable periodically.

deno compile --allow-read --allow-env updater.ts

You should now have an executable version!

./updater --help

Among other things, you can create executables for other platforms.

Wrapping Up and Additional CLI Tools for Deno

Thank you for joining me on this journey of learning Deno! If you have any questions or suggestions, please drop them in the comments.

Here are more CLI tools for Deno you might explore!

  • ask: additional command-line prompts for Deno.
  • chalk: custom text styles and color for the command-line.
  • cliffy: advanced command-line tools for Deno.
Read the whole story
alvinashcraft
16 hours ago
reply
West Grove, PA
Share this story
Delete

Azure Network Security Best Practices to Protect Your Cloud Infrastructure

1 Share

Securing network environments in Microsoft Azure is paramount when organizations migrate their infrastructure and applications to the cloud. Azure, as one of the leading cloud service providers, offers a broad spectrum of security tools and best practices designed to protect resources against a multitude of network threats. Azure network security plays a critical role in […]

The article Azure Network Security Best Practices to Protect Your Cloud Infrastructure was originally published on Build5Nines. To stay up-to-date, Subscribe to the Build5Nines Newsletter.

Read the whole story
alvinashcraft
1 day ago
reply
West Grove, PA
Share this story
Delete

Podcast: What Trump 2.0 means for tech, Diller nixes Expedia + Uber, and a GeekWire fall update

1 Share

This week on the GeekWire Podcast, it’s a grab-bag of topics, including self-driving wheelchairs, Expedia Group Chairman Barry Diller’s comments on the prospects for an acquisition by Uber, and an update on GeekWire’s upcoming events and coverage. In the final segment, we discuss what the new Trump administration could mean for technology regulation, including the FTC’s antitrust case against Amazon and oversight for tech M&A.

Subscribe to GeekWire in Apple Podcasts, Spotify, or wherever you listen.

Related coverage and links

Upcoming GeekWire Events

With GeekWire co-founders Todd Bishop and John Cook.

Read the whole story
alvinashcraft
1 day ago
reply
West Grove, PA
Share this story
Delete

Building .NET While Offline Using the Local NuGet Cache

1 Share

There are plenty of times when I’m offline and want to create a new .NET application. If it’s a trivial application without NuGet packages, then I’m fine.

But usually, I need to add at least one NuGet package, now I have a problem. I’ve always found this a little strange - whenever I add a NuGet package it is downloaded to my computer, I have over 800 on the computer I’m using right now.

Why don’t the restore or build commands use the package that is cached locally. It might be to look for the newest version, but I’m offline, so I can’t get the latest version anyway. The most recent will do.

Fortunately, there is a way to get the restore and build commands to use the local cache.

Find the Local NuGet Cache

First I have to locate the local NuGet cache.

Run the following command to see where you are storing NuGet packages -

dotnet nuget locals all --list

It will show output like -

http-cache: C:\Users\bryan\AppData\Local\NuGet\v3-cache
global-packages: C:\Users\bryan\.nuget\packages\
temp: C:\Users\bryan\AppData\Local\Temp\NuGetScratch
plugins-cache: C:\Users\bryan\AppData\Local\NuGet\plugins-cache

The global-packages folder is the one you want.

Add this as a source for NuGet

NuGet can look at multiple sources for packages, by default the highest priority source is nuget.org. Take a look at the current sources by running -

dotnet nuget list source 
Registered Sources:
  1. nuget.org [Enabled]
 https://api.nuget.org/v3/index.json

You can see that nuget.org is the only source I have set. If you had Visual Studio, there would be a secondary source set up for that.

To get builds working offline, add the directory from step 1 to the list of sources.

dotnet nuget add source C:\Users\bryan\.nuget\packages\ -n local

Run the list source command again -

dotnet nuget list source 

You should see the new source -

Registered Sources:
  1. nuget.org [Enabled]
 https://api.nuget.org/v3/index.json
  2. local [Enabled]
 C:\Users\bryan\.nuget\packages\

That’s it. Now I can build almost anything while offline.

Read the whole story
alvinashcraft
1 day ago
reply
West Grove, PA
Share this story
Delete
Next Page of Stories