mirror of
https://github.com/discourse/discourse.git
synced 2024-11-22 10:57:04 +08:00
DEV: Drop workbox dependency (#26735)
This service-worker caching functionality was disabled by default in 1c58395bca
, and the setting to re-enable was marked as experimental. Now we are dropping all the related logic.
This commit is contained in:
parent
b5b2f80955
commit
dcd994a9f1
3
.github/dependabot.yml
vendored
3
.github/dependabot.yml
vendored
|
@ -81,9 +81,6 @@ updates:
|
|||
types:
|
||||
patterns:
|
||||
- "@types/*"
|
||||
workbox:
|
||||
patterns:
|
||||
- "workbox-*"
|
||||
# - package-ecosystem: "bundler"
|
||||
# directory: "migrations/config/gemfiles/convert"
|
||||
# schedule:
|
||||
|
|
|
@ -10,7 +10,6 @@ const discourseScss = require("./lib/discourse-scss");
|
|||
const generateScriptsTree = require("./lib/scripts");
|
||||
const funnel = require("broccoli-funnel");
|
||||
const DeprecationSilencer = require("deprecation-silencer");
|
||||
const generateWorkboxTree = require("./lib/workbox-tree-builder");
|
||||
const { compatBuild } = require("@embroider/compat");
|
||||
const { Webpack } = require("@embroider/webpack");
|
||||
const { StatsWriterPlugin } = require("webpack-stats-plugin");
|
||||
|
@ -114,7 +113,6 @@ module.exports = function (defaults) {
|
|||
createI18nTree(discourseRoot, vendorJs),
|
||||
parsePluginClientSettings(discourseRoot, vendorJs, app),
|
||||
funnel(`${discourseRoot}/public/javascripts`, { destDir: "javascripts" }),
|
||||
generateWorkboxTree(),
|
||||
applyTerser(
|
||||
concat(adminTree, {
|
||||
inputFiles: ["**/*.js"],
|
||||
|
|
|
@ -1,42 +0,0 @@
|
|||
const crypto = require("crypto");
|
||||
const mergeTrees = require("broccoli-merge-trees");
|
||||
const funnel = require("broccoli-funnel");
|
||||
|
||||
// Bump to cache-bust if there are any changes to the workbox compilation logic
|
||||
// which are not caused by a simple workbox version bump
|
||||
const COMPILER_VERSION = 2;
|
||||
|
||||
module.exports = function generateWorkboxTree() {
|
||||
const workboxDeps = [
|
||||
"workbox-sw",
|
||||
"workbox-routing",
|
||||
"workbox-core",
|
||||
"workbox-strategies",
|
||||
"workbox-expiration",
|
||||
"workbox-cacheable-response",
|
||||
];
|
||||
|
||||
const nodes = workboxDeps.map((name) => {
|
||||
return funnel(`../../../../node_modules/${name}/build`);
|
||||
});
|
||||
|
||||
const versions = workboxDeps.map((name) => {
|
||||
return require(`../../../../../node_modules/${name}/package.json`).version;
|
||||
});
|
||||
|
||||
// Normally Sprockets will create a cachebuster per-file. In this case we need it at the directory level since
|
||||
// workbox is responsible for loading its own files and doesn't support customized names per-file.
|
||||
// Sprockets' default behaviour for these files is disabled via freedom_patches/sprockets.rb.
|
||||
const versionHash = crypto
|
||||
.createHash("md5")
|
||||
.update(
|
||||
`${versions.join("|")}|${COMPILER_VERSION}|${
|
||||
process.env["DISCOURSE_ASSET_URL_SALT"] || ""
|
||||
}`
|
||||
)
|
||||
.digest("hex");
|
||||
|
||||
return funnel(mergeTrees(nodes), {
|
||||
destDir: `assets/workbox-${versionHash}`,
|
||||
});
|
||||
};
|
|
@ -116,12 +116,6 @@
|
|||
"virtual-dom": "^2.1.1",
|
||||
"webpack": "^5.91.0",
|
||||
"webpack-stats-plugin": "^1.1.3",
|
||||
"workbox-cacheable-response": "^7.0.0",
|
||||
"workbox-core": "^7.0.0",
|
||||
"workbox-expiration": "^7.0.0",
|
||||
"workbox-routing": "^7.0.0",
|
||||
"workbox-strategies": "^7.0.0",
|
||||
"workbox-sw": "^7.0.0",
|
||||
"xss": "^1.0.15"
|
||||
},
|
||||
"engines": {
|
||||
|
|
|
@ -1,150 +1,5 @@
|
|||
'use strict';
|
||||
|
||||
<%
|
||||
base = if GlobalSetting.use_s3? && GlobalSetting.s3_cdn_url
|
||||
GlobalSetting.s3_asset_cdn_url.presence || GlobalSetting.s3_cdn_url
|
||||
elsif GlobalSetting.cdn_url
|
||||
GlobalSetting.cdn_url + Discourse.base_path
|
||||
else
|
||||
Discourse.base_path
|
||||
end
|
||||
|
||||
workbox_base = "#{base}/assets/#{EmberCli.workbox_dir_name}"
|
||||
%>
|
||||
|
||||
<% if !GlobalSetting.disable_service_worker_cache %>
|
||||
|
||||
importScripts("<%= "#{workbox_base}/workbox-sw.js" %>");
|
||||
|
||||
workbox.setConfig({
|
||||
modulePathPrefix: "<%= workbox_base %>",
|
||||
debug: false
|
||||
});
|
||||
|
||||
var authUrls = ["auth", "session/sso_login", "session/sso", "srv/status"].map(path => `<%= Discourse.base_path %>/${path}`);
|
||||
|
||||
const oldCacheNames = [
|
||||
"discourse-1", "external-1"
|
||||
]
|
||||
|
||||
oldCacheNames.forEach(cacheName => caches.delete(cacheName))
|
||||
|
||||
var cacheVersion = "2";
|
||||
var discourseCacheName = "discourse-" + cacheVersion;
|
||||
var externalCacheName = "external-" + cacheVersion;
|
||||
|
||||
const expirationOptions = {
|
||||
maxAgeSeconds: 7 * 24 * 60 * 60, // 7 days
|
||||
maxEntries: 250,
|
||||
purgeOnQuotaError: true, // safe to automatically delete if exceeding the available storage
|
||||
matchOptions: {
|
||||
ignoreVary: true // Many discourse responses include `Vary` header. This option is required to ensure they are cleaned up correctly.
|
||||
}
|
||||
}
|
||||
|
||||
// Chrome 97 shipped with broken samesite cookie handling when proxying requests through service workers
|
||||
// https://bugs.chromium.org/p/chromium/issues/detail?id=1286367
|
||||
var chromeVersionMatch = navigator.userAgent.match(/Chrome\/97.0.(\d+)/);
|
||||
var isBrokenChrome97 = chromeVersionMatch && parseInt(chromeVersionMatch[1]) <= 4692;
|
||||
var isApple = /^((?!chrome|android).)*safari/i.test(navigator.userAgent);
|
||||
|
||||
// Cache all GET requests, so Discourse can be used while offline
|
||||
workbox.routing.registerRoute(
|
||||
function(args) {
|
||||
return args.url.origin === location.origin && !authUrls.some(u => args.url.pathname.startsWith(u)) && !isBrokenChrome97 && !isApple;
|
||||
}, // Match all except auth routes
|
||||
new workbox.strategies.NetworkFirst({ // This will only use the cache when a network request fails
|
||||
cacheName: discourseCacheName,
|
||||
plugins: [
|
||||
new workbox.cacheableResponse.CacheableResponsePlugin({
|
||||
statuses: [200] // opaque responses will return status code '0'
|
||||
}), // for s3 secure uploads signed urls
|
||||
new workbox.expiration.ExpirationPlugin(expirationOptions),
|
||||
],
|
||||
})
|
||||
);
|
||||
|
||||
var cdnUrls = [];
|
||||
|
||||
<% if GlobalSetting.try(:cdn_cors_enabled) %>
|
||||
cdnUrls = ["<%= "#{GlobalSetting.s3_cdn_url}" %>", "<%= "#{GlobalSetting.cdn_url}" %>"].filter(Boolean);
|
||||
|
||||
if (cdnUrls.length > 0) {
|
||||
var cdnCacheName = "cdn-" + cacheVersion;
|
||||
var cdnUrl = "<%= "#{GlobalSetting.cdn_url}" %>";
|
||||
|
||||
var appendQueryStringPlugin = {
|
||||
requestWillFetch: function (args) {
|
||||
var request = args.request;
|
||||
|
||||
if (request.url.startsWith(cdnUrl)) {
|
||||
var url = new URL(request.url);
|
||||
// Using this temporary query param to force browsers to redownload images from server.
|
||||
url.searchParams.append('refresh', 'true');
|
||||
return new Request(url.href, request);
|
||||
}
|
||||
|
||||
return request;
|
||||
}
|
||||
};
|
||||
|
||||
workbox.routing.registerRoute(
|
||||
function(args) {
|
||||
var matching = cdnUrls.filter(
|
||||
function(url) {
|
||||
return args.url.href.startsWith(url);
|
||||
}
|
||||
);
|
||||
return matching.length > 0;
|
||||
}, // Match all cdn resources
|
||||
new workbox.strategies.NetworkFirst({ // This will only use the cache when a network request fails
|
||||
cacheName: cdnCacheName,
|
||||
fetchOptions: {
|
||||
mode: 'cors',
|
||||
credentials: 'omit'
|
||||
},
|
||||
plugins: [
|
||||
new workbox.cacheableResponse.CacheableResponsePlugin({
|
||||
statuses: [200] // opaque responses will return status code '0'
|
||||
}),
|
||||
new workbox.expiration.ExpirationPlugin(expirationOptions),
|
||||
appendQueryStringPlugin
|
||||
],
|
||||
})
|
||||
);
|
||||
}
|
||||
<% end %>
|
||||
|
||||
workbox.routing.registerRoute(
|
||||
function(args) {
|
||||
if (args.url.origin === location.origin) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Exclude videos from service worker
|
||||
if (args.event.request.headers.has('range')) {
|
||||
return false;
|
||||
}
|
||||
|
||||
var matching = cdnUrls.filter(
|
||||
function(url) {
|
||||
return args.url.href.startsWith(url);
|
||||
}
|
||||
);
|
||||
return matching.length === 0;
|
||||
}, // Match all other external resources
|
||||
new workbox.strategies.NetworkFirst({ // This will only use the cache when a network request fails
|
||||
cacheName: externalCacheName,
|
||||
plugins: [
|
||||
new workbox.cacheableResponse.CacheableResponsePlugin({
|
||||
statuses: [200] // opaque responses will return status code '0'
|
||||
}),
|
||||
new workbox.expiration.ExpirationPlugin(expirationOptions),
|
||||
],
|
||||
})
|
||||
);
|
||||
<% end %>
|
||||
|
||||
var chatRegex = /\/chat\/channel\/(\d+)\//;
|
||||
var inlineReplyIcon = "<%= UrlHelper.absolute("/images/push-notifications/inline_reply.png") %>";
|
||||
|
||||
|
|
|
@ -401,7 +401,3 @@ log_line_max_chars = 160000
|
|||
# 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 =
|
||||
|
||||
# disable service-worker caching. An 'offline' page will still be used as an offline fallback.
|
||||
# This flag is for a temporary experiment, and may be removed at any time
|
||||
disable_service_worker_cache = true
|
||||
|
|
|
@ -46,15 +46,6 @@ class EmberCli < ActiveSupport::CurrentAttributes
|
|||
end
|
||||
end
|
||||
|
||||
def self.workbox_dir_name
|
||||
return @workbox_base_dir if defined?(@workbox_base_dir)
|
||||
|
||||
@workbox_base_dir =
|
||||
if (full_path = Dir.glob("app/assets/javascripts/discourse/dist/assets/workbox-*")[0])
|
||||
File.basename(full_path)
|
||||
end
|
||||
end
|
||||
|
||||
def self.has_tests?
|
||||
File.exist?("#{dist_dir}/tests/index.html")
|
||||
end
|
||||
|
|
|
@ -54,8 +54,6 @@ Sprockets::DirectiveProcessor.prepend(
|
|||
Sprockets::Asset.prepend(
|
||||
Module.new do
|
||||
def digest_path
|
||||
# Workbox assets are already in a folder with a digest in the name
|
||||
return logical_path if logical_path.start_with?("workbox-")
|
||||
# Webpack chunks are already named based on their contents
|
||||
return logical_path if logical_path.start_with?("chunk.")
|
||||
super
|
||||
|
|
|
@ -267,7 +267,6 @@ task "assets:precompile:compress_js": "environment" do
|
|||
manifest
|
||||
.files
|
||||
.select { |k, v| k =~ /\.js\z/ }
|
||||
.reject { |k, v| k =~ %r{/workbox-.*'/} }
|
||||
.each do |file, info|
|
||||
path = "#{assets_path}/#{file}"
|
||||
_file =
|
||||
|
|
75
yarn.lock
75
yarn.lock
|
@ -7553,11 +7553,6 @@ icss-utils@^5.0.0, icss-utils@^5.1.0:
|
|||
resolved "https://registry.yarnpkg.com/icss-utils/-/icss-utils-5.1.0.tgz#c6be6858abd013d768e98366ae47e25d5887b1ae"
|
||||
integrity sha512-soFhflCVWLfRNOPU3iv5Z9VUdT44xFRbzjLsEzSr5AQmgqPMTHdU3PMT1Cf1ssx8fLNJDA1juftYl+PUcv3MqA==
|
||||
|
||||
idb@^7.0.1:
|
||||
version "7.1.1"
|
||||
resolved "https://registry.yarnpkg.com/idb/-/idb-7.1.1.tgz#d910ded866d32c7ced9befc5bfdf36f572ced72b"
|
||||
integrity sha512-gchesWBzyvGHRO9W8tzUWFDycow5gwjvFKfyV9FF32Y7F50yZMp7mP+T2mJIWFx49zicqyC4uefHM17o6xKIVQ==
|
||||
|
||||
ieee754@^1.1.13:
|
||||
version "1.2.1"
|
||||
resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.2.1.tgz#8eb7a10a63fff25d15a57b001586d177d1b0d352"
|
||||
|
@ -11255,16 +11250,7 @@ string-template@~0.2.0, string-template@~0.2.1:
|
|||
resolved "https://registry.yarnpkg.com/string-template/-/string-template-0.2.1.tgz#42932e598a352d01fc22ec3367d9d84eec6c9add"
|
||||
integrity sha512-Yptehjogou2xm4UJbxJ4CxgZx12HBfeystp0y3x7s4Dj32ltVVG1Gg8YhKjHZkHicuKpZX/ffilA8505VbUbpw==
|
||||
|
||||
"string-width-cjs@npm:string-width@^4.2.0":
|
||||
version "4.2.3"
|
||||
resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010"
|
||||
integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==
|
||||
dependencies:
|
||||
emoji-regex "^8.0.0"
|
||||
is-fullwidth-code-point "^3.0.0"
|
||||
strip-ansi "^6.0.1"
|
||||
|
||||
"string-width@^1.0.2 || 2 || 3 || 4", string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3:
|
||||
"string-width-cjs@npm:string-width@^4.2.0", "string-width@^1.0.2 || 2 || 3 || 4", string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3:
|
||||
version "4.2.3"
|
||||
resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010"
|
||||
integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==
|
||||
|
@ -11351,7 +11337,7 @@ string_decoder@~1.1.1:
|
|||
dependencies:
|
||||
safe-buffer "~5.1.0"
|
||||
|
||||
"strip-ansi-cjs@npm:strip-ansi@^6.0.1":
|
||||
"strip-ansi-cjs@npm:strip-ansi@^6.0.1", strip-ansi@^6.0.0, strip-ansi@^6.0.1:
|
||||
version "6.0.1"
|
||||
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9"
|
||||
integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==
|
||||
|
@ -11372,13 +11358,6 @@ strip-ansi@^5.1.0, strip-ansi@^5.2.0:
|
|||
dependencies:
|
||||
ansi-regex "^4.1.0"
|
||||
|
||||
strip-ansi@^6.0.0, strip-ansi@^6.0.1:
|
||||
version "6.0.1"
|
||||
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9"
|
||||
integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==
|
||||
dependencies:
|
||||
ansi-regex "^5.0.1"
|
||||
|
||||
strip-ansi@^7.0.1:
|
||||
version "7.1.0"
|
||||
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-7.1.0.tgz#d5b6568ca689d8561370b0707685d22434faff45"
|
||||
|
@ -12467,45 +12446,6 @@ wordwrap@^1.0.0:
|
|||
resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-1.0.0.tgz#27584810891456a4171c8d0226441ade90cbcaeb"
|
||||
integrity sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==
|
||||
|
||||
workbox-cacheable-response@^7.0.0:
|
||||
version "7.0.0"
|
||||
resolved "https://registry.yarnpkg.com/workbox-cacheable-response/-/workbox-cacheable-response-7.0.0.tgz#ee27c036728189eed69d25a135013053277482d2"
|
||||
integrity sha512-0lrtyGHn/LH8kKAJVOQfSu3/80WDc9Ma8ng0p2i/5HuUndGttH+mGMSvOskjOdFImLs2XZIimErp7tSOPmu/6g==
|
||||
dependencies:
|
||||
workbox-core "7.0.0"
|
||||
|
||||
workbox-core@7.0.0, workbox-core@^7.0.0:
|
||||
version "7.0.0"
|
||||
resolved "https://registry.yarnpkg.com/workbox-core/-/workbox-core-7.0.0.tgz#dec114ec923cc2adc967dd9be1b8a0bed50a3545"
|
||||
integrity sha512-81JkAAZtfVP8darBpfRTovHg8DGAVrKFgHpOArZbdFd78VqHr5Iw65f2guwjE2NlCFbPFDoez3D3/6ZvhI/rwQ==
|
||||
|
||||
workbox-expiration@^7.0.0:
|
||||
version "7.0.0"
|
||||
resolved "https://registry.yarnpkg.com/workbox-expiration/-/workbox-expiration-7.0.0.tgz#3d90bcf2a7577241de950f89784f6546b66c2baa"
|
||||
integrity sha512-MLK+fogW+pC3IWU9SFE+FRStvDVutwJMR5if1g7oBJx3qwmO69BNoJQVaMXq41R0gg3MzxVfwOGKx3i9P6sOLQ==
|
||||
dependencies:
|
||||
idb "^7.0.1"
|
||||
workbox-core "7.0.0"
|
||||
|
||||
workbox-routing@^7.0.0:
|
||||
version "7.0.0"
|
||||
resolved "https://registry.yarnpkg.com/workbox-routing/-/workbox-routing-7.0.0.tgz#6668438a06554f60645aedc77244a4fe3a91e302"
|
||||
integrity sha512-8YxLr3xvqidnbVeGyRGkaV4YdlKkn5qZ1LfEePW3dq+ydE73hUUJJuLmGEykW3fMX8x8mNdL0XrWgotcuZjIvA==
|
||||
dependencies:
|
||||
workbox-core "7.0.0"
|
||||
|
||||
workbox-strategies@^7.0.0:
|
||||
version "7.0.0"
|
||||
resolved "https://registry.yarnpkg.com/workbox-strategies/-/workbox-strategies-7.0.0.tgz#dcba32b3f3074476019049cc490fe1a60ea73382"
|
||||
integrity sha512-dg3qJU7tR/Gcd/XXOOo7x9QoCI9nk74JopaJaYAQ+ugLi57gPsXycVdBnYbayVj34m6Y8ppPwIuecrzkpBVwbA==
|
||||
dependencies:
|
||||
workbox-core "7.0.0"
|
||||
|
||||
workbox-sw@^7.0.0:
|
||||
version "7.0.0"
|
||||
resolved "https://registry.yarnpkg.com/workbox-sw/-/workbox-sw-7.0.0.tgz#7350126411e3de1409f7ec243df8d06bb5b08b86"
|
||||
integrity sha512-SWfEouQfjRiZ7GNABzHUKUyj8pCoe+RwjfOIajcx6J5mtgKkN+t8UToHnpaJL5UVVOf5YhJh+OHhbVNIHe+LVA==
|
||||
|
||||
workerpool@^3.1.1:
|
||||
version "3.1.2"
|
||||
resolved "https://registry.yarnpkg.com/workerpool/-/workerpool-3.1.2.tgz#b34e79243647decb174b7481ab5b351dc565c426"
|
||||
|
@ -12520,7 +12460,7 @@ workerpool@^6.0.0, workerpool@^6.0.2, workerpool@^6.4.0:
|
|||
resolved "https://registry.yarnpkg.com/workerpool/-/workerpool-6.4.1.tgz#1398eb5f8f44fb2d21ed9225cf34bb0131504c1d"
|
||||
integrity sha512-zIK7qRgM1Mk+ySxOJl7ZpjX6SlKt5gugxzl8eXHPdbpXX8iDAaVIxYJz4Apn6JdDxP2buY/Ekqg0bOLNSf0u0g==
|
||||
|
||||
"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0":
|
||||
"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0", wrap-ansi@^7.0.0:
|
||||
version "7.0.0"
|
||||
resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43"
|
||||
integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==
|
||||
|
@ -12538,15 +12478,6 @@ wrap-ansi@^6.0.1:
|
|||
string-width "^4.1.0"
|
||||
strip-ansi "^6.0.0"
|
||||
|
||||
wrap-ansi@^7.0.0:
|
||||
version "7.0.0"
|
||||
resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43"
|
||||
integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==
|
||||
dependencies:
|
||||
ansi-styles "^4.0.0"
|
||||
string-width "^4.1.0"
|
||||
strip-ansi "^6.0.0"
|
||||
|
||||
wrap-ansi@^8.1.0:
|
||||
version "8.1.0"
|
||||
resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-8.1.0.tgz#56dc22368ee570face1b49819975d9b9a5ead214"
|
||||
|
|
Loading…
Reference in New Issue
Block a user