|
|
@@ -25,6 +25,20 @@ const DASHBOARD_SRC_DIR = isDashboardDir
|
|
|
const REQUIRED_PREFIX = '@/vdb';
|
|
|
// Banned import pattern
|
|
|
const BANNED_IMPORT = '@/vdb/index.js';
|
|
|
+// Lib directory (auto-exported via index.ts)
|
|
|
+const LIB_DIR = isDashboardDir
|
|
|
+ ? normalizePath(path.join(__dirname, '../src/lib'))
|
|
|
+ : normalizePath(path.join(currentDir, 'packages/dashboard/src/lib'));
|
|
|
+
|
|
|
+// Files allowed to have createContext + useContext in the same file.
|
|
|
+// These are UI primitives (e.g., shadcn components) where the context is internal
|
|
|
+// and not intended to be accessed by extensions.
|
|
|
+const CONTEXT_PATTERN_ALLOWLIST = [
|
|
|
+ 'components/ui/carousel.tsx',
|
|
|
+ 'components/ui/chart.tsx',
|
|
|
+ 'components/ui/form.tsx',
|
|
|
+ 'components/ui/toggle-group.tsx',
|
|
|
+];
|
|
|
|
|
|
function findHookFiles(dir) {
|
|
|
const files = [];
|
|
|
@@ -119,6 +133,92 @@ function checkFileForBannedImports(filePath) {
|
|
|
return badImports;
|
|
|
}
|
|
|
|
|
|
+/**
|
|
|
+ * Check for React Context module identity issues.
|
|
|
+ *
|
|
|
+ * Files in src/lib/ are auto-exported via index.ts. If a file defines a React Context
|
|
|
+ * AND also consumes it (via useContext), this causes module identity issues when
|
|
|
+ * extensions dynamically import from @vendure/dashboard - the context object in the
|
|
|
+ * extension's bundle will be different from the one in the main app's bundle.
|
|
|
+ *
|
|
|
+ * The fix is to split context definition and consumption into separate files:
|
|
|
+ * - Context definition in a dedicated file (e.g., paginated-list-context.ts)
|
|
|
+ * - Hook that consumes the context in src/lib/hooks/ (e.g., use-paginated-list.ts)
|
|
|
+ */
|
|
|
+function checkFileForContextPattern(filePath) {
|
|
|
+ const content = fs.readFileSync(filePath, 'utf8');
|
|
|
+ const issues = [];
|
|
|
+
|
|
|
+ // Check if file is in src/lib/ (auto-exported)
|
|
|
+ if (!filePath.includes(normalizePath('src/lib/'))) {
|
|
|
+ return issues;
|
|
|
+ }
|
|
|
+
|
|
|
+ // Check if file is in the allowlist
|
|
|
+ for (const allowedFile of CONTEXT_PATTERN_ALLOWLIST) {
|
|
|
+ if (filePath.includes(normalizePath(allowedFile))) {
|
|
|
+ return issues;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ const hasCreateContext = /createContext\s*[<(]/.test(content);
|
|
|
+ const hasUseContext = /useContext\s*\(/.test(content);
|
|
|
+
|
|
|
+ if (hasCreateContext && hasUseContext) {
|
|
|
+ // Find line numbers for better error reporting
|
|
|
+ const lines = content.split('\n');
|
|
|
+ let createContextLine = 0;
|
|
|
+ let useContextLine = 0;
|
|
|
+
|
|
|
+ for (let i = 0; i < lines.length; i++) {
|
|
|
+ if (/createContext\s*[<(]/.test(lines[i]) && createContextLine === 0) {
|
|
|
+ createContextLine = i + 1;
|
|
|
+ }
|
|
|
+ if (/useContext\s*\(/.test(lines[i]) && useContextLine === 0) {
|
|
|
+ useContextLine = i + 1;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ issues.push({
|
|
|
+ createContextLine,
|
|
|
+ useContextLine,
|
|
|
+ reason:
|
|
|
+ 'File defines a React Context (createContext) and also consumes it (useContext). ' +
|
|
|
+ 'This causes module identity issues when extensions dynamically import from @vendure/dashboard. ' +
|
|
|
+ 'Split into separate files: context definition in a dedicated file, hook in src/lib/hooks/',
|
|
|
+ });
|
|
|
+ }
|
|
|
+
|
|
|
+ return issues;
|
|
|
+}
|
|
|
+
|
|
|
+function findLibFiles(dir) {
|
|
|
+ const files = [];
|
|
|
+
|
|
|
+ function scanDirectory(currentDir) {
|
|
|
+ const items = fs.readdirSync(currentDir);
|
|
|
+
|
|
|
+ for (const item of items) {
|
|
|
+ const fullPath = normalizePath(path.join(currentDir, item));
|
|
|
+ const stat = fs.statSync(fullPath);
|
|
|
+
|
|
|
+ if (stat.isDirectory()) {
|
|
|
+ if (!['node_modules', '.git', 'dist', 'build'].includes(item)) {
|
|
|
+ scanDirectory(fullPath);
|
|
|
+ }
|
|
|
+ } else if (stat.isFile() && (item.endsWith('.ts') || item.endsWith('.tsx'))) {
|
|
|
+ // Skip test and story files
|
|
|
+ if (!item.endsWith('.spec.ts') && !item.endsWith('.stories.tsx')) {
|
|
|
+ files.push(fullPath);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ scanDirectory(dir);
|
|
|
+ return files;
|
|
|
+}
|
|
|
+
|
|
|
function main() {
|
|
|
console.log('🔍 Checking for import patterns in the dashboard app...\n');
|
|
|
|
|
|
@@ -206,7 +306,48 @@ function main() {
|
|
|
console.log(`🎉 All dashboard files are free of banned imports`);
|
|
|
}
|
|
|
|
|
|
- if (hasBadImports || hasBannedImports) {
|
|
|
+ // Check for React Context module identity issues in lib files
|
|
|
+ console.log('\n📁 Checking src/lib files for React Context patterns...');
|
|
|
+ console.log('✅ Context pattern requirements:');
|
|
|
+ console.log(' - Files must NOT both define (createContext) and consume (useContext) a React Context');
|
|
|
+ console.log(' - Split context definition and hooks into separate files to prevent module identity issues');
|
|
|
+ console.log('');
|
|
|
+
|
|
|
+ if (!fs.existsSync(LIB_DIR)) {
|
|
|
+ console.error('❌ src/lib directory not found!');
|
|
|
+ process.exit(1);
|
|
|
+ }
|
|
|
+
|
|
|
+ const libFiles = findLibFiles(LIB_DIR);
|
|
|
+ let hasContextIssues = false;
|
|
|
+ let totalContextIssues = 0;
|
|
|
+
|
|
|
+ for (const file of libFiles) {
|
|
|
+ const relativePath = normalizePath(path.relative(process.cwd(), file));
|
|
|
+ const contextIssues = checkFileForContextPattern(file);
|
|
|
+
|
|
|
+ if (contextIssues.length > 0) {
|
|
|
+ hasContextIssues = true;
|
|
|
+ totalContextIssues += contextIssues.length;
|
|
|
+
|
|
|
+ console.log(`❌ ${relativePath}:`);
|
|
|
+ for (const issue of contextIssues) {
|
|
|
+ console.log(` createContext at line ${issue.createContextLine}, useContext at line ${issue.useContextLine}`);
|
|
|
+ console.log(` Reason: ${issue.reason}`);
|
|
|
+ }
|
|
|
+ console.log('');
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ if (hasContextIssues) {
|
|
|
+ console.log(`❌ Found ${totalContextIssues} context pattern issue(s) in ${libFiles.length} lib file(s)`);
|
|
|
+ console.log('💡 Move context definitions to dedicated files and hooks to src/lib/hooks/');
|
|
|
+ } else {
|
|
|
+ console.log(`✅ No context pattern issues found in ${libFiles.length} lib file(s)`);
|
|
|
+ console.log('🎉 All lib files follow the correct context pattern');
|
|
|
+ }
|
|
|
+
|
|
|
+ if (hasBadImports || hasBannedImports || hasContextIssues) {
|
|
|
process.exit(1);
|
|
|
}
|
|
|
}
|