diff --git a/app/assets/javascripts/discourse/ember-cli-build.js b/app/assets/javascripts/discourse/ember-cli-build.js index 6d7a2f33706..16a838cb2df 100644 --- a/app/assets/javascripts/discourse/ember-cli-build.js +++ b/app/assets/javascripts/discourse/ember-cli-build.js @@ -16,6 +16,7 @@ const { Webpack } = require("@embroider/webpack"); const { StatsWriterPlugin } = require("webpack-stats-plugin"); const withSideWatch = require("./lib/with-side-watch"); const RawHandlebarsCompiler = require("discourse-hbr/raw-handlebars-compiler"); +const crypto = require("crypto"); const EMBER_MAJOR_VERSION = parseInt( require("ember-source/package.json").version.split(".")[0], @@ -153,6 +154,13 @@ module.exports = function (defaults) { 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, { staticAppPaths: ["static"], packagerOptions: { @@ -160,6 +168,8 @@ module.exports = function (defaults) { devtool: "source-map", output: { publicPath: "auto", + filename: `assets/chunk.[chunkhash].${cachebusterHash}.js`, + chunkFilename: `assets/chunk.[chunkhash].${cachebusterHash}.js`, }, cache: isProduction ? false diff --git a/app/assets/javascripts/discourse/lib/workbox-tree-builder.js b/app/assets/javascripts/discourse/lib/workbox-tree-builder.js index 3babdfb90c0..9bc9c210cbf 100644 --- a/app/assets/javascripts/discourse/lib/workbox-tree-builder.js +++ b/app/assets/javascripts/discourse/lib/workbox-tree-builder.js @@ -29,7 +29,11 @@ module.exports = function generateWorkboxTree() { // Sprockets' default behaviour for these files is disabled via freedom_patches/sprockets.rb. const versionHash = crypto .createHash("md5") - .update(`${versions.join("|")}|${COMPILER_VERSION}`) + .update( + `${versions.join("|")}|${COMPILER_VERSION}|${ + process.env["DISCOURSE_ASSET_URL_SALT"] || "" + }` + ) .digest("hex"); return funnel(mergeTrees(nodes), { diff --git a/app/models/javascript_cache.rb b/app/models/javascript_cache.rb index 1ebfbec15bd..e3dd443dd16 100644 --- a/app/models/javascript_cache.rb +++ b/app/models/javascript_cache.rb @@ -22,7 +22,8 @@ class JavascriptCache < ActiveRecord::Base end 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 def content_cannot_be_nil diff --git a/config/discourse_defaults.conf b/config/discourse_defaults.conf index 9fd3f0208a4..1d1fbd14902 100644 --- a/config/discourse_defaults.conf +++ b/config/discourse_defaults.conf @@ -386,3 +386,8 @@ allow_impersonation = true # The maximum number of characters allowed in a single log line. 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 = diff --git a/config/initializers/assets.rb b/config/initializers/assets.rb index 1eb5106f2f8..02e001566c8 100644 --- a/config/initializers/assets.rb +++ b/config/initializers/assets.rb @@ -6,7 +6,7 @@ Rails.application.config.assets.enabled = true # 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. Rails.application.config.assets.paths << "#{Rails.root}/config/locales" diff --git a/lib/highlight_js.rb b/lib/highlight_js.rb index da3c9ad1029..028bc21c522 100644 --- a/lib/highlight_js.rb +++ b/lib/highlight_js.rb @@ -35,7 +35,8 @@ module HighlightJs cache_info = { 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 diff --git a/spec/requests/theme_javascripts_controller_spec.rb b/spec/requests/theme_javascripts_controller_spec.rb index 52600fbe01c..093c33fb9b7 100644 --- a/spec/requests/theme_javascripts_controller_spec.rb +++ b/spec/requests/theme_javascripts_controller_spec.rb @@ -167,12 +167,15 @@ RSpec.describe ThemeJavascriptsController do component.save! _, 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" expect(response.body).to include( "require(\"discourse/lib/theme-settings-store\").registerSettings(" + "#{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\":" + - "\"/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);") ensure