DEV: Introduce `DISCOURSE_ASSET_URL_SALT` (#24596)

This value is included when generating static asset URLs. Updating the value will allow site operators to invalidate all asset urls to recover from configuration issues which may have been cached by CDNs/browsers.
This commit is contained in:
David Taylor 2023-11-28 11:28:40 +00:00 committed by GitHub
parent 22ce638ec3
commit 5783f231f8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 29 additions and 5 deletions

View File

@ -16,6 +16,7 @@ const { Webpack } = require("@embroider/webpack");
const { StatsWriterPlugin } = require("webpack-stats-plugin"); const { StatsWriterPlugin } = require("webpack-stats-plugin");
const withSideWatch = require("./lib/with-side-watch"); const withSideWatch = require("./lib/with-side-watch");
const RawHandlebarsCompiler = require("discourse-hbr/raw-handlebars-compiler"); const RawHandlebarsCompiler = require("discourse-hbr/raw-handlebars-compiler");
const crypto = require("crypto");
const EMBER_MAJOR_VERSION = parseInt( const EMBER_MAJOR_VERSION = parseInt(
require("ember-source/package.json").version.split(".")[0], require("ember-source/package.json").version.split(".")[0],
@ -153,6 +154,13 @@ module.exports = function (defaults) {
testStylesheetTree, testStylesheetTree,
]; ];
const assetCachebuster = process.env["DISCOURSE_ASSET_URL_SALT"] || "";
const cachebusterHash = crypto
.createHash("md5")
.update(assetCachebuster)
.digest("hex")
.slice(0, 8);
const appTree = compatBuild(app, Webpack, { const appTree = compatBuild(app, Webpack, {
staticAppPaths: ["static"], staticAppPaths: ["static"],
packagerOptions: { packagerOptions: {
@ -160,6 +168,8 @@ module.exports = function (defaults) {
devtool: "source-map", devtool: "source-map",
output: { output: {
publicPath: "auto", publicPath: "auto",
filename: `assets/chunk.[chunkhash].${cachebusterHash}.js`,
chunkFilename: `assets/chunk.[chunkhash].${cachebusterHash}.js`,
}, },
cache: isProduction cache: isProduction
? false ? false

View File

@ -29,7 +29,11 @@ module.exports = function generateWorkboxTree() {
// Sprockets' default behaviour for these files is disabled via freedom_patches/sprockets.rb. // Sprockets' default behaviour for these files is disabled via freedom_patches/sprockets.rb.
const versionHash = crypto const versionHash = crypto
.createHash("md5") .createHash("md5")
.update(`${versions.join("|")}|${COMPILER_VERSION}`) .update(
`${versions.join("|")}|${COMPILER_VERSION}|${
process.env["DISCOURSE_ASSET_URL_SALT"] || ""
}`
)
.digest("hex"); .digest("hex");
return funnel(mergeTrees(nodes), { return funnel(mergeTrees(nodes), {

View File

@ -22,7 +22,8 @@ class JavascriptCache < ActiveRecord::Base
end end
def update_digest def update_digest
self.digest = Digest::SHA1.hexdigest(content) if content_changed? self.digest =
Digest::SHA1.hexdigest("#{content}|#{GlobalSetting.asset_url_salt}") if content_changed?
end end
def content_cannot_be_nil def content_cannot_be_nil

View File

@ -386,3 +386,8 @@ allow_impersonation = true
# The maximum number of characters allowed in a single log line. # The maximum number of characters allowed in a single log line.
log_line_max_chars = 160000 log_line_max_chars = 160000
# this value is included when generating static asset URLs.
# Updating the value will allow site operators to invalidate all asset urls
# to recover from configuration issues which may have been cached by CDNs/browsers.
asset_url_salt =

View File

@ -6,7 +6,7 @@
Rails.application.config.assets.enabled = true Rails.application.config.assets.enabled = true
# Version of your assets, change this if you want to expire all your assets. # Version of your assets, change this if you want to expire all your assets.
Rails.application.config.assets.version = "2" Rails.application.config.assets.version = "2-#{GlobalSetting.asset_url_salt}"
# Add additional assets to the asset load path. # Add additional assets to the asset load path.
Rails.application.config.assets.paths << "#{Rails.root}/config/locales" Rails.application.config.assets.paths << "#{Rails.root}/config/locales"

View File

@ -35,7 +35,8 @@ module HighlightJs
cache_info = { cache_info = {
lang_string: lang_string, lang_string: lang_string,
digest: Digest::SHA1.hexdigest(bundle(lang_string.split("|"))), digest:
Digest::SHA1.hexdigest(bundle(lang_string.split("|")) + "|#{GlobalSetting.asset_url_salt}"),
} }
cache[RailsMultisite::ConnectionManagement.current_db] = cache_info cache[RailsMultisite::ConnectionManagement.current_db] = cache_info

View File

@ -167,12 +167,15 @@ RSpec.describe ThemeJavascriptsController do
component.save! component.save!
_, digest = component.baked_js_tests_with_digest _, digest = component.baked_js_tests_with_digest
theme_javascript_hash =
component.theme_fields.find_by(upload_id: js_upload.id).javascript_cache.digest
get "/theme-javascripts/tests/#{component.id}-#{digest}.js" get "/theme-javascripts/tests/#{component.id}-#{digest}.js"
expect(response.body).to include( expect(response.body).to include(
"require(\"discourse/lib/theme-settings-store\").registerSettings(" + "require(\"discourse/lib/theme-settings-store\").registerSettings(" +
"#{component.id}, {\"num_setting\":5,\"theme_uploads\":{\"vendorlib\":" + "#{component.id}, {\"num_setting\":5,\"theme_uploads\":{\"vendorlib\":" +
"\"/uploads/default/test_#{ENV["TEST_ENV_NUMBER"].presence || "0"}/original/1X/#{js_upload.sha1}.js\"},\"theme_uploads_local\":{\"vendorlib\":" + "\"/uploads/default/test_#{ENV["TEST_ENV_NUMBER"].presence || "0"}/original/1X/#{js_upload.sha1}.js\"},\"theme_uploads_local\":{\"vendorlib\":" +
"\"/theme-javascripts/#{js_upload.sha1}.js?__ws=test.localhost\"}}, { force: true });", "\"/theme-javascripts/#{theme_javascript_hash}.js?__ws=test.localhost\"}}, { force: true });",
) )
expect(response.body).to include("assert.ok(true);") expect(response.body).to include("assert.ok(true);")
ensure ensure