DEV Community

MartinJ
MartinJ

Posted on • Edited on

NgSysV2-4.3: Automated Svelte Pre-render Builds

This post series is indexed at NgateSystems.com. You'll find a super-useful keyword search facility there too.

Last reviewed: Dec '24

1. Introduction

Post 4.2 floated the concept of pre-rendering a web page. The idea was that if a page never changes (or, at least, doesn't change too often) then it might as well be turned into HTMl during the project's "build".

This is fine but, if the underlying data changes too often, running builds to bring pre-rendered pages up to date manually will become annoying. Automation is surely the answer.

This post describes how you might create a script to run the build/deploy sequence and then configure the Windows scheduler to run this automatically.

2. A Powershell Build/Deploy script

Here's a ps1 script you might use:

$projectId = [myProjectId]
$projectPath = [myProjectPath]

# ensure the failure of "build" prevents execution of "deploy"
$ErrorActionPreference = "Stop"

# Define log file path
$logPath = "$projectPath\log.txt"

# Overwrite the current log file with a timestamp at the beginning
$timestamp = Get-Date -Format "yyyy-MM-dd HH:mm:ss"
"Log started at $timestamp" | Out-File -FilePath $logPath -Force

# list known accounts so you can check that your project email is marked as active 
gcloud auth list  | Out-File -FilePath $logPath -Append

# Set the project ID
gcloud config set project $projectId  | Out-File -FilePath $logPath -Append

# Redirect output to log file
try {

    cd $projectPath 2>&1 | Out-File -FilePath $logPath -Append

    #Build the app
    npm run build | Out-File -FilePath $logPath -Append

    # Fetch all versions ordered by creation date, excluding the latest 10
    $oldVersions = gcloud app versions list  `
        --sort-by="~version.createTime" `
        --format="value(version.id)" | Select-Object -Skip 10

    # Log the old versions to be deleted
    "Old versions to delete: $($oldVersions -join ', ')" | Out-File -FilePath $logPath -Append

    # Delete the old versions if there are any
    if ($oldVersions.Count -gt 0) {
        try {
         $oldVersions | ForEach-Object {
            gcloud app versions delete --service=default --quiet $_  2>&1 | Out-File -FilePath $logPath -Append
       } 
       } catch {
         "Batch deletion encountered an error: $_" | Out-File -FilePath $logPath -Append
       }
    } else {
        "No old versions to delete. The limit of 10 is not exceeded." | Out-File -FilePath $logPath -Append
    }

    # Deploy the app
    gcloud app deploy "$projectPath\build\app.yaml" --quiet 2>&1 | Out-File -FilePath $logPath -Append

    } catch {
        "An error occurred: $_" | Out-File -FilePath $logPath -Append
}
Enter fullscreen mode Exit fullscreen mode

The script looks more complicated than you might have expected. Here's why:

Because you intend to schedule the script automatically, you need to maintain a log file to tell you what has gone wrong if it errors. This adds much unavoidable"clutter". In the code above, [myProjectId} is your Google projectId - eg "svelte-dev-80286" and [myProjectPath] is the full pathname for your VSCode project - eg "C:\Users\mjoyc\Desktop\GitProjects\svelte-dev". The output log.txt file built by the Force and Append instructions in the script now ends up in the root of your VSCode project folder.

But the script also contains a strange "version deletion" section. You need this because, each time you run a "build", Google will create a new version in cloud storage.

In the script above, I limit the number of versions maintained to just 10 (I'm paying for my App Engine hosting now!). In any case, there's a limit to the number of versions you can create (around 200). Your script will error if this is exceeded.

You can test your script file in a VSCode terminal session by opening it in the editor and then selecting "Terminal -> Run Active File". For production purposes, however, you need some automation.

3. Configuring a Windows Schedule to run the PowerShell script

Here's a procedure for registering a Windows Scheduler task to run the build script.

  1. Type "Task Scheduler" in the Windows search bar and open the app.
  2. In the Actions menu, click on “Create a Basic Task”.
  3. Supply a name and description for the task
  4. On the Triggers tab, Select the interval you want to run the program, such as “Daily”, “Weekly”, etc.
  5. Specify the start date/time and frequency for the task.
  6. Select the “Start a program” option button.
  7. Now, in the “Start a Program” window: In Program/script: Use "browse" to help you enter the path of pwsh.exe (the terminal shell you use in VSCode - "C:\Program Files\PowerShell\7\pwsh.exe"). In Arguments: Enter the full path of the script. eg: [full path to the script][my script filename].ps1. Leave “Start in” blank
  8. In the next window, select the checkbox “Open the Properties dialog for this task when I click Finish”, and click the Finish button.
  9. In the General tab of the properties dialogue, ensure that the “Run when user is logged on or not” and “Run with highest privileges” checkboxes are selected. This ensures you are running the script with Administrator rights.
  10. Click the OK button and confirm your right to save your new scheduler task by responding to a login prompt with your Microsoft username and password for your machine.
  11. Test the new task by opening the Task Schedule Library, right-clicking on the task's entry here and selecting "Run"

I use a Windows Scheduler task created using the above procedure to run a nightly build for the pre-rendered "ngatesystems.com" keyword-search page. Though new posts are now added only rarely, I'm still making regular edits to existing pages. The nightly run arrangement means the search page is never more than a day behind the live data.

4. When things go wrong - Writing Scripts for Schedulers

One look at the sample script above and the Windows Task Scheduler that runs it will tell you all you need to know. This stuff is a "techy" mess with its roots in the last century and unlikely to improve much, if ever. However, it's a useful mess so you need to make the most of it. Here are a few pointers:

The syntax of "shell languages is broadly similar to that of Javascript. Unless you're going to be doing a lot of work in this area, just use chatGPT to get an explanation of the mechanics of individual statements. But there are a couple of features that are peculiar to the case where you're developing a script specifically to run in a scheduler. Here they are:

  1. When a script is fired by the scheduler, it does so in its own "terminal session". This means that it doesn't know anything about the location of your project. It's very easy to get yourself ito a situation where your script runs fine in a VSCode terminal session (where you've implicitly defined the root address of your project), then fails in the scheduler. This will often be because you've inadvertently used a relative address to reference a file. So, for example, an app deploy command would normally be written as gcloud run /build/app.yaml and will be fine for local purposes. But when it's being written for the scheduler, it must reference the full file path gcloud app deploy $projectPath\build\app.yaml, where $projectPath has previously been initialised with the project's absolute root address.

  2. You won't see any output from the Scheduler's terminal session so you need to "pipe" this into a log file in your project. You'll then find that you can see this in VSCode. The --Force flag on the first output command in the script clears any previous content. Subsequent --Append lines are then added sequentially.

  3. A feature of many terminal commands is that at certain points they're written to halt and request approval before proceeding to some critical process. The idea is to give the user a chance to take stock of a summary of what the job is just about to do and assure themselves that this is really what they want. When a job is being run automatically by a scheduler, however, this doesn't make a lot of sense. It seems that you're expected to override this behaviour by including a --quiet flag to the command. Without this, scheduled jobs may simply "hang" in a most perplexing way. In the example above, --quiet flags have been added to both the gcloud app versions delete and gcloud app deploy commands. Problems like this are one reason why scheduler scripts will generally include generous amounts of logging.

  4. Numerous command shells exist and they don't all support the same command syntax. My personal choice in VSCode has been the pwsh.exe shell. Whatever you opt for here, it makes sense to ensure that your scheduler uses the same shell. You can specify this for a Scheduler task in its "Actions" window" (see above for advice).

  5. The Windows Task Scheduler is a very powerful tool, but its interface is idiosyncratic. The procedure for building a new task is obvious enough, but you need to know that to edit this subsequently you need to right-click on its entry in the task list and select "properties". You can then select individual windows such as "Actions" (though be prepared to quote your Window password to authenticate field changes- no face-id or pin number here). Another good thing to know is that you can test a task by right-clicking and selecting "run". This will ignore whatever scheduling you've specified and fire the script immediately. Finally, if you want to copy a Task, do this by exporting it and re-importing it under a new name.

  6. The sample scheduler script shown above includes a catch-all try/catch block that adds $_, the "error object", to the output log file. This is all very well if someone is looking for errors, but for a serious application, you'd want a positive "heads up" arrangement that sends somebody an email or text alert. In the past, this would have been a trivial task, but "spamming" issues mean that the methods you might have used for this have now all been closed off. For simple cases, the answer might be to use a low-cost system like Pushover. This lets you send text messages to a phone for a one-off payment of just $5. After installing the app on your notification target, you'll receive a "user" key in an email to your registered address. You then need to confirm your address and use the PI tab to "register an API" (just log an application name) and receive an API "token". You can then use these keys in a curl command submitted via a pwsh terminal script as follows:

curl -s `
  --form-string "user=uec ..obfuscated.. kcmpzoixjqjrv54u22" `
  --form-string "token=aiu7yk ..obfuscated.. rtf35uehqq6ix" `
  --form-string "message=hello world" `
  https://api.pushover.net/1/messages.json
Enter fullscreen mode Exit fullscreen mode

Top comments (0)