#!/usr/bin/env node
//
// This file is part of Canvas.
// Copyright (C) 2026-present Instructure, Inc.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as
// published by the Free Software Foundation, either version 3 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see .
//
/*
Downloads and imports all SVG icons from instructure-ui into the InstUI Swift package.
Called by build-instui.js via buildIcons(version).
Outputs:
packages/InstUI/Resources/Icons.xcassets/ — SVG asset catalog (cleared each run)
packages/InstUI/Sources/Icon/Generated/InstUI.Icons.swift — Image.iui.* accessors
*/
const fs = require('fs')
const path = require('path')
const https = require('https')
const DOWNLOAD_TIMEOUT_MS = 30_000
function apiGet(url) {
return new Promise((resolve, reject) => {
const req = https.get(
url,
{ headers: { Accept: 'application/vnd.github.v3+json', 'User-Agent': 'build-instui' } },
res => {
let data = ''
res.on('data', chunk => { data += chunk })
res.on('end', () => {
if (res.statusCode !== 200) {
reject(new Error(`GitHub API HTTP ${res.statusCode} for ${url}`))
} else {
resolve(JSON.parse(data))
}
})
res.on('error', reject)
}
)
req.setTimeout(DOWNLOAD_TIMEOUT_MS, () => {
req.destroy(new Error(`Timed out after ${DOWNLOAD_TIMEOUT_MS}ms: ${url}`))
})
req.on('error', reject)
})
}
async function mapConcurrent(items, limit, fn) {
const results = []
let index = 0
async function worker() {
while (index < items.length) {
const i = index++
results[i] = await fn(items[i])
}
}
await Promise.all(Array.from({ length: limit }, worker))
return results
}
function download(url) {
return new Promise((resolve, reject) => {
const req = https.get(url, res => {
let data = ''
res.on('data', chunk => { data += chunk })
res.on('end', () => {
if (res.statusCode !== 200) {
reject(new Error(`HTTP ${res.statusCode} for ${url}`))
} else {
resolve(data)
}
})
res.on('error', reject)
})
req.setTimeout(DOWNLOAD_TIMEOUT_MS, () => {
req.destroy(new Error(`Timed out after ${DOWNLOAD_TIMEOUT_MS}ms: ${url}`))
})
req.on('error', reject)
})
}
function toCamelCase(name) {
const result = name.replace(/\W+(\w)/g, (_, c) => c.toUpperCase())
return result.charAt(0).toLowerCase() + result.slice(1)
}
function capitalize(str) {
return str.charAt(0).toUpperCase() + str.slice(1)
}
function transformSvg(svgSrc) {
const svgTagAttrs = (svgSrc.match(/