@abyss-project/commons-front-core 13.2.0 → 13.3.0

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.
@@ -111,7 +111,6 @@ declare const _default: {
111
111
  LINK_RESOLUTIONS_MONTH: string;
112
112
  MONITOR_WEBHOOK_INVOCATIONS_MONTH: string;
113
113
  FLOW_WORKFLOW_CREDITS_MONTH: string;
114
- REFLEX_EXECUTIONS_MONTH: string;
115
114
  CONSOLE_VAULT_ENTRIES_COUNT: string;
116
115
  RUM_SESSIONS_MONTH: string;
117
116
  REHYDRATION_CONCURRENT_JOBS: string;
@@ -111,7 +111,6 @@ declare const _default: {
111
111
  LINK_RESOLUTIONS_MONTH: string;
112
112
  MONITOR_WEBHOOK_INVOCATIONS_MONTH: string;
113
113
  FLOW_WORKFLOW_CREDITS_MONTH: string;
114
- REFLEX_EXECUTIONS_MONTH: string;
115
114
  CONSOLE_VAULT_ENTRIES_COUNT: string;
116
115
  RUM_SESSIONS_MONTH: string;
117
116
  REHYDRATION_CONCURRENT_JOBS: string;
@@ -8,4 +8,14 @@ import { QuotaDialogPayload } from '../hooks/quota-dialog.store';
8
8
  * returns the offending metric.
9
9
  */
10
10
  export declare const parseQuotaError: (error: unknown) => QuotaDialogPayload | null;
11
- export declare const attachQuotaInterceptor: (instances: AxiosInstance[]) => void;
11
+ export interface QuotaInterceptorOptions {
12
+ /**
13
+ * Tells whether a location is a public route (a page a visitor opens without being the account
14
+ * owner). Evaluated when the request leaves, never when the response arrives: a request that left
15
+ * from a public route never opens the quota dialog, even if the route changed in between. A
16
+ * request that left from a private route keeps opening it, even when its response arrives on a
17
+ * public page, because the quota that was hit is the visitor's own.
18
+ */
19
+ isPublicRoute?: (location: Pick<Location, 'pathname'>) => boolean;
20
+ }
21
+ export declare const attachQuotaInterceptor: (instances: AxiosInstance[], options?: QuotaInterceptorOptions) => void;
@@ -8,6 +8,7 @@ export * from './share-notification.utils';
8
8
  export * from './space-display-name.utils';
9
9
  export * from './cloud-space-url.utils';
10
10
  export * from './billing-quota-error.utils';
11
+ export * from './quota-refusal-toast.utils';
11
12
  export * from './cookie-storage.utils';
12
13
  export * from './cookie-consent-reconcile.utils';
13
14
  export * from './cookie-consent.types';
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Shows an error toast unless the error is a quota refusal: that one already opens the shared quota
3
+ * dialog (attachQuotaInterceptor), and a generic toast on top of it would contradict it and hide its
4
+ * call to action on a phone.
5
+ */
6
+ export declare const toastErrorUnlessQuotaRefusal: (error: unknown, message: string) => void;
7
+ /**
8
+ * Same as `toastErrorUnlessQuotaRefusal` for a batch of calls (an upload of many files, a bulk
9
+ * action): the quota dialog opens as soon as ONE of them is refused, so no toast follows when any
10
+ * error of the batch is a quota refusal, whatever the order in which the errors arrived.
11
+ */
12
+ export declare const toastErrorUnlessAnyQuotaRefusal: (errors: readonly unknown[], message: string) => void;
@@ -0,0 +1 @@
1
+ export {};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@abyss-project/commons-front-core",
3
- "version": "13.2.0",
3
+ "version": "13.3.0",
4
4
  "description": "Front Core package of AbyssProject",
5
5
  "main": "dist/index.js",
6
6
  "module": "./dist/index.js",
@@ -86,6 +86,7 @@
86
86
  "react-intl": "^6.2.5",
87
87
  "react-is": "^18.0.0 || ^19.0.0",
88
88
  "react-router-dom": "^6.15.0",
89
+ "react-toastify": "^8.0.2 || ^10.0.0",
89
90
  "zustand": "^5.0.0",
90
91
  "@abyss-project/main": "27.7.0"
91
92
  },
@@ -135,7 +136,6 @@
135
136
  "react-dropzone": "^14.3.8",
136
137
  "react-error-boundary": "^6.1.1",
137
138
  "react-router-dom": "^6.15.0",
138
- "react-toastify": "^8.0.2",
139
139
  "use-debounce": "^10.0.5",
140
140
  "uuid": "^9.0.0",
141
141
  "yup": "^0.32.11",
@@ -171,6 +171,7 @@
171
171
  "eslint-plugin-react-hooks": "^4.6.2",
172
172
  "jsdom": "^29.1.1",
173
173
  "prettier": "^2.5.1",
174
+ "react-toastify": "^8.0.2",
174
175
  "tsc-watch": "^4.6.2",
175
176
  "typescript": "5.9.3",
176
177
  "vite": "^6.3.5",
@@ -182,7 +183,7 @@
182
183
  "lint": "eslint \"{src,apps,libs,test,core,catalog}/**/*.{ts,tsx}\" --fix",
183
184
  "lint:check": "eslint \"{src,apps,libs,test,core,catalog}/**/*.{ts,tsx}\" --max-warnings 0",
184
185
  "tsc": "tsc --noEmit && tsc --noEmit -p catalog/tsconfig.json",
185
- "build": "vite build && node scripts/check-auth-entry-isolation.cjs",
186
+ "build": "vite build && node scripts/check-auth-entry-isolation.cjs && node scripts/check-external-react-toastify.cjs",
186
187
  "dev": "vite build --watch",
187
188
  "test": "vitest run --config vitest.config.ts",
188
189
  "test:watch": "vitest --config vitest.config.ts",
@@ -0,0 +1,104 @@
1
+ #!/usr/bin/env node
2
+ /* eslint-disable no-console */
3
+ 'use strict';
4
+
5
+ /**
6
+ * check-external-react-toastify.cjs - build guard for the `react-toastify` peer dependency (#766).
7
+ *
8
+ * `react-toastify` is a peerDependency of the package and is declared external in `vite.config.ts`.
9
+ * Bundled instead, the toasts emitted from the core (the quota helpers, the legacy DropZone,
10
+ * CloudImport, ProjectLinkSelector and SupportMessageThread) would go to a private copy of the
11
+ * library that no `ToastContainer` listens to, and be lost silently.
12
+ *
13
+ * This walks `dist/index.js` and `dist/legacy.js` plus every chunk they import by a relative path,
14
+ * and fails if, for either entry:
15
+ * - no reachable file imports the bare specifier "react-toastify";
16
+ * - any reachable file carries the internal code of the library (markers below).
17
+ *
18
+ * Exit 0: external. Exit 1: bundled or not imported. Exit 2: usage or tooling error (dist absent -
19
+ * build the package first). Run from the package's own `build` script, after `vite build`.
20
+ */
21
+
22
+ const fs = require('fs');
23
+ const path = require('path');
24
+
25
+ const DIST_DIR = path.resolve(__dirname, '../dist');
26
+ const ENTRIES = ['index.js', 'legacy.js'];
27
+
28
+ // Internal symbols of react-toastify that only exist when its code is bundled.
29
+ const BUNDLED_MARKERS = ['useToastContainer', 'eventManager'];
30
+
31
+ const BARE_IMPORT = /(?:import|export)\s[^'"]*?from\s*["']react-toastify["']|import\s*["']react-toastify["']/;
32
+
33
+ class UsageError extends Error {}
34
+
35
+ function localImportSpecifiers(source) {
36
+ const specifiers = new Set();
37
+ const re = /(?:import|export)\s[^'"]*?from\s*["']([^"']+)["']|import\s*["']([^"']+)["']/g;
38
+ let match = re.exec(source);
39
+ while (match !== null) {
40
+ const specifier = match[1] || match[2];
41
+ if (specifier.startsWith('.')) {
42
+ specifiers.add(specifier);
43
+ }
44
+ match = re.exec(source);
45
+ }
46
+ return specifiers;
47
+ }
48
+
49
+ function walk(entry) {
50
+ const files = new Map();
51
+ const queue = [entry];
52
+ while (queue.length > 0) {
53
+ const file = queue.shift();
54
+ if (files.has(file)) {
55
+ continue;
56
+ }
57
+ if (!fs.existsSync(file)) {
58
+ throw new UsageError(`${file} is missing: build the package first (\`vite build\`).`);
59
+ }
60
+ const source = fs.readFileSync(file, 'utf8');
61
+ files.set(file, source);
62
+ for (const specifier of localImportSpecifiers(source)) {
63
+ queue.push(path.resolve(path.dirname(file), specifier));
64
+ }
65
+ }
66
+ return files;
67
+ }
68
+
69
+ function run() {
70
+ const failures = [];
71
+ for (const name of ENTRIES) {
72
+ const files = walk(path.join(DIST_DIR, name));
73
+ const sources = [...files.entries()];
74
+ if (!sources.some(([, source]) => BARE_IMPORT.test(source))) {
75
+ failures.push(`${name} does not import "react-toastify"`);
76
+ }
77
+ for (const [file, source] of sources) {
78
+ for (const marker of BUNDLED_MARKERS) {
79
+ if (source.includes(marker)) {
80
+ failures.push(
81
+ `${name}: ${path.relative(DIST_DIR, file)} embeds react-toastify code ("${marker}")`,
82
+ );
83
+ }
84
+ }
85
+ }
86
+ }
87
+ if (failures.length > 0) {
88
+ console.error('REACT-TOASTIFY EXTERNAL REFUSE :');
89
+ for (const failure of failures) console.error(` - ${failure}`);
90
+ return 1;
91
+ }
92
+ console.log('REACT-TOASTIFY EXTERNAL OK : imported by index.js and legacy.js, not bundled.');
93
+ return 0;
94
+ }
95
+
96
+ try {
97
+ process.exit(run());
98
+ } catch (error) {
99
+ if (error instanceof UsageError) {
100
+ console.error(error.message);
101
+ process.exit(2);
102
+ }
103
+ throw error;
104
+ }
package/vite.config.ts CHANGED
@@ -93,7 +93,7 @@ export default defineConfig({
93
93
  // 'react' silently externalizes 'react-is', 'react-fast-compare', etc.
94
94
  external: (id) => {
95
95
  const exactNames = [
96
- 'react', 'react-dom', 'react-intl', 'react-router-dom',
96
+ 'react', 'react-dom', 'react-intl', 'react-router-dom', 'react-toastify',
97
97
  'pdfjs-dist', 'pdf-lib',
98
98
  // Monaco: one copy per app, resolved by the consumer (like pdf.js).
99
99
  '@monaco-editor/react',