diff --git a/.eslintignore b/.eslintignore index de7123a..21676bc 100644 --- a/.eslintignore +++ b/.eslintignore @@ -6,6 +6,7 @@ .yarnrc.yml /_site/ /old_source/ +/source/_data/versionCache.json coverage/ node_modules/ source/assets/dist/ diff --git a/.gitignore b/.gitignore index 9ec4ab8..d4eb89f 100644 --- a/.gitignore +++ b/.gitignore @@ -3,15 +3,12 @@ /.bundle bundle /.cache -/data/version.yml +/source/_data/versionCache.json .DS_Store .DS_Store? /Icon "Icon\r" .ruby-version -/.dart-sass -/.libsass -/.language /.sass-cache *.scssc Thumbs.db diff --git a/.prettierignore b/.prettierignore index de7123a..21676bc 100644 --- a/.prettierignore +++ b/.prettierignore @@ -6,6 +6,7 @@ .yarnrc.yml /_site/ /old_source/ +/source/_data/versionCache.json coverage/ node_modules/ source/assets/dist/ diff --git a/.stylelintignore b/.stylelintignore index de7123a..21676bc 100644 --- a/.stylelintignore +++ b/.stylelintignore @@ -6,6 +6,7 @@ .yarnrc.yml /_site/ /old_source/ +/source/_data/versionCache.json coverage/ node_modules/ source/assets/dist/ diff --git a/docker-compose.yml b/docker-compose.yml index 3fde921..7249ef3 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -10,3 +10,4 @@ services: - /app/node_modules ports: - '8080:8080' + tty: true diff --git a/eleventy.config.cjs b/eleventy.config.cjs index 9330bc7..d7d14c7 100644 --- a/eleventy.config.cjs +++ b/eleventy.config.cjs @@ -18,6 +18,7 @@ module.exports = (eleventyConfig) => { jsTruthy: true, }); eleventyConfig.setUseGitIgnore(false); + eleventyConfig.watchIgnores.add('source/_data/versionCache.json'); const mdown = markdown({ html: true, diff --git a/package.json b/package.json index a9e3424..d637410 100644 --- a/package.json +++ b/package.json @@ -18,10 +18,10 @@ "packageManager": "yarn@3.3.1", "scripts": { "serve": "run-p 'watch:**'", - "build": "run-s build-dev:scripts 'build:**'", - "build-prod": "run-s build-prod:scripts 'build:**'", - "build:sass": "sass --style=compressed --load-path=node_modules ./source/assets/sass/sass.scss:./source/assets/dist/css/sass.css ./source/assets/sass/noscript.scss:./source/assets/dist/css/noscript.css", - "watch:sass": "sass --watch --load-path=node_modules ./source/assets/sass/sass.scss:./source/assets/dist/css/sass.css ./source/assets/sass/noscript.scss:./source/assets/dist/css/noscript.css", + "build": "REBUILD_VERSION_CACHE=true run-s build-dev:scripts 'build:**'", + "build-prod": "NETLIFY=true run-s build-prod:scripts 'build:**'", + "build:sass": "sass --style=compressed ./source/assets/sass/sass.scss:./source/assets/dist/css/sass.css ./source/assets/sass/noscript.scss:./source/assets/dist/css/noscript.css", + "watch:sass": "sass --watch ./source/assets/sass/sass.scss:./source/assets/dist/css/sass.css ./source/assets/sass/noscript.scss:./source/assets/dist/css/noscript.css", "build-dev:scripts": "rollup -c", "build-prod:scripts": "BABEL_ENV=production rollup -c", "watch:scripts": "npm run build-dev:scripts -- -w", @@ -55,6 +55,7 @@ "@typescript-eslint/eslint-plugin": "^5.49.0", "@typescript-eslint/parser": "^5.49.0", "date-fns": "^2.29.3", + "deep-equal": "^2.2.0", "eslint": "^8.33.0", "eslint-config-prettier": "^8.6.0", "eslint-import-resolver-typescript": "^3.5.3", @@ -63,12 +64,14 @@ "jquery": "^3.6.3", "jquery-ui": "^1.13.2", "js-yaml": "^4.1.0", + "kleur": "^4.1.5", "markdown-it-deflist": "^2.1.0", "netlify-plugin-11ty": "^1.3.0", "npm-run-all": "^4.1.5", "prettier": "^2.8.3", "rollup": "^3.12.0", "sass": "^1.57.1", + "semver-parser": "^4.0.1", "stylelint": "^14.16.1", "stylelint-config-prettier": "^9.0.4", "stylelint-config-standard-scss": "^6.1.0", diff --git a/source/_data/releases.cjs b/source/_data/releases.cjs new file mode 100644 index 0000000..943803b --- /dev/null +++ b/source/_data/releases.cjs @@ -0,0 +1,102 @@ +const { spawn: nodeSpawn } = require('node:child_process'); +const fs = require('node:fs/promises'); +const deepEqual = require('deep-equal'); + +const chalk = require('kleur'); + +const VERSION_CACHE_PATH = './source/_data/versionCache.json'; + +// Promise version of `spawn` to avoid blocking the main thread while waiting +// for the child processes +function spawn(cmd, args, options) { + return new Promise((resolve, reject) => { + const child = nodeSpawn(cmd, args, options); + const stderr = []; + const stdout = []; + child.stdout.on('data', (data) => { + stdout.push(data.toString()); + }); + child.on('error', (e) => { + stderr.push(e.toString()); + }); + child.on('close', () => { + if (stderr.length) reject(stderr.join('')); + else resolve(stdout.join('')); + }); + }); +} + +async function getCacheFile() { + if (process.env.NETLIFY || process.env.REBUILD_VERSION_CACHE) return {}; + let versionCache; + try { + versionCache = JSON.parse(await fs.readFile(VERSION_CACHE_PATH)); + } catch (err) { + if (err.code === 'ENOENT') { + versionCache = {}; // Cache is missing and needs to be created + } else { + throw err; + } + } + return versionCache; +} + +async function writeCacheFile(cache) { + // eslint-disable-next-line no-console + console.info(chalk.green(`[11ty] Writing version cache file...`)); + await fs.writeFile(VERSION_CACHE_PATH, JSON.stringify(cache)); +} + +// Retrieve the highest stable version of `repo`, based on its git tags +async function getLatestVersion(repo) { + // eslint-disable-next-line no-console + console.info(chalk.cyan(`[11ty] Fetching version information for ${repo}`)); + const { parseSemVer, compareSemVer } = await import('semver-parser'); + let stdout; + try { + stdout = await spawn( + 'git', + ['ls-remote', '--tags', '--refs', `https://github.com/${repo}`], + { env: { ...process.env, GIT_TERMINAL_PROMPT: 0 } }, + ); + } catch (err) { + // eslint-disable-next-line no-console + console.error(chalk.red(`[11ty] Failed to fetch git tags for ${repo}`)); + throw err; + } + const isNotPreRelease = (version) => { + const parsed = parseSemVer(version); + return parsed.matches && !parsed.pre; + }; + const version = stdout + .split('\n') + .map((line) => line.split('refs/tags/').at(-1)) + .filter(isNotPreRelease) + .sort(compareSemVer) + .at(-1); + + return version; +} + +module.exports = async () => { + const repos = ['sass/libsass', 'sass/dart-sass', 'sass/migrator']; + const cache = await getCacheFile(); + + const versions = await Promise.all( + repos.map(async (repo) => [ + repo, + cache[repo] ?? (await getLatestVersion(repo)), + ]), + ); + const data = Object.fromEntries( + versions.map(([repo, version]) => [ + repo.replace('sass/', ''), + { version, url: `https://github.com/${repo}/releases/tag/${version}` }, + ]), + ); + + const nextCache = Object.fromEntries(versions); + if (!deepEqual(cache, nextCache)) await writeCacheFile(nextCache); + + return data; +}; diff --git a/source/_layouts/base.liquid b/source/_layouts/base.liquid index 79b858f..6c8a976 100644 --- a/source/_layouts/base.liquid +++ b/source/_layouts/base.liquid @@ -73,8 +73,8 @@
diff --git a/source/install.md b/source/install.md index 5139db4..1575f4b 100644 --- a/source/install.md +++ b/source/install.md @@ -59,8 +59,9 @@ sass source/stylesheets/index.scss build/stylesheets/index.css First install Sass using one of the options below, then run `sass --version` to be sure it installed correctly. If it did, this will -include `#{impl_version(:dart)}`. You can also run `sass --help` for more -information about the command-line interface. +include `{{ releases['dart-sass'].version }}`. +You can also run `sass --help` for more information +about the command-line interface. Once it's all set up, **go and play**. If you're brand new to Sass we've set up some resources to help you learn pretty darn quick. diff --git a/yarn.lock b/yarn.lock index e9a78f1..c5cc261 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2765,6 +2765,31 @@ __metadata: languageName: node linkType: hard +"deep-equal@npm:^2.2.0": + version: 2.2.0 + resolution: "deep-equal@npm:2.2.0" + dependencies: + call-bind: ^1.0.2 + es-get-iterator: ^1.1.2 + get-intrinsic: ^1.1.3 + is-arguments: ^1.1.1 + is-array-buffer: ^3.0.1 + is-date-object: ^1.0.5 + is-regex: ^1.1.4 + is-shared-array-buffer: ^1.0.2 + isarray: ^2.0.5 + object-is: ^1.1.5 + object-keys: ^1.1.1 + object.assign: ^4.1.4 + regexp.prototype.flags: ^1.4.3 + side-channel: ^1.0.4 + which-boxed-primitive: ^1.0.2 + which-collection: ^1.0.1 + which-typed-array: ^1.1.9 + checksum: 46a34509d2766d6c6dc5aec4756089cf0cc137e46787e91f08f1ee0bb570d874f19f0493146907df0cf18aed4a7b4b50f6f62c899240a76c323f057528b122e3 + languageName: node + linkType: hard + "deep-is@npm:^0.1.3": version: 0.1.4 resolution: "deep-is@npm:0.1.4" @@ -3083,6 +3108,23 @@ __metadata: languageName: node linkType: hard +"es-get-iterator@npm:^1.1.2": + version: 1.1.3 + resolution: "es-get-iterator@npm:1.1.3" + dependencies: + call-bind: ^1.0.2 + get-intrinsic: ^1.1.3 + has-symbols: ^1.0.3 + is-arguments: ^1.1.1 + is-map: ^2.0.2 + is-set: ^2.0.2 + is-string: ^1.0.7 + isarray: ^2.0.5 + stop-iteration-iterator: ^1.0.0 + checksum: 8fa118da42667a01a7c7529f8a8cca514feeff243feec1ce0bb73baaa3514560bd09d2b3438873cf8a5aaec5d52da248131de153b28e2638a061b6e4df13267d + languageName: node + linkType: hard + "es-set-tostringtag@npm:^2.0.1": version: 2.0.1 resolution: "es-set-tostringtag@npm:2.0.1" @@ -4147,6 +4189,16 @@ __metadata: languageName: node linkType: hard +"is-arguments@npm:^1.1.1": + version: 1.1.1 + resolution: "is-arguments@npm:1.1.1" + dependencies: + call-bind: ^1.0.2 + has-tostringtag: ^1.0.0 + checksum: 7f02700ec2171b691ef3e4d0e3e6c0ba408e8434368504bb593d0d7c891c0dbfda6d19d30808b904a6cb1929bca648c061ba438c39f296c2a8ca083229c49f27 + languageName: node + linkType: hard + "is-array-buffer@npm:^3.0.1": version: 3.0.1 resolution: "is-array-buffer@npm:3.0.1" @@ -4218,7 +4270,7 @@ __metadata: languageName: node linkType: hard -"is-date-object@npm:^1.0.1": +"is-date-object@npm:^1.0.1, is-date-object@npm:^1.0.5": version: 1.0.5 resolution: "is-date-object@npm:1.0.5" dependencies: @@ -4297,6 +4349,13 @@ __metadata: languageName: node linkType: hard +"is-map@npm:^2.0.1, is-map@npm:^2.0.2": + version: 2.0.2 + resolution: "is-map@npm:2.0.2" + checksum: ace3d0ecd667bbdefdb1852de601268f67f2db725624b1958f279316e13fecb8fa7df91fd60f690d7417b4ec180712f5a7ee967008e27c65cfd475cc84337728 + languageName: node + linkType: hard + "is-module@npm:^1.0.0": version: 1.0.0 resolution: "is-module@npm:1.0.0" @@ -4374,6 +4433,13 @@ __metadata: languageName: node linkType: hard +"is-set@npm:^2.0.1, is-set@npm:^2.0.2": + version: 2.0.2 + resolution: "is-set@npm:2.0.2" + checksum: b64343faf45e9387b97a6fd32be632ee7b269bd8183701f3b3f5b71a7cf00d04450ed8669d0bd08753e08b968beda96fca73a10fd0ff56a32603f64deba55a57 + languageName: node + linkType: hard + "is-shared-array-buffer@npm:^1.0.2": version: 1.0.2 resolution: "is-shared-array-buffer@npm:1.0.2" @@ -4414,6 +4480,13 @@ __metadata: languageName: node linkType: hard +"is-weakmap@npm:^2.0.1": + version: 2.0.1 + resolution: "is-weakmap@npm:2.0.1" + checksum: 1222bb7e90c32bdb949226e66d26cb7bce12e1e28e3e1b40bfa6b390ba3e08192a8664a703dff2a00a84825f4e022f9cd58c4599ff9981ab72b1d69479f4f7f6 + languageName: node + linkType: hard + "is-weakref@npm:^1.0.2": version: 1.0.2 resolution: "is-weakref@npm:1.0.2" @@ -4423,6 +4496,16 @@ __metadata: languageName: node linkType: hard +"is-weakset@npm:^2.0.1": + version: 2.0.2 + resolution: "is-weakset@npm:2.0.2" + dependencies: + call-bind: ^1.0.2 + get-intrinsic: ^1.1.1 + checksum: 5d8698d1fa599a0635d7ca85be9c26d547b317ed8fd83fc75f03efbe75d50001b5eececb1e9971de85fcde84f69ae6f8346bc92d20d55d46201d328e4c74a367 + languageName: node + linkType: hard + "is-wsl@npm:^2.2.0": version: 2.2.0 resolution: "is-wsl@npm:2.2.0" @@ -4432,6 +4515,13 @@ __metadata: languageName: node linkType: hard +"isarray@npm:^2.0.5": + version: 2.0.5 + resolution: "isarray@npm:2.0.5" + checksum: bd5bbe4104438c4196ba58a54650116007fa0262eccef13a4c55b2e09a5b36b59f1e75b9fcc49883dd9d4953892e6fc007eef9e9155648ceea036e184b0f930a + languageName: node + linkType: hard + "isexe@npm:^2.0.0": version: 2.0.0 resolution: "isexe@npm:2.0.0" @@ -5383,6 +5473,16 @@ __metadata: languageName: node linkType: hard +"object-is@npm:^1.1.5": + version: 1.1.5 + resolution: "object-is@npm:1.1.5" + dependencies: + call-bind: ^1.0.2 + define-properties: ^1.1.3 + checksum: 989b18c4cba258a6b74dc1d74a41805c1a1425bce29f6cabb50dcb1a6a651ea9104a1b07046739a49a5bb1bc49727bcb00efd5c55f932f6ea04ec8927a7901fe + languageName: node + linkType: hard + "object-keys@npm:^1.1.1": version: 1.1.1 resolution: "object-keys@npm:1.1.1" @@ -6293,6 +6393,7 @@ __metadata: "@typescript-eslint/eslint-plugin": ^5.49.0 "@typescript-eslint/parser": ^5.49.0 date-fns: ^2.29.3 + deep-equal: ^2.2.0 eslint: ^8.33.0 eslint-config-prettier: ^8.6.0 eslint-import-resolver-typescript: ^3.5.3 @@ -6301,12 +6402,14 @@ __metadata: jquery: ^3.6.3 jquery-ui: ^1.13.2 js-yaml: ^4.1.0 + kleur: ^4.1.5 markdown-it-deflist: ^2.1.0 netlify-plugin-11ty: ^1.3.0 npm-run-all: ^4.1.5 prettier: ^2.8.3 rollup: ^3.12.0 sass: ^1.57.1 + semver-parser: ^4.0.1 stylelint: ^14.16.1 stylelint-config-prettier: ^9.0.4 stylelint-config-standard-scss: ^6.1.0 @@ -6346,6 +6449,13 @@ __metadata: languageName: node linkType: hard +"semver-parser@npm:^4.0.1": + version: 4.0.1 + resolution: "semver-parser@npm:4.0.1" + checksum: a737d33882124b64936e1ae3ea8f39c9c78077e8eecfcbee2958f3062debd6410be511a59cb089b93e62f7c1546ee2a252a38b8721f00ab80f916e25ff3eda7e + languageName: node + linkType: hard + "semver@npm:2 || 3 || 4 || 5, semver@npm:^5.5.0": version: 5.7.1 resolution: "semver@npm:5.7.1" @@ -6623,6 +6733,15 @@ __metadata: languageName: node linkType: hard +"stop-iteration-iterator@npm:^1.0.0": + version: 1.0.0 + resolution: "stop-iteration-iterator@npm:1.0.0" + dependencies: + internal-slot: ^1.0.4 + checksum: d04173690b2efa40e24ab70e5e51a3ff31d56d699550cfad084104ab3381390daccb36652b25755e420245f3b0737de66c1879eaa2a8d4fc0a78f9bf892fcb42 + languageName: node + linkType: hard + "string-width@npm:^1.0.2 || 2 || 3 || 4, string-width@npm:^4.2.3": version: 4.2.3 resolution: "string-width@npm:4.2.3" @@ -7297,6 +7416,18 @@ __metadata: languageName: node linkType: hard +"which-collection@npm:^1.0.1": + version: 1.0.1 + resolution: "which-collection@npm:1.0.1" + dependencies: + is-map: ^2.0.1 + is-set: ^2.0.1 + is-weakmap: ^2.0.1 + is-weakset: ^2.0.1 + checksum: c815bbd163107ef9cb84f135e6f34453eaf4cca994e7ba85ddb0d27cea724c623fae2a473ceccfd5549c53cc65a5d82692de418166df3f858e1e5dc60818581c + languageName: node + linkType: hard + "which-typed-array@npm:^1.1.9": version: 1.1.9 resolution: "which-typed-array@npm:1.1.9"