DEV: Drop ember-cli-based SCSS and locale compilation (#31407)

This totally separate SCSS and i18n compilation pipelines only existed
so that we could run `ember exam` in CI without starting Rails.

Now that our CI has such heavy caching of Ruby dependencies and database
migrations, the speed benefit of this is not worth the cost of
maintaining these separate pipelines.

Therefore, this commit removes that system, and updates CI to use
`bin/rake qunit:test`. That will start up a Rails server and proxy
stylesheet/locale requests to it. This strategy was already used for our
theme and plugin qunit test runs.
This commit is contained in:
David Taylor
2025-02-21 11:15:04 +00:00
committed by GitHub
parent 4461256f9e
commit 00907363d4
14 changed files with 65 additions and 492 deletions

View File

@ -28,7 +28,7 @@ permissions:
jobs:
build:
if: github.event_name == 'pull_request' || github.repository != 'discourse/discourse-private-mirror'
name: ${{ matrix.target }} ${{ matrix.build_type }} # Update fetch-job-id step if changing this
name: ${{ matrix.target }} ${{ matrix.build_type }}${{ (matrix.target == 'core' && matrix.build_type == 'frontend' && format(' ({0})', matrix.browser)) || '' }} # Update fetch-job-id step if changing this
runs-on: ${{ (github.repository_owner == 'discourse' && 'debian-12') || 'ubuntu-latest' }}
container: discourse/discourse_test:release
timeout-minutes: 20
@ -42,8 +42,9 @@ jobs:
MINIO_RUNNER_LOG_LEVEL: DEBUG
DISCOURSE_TURBO_RSPEC_RETRY_AND_LOG_FLAKY_TESTS: ${{ (matrix.build_type == 'system' || matrix.build_type == 'backend') && '1' }}
CHEAP_SOURCE_MAPS: "1"
TESTEM_DEFAULT_BROWSER: Chrome
MINIO_RUNNER_INSTALL_DIR: /home/discourse/.minio_runner
TESTEM_BROWSER: ${{ (startsWith(matrix.browser, 'Firefox') && 'Firefox') || matrix.browser }}
TESTEM_FIREFOX_PATH: ${{ (matrix.browser == 'Firefox Evergreen') && '/opt/firefox-evergreen/firefox' }}
strategy:
fail-fast: false
@ -51,6 +52,7 @@ jobs:
matrix:
build_type: [backend, frontend, system, annotations]
target: [core, plugins, themes]
browser: [Chrome]
exclude:
- build_type: annotations
target: plugins
@ -58,11 +60,15 @@ jobs:
target: themes
- build_type: backend
target: themes
- build_type: frontend
target: core # Handled by core_frontend_tests job (below)
include:
- build_type: system
target: chat
- build_type: frontend
target: core
browser: Firefox Evergreen
- build_type: frontend
target: core
browser: Firefox ESR
steps:
- name: Set working directory owner
@ -233,6 +239,11 @@ jobs:
if: matrix.build_type == 'backend' && matrix.target == 'plugins'
run: bin/rake plugin:turbo_spec['*','--verbose --format=documentation --use-runtime-info --profile=50']
- name: Core QUnit
if: matrix.build_type == 'frontend' && matrix.target == 'core'
run: QUNIT_WRITE_EXECUTION_FILE=1 bin/rake qunit:test
timeout-minutes: 30
- name: Plugin QUnit
if: matrix.build_type == 'frontend' && matrix.target == 'plugins'
run: QUNIT_WRITE_EXECUTION_FILE=1 bin/rake plugin:qunit['*']
@ -244,9 +255,9 @@ jobs:
timeout-minutes: 10
- uses: actions/upload-artifact@v4
if: always() && matrix.build_type == 'frontend' && matrix.target == 'plugins'
if: always() && matrix.build_type == 'frontend'
with:
name: ember-exam-execution-plugins-frontend-${{ hashFiles('./app/assets/javascripts/discourse/test-execution-*.json') }}
name: ember-exam-execution-${{ matrix.target }}-${{ matrix.browser }}-frontend-${{ hashFiles('./app/assets/javascripts/discourse/test-execution-*.json') }}
path: ./app/assets/javascripts/discourse/test-execution-*.json
- name: Ember Build for System Tests
@ -322,7 +333,7 @@ jobs:
id: fetch-job-id
if: steps.check-flaky-spec-report.outputs.exists == 'true'
run: |
job_id=$(ruby script/get_github_workflow_run_job_id.rb ${{ github.run_id }} ${{ github.run_attempt }} '${{ matrix.target }} ${{ matrix.build_type }}')
job_id=$(ruby script/get_github_workflow_run_job_id.rb ${{ github.run_id }} ${{ github.run_attempt }} '${{ matrix.target }} ${{ matrix.build_type }}${{ (matrix.target == 'core' && matrix.build_type == 'frontend' && format(' ({0})', matrix.browser)) || '' }}')
echo "job_id=$job_id" >> $GITHUB_OUTPUT
- name: Create flaky tests report artifact
@ -353,56 +364,6 @@ jobs:
fi
timeout-minutes: 30
core_frontend_tests:
if: github.event_name == 'pull_request' || github.repository != 'discourse/discourse-private-mirror'
name: core frontend (${{ matrix.browser }})
runs-on: ${{ (github.repository_owner == 'discourse' && 'debian-12') || 'ubuntu-latest' }}
container:
image: discourse/discourse_test:release
options: --user discourse
timeout-minutes: 35
strategy:
fail-fast: false
matrix:
browser: ["Chrome", "Firefox ESR", "Firefox Evergreen"]
env:
TESTEM_BROWSER: ${{ (startsWith(matrix.browser, 'Firefox') && 'Firefox') || matrix.browser }}
TESTEM_FIREFOX_PATH: ${{ (matrix.browser == 'Firefox Evergreen') && '/opt/firefox-evergreen/firefox' }}
CHEAP_SOURCE_MAPS: "1"
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 1
- name: Setup Git
run: |
git config --global user.email "ci@ci.invalid"
git config --global user.name "Discourse CI"
- name: pnpm install
run: pnpm install --frozen-lockfile
- name: Ember Build
working-directory: ./app/assets/javascripts/discourse
run: |
mkdir /tmp/emberbuild
pnpm ember build --environment=test -o /tmp/emberbuild
- name: Core QUnit
working-directory: ./app/assets/javascripts/discourse
run: |
pnpm ember exam --path /tmp/emberbuild --load-balance --parallel=$(($(nproc) / 2)) --launch "${{ env.TESTEM_BROWSER }}" --write-execution-file --random
timeout-minutes: 15
- uses: actions/upload-artifact@v4
if: ${{ always() }}
with:
name: ember-exam-execution-${{ matrix.browser }}-${{ hashFiles('./app/assets/javascripts/discourse/test-execution-*.json') }}
path: ./app/assets/javascripts/discourse/test-execution-*.json
merge:
if: github.repository == 'discourse/discourse' && github.ref == 'refs/heads/main'

View File

@ -4,9 +4,7 @@ const EmberApp = require("ember-cli/lib/broccoli/ember-app");
const path = require("path");
const mergeTrees = require("broccoli-merge-trees");
const concat = require("broccoli-concat");
const { createI18nTree } = require("./lib/translation-plugin");
const { parsePluginClientSettings } = require("./lib/site-settings-plugin");
const discourseScss = require("./lib/discourse-scss");
const generateScriptsTree = require("./lib/scripts");
const funnel = require("broccoli-funnel");
const DeprecationSilencer = require("deprecation-silencer");
@ -96,20 +94,12 @@ module.exports = function (defaults) {
const adminTree = app.project.findAddonByName("admin").treeForAddonBundle();
const testStylesheetTree = mergeTrees([
discourseScss(`${discourseRoot}/app/assets/stylesheets`, "qunit.scss"),
discourseScss(
`${discourseRoot}/app/assets/stylesheets`,
"qunit-custom.scss"
),
]);
app.project.liveReloadFilterPatterns = [/.*\.scss/];
const terserPlugin = app.project.findAddonByName("ember-cli-terser");
const applyTerser = (tree) => terserPlugin.postprocessTree("all", tree);
let extraPublicTrees = [
createI18nTree(discourseRoot, vendorJs),
parsePluginClientSettings(discourseRoot, vendorJs, app),
funnel(`${discourseRoot}/public/javascripts`, { destDir: "javascripts" }),
applyTerser(
@ -120,7 +110,6 @@ module.exports = function (defaults) {
),
applyTerser(generateScriptsTree(app)),
applyTerser(discoursePluginsTree),
testStylesheetTree,
];
const assetCachebuster = process.env["DISCOURSE_ASSET_URL_SALT"] || "";

View File

@ -1,64 +0,0 @@
const Plugin = require("broccoli-plugin");
const sass = require("sass");
const fs = require("fs");
const concat = require("broccoli-concat");
let builtSet = new Set();
class DiscourseScss extends Plugin {
constructor(inputNodes, inputFile, options) {
super(inputNodes, {
...options,
persistentOutput: true,
});
this.inputFile = inputFile;
}
build() {
let file = this.inputPaths[0] + "/" + this.inputFile;
// We could get fancy eventually and do this based on whether the css changes
// but this is just used for tests right now.
if (builtSet.has(file)) {
return;
}
let deprecationCount = 0;
let result = sass.renderSync({
file,
includePaths: this.inputPaths,
silenceDeprecations: ["color-functions", "import", "global-builtin"],
verbose: true, // call warn() for all deprecations
logger: {
warn(message, options) {
if (options.deprecation) {
deprecationCount += 1;
} else {
// eslint-disable-next-line no-console
console.warn(`\nWARNING: ${message}`);
}
},
},
});
if (deprecationCount > 0) {
// eslint-disable-next-line no-console
console.warn(
`\nWARNING: ${deprecationCount} deprecations encountered while compiling scss. (we cannot correct these until the Ruby SCSS pipeline is updated)`
);
}
fs.writeFileSync(
`${this.outputPath}/` + this.inputFile.replace(".scss", ".css"),
result.css
);
builtSet.add(file);
}
}
module.exports = function scss(path, file) {
return concat(new DiscourseScss([path], file), {
outputFile: `assets/${file.replace(".scss", ".css")}`,
});
};

View File

@ -1,137 +0,0 @@
const Plugin = require("broccoli-plugin");
const Yaml = require("js-yaml");
const fs = require("fs");
const concat = require("broccoli-concat");
const mergeTrees = require("broccoli-merge-trees");
const MessageFormat = require("@messageformat/core");
const deepmerge = require("deepmerge");
const glob = require("glob");
const { shouldLoadPlugins } = require("discourse-plugins");
let built = false;
class TranslationPlugin extends Plugin {
constructor(inputNodes, inputFile, options) {
super(inputNodes, {
...options,
persistentOutput: true,
});
this.inputFile = inputFile;
}
replaceMF(formats, input, path = []) {
if (!input) {
return;
}
Object.keys(input).forEach((key) => {
let value = input[key];
let subpath = path.concat(key);
if (typeof value === "object") {
this.replaceMF(formats, value, subpath);
} else if (key.endsWith("_MF")) {
// omit locale.js
let mfPath = subpath.slice(2).join(".");
formats[mfPath] = this.mf.compile(value);
}
});
}
build() {
// We could get fancy eventually and do this based on whether the yaml
// or vendor files change but in practice we shouldn't need exact up to date
// translations in admin.
if (built) {
return;
}
let parsed = {};
this.inputPaths.forEach((path) => {
let file = path + "/" + this.inputFile;
let yaml = fs.readFileSync(file, { encoding: "UTF-8" });
let loaded = Yaml.load(yaml, { json: true });
parsed = deepmerge(parsed, loaded);
});
let extras = {
en: {
admin: parsed.en.admin_js.admin,
wizard: parsed.en.wizard_js.wizard,
},
};
delete parsed.en.admin_js;
delete parsed.en.wizard_js;
let formats = {};
this.mf = new MessageFormat("en");
this.replaceMF(formats, parsed);
this.replaceMF(formats, extras);
formats = Object.entries(formats).map(([k, v]) => `"${k}": ${v}`);
let contents = `
(function() {
I18n.locale = 'en';
I18n.translations = ${JSON.stringify(parsed)};
I18n.extras = ${JSON.stringify(extras)};
const Messages = require("@messageformat/runtime/messages").default;
const { number, plural, select } = require("@messageformat/runtime");
const { en } = require("@messageformat/runtime/lib/cardinals");
const msgData = { en: { ${formats.join(",\n")} } };
const messages = new Messages(msgData, "en");
messages.defaultLocale = "en";
I18n._mfMessages = messages;
})()
`;
fs.writeFileSync(
`${this.outputPath}/` + this.inputFile.replace(".yml", ".js"),
contents
);
built = true;
}
}
module.exports = function translatePlugin(...params) {
return new TranslationPlugin(...params);
};
module.exports.createI18nTree = function (discourseRoot, vendorJs) {
let translations = [discourseRoot + "/config/locales"];
if (shouldLoadPlugins()) {
translations = translations.concat(
glob
.sync(discourseRoot + "/plugins/*/config/locales/client.en.yml")
.map((f) => f.replace(/\/client\.en\.yml$/, ""))
);
}
let en = new TranslationPlugin(translations, "client.en.yml");
return concat(
mergeTrees([
vendorJs,
discourseRoot + "/app/assets/javascripts/locales",
en,
]),
{
inputFiles: [
"i18n.js",
"moment.js",
"moment-timezone-with-data.js",
"client.en.js",
],
headerFiles: ["i18n.js", "moment.js", "moment-timezone-with-data.js"],
footerFiles: ["client.en.js"],
outputFile: `assets/test-i18n.js`,
}
);
};
module.exports.TranslationPlugin = TranslationPlugin;

View File

@ -127,7 +127,6 @@
"pretender": "^3.4.7",
"qunit": "^2.24.1",
"qunit-dom": "^3.4.0",
"sass": "^1.85.0",
"select-kit": "workspace:1.0.0",
"sinon": "^19.0.2",
"source-map": "^0.7.4",

View File

@ -1,5 +1,4 @@
const TapReporter = require("testem/lib/reporters/tap_reporter");
const { shouldLoadPlugins } = require("discourse-plugins");
const fs = require("fs");
const displayUtils = require("testem/lib/utils/displayutils");
const colors = require("@colors/colors/safe");
@ -177,6 +176,16 @@ if (process.env.TESTEM_FIREFOX_PATH) {
}
const target = `http://127.0.0.1:${process.env.UNICORN_PORT || "3000"}`;
fetch(`${target}/about.json`).catch(() => {
// eslint-disable-next-line no-console
console.error(
colors.red(
`Error connecting to Rails server on ${target}. Is it running? Use 'bin/rake qunit:test' or 'plugin:qunit' to start automatically.`
)
);
});
const themeTestPages = process.env.THEME_TEST_PAGES;
if (themeTestPages) {
@ -204,20 +213,26 @@ if (themeTestPages) {
});
},
];
} else if (shouldLoadPlugins()) {
} else {
// Running with ember cli, but we want to pass through plugin request to Rails
module.exports.proxies = {
"/assets/locales/*.js": {
target,
},
"/assets/plugins/*_extra.js": {
target,
},
"/plugins/": {
target,
},
"/bootstrap/plugin-css-for-tests.css": {
"/bootstrap/": {
target,
},
"/stylesheets/": {
target,
},
"/extra-locales/": {
target,
},
};
}

View File

@ -20,7 +20,7 @@
<link rel="stylesheet" href="{{rootURL}}assets/vendor.css" />
<link rel="stylesheet" href="{{rootURL}}assets/discourse.css">
<link rel="stylesheet" href="{{rootURL}}assets/test-support.css" />
<link rel="stylesheet" href="{{rootURL}}assets/qunit.css" data-embroider-ignore />
<link rel="stylesheet" href="{{rootURL}}bootstrap/core-css-for-tests.css" data-embroider-ignore />
{{content-for "head-footer"}} {{content-for "test-head-footer"}}
@ -53,7 +53,10 @@
<script src="{{rootURL}}assets/discourse.js"></script>
<script src="{{rootURL}}assets/test-i18n.js" data-embroider-ignore></script>
<script src="{{rootURL}}assets/locales/en.js" data-embroider-ignore></script>
<script src="{{rootURL}}extra-locales/mf" data-embroider-ignore></script>
<script src="{{rootURL}}extra-locales/admin" data-embroider-ignore></script>
<script src="{{rootURL}}extra-locales/wizard" data-embroider-ignore></script>
<script src="{{rootURL}}assets/test-site-settings.js" data-embroider-ignore></script>
<script src="{{rootURL}}assets/admin.js" data-embroider-ignore></script>
@ -72,6 +75,6 @@
<!-- This script takes the <template>, filters plugin assets as required, then appends to discourse-dynamic-test-js -->
<script src="{{rootURL}}assets/scripts/discourse-test-load-dynamic-js.js" data-embroider-ignore></script>
<link rel="stylesheet" href="{{rootURL}}assets/qunit-custom.css" data-embroider-ignore />
<link rel="stylesheet" href="{{rootURL}}stylesheets/qunit-custom.css" data-embroider-ignore />
</body>
</html>

View File

@ -1,11 +0,0 @@
// Styles for Ember CLI test environment
:root {
--font-family: "Arial";
}
@import "common/foundation/colors";
@import "common/foundation/variables";
@import "common/foundation/mixins";
@import "desktop";
@import "color_definitions";
@import "admin";

View File

@ -4,16 +4,26 @@ class BootstrapController < ApplicationController
skip_before_action :redirect_to_login_if_required, :check_xhr
def plugin_css_for_tests
targets = Discourse.find_plugin_css_assets(include_disabled: true, desktop_view: true)
render_css_for_tests(targets)
end
def core_css_for_tests
targets = %w[color_definitions desktop admin]
render_css_for_tests(targets)
end
private
def render_css_for_tests(targets)
urls =
Discourse
.find_plugin_css_assets(include_disabled: true, desktop_view: true)
.map do |target|
details = Stylesheet::Manager.new().stylesheet_details(target, "all")
details[0][:new_href]
end
targets.map do |target|
details = Stylesheet::Manager.new().stylesheet_details(target, "all")
details[0][:new_href]
end
stylesheet = <<~CSS
/* For use in tests only - `@import`s all plugin stylesheets */
/* For use in tests only */
#{urls.map { |url| "@import \"#{url}\";" }.join("\n")}
CSS

View File

@ -85,6 +85,7 @@ class ExtraLocalesController < ApplicationController
private
def valid_bundle?(bundle)
bundle.in?(BUNDLES) || (bundle =~ /\A(admin|wizard)\z/ && current_user&.staff?)
return true if bundle.in?(BUNDLES)
bundle =~ /\A(admin|wizard)\z/ && (current_user&.staff? || Rails.env.local?)
end
end

View File

@ -29,6 +29,7 @@ Discourse::Application.routes.draw do
if Rails.env.test? || Rails.env.development?
get "/bootstrap/plugin-css-for-tests.css" => "bootstrap#plugin_css_for_tests"
get "/bootstrap/core-css-for-tests.css" => "bootstrap#core_css_for_tests"
end
# This is not a valid production route and is causing routing errors to be raised in

View File

@ -300,10 +300,7 @@ task "docker:test" do
unless ENV["RUBY_ONLY"]
unless ENV["SKIP_CORE"]
@good &&=
run_or_fail(
"cd app/assets/javascripts/discourse && CI=1 pnpm ember exam --load-balance --parallel=#{qunit_concurrency} --random",
)
@good &&= run_or_fail("CI=1 QUNIT_PARALLEL=#{qunit_concurrency} bin/rake qunit:test")
end
unless ENV["SKIP_PLUGINS"]

192
pnpm-lock.yaml generated
View File

@ -597,9 +597,6 @@ importers:
qunit-dom:
specifier: ^3.4.0
version: 3.4.0
sass:
specifier: ^1.85.0
version: 1.85.0
select-kit:
specifier: workspace:1.0.0
version: link:../select-kit
@ -2423,88 +2420,6 @@ packages:
resolution: {integrity: sha512-y7efHHwghQfk28G2z3tlZ67pLG0XdfYbcVG26r7YIXALRsrVQcTq4/tdenSmdOrEsNahIYA/eh8aEVROWGFUDg==}
engines: {node: ^16.14.0 || >=18.0.0}
'@parcel/watcher-android-arm64@2.5.1':
resolution: {integrity: sha512-KF8+j9nNbUN8vzOFDpRMsaKBHZ/mcjEjMToVMJOhTozkDonQFFrRcfdLWn6yWKCmJKmdVxSgHiYvTCef4/qcBA==}
engines: {node: '>= 10.0.0'}
cpu: [arm64]
os: [android]
'@parcel/watcher-darwin-arm64@2.5.1':
resolution: {integrity: sha512-eAzPv5osDmZyBhou8PoF4i6RQXAfeKL9tjb3QzYuccXFMQU0ruIc/POh30ePnaOyD1UXdlKguHBmsTs53tVoPw==}
engines: {node: '>= 10.0.0'}
cpu: [arm64]
os: [darwin]
'@parcel/watcher-darwin-x64@2.5.1':
resolution: {integrity: sha512-1ZXDthrnNmwv10A0/3AJNZ9JGlzrF82i3gNQcWOzd7nJ8aj+ILyW1MTxVk35Db0u91oD5Nlk9MBiujMlwmeXZg==}
engines: {node: '>= 10.0.0'}
cpu: [x64]
os: [darwin]
'@parcel/watcher-freebsd-x64@2.5.1':
resolution: {integrity: sha512-SI4eljM7Flp9yPuKi8W0ird8TI/JK6CSxju3NojVI6BjHsTyK7zxA9urjVjEKJ5MBYC+bLmMcbAWlZ+rFkLpJQ==}
engines: {node: '>= 10.0.0'}
cpu: [x64]
os: [freebsd]
'@parcel/watcher-linux-arm-glibc@2.5.1':
resolution: {integrity: sha512-RCdZlEyTs8geyBkkcnPWvtXLY44BCeZKmGYRtSgtwwnHR4dxfHRG3gR99XdMEdQ7KeiDdasJwwvNSF5jKtDwdA==}
engines: {node: '>= 10.0.0'}
cpu: [arm]
os: [linux]
'@parcel/watcher-linux-arm-musl@2.5.1':
resolution: {integrity: sha512-6E+m/Mm1t1yhB8X412stiKFG3XykmgdIOqhjWj+VL8oHkKABfu/gjFj8DvLrYVHSBNC+/u5PeNrujiSQ1zwd1Q==}
engines: {node: '>= 10.0.0'}
cpu: [arm]
os: [linux]
'@parcel/watcher-linux-arm64-glibc@2.5.1':
resolution: {integrity: sha512-LrGp+f02yU3BN9A+DGuY3v3bmnFUggAITBGriZHUREfNEzZh/GO06FF5u2kx8x+GBEUYfyTGamol4j3m9ANe8w==}
engines: {node: '>= 10.0.0'}
cpu: [arm64]
os: [linux]
'@parcel/watcher-linux-arm64-musl@2.5.1':
resolution: {integrity: sha512-cFOjABi92pMYRXS7AcQv9/M1YuKRw8SZniCDw0ssQb/noPkRzA+HBDkwmyOJYp5wXcsTrhxO0zq1U11cK9jsFg==}
engines: {node: '>= 10.0.0'}
cpu: [arm64]
os: [linux]
'@parcel/watcher-linux-x64-glibc@2.5.1':
resolution: {integrity: sha512-GcESn8NZySmfwlTsIur+49yDqSny2IhPeZfXunQi48DMugKeZ7uy1FX83pO0X22sHntJ4Ub+9k34XQCX+oHt2A==}
engines: {node: '>= 10.0.0'}
cpu: [x64]
os: [linux]
'@parcel/watcher-linux-x64-musl@2.5.1':
resolution: {integrity: sha512-n0E2EQbatQ3bXhcH2D1XIAANAcTZkQICBPVaxMeaCVBtOpBZpWJuf7LwyWPSBDITb7In8mqQgJ7gH8CILCURXg==}
engines: {node: '>= 10.0.0'}
cpu: [x64]
os: [linux]
'@parcel/watcher-win32-arm64@2.5.1':
resolution: {integrity: sha512-RFzklRvmc3PkjKjry3hLF9wD7ppR4AKcWNzH7kXR7GUe0Igb3Nz8fyPwtZCSquGrhU5HhUNDr/mKBqj7tqA2Vw==}
engines: {node: '>= 10.0.0'}
cpu: [arm64]
os: [win32]
'@parcel/watcher-win32-ia32@2.5.1':
resolution: {integrity: sha512-c2KkcVN+NJmuA7CGlaGD1qJh1cLfDnQsHjE89E60vUEMlqduHGCdCLJCID5geFVM0dOtA3ZiIO8BoEQmzQVfpQ==}
engines: {node: '>= 10.0.0'}
cpu: [ia32]
os: [win32]
'@parcel/watcher-win32-x64@2.5.1':
resolution: {integrity: sha512-9lHBdJITeNR++EvSQVUcaZoWupyHfXe1jZvGZ06O/5MflPcuPLtEphScIBL+AiCWBO46tDSHzWyD0uDmmZqsgA==}
engines: {node: '>= 10.0.0'}
cpu: [x64]
os: [win32]
'@parcel/watcher@2.5.1':
resolution: {integrity: sha512-dfUnCxiN9H4ap84DvD2ubjw+3vUNpstxa0TneY/Paat8a3R4uQZDLSvWjmznAY/DoahqTHl9V46HF/Zs3F29pg==}
engines: {node: '>= 10.0.0'}
'@pkgjs/parseargs@0.11.0':
resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==}
engines: {node: '>=14'}
@ -3612,10 +3527,6 @@ packages:
peerDependencies:
chart.js: '>=3.0.0'
chokidar@4.0.3:
resolution: {integrity: sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==}
engines: {node: '>= 14.16.0'}
chownr@2.0.0:
resolution: {integrity: sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==}
engines: {node: '>=10'}
@ -4238,11 +4149,6 @@ packages:
resolution: {integrity: sha512-Mc7QhQ8s+cLrnUfU/Ji94vG/r8M26m8f++vyres4ZoojaRDpZ1eSIh/EpzLNwlWuvzSZ3UbDFspjFvTDXe6e/g==}
engines: {node: '>=12.20'}
detect-libc@1.0.3:
resolution: {integrity: sha512-pGjwhsmsp4kL2RTz08wcOlGN83otlqHeD/Z5T8GXZB+/YcpQ/dgo+lbU8ZsGxV0HIvqqxo9l7mqYwyYMD9bKDg==}
engines: {node: '>=0.10'}
hasBin: true
detect-newline@4.0.1:
resolution: {integrity: sha512-qE3Veg1YXzGHQhlA6jzebZN2qVf6NX+A7m7qlhCGG30dJixrAQhYOsJjsnBjJkCSmuOPpCk30145fr8FV0bzog==}
engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
@ -5515,9 +5421,6 @@ packages:
immer@10.1.1:
resolution: {integrity: sha512-s2MPrmjovJcoMaHtx6K11Ra7oD05NT97w1IC5zpMkT6Atjr7H8LjaDd81iIxUYpMKSRRNMJE703M1Fhr/TctHw==}
immutable@5.0.3:
resolution: {integrity: sha512-P8IdPQHq3lA1xVeBRi5VPqUm5HDgKnx0Ru51wZz5mjxHr5n3RWhjIpOFU7ybkUxfB+5IToy+OLaHYDBIWsv+uw==}
import-cwd@3.0.0:
resolution: {integrity: sha512-4pnzH16plW+hgvRECbDWpQl3cqtvSofHWh44met7ESfZ8UZOWWddm8hEyDTqREJ9RbYHY8gi8DqmaelApoOGMg==}
engines: {node: '>=8'}
@ -6594,9 +6497,6 @@ packages:
no-case@3.0.4:
resolution: {integrity: sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg==}
node-addon-api@7.1.1:
resolution: {integrity: sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==}
node-domexception@1.0.0:
resolution: {integrity: sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==}
engines: {node: '>=10.5.0'}
@ -7325,10 +7225,6 @@ packages:
resolution: {integrity: sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==}
engines: {node: '>= 6'}
readdirp@4.1.2:
resolution: {integrity: sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==}
engines: {node: '>= 14.18.0'}
recast@0.18.10:
resolution: {integrity: sha512-XNvYvkfdAN9QewbrxeTOjgINkdY/odTgTS56ZNEWL9Ml0weT4T3sFtvnTuF+Gxyu46ANcRm1ntrF6F5LAJPAaQ==}
engines: {node: '>= 4'}
@ -7592,11 +7488,6 @@ packages:
engines: {node: 10.* || >= 12.*}
hasBin: true
sass@1.85.0:
resolution: {integrity: sha512-3ToiC1xZ1Y8aU7+CkgCI/tqyuPXEmYGJXO7H4uqp0xkLXUqp88rQQ4j1HmP37xSJLbCJPaIiv+cT1y+grssrww==}
engines: {node: '>=14.0.0'}
hasBin: true
saxes@6.0.0:
resolution: {integrity: sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==}
engines: {node: '>=v12.22.7'}
@ -10598,67 +10489,6 @@ snapshots:
- bluebird
- supports-color
'@parcel/watcher-android-arm64@2.5.1':
optional: true
'@parcel/watcher-darwin-arm64@2.5.1':
optional: true
'@parcel/watcher-darwin-x64@2.5.1':
optional: true
'@parcel/watcher-freebsd-x64@2.5.1':
optional: true
'@parcel/watcher-linux-arm-glibc@2.5.1':
optional: true
'@parcel/watcher-linux-arm-musl@2.5.1':
optional: true
'@parcel/watcher-linux-arm64-glibc@2.5.1':
optional: true
'@parcel/watcher-linux-arm64-musl@2.5.1':
optional: true
'@parcel/watcher-linux-x64-glibc@2.5.1':
optional: true
'@parcel/watcher-linux-x64-musl@2.5.1':
optional: true
'@parcel/watcher-win32-arm64@2.5.1':
optional: true
'@parcel/watcher-win32-ia32@2.5.1':
optional: true
'@parcel/watcher-win32-x64@2.5.1':
optional: true
'@parcel/watcher@2.5.1':
dependencies:
detect-libc: 1.0.3
is-glob: 4.0.3
micromatch: 4.0.8
node-addon-api: 7.1.1
optionalDependencies:
'@parcel/watcher-android-arm64': 2.5.1
'@parcel/watcher-darwin-arm64': 2.5.1
'@parcel/watcher-darwin-x64': 2.5.1
'@parcel/watcher-freebsd-x64': 2.5.1
'@parcel/watcher-linux-arm-glibc': 2.5.1
'@parcel/watcher-linux-arm-musl': 2.5.1
'@parcel/watcher-linux-arm64-glibc': 2.5.1
'@parcel/watcher-linux-arm64-musl': 2.5.1
'@parcel/watcher-linux-x64-glibc': 2.5.1
'@parcel/watcher-linux-x64-musl': 2.5.1
'@parcel/watcher-win32-arm64': 2.5.1
'@parcel/watcher-win32-ia32': 2.5.1
'@parcel/watcher-win32-x64': 2.5.1
optional: true
'@pkgjs/parseargs@0.11.0':
optional: true
@ -12139,10 +11969,6 @@ snapshots:
dependencies:
chart.js: 3.5.1
chokidar@4.0.3:
dependencies:
readdirp: 4.1.2
chownr@2.0.0: {}
chrome-launcher@1.1.2:
@ -12580,9 +12406,6 @@ snapshots:
detect-indent@7.0.1: {}
detect-libc@1.0.3:
optional: true
detect-newline@4.0.1: {}
devtools-protocol@0.0.1402036: {}
@ -14665,8 +14488,6 @@ snapshots:
immer@10.1.1: {}
immutable@5.0.3: {}
import-cwd@3.0.0:
dependencies:
import-from: 3.0.0
@ -15768,9 +15589,6 @@ snapshots:
lower-case: 2.0.2
tslib: 2.8.1
node-addon-api@7.1.1:
optional: true
node-domexception@1.0.0: {}
node-fetch@2.7.0(encoding@0.1.13):
@ -16566,8 +16384,6 @@ snapshots:
string_decoder: 1.3.0
util-deprecate: 1.0.2
readdirp@4.1.2: {}
recast@0.18.10:
dependencies:
ast-types: 0.13.3
@ -16838,14 +16654,6 @@ snapshots:
minimist: 1.2.8
walker: 1.0.8
sass@1.85.0:
dependencies:
chokidar: 4.0.3
immutable: 5.0.3
source-map-js: 1.2.1
optionalDependencies:
'@parcel/watcher': 2.5.1
saxes@6.0.0:
dependencies:
xmlchars: 2.2.0

View File

@ -14,7 +14,8 @@ RSpec.describe ExtraLocalesController do
expect(response.status).to eq(403)
end
it "requires staff access" do
it "requires staff access in production" do
Rails.env.stubs(:local?).returns(false)
get "/extra-locales/admin"
expect(response.status).to eq(403)