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: jobs:
build: build:
if: github.event_name == 'pull_request' || github.repository != 'discourse/discourse-private-mirror' 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' }} runs-on: ${{ (github.repository_owner == 'discourse' && 'debian-12') || 'ubuntu-latest' }}
container: discourse/discourse_test:release container: discourse/discourse_test:release
timeout-minutes: 20 timeout-minutes: 20
@ -42,8 +42,9 @@ jobs:
MINIO_RUNNER_LOG_LEVEL: DEBUG MINIO_RUNNER_LOG_LEVEL: DEBUG
DISCOURSE_TURBO_RSPEC_RETRY_AND_LOG_FLAKY_TESTS: ${{ (matrix.build_type == 'system' || matrix.build_type == 'backend') && '1' }} DISCOURSE_TURBO_RSPEC_RETRY_AND_LOG_FLAKY_TESTS: ${{ (matrix.build_type == 'system' || matrix.build_type == 'backend') && '1' }}
CHEAP_SOURCE_MAPS: "1" CHEAP_SOURCE_MAPS: "1"
TESTEM_DEFAULT_BROWSER: Chrome
MINIO_RUNNER_INSTALL_DIR: /home/discourse/.minio_runner 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: strategy:
fail-fast: false fail-fast: false
@ -51,6 +52,7 @@ jobs:
matrix: matrix:
build_type: [backend, frontend, system, annotations] build_type: [backend, frontend, system, annotations]
target: [core, plugins, themes] target: [core, plugins, themes]
browser: [Chrome]
exclude: exclude:
- build_type: annotations - build_type: annotations
target: plugins target: plugins
@ -58,11 +60,15 @@ jobs:
target: themes target: themes
- build_type: backend - build_type: backend
target: themes target: themes
- build_type: frontend
target: core # Handled by core_frontend_tests job (below)
include: include:
- build_type: system - build_type: system
target: chat target: chat
- build_type: frontend
target: core
browser: Firefox Evergreen
- build_type: frontend
target: core
browser: Firefox ESR
steps: steps:
- name: Set working directory owner - name: Set working directory owner
@ -233,6 +239,11 @@ jobs:
if: matrix.build_type == 'backend' && matrix.target == 'plugins' if: matrix.build_type == 'backend' && matrix.target == 'plugins'
run: bin/rake plugin:turbo_spec['*','--verbose --format=documentation --use-runtime-info --profile=50'] 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 - name: Plugin QUnit
if: matrix.build_type == 'frontend' && matrix.target == 'plugins' if: matrix.build_type == 'frontend' && matrix.target == 'plugins'
run: QUNIT_WRITE_EXECUTION_FILE=1 bin/rake plugin:qunit['*'] run: QUNIT_WRITE_EXECUTION_FILE=1 bin/rake plugin:qunit['*']
@ -244,9 +255,9 @@ jobs:
timeout-minutes: 10 timeout-minutes: 10
- uses: actions/upload-artifact@v4 - uses: actions/upload-artifact@v4
if: always() && matrix.build_type == 'frontend' && matrix.target == 'plugins' if: always() && matrix.build_type == 'frontend'
with: 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 path: ./app/assets/javascripts/discourse/test-execution-*.json
- name: Ember Build for System Tests - name: Ember Build for System Tests
@ -322,7 +333,7 @@ jobs:
id: fetch-job-id id: fetch-job-id
if: steps.check-flaky-spec-report.outputs.exists == 'true' if: steps.check-flaky-spec-report.outputs.exists == 'true'
run: | 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 echo "job_id=$job_id" >> $GITHUB_OUTPUT
- name: Create flaky tests report artifact - name: Create flaky tests report artifact
@ -353,56 +364,6 @@ jobs:
fi fi
timeout-minutes: 30 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: merge:
if: github.repository == 'discourse/discourse' && github.ref == 'refs/heads/main' 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 path = require("path");
const mergeTrees = require("broccoli-merge-trees"); const mergeTrees = require("broccoli-merge-trees");
const concat = require("broccoli-concat"); const concat = require("broccoli-concat");
const { createI18nTree } = require("./lib/translation-plugin");
const { parsePluginClientSettings } = require("./lib/site-settings-plugin"); const { parsePluginClientSettings } = require("./lib/site-settings-plugin");
const discourseScss = require("./lib/discourse-scss");
const generateScriptsTree = require("./lib/scripts"); const generateScriptsTree = require("./lib/scripts");
const funnel = require("broccoli-funnel"); const funnel = require("broccoli-funnel");
const DeprecationSilencer = require("deprecation-silencer"); const DeprecationSilencer = require("deprecation-silencer");
@ -96,20 +94,12 @@ module.exports = function (defaults) {
const adminTree = app.project.findAddonByName("admin").treeForAddonBundle(); 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/]; app.project.liveReloadFilterPatterns = [/.*\.scss/];
const terserPlugin = app.project.findAddonByName("ember-cli-terser"); const terserPlugin = app.project.findAddonByName("ember-cli-terser");
const applyTerser = (tree) => terserPlugin.postprocessTree("all", tree); const applyTerser = (tree) => terserPlugin.postprocessTree("all", tree);
let extraPublicTrees = [ let extraPublicTrees = [
createI18nTree(discourseRoot, vendorJs),
parsePluginClientSettings(discourseRoot, vendorJs, app), parsePluginClientSettings(discourseRoot, vendorJs, app),
funnel(`${discourseRoot}/public/javascripts`, { destDir: "javascripts" }), funnel(`${discourseRoot}/public/javascripts`, { destDir: "javascripts" }),
applyTerser( applyTerser(
@ -120,7 +110,6 @@ module.exports = function (defaults) {
), ),
applyTerser(generateScriptsTree(app)), applyTerser(generateScriptsTree(app)),
applyTerser(discoursePluginsTree), applyTerser(discoursePluginsTree),
testStylesheetTree,
]; ];
const assetCachebuster = process.env["DISCOURSE_ASSET_URL_SALT"] || ""; 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", "pretender": "^3.4.7",
"qunit": "^2.24.1", "qunit": "^2.24.1",
"qunit-dom": "^3.4.0", "qunit-dom": "^3.4.0",
"sass": "^1.85.0",
"select-kit": "workspace:1.0.0", "select-kit": "workspace:1.0.0",
"sinon": "^19.0.2", "sinon": "^19.0.2",
"source-map": "^0.7.4", "source-map": "^0.7.4",

View File

@ -1,5 +1,4 @@
const TapReporter = require("testem/lib/reporters/tap_reporter"); const TapReporter = require("testem/lib/reporters/tap_reporter");
const { shouldLoadPlugins } = require("discourse-plugins");
const fs = require("fs"); const fs = require("fs");
const displayUtils = require("testem/lib/utils/displayutils"); const displayUtils = require("testem/lib/utils/displayutils");
const colors = require("@colors/colors/safe"); 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"}`; 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; const themeTestPages = process.env.THEME_TEST_PAGES;
if (themeTestPages) { 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 // Running with ember cli, but we want to pass through plugin request to Rails
module.exports.proxies = { module.exports.proxies = {
"/assets/locales/*.js": {
target,
},
"/assets/plugins/*_extra.js": { "/assets/plugins/*_extra.js": {
target, target,
}, },
"/plugins/": { "/plugins/": {
target, target,
}, },
"/bootstrap/plugin-css-for-tests.css": { "/bootstrap/": {
target, target,
}, },
"/stylesheets/": { "/stylesheets/": {
target, target,
}, },
"/extra-locales/": {
target,
},
}; };
} }

View File

@ -20,7 +20,7 @@
<link rel="stylesheet" href="{{rootURL}}assets/vendor.css" /> <link rel="stylesheet" href="{{rootURL}}assets/vendor.css" />
<link rel="stylesheet" href="{{rootURL}}assets/discourse.css"> <link rel="stylesheet" href="{{rootURL}}assets/discourse.css">
<link rel="stylesheet" href="{{rootURL}}assets/test-support.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"}} {{content-for "head-footer"}} {{content-for "test-head-footer"}}
@ -53,7 +53,10 @@
<script src="{{rootURL}}assets/discourse.js"></script> <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/test-site-settings.js" data-embroider-ignore></script>
<script src="{{rootURL}}assets/admin.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 --> <!-- 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> <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> </body>
</html> </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 skip_before_action :redirect_to_login_if_required, :check_xhr
def plugin_css_for_tests 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 = urls =
Discourse targets.map do |target|
.find_plugin_css_assets(include_disabled: true, desktop_view: true) details = Stylesheet::Manager.new().stylesheet_details(target, "all")
.map do |target| details[0][:new_href]
details = Stylesheet::Manager.new().stylesheet_details(target, "all") end
details[0][:new_href]
end
stylesheet = <<~CSS stylesheet = <<~CSS
/* For use in tests only - `@import`s all plugin stylesheets */ /* For use in tests only */
#{urls.map { |url| "@import \"#{url}\";" }.join("\n")} #{urls.map { |url| "@import \"#{url}\";" }.join("\n")}
CSS CSS

View File

@ -85,6 +85,7 @@ class ExtraLocalesController < ApplicationController
private private
def valid_bundle?(bundle) 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
end end

View File

@ -29,6 +29,7 @@ Discourse::Application.routes.draw do
if Rails.env.test? || Rails.env.development? if Rails.env.test? || Rails.env.development?
get "/bootstrap/plugin-css-for-tests.css" => "bootstrap#plugin_css_for_tests" 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 end
# This is not a valid production route and is causing routing errors to be raised in # 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["RUBY_ONLY"]
unless ENV["SKIP_CORE"] unless ENV["SKIP_CORE"]
@good &&= @good &&= run_or_fail("CI=1 QUNIT_PARALLEL=#{qunit_concurrency} bin/rake qunit:test")
run_or_fail(
"cd app/assets/javascripts/discourse && CI=1 pnpm ember exam --load-balance --parallel=#{qunit_concurrency} --random",
)
end end
unless ENV["SKIP_PLUGINS"] unless ENV["SKIP_PLUGINS"]

192
pnpm-lock.yaml generated
View File

@ -597,9 +597,6 @@ importers:
qunit-dom: qunit-dom:
specifier: ^3.4.0 specifier: ^3.4.0
version: 3.4.0 version: 3.4.0
sass:
specifier: ^1.85.0
version: 1.85.0
select-kit: select-kit:
specifier: workspace:1.0.0 specifier: workspace:1.0.0
version: link:../select-kit version: link:../select-kit
@ -2423,88 +2420,6 @@ packages:
resolution: {integrity: sha512-y7efHHwghQfk28G2z3tlZ67pLG0XdfYbcVG26r7YIXALRsrVQcTq4/tdenSmdOrEsNahIYA/eh8aEVROWGFUDg==} resolution: {integrity: sha512-y7efHHwghQfk28G2z3tlZ67pLG0XdfYbcVG26r7YIXALRsrVQcTq4/tdenSmdOrEsNahIYA/eh8aEVROWGFUDg==}
engines: {node: ^16.14.0 || >=18.0.0} 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': '@pkgjs/parseargs@0.11.0':
resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==}
engines: {node: '>=14'} engines: {node: '>=14'}
@ -3612,10 +3527,6 @@ packages:
peerDependencies: peerDependencies:
chart.js: '>=3.0.0' chart.js: '>=3.0.0'
chokidar@4.0.3:
resolution: {integrity: sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==}
engines: {node: '>= 14.16.0'}
chownr@2.0.0: chownr@2.0.0:
resolution: {integrity: sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==} resolution: {integrity: sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==}
engines: {node: '>=10'} engines: {node: '>=10'}
@ -4238,11 +4149,6 @@ packages:
resolution: {integrity: sha512-Mc7QhQ8s+cLrnUfU/Ji94vG/r8M26m8f++vyres4ZoojaRDpZ1eSIh/EpzLNwlWuvzSZ3UbDFspjFvTDXe6e/g==} resolution: {integrity: sha512-Mc7QhQ8s+cLrnUfU/Ji94vG/r8M26m8f++vyres4ZoojaRDpZ1eSIh/EpzLNwlWuvzSZ3UbDFspjFvTDXe6e/g==}
engines: {node: '>=12.20'} 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: detect-newline@4.0.1:
resolution: {integrity: sha512-qE3Veg1YXzGHQhlA6jzebZN2qVf6NX+A7m7qlhCGG30dJixrAQhYOsJjsnBjJkCSmuOPpCk30145fr8FV0bzog==} resolution: {integrity: sha512-qE3Veg1YXzGHQhlA6jzebZN2qVf6NX+A7m7qlhCGG30dJixrAQhYOsJjsnBjJkCSmuOPpCk30145fr8FV0bzog==}
engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
@ -5515,9 +5421,6 @@ packages:
immer@10.1.1: immer@10.1.1:
resolution: {integrity: sha512-s2MPrmjovJcoMaHtx6K11Ra7oD05NT97w1IC5zpMkT6Atjr7H8LjaDd81iIxUYpMKSRRNMJE703M1Fhr/TctHw==} resolution: {integrity: sha512-s2MPrmjovJcoMaHtx6K11Ra7oD05NT97w1IC5zpMkT6Atjr7H8LjaDd81iIxUYpMKSRRNMJE703M1Fhr/TctHw==}
immutable@5.0.3:
resolution: {integrity: sha512-P8IdPQHq3lA1xVeBRi5VPqUm5HDgKnx0Ru51wZz5mjxHr5n3RWhjIpOFU7ybkUxfB+5IToy+OLaHYDBIWsv+uw==}
import-cwd@3.0.0: import-cwd@3.0.0:
resolution: {integrity: sha512-4pnzH16plW+hgvRECbDWpQl3cqtvSofHWh44met7ESfZ8UZOWWddm8hEyDTqREJ9RbYHY8gi8DqmaelApoOGMg==} resolution: {integrity: sha512-4pnzH16plW+hgvRECbDWpQl3cqtvSofHWh44met7ESfZ8UZOWWddm8hEyDTqREJ9RbYHY8gi8DqmaelApoOGMg==}
engines: {node: '>=8'} engines: {node: '>=8'}
@ -6594,9 +6497,6 @@ packages:
no-case@3.0.4: no-case@3.0.4:
resolution: {integrity: sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg==} resolution: {integrity: sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg==}
node-addon-api@7.1.1:
resolution: {integrity: sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==}
node-domexception@1.0.0: node-domexception@1.0.0:
resolution: {integrity: sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==} resolution: {integrity: sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==}
engines: {node: '>=10.5.0'} engines: {node: '>=10.5.0'}
@ -7325,10 +7225,6 @@ packages:
resolution: {integrity: sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==} resolution: {integrity: sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==}
engines: {node: '>= 6'} engines: {node: '>= 6'}
readdirp@4.1.2:
resolution: {integrity: sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==}
engines: {node: '>= 14.18.0'}
recast@0.18.10: recast@0.18.10:
resolution: {integrity: sha512-XNvYvkfdAN9QewbrxeTOjgINkdY/odTgTS56ZNEWL9Ml0weT4T3sFtvnTuF+Gxyu46ANcRm1ntrF6F5LAJPAaQ==} resolution: {integrity: sha512-XNvYvkfdAN9QewbrxeTOjgINkdY/odTgTS56ZNEWL9Ml0weT4T3sFtvnTuF+Gxyu46ANcRm1ntrF6F5LAJPAaQ==}
engines: {node: '>= 4'} engines: {node: '>= 4'}
@ -7592,11 +7488,6 @@ packages:
engines: {node: 10.* || >= 12.*} engines: {node: 10.* || >= 12.*}
hasBin: true 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: saxes@6.0.0:
resolution: {integrity: sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==} resolution: {integrity: sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==}
engines: {node: '>=v12.22.7'} engines: {node: '>=v12.22.7'}
@ -10598,67 +10489,6 @@ snapshots:
- bluebird - bluebird
- supports-color - 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': '@pkgjs/parseargs@0.11.0':
optional: true optional: true
@ -12139,10 +11969,6 @@ snapshots:
dependencies: dependencies:
chart.js: 3.5.1 chart.js: 3.5.1
chokidar@4.0.3:
dependencies:
readdirp: 4.1.2
chownr@2.0.0: {} chownr@2.0.0: {}
chrome-launcher@1.1.2: chrome-launcher@1.1.2:
@ -12580,9 +12406,6 @@ snapshots:
detect-indent@7.0.1: {} detect-indent@7.0.1: {}
detect-libc@1.0.3:
optional: true
detect-newline@4.0.1: {} detect-newline@4.0.1: {}
devtools-protocol@0.0.1402036: {} devtools-protocol@0.0.1402036: {}
@ -14665,8 +14488,6 @@ snapshots:
immer@10.1.1: {} immer@10.1.1: {}
immutable@5.0.3: {}
import-cwd@3.0.0: import-cwd@3.0.0:
dependencies: dependencies:
import-from: 3.0.0 import-from: 3.0.0
@ -15768,9 +15589,6 @@ snapshots:
lower-case: 2.0.2 lower-case: 2.0.2
tslib: 2.8.1 tslib: 2.8.1
node-addon-api@7.1.1:
optional: true
node-domexception@1.0.0: {} node-domexception@1.0.0: {}
node-fetch@2.7.0(encoding@0.1.13): node-fetch@2.7.0(encoding@0.1.13):
@ -16566,8 +16384,6 @@ snapshots:
string_decoder: 1.3.0 string_decoder: 1.3.0
util-deprecate: 1.0.2 util-deprecate: 1.0.2
readdirp@4.1.2: {}
recast@0.18.10: recast@0.18.10:
dependencies: dependencies:
ast-types: 0.13.3 ast-types: 0.13.3
@ -16838,14 +16654,6 @@ snapshots:
minimist: 1.2.8 minimist: 1.2.8
walker: 1.0.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: saxes@6.0.0:
dependencies: dependencies:
xmlchars: 2.2.0 xmlchars: 2.2.0

View File

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