Building and deploying fractal.build to Azure from Team Foundation Server

This year we started with creating a Design Language System for Nextens. It runs on Fractal, “a tool to help you build and document web component libraries, and then integrate them into your projects”. We use it to create and document the atoms, molecules and organisms that make up our components.

The site is deployed continuously to an App Service in Azure from our on-premise Microsoft Visual Studio Team Foundation Server 2017.3.1. This post documents how this is done.

web.config

In the source folder we have a web.config that fixes the MIME types of fonts so they load correctly in Azure, and which adds a rewrite rule that enables extension-less URLs in the documentation pages of Fractal.

The latter is needed because we have this setting in \gulp\fractal-setup.js:

1
fractal.web.set('builder.urls.ext', null); // default is '.html'

The content of web.config:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<system.web>
<compilation debug="true" targetFramework="4.0"/>
</system.web>
<system.webServer>
<httpProtocol>
<customHeaders>
<add name="Access-Control-Allow-Origin" value="*" />
</customHeaders>
</httpProtocol>
<staticContent>
<remove fileExtension=".svg" />
<remove fileExtension=".woff" />
<remove fileExtension=".woff2" />
<mimeMap fileExtension=".svg" mimeType="image/svg+xml" />
<mimeMap fileExtension=".woff" mimeType="application/x-font-woff" />
<mimeMap fileExtension=".woff2" mimeType="application/font-woff2" />
</staticContent>
<rewrite>
<rules>
<rule name="docs/pattern-overview">
<match url="(^docs|^components)(\/.+)+$"/>
<action type="Rewrite" url="{R:0}.html"/>
<conditions>
<add input="{URL}" pattern=".(html|svg|scss|css)$" negate="true"/>
</conditions>
</rule>
</rules>
</rewrite>
</system.webServer>
</configuration>

The web.config file is copied from the source folder to the public folder by a gulp task (gulp\gulp-tasks\copy.js):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
/* global module */
module.exports = function (gulp, config, plugins, build_base) {
'use strict';

gulp.task('copy:js', function (done) {
gulp.src(config.src.js)
.pipe(plugins.plumber({ errorHandler: config.onError }))
.pipe(plugins.include())
.pipe(plugins.uglify())
.pipe(gulp.dest(config.build.js))

gulp.src(config.fractal.docsImages)
.pipe(gulp.dest(build_base + 'docs/'))

gulp.src(config.src.path + '*.*') // favicon.ico and web.config
.pipe(gulp.dest(build_base))

done();
});
};

The config.src.path is __dirname + '/source/'

The Build tasks

NPM install

To install the npm packages defined in your package.json file add an npm task with these settings:

  • The working folder should point to the directory that contains the package.json file.
  • The npm command to use is install

Install gulp –save-dev

To install the gulp npm package add an npm task with these settings:

  • npm command: install
  • arguments: gulp --save-dev

Install gulp-cli –save-dev

To install the gulp-cli npm package add an npm task with these settings:

  • npm command: install
  • arguments: gulp-cli --save-dev

Gulp build

To run the actual Gulp build task add a Gulp task with these settings:

  • Gulp File Path: the path to your gulpfile.js
  • Gulp Task(s): build
  • Advanced > Working Directory: the same as the working folder in the first two tasks
  • Advanced > gulp.js location: node_modules/gulp/bin/gulp.js

The log of this task should look something like this:

1
2
3
4
5
6
7
8
9
10
11
12
13
##[section]Starting: gulp build
[...]
[00:44:44] Starting 'build'...
[...]
[00:45:41] Finished 'build:all' after 55 s
[00:45:41] Starting 'fractal:build'...
[?25l⚑ Exported 1 of 635 items
[...]
[?25l⚑ Exported 635 of 635 items
✔ Fractal build completed!
[00:51:17] Finished 'fractal:build' after 5.6 min
[00:51:17] Finished 'build' after 6.55 min
##[section]Finishing: gulp build

Publish Build Artifacts

Add a Publish Build Artifacts task with these settings:

Path to Publish

The path to publish is the folder named public created by the Gulp build.

1
$/Cumulus/Stitch-DLS/$(BranchName)/rbi-stitch/public

Artifact Name

1
drop

The Release

The release is done with an Azure App Service Deploy task.

Point the Package or folder to the public folder created by the build:
$(System.DefaultWorkingDirectory)/Dev05-Stitch/drop

The log of this task should look something like this:

1
2
3
4
5
6
Info: Updating file (*****\assets.html).
[...]
Info: Updating file (*****\themes\mandelbrot\js\mandelbrot.js.map).
Info: Updating file (*****\Web.config).
Total changes: 1211 (0 added, 0 deleted, 1211 updated, 0 parameters changed, 75632972 bytes copied)
Successfully deployed web package to App Service.

A PowerShell Script To Calculate The Perfect Time For Lunch

Because I don’t always start at the same time I found it hard to remember at what time I came into the office. To fix that I wrote a Powershell script.

  • It retrieves the time at which I started up my computer. It starts looking for the first start-up after 5 in the morning, so I can reboot during the day without influencing the output of the script.
  • From this start-up time it calculates the perfect time for lunch and the end of my working day.
1
2
3
4
5
6
7
8
9
10
11
12
13
$hoursPerWeek = 36
$daysPerWeek = 5
$lunchBreakHours = 0.5

$startedAt = (Get-EventLog -LogName "System" -After (Get-Date).Date.AddHours(5) | Sort-Object -Property TimeGenerated | Select-Object -First 1).TimeGenerated
$finishedAt = (Get-Date($startedAt)).AddHours(($hoursPerWeek/$daysPerWeek) + $lunchBreakHours)
$timeRemaining = New-Timespan -Start (Get-Date) -End $finishedAt
$lunchAt = (Get-Date($startedAt)).AddHours($hoursPerWeek/10)

Write-Output ("Your computer started up at {0} on {1} (day {2} of the year)," -f $startedAt.ToShortTimeString(), (Get-Date).ToString("D"), (Get-Date).DayOfYear)
Write-Output ("you work {0} hours in {1} days and have a {2} minute lunchbreak ({3} hours per day)," -f $hoursPerWeek, $daysPerWeek, ($lunchBreakHours*60), (($hoursPerWeek/$daysPerWeek) + $lunchBreakHours))
Write-Output ("which means you're finished today at {0} (that's in {1}:{2})." -f $finishedAt.ToShortTimeString(), $timeRemaining.Hours, $timeRemaining.Minutes)
Write-Output "The perfect time for lunch is $($lunchAt.ToShortTimeString())."

Output:

1
2
3
4
Your computer started up at 09:01 on Thursday, November 22, 2018 (day 326 of the year),
you work 36 hours in 5 days and have a 30 minute lunchbreak (7.7 hours per day),
which means you're finished today at 16:43 (that's in 0:10).
The perfect time for lunch is 12:37.

You should change the $hoursPerWeek, $daysPerWeek and $lunchBreakHours for your personal situation. So if you work 40 hours in 4 days and have a one hour lunch you change it to this:

1
2
3
$hoursPerWeek = 40
$daysPerWeek = 4
$lunchBreakHours = 1

Output:

1
2
3
4
Your computer started up at 09:01 on Thursday, November 22, 2018 (day 326 of the year),
you work 40 hours in 4 days and have a 60 minute lunchbreak (11 hours per day),
which means you're finished today at 20:01 (that's in 3:20).
The perfect time for lunch is 13:01.