Issues

Package Script Writer

What is Package Script Writer?

Package Script Writer is a website I created as a tool to make it easier for people to install Umbraco and the associated nuget packages. It generates the correct command line scripts so you can copy it and paste it into a terminal. If you paste the default script into a terminal, it will install the latest version of Umbraco for you with a SQLite database and with the default Umbraco Starter Kit.

Why did I build it?

When Umbraco 9 was released and we went from .NET Framework to .NET, I started to use the command line for installing the latest Umbraco Templates and then for creating new projects.

I prefer to use the command line for everything now, but I still struggle to remember all of the correct commands and switches to do everything, so I always had to look it up all the time. This felt like a bit of a barrier for me and must have been even worse for beginners with Umbraco. I like to create ways to make working with Umbraco easier for beginners, that’s why I make tutorial videos on YouTube and also build simple starter kits.

The main reason why I created it though was because I was on a judging panel for the Umbraco Package Awards and we all needed a way to review lots of packages quickly and easily. It is quite a daunting and time consuming task to install lots of different packages from scratch. Imagine all 7 of the judging panel having to work out what the right commands were to install each package. How much time would be wasted between us all. What if there was an easier way?

So that is why I built the website in such a way that you can pick your packages and starter kit, have the unattended install all configured too and the icing on the cake is the ability to share or save the URLs because it uses the query string to load in all of the settings. We were then able to add a URL into the google sheet next to each package, giving all of the judges exactly the same commands to run for installing and reviewing each package.

I was also due to give a talk about my favourite packages at Codegarden, so it made my life easier for that too.

How do you use it?

  1. Visit the website https://psw.codeshare.co.uk
  2. Use the search bar to find the packages you want to install. When you have found the packages, if there are any you want to install then check the box under package name to select it. You can also choose a specific version of a package using the dropdown under the checkbox.

3. If you want to change the default settings to choose a specific version of Umbraco to install or which starter kit etc. you can use the Options tab to do all of that.

4. Click on the Install Script tab to see the generated script.

5. Now click on copy script

6. Open up your terminal of choice in the correct folder where you want to install the umbraco site, or add packages to an existing one.

7. Paste in the copied commands and press enter. Watch the terminal process each command and do all the work for you.

8. If you have created a script that you would like to save for using again later or share with others you can copy the full URL with the query strings and that will load the same exact same script next time. This means you can bookmark your favourite scripts for later use.

9. I also made a YouTube video to show you how to use it.

How did I build it?

Originally I built the website in one of the Umbraco 10 release candidate (RC) versions, because I thought it would be cool to test out the latest RC and also create my first v10 site.

The main point of the code is to generate the script that you need to paste into the terminal. This is done in the script generator service. There is a method called GenerateScript and it is broken down into several other methods to generate the different parts of the script. This is what the GenerateScript method looks like:

public string GenerateScript(PackagesViewModel model)
{
    var output = new StringBuilder();

    var renderPackageName = !string.IsNullOrWhiteSpace(model.ProjectName);

    if (model.InstallUmbracoTemplate)
    {
        output.Append(GenerateUmbracoTemplatesSectionScript(model));

        output.Append(GenerateCreateSoltionFileScript(model));

        output.Append(GenerateCreateProjectScript(model));

        output.Append(GenerateAddProjectToSolutionScript(model));

        output.AppendLine();
    }

    output.Append(GenerateAddStarterKitScript(model, renderPackageName));

    output.Append(GenerateAddPackagesScript(model, renderPackageName));

    output.Append(GenerateRunProjectScript(model, renderPackageName));

    return output.ToString();
}

By writing it in this way, when it comes to make a change we can focus on changing an individual method.

Here is an example of one of the methods that it calls:

public string GenerateCreateSoltionFileScript(PackagesViewModel model)
{
    var output = new StringBuilder();

    if (model.CreateSolutionFile)
    {
        output.AppendLine("# Create solution/project");
        if (!string.IsNullOrWhiteSpace(model.SolutionName))
        {
            output.AppendLine($"dotnet new sln --name \"{model.SolutionName}\"");
        }
    }

    return output.ToString();
}

I ran into some issues when I came to deploying the site, so I reached out to Aaron from UmbHost to see if he could help me. He tried deploying it to his hosting environment and he had the same issues. So I decided to create it as a .NET 6 MVC website without Umbraco. The cool thing about this was that there really wasn’t much code to change because Umbraco 10 is so close to being a plain MVC site already. It took me about 30 - 60 minutes to strip out Umbraco and deploy it as a standalone MVC website.

It turns out the issue I had with deploying the Umbraco 10 site was down to the version of .NET 6 SDK that was installed on the server.

To be honest, there was no need to have Umbraco with a database etc for this simple site. It’s not a content site, it’s just a form that generates some text.

To get the information about the Umbraco Template versions, the website calls the NuGet API and caches the results for an hour. This means the site will always stay up to date with the Umbraco versions without having to change any of the code.

Here is what the method for getting the data from NuGet looks like:

public List GetNugetPackageVersions(string packageUrl)
{
    var client = clientFactory.CreateClient();
    var result = client.GetAsync(packageUrl).Result;
    if (result.IsSuccessStatusCode)
    {
        var data = result.Content.ReadAsStringAsync().Result;
        var packageVersions = JsonSerializer.Deserialize(data);

        if (packageVersions is { Versions: { } })
        {
            return packageVersions.Versions.Reverse().ToList();
        }
    }

    return new List();
}

Aaron refactored this and submitted a PR to fix this for me when it stopped working. Thanks again Aaron.

To load in all of the Umbraco Packages for the search and selection of packages, the website calls the Our Umbraco API and caches the results for an hour too. This means the site gets to use all of the new packages and changes to the existing packages without having to change anything in the code.

Here is what the method looks like for loading in the packages data from Our Umbraco API:

public List GetAllPackagesFromUmbraco()
{

    int pageIndex = 0;
    var pageSize = 200;
    var carryOn = true;
    var allPackages = new List();
    var total = 0;
    var totalSoFar = 0;
    while (carryOn && (pageIndex == 0 || totalSoFar < total))
    {
        totalSoFar = (pageIndex + 1) * pageSize;
        var url = $"https://our.umbraco.com/webapi/packages/v1?pageIndex={pageIndex}&pageSize={pageSize}&category=&query=&order=Latest&version=9.5.0";

        var XmlReader = new XmlTextReader(url);

        var serializer = new XmlSerializer(typeof(PagedPackages));

        try
        {
            PagedPackages? packageFeed = serializer.Deserialize(XmlReader) as PagedPackages;
            if (packageFeed?.Packages != null)
            {
                if (pageIndex == 0)
                {
                    total = packageFeed.Total;
                }
                allPackages.AddRange(packageFeed.Packages.Where(x => x != null));
                carryOn = true;
            }
            else
            {
                carryOn = false;
            }
        }
        catch
        {
            carryOn = false;
            break;
        }

        pageIndex++;
    }
    return allPackages;
}

If you think any of the code above could be improved, please read the next section about how to contribute.

How can I contribute to it?

If you want to see the code or contribute, it is publicly available on GitHub.

The process to follow when contributing is to

  1. Raise an issue if there isn’t one already. This can be for a bug or a feature. Give a detailed description about the problem, what were you expecting and what actually happened etc.
  2. We can discuss new features in the issue to make sure that your PR is something I want to include on the site before you go and spend a long time working on a PR that might not get merged in.
  3. Fork the repository in your own GitHub account.
  4. Work on the code in a branch on your fork.
  5. Push your changes in your branch up to your repository.
  6. Create a PR back to the main repository that shows what the problem was, how you fixed it and what happens now.
  7. Hopefully I will be able to review the PR, test it out, and all being well, merge it in.

Spread the word

Please share the Package Script Writer with your colleagues or friends in the Umbraco Community. Let them know there is a tool out there to make their lives easier when it comes to working with Umbraco 9, 10 and beyond.

Paul Seal

Paul is an Umbraco MVP, working for the Umbraco Gold Partner Moriyama. He is passionate about Umbraco and Web Development in general. He loves to create open source packages and likes to share his experience and knowledge through his website codeshare.co.uk and his YouTube channel.

comments powered by Disqus