@corva/dc-platform-shared 0.0.0-d06d228 → 0.0.0-d3af965

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1 +1 @@
1
- {"name":"@corva/dc-platform-shared","version":"0.0.0-d06d228","description":"A library to share internal DC platform specific code","main":"./dist","module":"./src/index.js","repository":{"type":"git","url":"https://github.com/corva-ai/corva-dc-platform-shared"},"author":"Oleksandr Krupko <oleksandr.krupko@corva.ai>","license":"MIT","//sass-alias":"sass-embedded installed under the name 'sass' (see dependencies) because rollup-plugin-postcss@4.0.2 hard-requires require('sass') with no override hook (see rollup.config.js). Drop this alias when rollup-plugin-postcss is replaced.","scripts":{"helper-cli":"npx @corva/fe-dev-helper-cli@latest","lint":"eslint --cache ./src/","lint-staged":"lint-staged","build":"./scripts/build.sh production","build-watch":"./scripts/build.sh development -w","get-changelog":"conventional-changelog -r 2 -p angular","release":"git fetch --tags && git add -A && standard-version -a","generate-css-themes":"echo 'skip: css themes generated by @corva/ui build'","start":"yarn generate-css-themes && cross-env NODE_ENV=local rollup -c -w","test":"jest","test-coverage":"jest --watchAll --coverage && http-serve coverage","test-watch":"jest --watch"},"lint-staged":{"*.js":["eslint --fix"]},"standard-version":{"skip":{"tag":true}},"jest":{"collectCoverageFrom":["./**/*.{js,jsx,mjs}"],"coverageReporters":["html","text-summary"],"moduleNameMapper":{"~(.*)":"<rootDir>/src/$1","\\.(css|less|scss|sass)$":"identity-obj-proxy"},"setupFiles":["<rootDir>/jest/setEnvVars.js"],"testMatch":["**/__tests__/**/?(*.)+(spec|test).[jt]s?(x)"],"transform":{"^.+\\.jsx?$":"babel-jest","^.+\\.mjs$":"babel-jest","^.+\\.svg$":"<rootDir>/jest/svgTransform.js"},"transformIgnorePatterns":["/node_modules/(?!@corva/ui|@icon-park/react)"],"moduleFileExtensions":["js","jsx","json","mjs","node"],"testEnvironment":"jsdom"},"dependencies":{"@babel/core":"^7.12.10","@babel/plugin-transform-react-jsx":"^7.12.11","@babel/plugin-transform-runtime":"^7.12.10","@babel/preset-env":"^7.12.11","@babel/preset-react":"^7.12.10","@corva/eslint-config-browser":"^0.0.4","@date-io/moment":"1.3.13","@icon-park/react":"1.4.2","@material-ui/core":"4.11.2","@material-ui/icons":"4.9.1","@material-ui/lab":"4.0.0-alpha.57","@material-ui/pickers":"3.2.10","@pmmmwh/react-refresh-webpack-plugin":"0.5.10","@vitejs/plugin-react":"6.1.1","@vitest/coverage-v8":"4.1.11","babel-eslint":"10.0.3","babel-jest":"^30.2.0","babel-loader":"^10.0.0","babel-plugin-file-loader":"^2.0.0","babel-plugin-root-import":"^6.5.0","chalk":"4.1.1","classnames":"2.2.6","clean-webpack-plugin":"3.0.0","copy-webpack-plugin":"^13.0.1","css-loader":"^7.1.0","dotenv":"16.4.7","dotenv-expand":"11.0.7","eslint-config-airbnb":"17.1.0","eslint-plugin-jest-dom":"3.9.4","eslint-plugin-prettier":"3.1.1","eslint-plugin-testing-library":"^3.9.2","express":"4.21.2","fs":"0.0.1-security","html-webpack-plugin":"^5.6.4","identity-obj-proxy":"^3.0.0","jest":"^30.2.0","jest-environment-jsdom":"^30.2.0","jsdom":"22.1.0","jsona":"1.5.1","lodash":"4.18.1","moment":"2.29.4","node-fetch":"^2.6.1","path":"0.12.7","postcss":"^8.5.26","postcss-import":"12.0.1","postcss-loader":"^8.1.0","postcss-preset-env":"^11.3.0","prettier":"^2.1.2","process":"^0.11.10","prop-types":"15.7.2","react":"18.3.1","react-dom":"18.3.1","react-grid-layout":"1.3.4","react-router-dom":"^5.3.4","react-sizeme":"2.5.2","rollup-plugin-copy":"^3.4.0","rollup-plugin-license":"3.6.0","sass":"npm:sass-embedded@^1.99.0","sass-loader":"^16.0.5","semver":"7.5.2","style-loader":"^4.0.0","svg-url-loader":"^8.1.0","terser-webpack-plugin":"^5.3.14","uuid":"3.4.0","vite":"8.3.0","vite-plugin-static-copy":"4.1.0","vitest":"4.1.11","webpack":"5.94.0","webpack-cli":"5.1.4","webpack-dev-server":"^5.2.2","webpack-merge":"5.9.0"},"peerDependencies":{"@corva/ui":"*"},"devDependencies":{"@corva/ui":"3.78.0-2","@rollup/plugin-babel":"^5.2.1","@rollup/plugin-commonjs":"^15.0.0","@rollup/plugin-image":"^2.0.6","@rollup/plugin-json":"^4.1.0","@rollup/plugin-node-resolve":"^9.0.0","@testing-library/jest-dom":"5.16.5","@testing-library/react":"^14.0.0","babel-plugin-external-helpers":"^6.22.0","conventional-changelog-cli":"^2.1.0","cross-env":"^7.0.3","git-conventional-commits":"^1.1.0","lint-staged":"^10.5.4","postcss-url":"9.0.0","pre-commit":"1.2.2","rollup":"^2.28.2","rollup-plugin-cleanup":"^3.2.1","rollup-plugin-css-bundle":"^1.0.4","rollup-plugin-css-only":"^2.1.0","rollup-plugin-node-externals":"^2.2.0","rollup-plugin-postcss":"^4.0.2","rollup-plugin-rename-node-modules":"^1.0.1","rollup-plugin-terser":"^7.0.2","rollup-plugin-visualizer":"^4.1.1","standard-version":"^9.0.0"},"engines":{"node":">=24.15.0"},"resolutions":{"braces":"3.0.3","@babel/runtime":"^7.18.0","lodash":"4.18.1"}}
1
+ {"name":"@corva/dc-platform-shared","version":"0.0.0-d3af965","description":"A library to share internal DC platform specific code","main":"./dist","module":"./src/index.js","repository":{"type":"git","url":"https://github.com/corva-ai/corva-dc-platform-shared"},"author":"Oleksandr Krupko <oleksandr.krupko@corva.ai>","license":"MIT","//sass-alias":"sass-embedded installed under the name 'sass' (see dependencies) because rollup-plugin-postcss@4.0.2 hard-requires require('sass') with no override hook (see rollup.config.js). Drop this alias when rollup-plugin-postcss is replaced.","scripts":{"helper-cli":"npx @corva/fe-dev-helper-cli@latest","lint":"eslint --cache ./src/","lint-staged":"lint-staged","build":"./scripts/build.sh production","build-watch":"./scripts/build.sh development -w","get-changelog":"conventional-changelog -r 2 -p angular","release":"git fetch --tags && git add -A && standard-version -a","generate-css-themes":"echo 'skip: css themes generated by @corva/ui build'","start":"yarn generate-css-themes && cross-env NODE_ENV=local rollup -c -w","test":"jest","test-coverage":"jest --watchAll --coverage && http-serve coverage","test-watch":"jest --watch"},"lint-staged":{"*.js":["eslint --fix"]},"standard-version":{"skip":{"tag":true}},"jest":{"collectCoverageFrom":["./**/*.{js,jsx,mjs}"],"coverageReporters":["html","text-summary"],"moduleNameMapper":{"~(.*)":"<rootDir>/src/$1","\\.(css|less|scss|sass)$":"identity-obj-proxy"},"setupFiles":["<rootDir>/jest/setEnvVars.js"],"testMatch":["**/__tests__/**/?(*.)+(spec|test).[jt]s?(x)"],"transform":{"^.+\\.jsx?$":"babel-jest","^.+\\.mjs$":"babel-jest","^.+\\.svg$":"<rootDir>/jest/svgTransform.js"},"transformIgnorePatterns":["/node_modules/(?!@corva/ui|@icon-park/react)"],"moduleFileExtensions":["js","jsx","json","mjs","node"],"testEnvironment":"jsdom"},"dependencies":{"@babel/core":"^7.12.10","@babel/plugin-transform-react-jsx":"^7.12.11","@babel/plugin-transform-runtime":"^7.12.10","@babel/preset-env":"^7.12.11","@babel/preset-react":"^7.12.10","@corva/eslint-config-browser":"^0.0.4","@date-io/moment":"1.3.13","@icon-park/react":"1.4.2","@material-ui/core":"4.11.2","@material-ui/icons":"4.9.1","@material-ui/lab":"4.0.0-alpha.57","@material-ui/pickers":"3.2.10","@pmmmwh/react-refresh-webpack-plugin":"0.5.10","@vitejs/plugin-react":"6.1.1","@vitest/coverage-v8":"4.1.11","babel-eslint":"10.0.3","babel-jest":"^30.2.0","babel-loader":"^10.0.0","babel-plugin-file-loader":"^2.0.0","babel-plugin-root-import":"^6.5.0","chalk":"4.1.1","classnames":"2.2.6","clean-webpack-plugin":"3.0.0","copy-webpack-plugin":"^13.0.1","css-loader":"^7.1.0","dotenv":"16.4.7","dotenv-expand":"11.0.7","eslint-config-airbnb":"17.1.0","eslint-plugin-jest-dom":"3.9.4","eslint-plugin-prettier":"3.1.1","eslint-plugin-testing-library":"^3.9.2","express":"4.21.2","fs":"0.0.1-security","html-webpack-plugin":"^5.6.4","identity-obj-proxy":"^3.0.0","jest":"^30.2.0","jest-environment-jsdom":"^30.2.0","jsdom":"22.1.0","jsona":"1.5.1","lodash":"4.18.1","moment":"2.29.4","node-fetch":"^2.6.1","path":"0.12.7","postcss":"^8.5.26","postcss-import":"12.0.1","postcss-loader":"^8.1.0","postcss-preset-env":"^11.3.0","prettier":"^2.1.2","process":"^0.11.10","prop-types":"15.7.2","react":"18.3.1","react-dom":"18.3.1","react-grid-layout":"1.3.4","react-router-dom":"^5.3.4","react-sizeme":"2.5.2","rollup-plugin-copy":"^3.4.0","rollup-plugin-license":"3.6.0","sass":"npm:sass-embedded@^1.99.0","sass-loader":"^16.0.5","semver":"7.5.2","style-loader":"^4.0.0","svg-url-loader":"^8.1.0","terser-webpack-plugin":"^5.3.14","uuid":"3.4.0","vite":"8.3.0","vite-plugin-static-copy":"4.1.0","vitest":"4.1.11","webpack":"5.94.0","webpack-cli":"5.1.4","webpack-dev-server":"^5.2.2","webpack-merge":"5.9.0"},"peerDependencies":{"@corva/ui":"*"},"devDependencies":{"@corva/ui":"3.78.0-2","@rollup/plugin-babel":"^5.2.1","@rollup/plugin-commonjs":"^15.0.0","@rollup/plugin-image":"^2.0.6","@rollup/plugin-json":"^4.1.0","@rollup/plugin-node-resolve":"^9.0.0","@testing-library/jest-dom":"5.16.5","@testing-library/react":"^14.0.0","babel-plugin-external-helpers":"^6.22.0","conventional-changelog-cli":"^2.1.0","cross-env":"^7.0.3","git-conventional-commits":"^1.1.0","lint-staged":"^10.5.4","postcss-url":"9.0.0","pre-commit":"1.2.2","rollup":"^2.28.2","rollup-plugin-cleanup":"^3.2.1","rollup-plugin-css-bundle":"^1.0.4","rollup-plugin-css-only":"^2.1.0","rollup-plugin-node-externals":"^2.2.0","rollup-plugin-postcss":"^4.0.2","rollup-plugin-rename-node-modules":"^1.0.1","rollup-plugin-terser":"^7.0.2","rollup-plugin-visualizer":"^4.1.1","standard-version":"^9.0.0"},"engines":{"node":">=24.15.0"},"resolutions":{"braces":"3.0.3","@babel/runtime":"^7.18.0","lodash":"4.18.1"}}
@@ -4,7 +4,7 @@
4
4
  import { randomUUID } from 'node:crypto';
5
5
  import { builtinModules, createRequire } from 'node:module';
6
6
  import { existsSync, readdirSync, readFileSync, statSync, unlinkSync, writeFileSync } from 'node:fs';
7
- import { extname, join, relative, resolve } from 'node:path';
7
+ import { join, relative, resolve } from 'node:path';
8
8
 
9
9
  import dotenv from 'dotenv';
10
10
  import { expand } from 'dotenv-expand';
@@ -14,6 +14,8 @@ import { viteStaticCopy } from 'vite-plugin-static-copy';
14
14
  import presetEnv from 'postcss-preset-env';
15
15
  import { defineConfig, mergeConfig } from 'vitest/config';
16
16
 
17
+ import { rejectUnscopedCssPlugin } from './unscoped-css.mjs';
18
+
17
19
  const require = createRequire(import.meta.url);
18
20
 
19
21
  const {
@@ -145,9 +147,29 @@ const finalizeJsBundle = ({ outDir, cssFileNames, appIdentifier, emittedGlobalNa
145
147
  // inlined only SVGs under svg-url-loader's 10 KB limit. Vite's library mode inlines every asset
146
148
  // whatever `assetsInlineLimit` says — `?no-inline` is the one thing it still honours, so the
147
149
  // same split is restored by tagging the id.
148
- const EMITTED_ASSET_RE = /\.(png|jpe?g|gif|webp|avif|woff2?|eot|ttf|otf|mp4|webm|ogg|mp3|wav|glb|gltf)$/i;
150
+ const EMITTED_ASSET_EXTENSIONS =
151
+ 'png|jpe?g|gif|webp|avif|woff2?|eot|ttf|otf|mp4|webm|ogg|mp3|wav|glb|gltf|wasm';
152
+ const EMITTED_ASSET_RE = new RegExp(`\\.(${EMITTED_ASSET_EXTENSIONS})$`, 'i');
153
+ // `new URL('./x.wasm', import.meta.url)` names an asset without importing it, so the id never
154
+ // reaches resolveId; the tag has to be written into the literal before vite:asset reads it.
155
+ const NEW_URL_ASSET_RE = new RegExp(
156
+ `(new\\s+URL\\(\\s*)(['"])([^'"?]+\\.(?:${EMITTED_ASSET_EXTENSIONS}))(\\2\\s*,\\s*import\\.meta\\.url)`,
157
+ 'gi',
158
+ );
149
159
  const SVG_INLINE_LIMIT = 10000;
150
160
 
161
+ const tagNewUrlAssetsPlugin = () => ({
162
+ name: 'corva-tag-new-url-assets',
163
+ enforce: 'pre',
164
+ transform(code) {
165
+ if (!code.includes('import.meta.url')) return null;
166
+
167
+ const tagged = code.replace(NEW_URL_ASSET_RE, '$1$2$3?no-inline$4');
168
+
169
+ return tagged === code ? null : { code: tagged, map: null };
170
+ },
171
+ });
172
+
151
173
  const emitAssetsAsFilesPlugin = () => {
152
174
  const resolving = new Set();
153
175
 
@@ -188,64 +210,6 @@ const emitAssetsAsFilesPlugin = () => {
188
210
  };
189
211
  };
190
212
 
191
- // Every DC app on a dashboard renders into the same document, which is why webpack's css-loader
192
- // ran with `modules` on for every stylesheet: nothing an app shipped could leak onto its
193
- // neighbours. Vite decides by filename, so a plain `.css` would quietly become global. Refuse it
194
- // instead, and give the genuinely global ones one spelling: `*.globals.css`. That extension stays
195
- // a plain stylesheet, which is what keeps it in the bundle — a `.module` file imported for its
196
- // side effect alone is treeshaken away, with its scoped rules as well as its `:global()` ones.
197
- const UNSCOPED_CSS_RE = /(?<!\.module)(?<!\.globals)\.(css|scss|sass)(\?|$)/;
198
- const STYLESHEET_IMPORTER_RE = /\.(css|scss|sass)(\?|$)/;
199
-
200
- const rejectUnscopedCssPlugin = pwd => {
201
- const resolving = new Set();
202
-
203
- return {
204
- name: 'corva-reject-unscoped-css',
205
- enforce: 'pre',
206
- async resolveId(source, importer, options) {
207
- // no importer: an entry. Stylesheet importer: a sass/postcss partial, which is not a module
208
- // in either build system.
209
- if (!importer || !UNSCOPED_CSS_RE.test(source) || STYLESHEET_IMPORTER_RE.test(importer)) {
210
- return null;
211
- }
212
-
213
- const key = `${importer}\u0000${source}`;
214
-
215
- if (resolving.has(key)) {
216
- return null;
217
- }
218
-
219
- resolving.add(key);
220
-
221
- let resolved;
222
-
223
- try {
224
- resolved = await this.resolve(source, importer, options);
225
- } finally {
226
- resolving.delete(key);
227
- }
228
-
229
- // a dependency's own stylesheet is its business, and it was never scoped to this app
230
- if (!resolved || resolved.external || resolved.id.includes('node_modules')) {
231
- return null;
232
- }
233
-
234
- throw new Error(
235
- `${relative(pwd, resolved.id)} is imported as a plain stylesheet by ${relative(pwd, importer)}.\n` +
236
- 'Please use *.module.css — global styles may leak into other platform elements, and ' +
237
- 'that surfaces later as weird bugs nobody traces back to this file.\n' +
238
- 'When the styles genuinely have to be global — markup this app does not render, or class ' +
239
- 'names another module reaches with querySelector — rename the file to ' +
240
- `*.globals${extname(resolved.id)} and keep importing it for its side effect.\n` +
241
- 'Do not reach for a .module file with :global() instead: a module stylesheet imported ' +
242
- 'for its side effect alone is treeshaken out of the bundle, styles and all, and the ' +
243
- 'build stays green.',
244
- );
245
- },
246
- };
247
- };
248
-
249
213
  const getCorvaApiEnvVariables = () => {
250
214
  const apiEnv = process.env.CORVA_API_ENV || CORVA_API_ENVS.production;
251
215
 
@@ -325,7 +289,16 @@ const show = error => {
325
289
  );
326
290
  };
327
291
 
328
- window.addEventListener('error', event => show(event.error ?? event.message));
292
+ // Browsers fire this when a ResizeObserver callback schedules another layout pass. It is a
293
+ // notice, not a failure, and webpack-dev-server's overlay filtered it out too; showing it puts a
294
+ // full-screen overlay over a working app.
295
+ const isBenign = error => /ResizeObserver loop/.test(error?.message ?? String(error ?? ''));
296
+
297
+ window.addEventListener('error', event => {
298
+ const error = event.error ?? event.message;
299
+
300
+ if (!isBenign(error)) show(error);
301
+ });
329
302
 
330
303
  window.addEventListener('unhandledrejection', event => {
331
304
  const authRecovery = AuthRecovery.getInstance();
@@ -335,7 +308,7 @@ window.addEventListener('unhandledrejection', event => {
335
308
  return;
336
309
  }
337
310
 
338
- show(event.reason);
311
+ if (!isBenign(event.reason)) show(event.reason);
339
312
  });
340
313
  `;
341
314
 
@@ -513,12 +486,19 @@ const scanAppImports = pwd => {
513
486
  // ("optimized dependencies changed. reloading"). Listing them up front makes the first load one
514
487
  // pass instead of two — 4.6 s to a rendered app becomes 2.0 s on a 169-file app, cold cache.
515
488
  // Interop does not matter here the way it does for tests, so this list is everything importable.
489
+ // This package's own CJS entry is `getWebpackConfig`, a Node-side build tool. An app that keeps a
490
+ // webpack config under `src/` (dashflow's embedded submodule) pulls it into the specifier scan, and
491
+ // pre-bundling it drags webpack and vite in behind it — vite then reads `lightningcss/package.json`,
492
+ // which lightningcss does not export, and the optimizer dies before the server or the first test.
493
+ const isOwnBuildTool = specifier => specifier.startsWith('@corva/dc-platform-shared');
494
+
516
495
  const getDevOptimizeDeps = (pwd, { specifiers }) => {
517
496
  const appRequire = createRequire(join(pwd, 'package.json'));
518
497
 
519
498
  return specifiers
520
499
  .filter(specifier => /^@?[\w.-]/.test(specifier) && !specifier.startsWith('@/'))
521
500
  .filter(specifier => !builtinModules.includes(specifier.replace(/^node:/, '')))
501
+ .filter(specifier => !isOwnBuildTool(specifier))
522
502
  .filter(specifier => hasEntryFile(pwd, appRequire, specifier))
523
503
  .sort();
524
504
  };
@@ -530,6 +510,7 @@ const getTestOptimizeDeps = (pwd, { specifiers, spied }) => {
530
510
  .filter(specifier => /^@?[\w.-]/.test(specifier) && !specifier.startsWith('@/'))
531
511
  .filter(specifier => !builtinModules.includes(specifier.replace(/^node:/, '')))
532
512
  .filter(specifier => !TEST_EXTERNAL_DEP_RE.test(specifier))
513
+ .filter(specifier => !isOwnBuildTool(specifier))
533
514
  .filter(specifier => !spied.has(specifier))
534
515
  .filter(specifier => isEsmPackage(pwd, specifier) && hasEntryFile(pwd, appRequire, specifier))
535
516
  .sort();
@@ -648,6 +629,9 @@ export const createViteConfig = ({ pwd, overrides = {} }) => {
648
629
  return defaultAlias;
649
630
  }
650
631
 
632
+ // tsc resolves every `paths` value against `baseUrl`, not against the tsconfig's own directory
633
+ const pathsRoot = resolve(pwd, compilerOptions?.baseUrl || '.');
634
+
651
635
  return {
652
636
  ...defaultAlias,
653
637
  ...Object.fromEntries(
@@ -660,8 +644,8 @@ export const createViteConfig = ({ pwd, overrides = {} }) => {
660
644
  key.endsWith('/*')
661
645
  // keep the `/*` boundary: a bare `@` prefix-matches @types/react, @corva/ui and
662
646
  // every other scoped package and rewrites them into src/
663
- ? [`${key.slice(0, -1)}`, `${resolve(pwd, value.replace('/*', ''))}/`]
664
- : [key, resolve(pwd, value)]
647
+ ? [`${key.slice(0, -1)}`, `${resolve(pathsRoot, value.replace('/*', ''))}/`]
648
+ : [key, resolve(pathsRoot, value)]
665
649
  )),
666
650
  ),
667
651
  };
@@ -802,9 +786,14 @@ export const createViteConfig = ({ pwd, overrides = {} }) => {
802
786
  'process.env': { NODE_ENV: 'production' },
803
787
  'process.env.NODE_ENV': '"production"',
804
788
  global: 'globalThis',
789
+ // an iife has no import.meta, and rolldown turns every read of it into `{}` — which
790
+ // makes `new URL(asset, '' + import.meta.url)` throw on an "undefined" base. The
791
+ // bundle's own location is what webpack handed modules here.
792
+ 'import.meta.url': '__CORVA_ASSET_BASE__',
805
793
  },
806
794
  plugins: [
807
795
  ...config.plugins,
796
+ tagNewUrlAssetsPlugin(),
808
797
  emitAssetsAsFilesPlugin(),
809
798
  viteStaticCopy({ targets: [{ src: 'manifest.json', dest: '.' }] }),
810
799
  {
@@ -0,0 +1,63 @@
1
+ import { extname, relative } from 'node:path';
2
+
3
+ // Every DC app on a dashboard renders into the same document, which is why webpack's css-loader
4
+ // ran with `modules` on for every stylesheet: nothing an app shipped could leak onto its
5
+ // neighbours. Vite decides by filename, so a plain `.css` would quietly become global. Refuse it
6
+ // instead, and give the genuinely global ones one spelling: `globals.css` or `<name>.globals.css`.
7
+ // That extension stays a plain stylesheet, which is what keeps it in the bundle — a `.module` file
8
+ // imported for its side effect ALONE is treeshaken away, rules and all.
9
+ export const UNSCOPED_CSS_RE = /(?<!\.module)(?<![./]globals)\.(css|scss|sass)(\?|$)/;
10
+ export const STYLESHEET_IMPORTER_RE = /\.(css|scss|sass)(\?|$)/;
11
+
12
+ export const rejectUnscopedCssPlugin = pwd => {
13
+ const resolving = new Set();
14
+
15
+ return {
16
+ name: 'corva-reject-unscoped-css',
17
+ enforce: 'pre',
18
+ async resolveId(source, importer, options) {
19
+ // no importer: an entry. Stylesheet importer: a sass/postcss partial, which is not a module
20
+ // in either build system.
21
+ if (!importer || !UNSCOPED_CSS_RE.test(source) || STYLESHEET_IMPORTER_RE.test(importer)) {
22
+ return null;
23
+ }
24
+
25
+ const key = `${importer}\u0000${source}`;
26
+
27
+ if (resolving.has(key)) {
28
+ return null;
29
+ }
30
+
31
+ resolving.add(key);
32
+
33
+ let resolved;
34
+
35
+ try {
36
+ resolved = await this.resolve(source, importer, options);
37
+ } finally {
38
+ resolving.delete(key);
39
+ }
40
+
41
+ // a dependency's own stylesheet is its business, and it was never scoped to this app
42
+ if (!resolved || resolved.external || resolved.id.includes('node_modules')) {
43
+ return null;
44
+ }
45
+
46
+ const ext = extname(resolved.id.split('?')[0]);
47
+
48
+ throw new Error(
49
+ `${relative(pwd, resolved.id)} is imported as a plain stylesheet by ${relative(pwd, importer)}.\n` +
50
+ 'Please use *.module.css — global styles may leak into other platform elements, and ' +
51
+ 'that surfaces later as weird bugs nobody traces back to this file.\n' +
52
+ 'When a class name genuinely has to stay unscoped:\n' +
53
+ '1. If the markup is rendered by this app, keep the file a module you already import ' +
54
+ 'for its classes and nest the selector under a scoped parent: ' +
55
+ '`.chart :global(.outer-circle) { … }`. Only a module imported for its side effect ' +
56
+ 'alone is treeshaken out.\n' +
57
+ `2. For markup outside the app's root — portals, tooltips, third-party DOM — rename the ` +
58
+ `file to globals${ext} or <name>.globals${ext} and keep importing it for its side effect.\n` +
59
+ '3. In a `.globals` file, prefix every selector with an app-specific class.',
60
+ );
61
+ },
62
+ };
63
+ };