@idevconn/create-icore 0.3.1 → 0.4.1

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.
@@ -1,7 +1,12 @@
1
+ approvedGitRepositories:
2
+ - "**"
3
+
1
4
  enableGlobalCache: true
2
5
 
3
6
  enableScripts: true
4
7
 
5
8
  nodeLinker: node-modules
6
9
 
7
- yarnPath: .yarn/releases/yarn-4.5.0.cjs
10
+ npmMinimalAgeGate: 0
11
+
12
+ yarnPath: .yarn/releases/yarn-4.15.0.cjs
@@ -5,7 +5,9 @@ import { createClient } from '@supabase/supabase-js';
5
5
  import * as admin from 'firebase-admin';
6
6
  import { SupabaseAuthStrategy } from '@icore/auth-supabase';
7
7
  import { FirebaseAuthStrategy, HttpIdentityToolkitClient } from '@icore/auth-firebase';
8
+ import { FakeAuthStrategy } from '@icore/shared';
8
9
  import type { AuthStrategy } from '@icore/shared';
10
+ import { Logger } from '@nestjs/common';
9
11
  import { AuthController } from './auth.controller';
10
12
 
11
13
  function requireEnv(cfg: ConfigService, key: string): string {
@@ -47,20 +49,28 @@ function makeFirebaseStrategy(cfg: ConfigService): AuthStrategy {
47
49
  {
48
50
  provide: 'AuthStrategy',
49
51
  useFactory: (cfg: ConfigService): AuthStrategy => {
50
- const provider = requireEnv(cfg, 'AUTH_PROVIDER');
51
- switch (provider) {
52
- case 'supabase': {
53
- const client = createClient(
54
- requireEnv(cfg, 'SUPABASE_URL'),
55
- requireEnv(cfg, 'SUPABASE_SERVICE_ROLE_KEY'),
56
- { auth: { autoRefreshToken: false, persistSession: false } },
57
- );
58
- return new SupabaseAuthStrategy({ client });
52
+ try {
53
+ const provider = requireEnv(cfg, 'AUTH_PROVIDER');
54
+ switch (provider) {
55
+ case 'supabase': {
56
+ const client = createClient(
57
+ requireEnv(cfg, 'SUPABASE_URL'),
58
+ requireEnv(cfg, 'SUPABASE_SERVICE_ROLE_KEY'),
59
+ { auth: { autoRefreshToken: false, persistSession: false } },
60
+ );
61
+ return new SupabaseAuthStrategy({ client });
62
+ }
63
+ case 'firebase':
64
+ return makeFirebaseStrategy(cfg);
65
+ default:
66
+ throw new Error(`Unsupported AUTH_PROVIDER: ${provider}`);
59
67
  }
60
- case 'firebase':
61
- return makeFirebaseStrategy(cfg);
62
- default:
63
- throw new Error(`Unsupported AUTH_PROVIDER: ${provider}`);
68
+ } catch (err) {
69
+ new Logger('AuthStrategy').warn(
70
+ `Not configured: ${err instanceof Error ? err.message : String(err)}. ` +
71
+ `Requests will fail until apps/microservices/auth/.env is set.`,
72
+ );
73
+ return new FakeAuthStrategy();
64
74
  }
65
75
  },
66
76
  inject: [ConfigService],
@@ -5,7 +5,9 @@ import { createClient } from '@supabase/supabase-js';
5
5
  import * as admin from 'firebase-admin';
6
6
  import { SupabaseDBStrategy } from '@icore/db-supabase';
7
7
  import { FirestoreDBStrategy } from '@icore/db-firestore';
8
+ import { FakeDBStrategy } from '@icore/shared';
8
9
  import type { DBStrategy } from '@icore/shared';
10
+ import { Logger } from '@nestjs/common';
9
11
  import { NotesController } from './notes.controller';
10
12
 
11
13
  function requireEnv(cfg: ConfigService, key: string): string {
@@ -29,32 +31,40 @@ function requireEnv(cfg: ConfigService, key: string): string {
29
31
  {
30
32
  provide: 'DBStrategy',
31
33
  useFactory: (cfg: ConfigService): DBStrategy => {
32
- const provider = requireEnv(cfg, 'DB_PROVIDER');
33
- if (provider === 'supabase') {
34
- const client = createClient(
35
- requireEnv(cfg, 'SUPABASE_URL'),
36
- requireEnv(cfg, 'SUPABASE_SERVICE_ROLE_KEY'),
37
- { auth: { autoRefreshToken: false, persistSession: false } },
38
- );
39
- return new SupabaseDBStrategy({ client });
40
- }
41
- if (provider === 'firestore' || provider === 'firebase') {
42
- if (admin.apps.length === 0) {
43
- admin.initializeApp({
44
- credential: admin.credential.cert({
45
- projectId: requireEnv(cfg, 'FB_ADMIN_PROJECT_ID'),
46
- clientEmail: requireEnv(cfg, 'FB_ADMIN_CLIENT_EMAIL'),
47
- privateKey: requireEnv(cfg, 'FB_ADMIN_PRIVATE_KEY').replace(/\\n/g, '\n'),
48
- }),
34
+ try {
35
+ const provider = requireEnv(cfg, 'DB_PROVIDER');
36
+ if (provider === 'supabase') {
37
+ const client = createClient(
38
+ requireEnv(cfg, 'SUPABASE_URL'),
39
+ requireEnv(cfg, 'SUPABASE_SERVICE_ROLE_KEY'),
40
+ { auth: { autoRefreshToken: false, persistSession: false } },
41
+ );
42
+ return new SupabaseDBStrategy({ client });
43
+ }
44
+ if (provider === 'firestore' || provider === 'firebase') {
45
+ if (admin.apps.length === 0) {
46
+ admin.initializeApp({
47
+ credential: admin.credential.cert({
48
+ projectId: requireEnv(cfg, 'FB_ADMIN_PROJECT_ID'),
49
+ clientEmail: requireEnv(cfg, 'FB_ADMIN_CLIENT_EMAIL'),
50
+ privateKey: requireEnv(cfg, 'FB_ADMIN_PRIVATE_KEY').replace(/\\n/g, '\n'),
51
+ }),
52
+ });
53
+ }
54
+ return new FirestoreDBStrategy({
55
+ db: admin.firestore() as unknown as ConstructorParameters<
56
+ typeof FirestoreDBStrategy
57
+ >[0]['db'],
49
58
  });
50
59
  }
51
- return new FirestoreDBStrategy({
52
- db: admin.firestore() as unknown as ConstructorParameters<
53
- typeof FirestoreDBStrategy
54
- >[0]['db'],
55
- });
60
+ throw new Error(`Unsupported DB_PROVIDER: ${provider}`);
61
+ } catch (err) {
62
+ new Logger('DBStrategy').warn(
63
+ `Not configured: ${err instanceof Error ? err.message : String(err)}. ` +
64
+ `Requests will fail until apps/microservices/notes/.env is set.`,
65
+ );
66
+ return new FakeDBStrategy();
56
67
  }
57
- throw new Error(`Unsupported DB_PROVIDER: ${provider}`);
58
68
  },
59
69
  inject: [ConfigService],
60
70
  },
@@ -7,7 +7,9 @@ import { v2 as cloudinary } from 'cloudinary';
7
7
  import { SupabaseStorageStrategy } from '@icore/storage-supabase';
8
8
  import { FirebaseStorageStrategy } from '@icore/storage-firebase';
9
9
  import { CloudinaryStorageStrategy, type CloudinaryApiLike } from '@icore/storage-cloudinary';
10
+ import { FakeStorageStrategy } from '@icore/shared';
10
11
  import type { StorageStrategy } from '@icore/shared';
12
+ import { Logger } from '@nestjs/common';
11
13
  import { StorageController } from './storage.controller';
12
14
 
13
15
  function requireEnv(cfg: ConfigService, key: string): string {
@@ -95,25 +97,33 @@ function makeCloudinaryStorage(cfg: ConfigService): StorageStrategy {
95
97
  {
96
98
  provide: 'StorageStrategy',
97
99
  useFactory: (cfg: ConfigService): StorageStrategy => {
98
- const provider = requireEnv(cfg, 'STORAGE_PROVIDER');
99
- switch (provider) {
100
- case 'supabase': {
101
- const client = createClient(
102
- requireEnv(cfg, 'SUPABASE_URL'),
103
- requireEnv(cfg, 'SUPABASE_SERVICE_ROLE_KEY'),
104
- { auth: { autoRefreshToken: false, persistSession: false } },
105
- );
106
- return new SupabaseStorageStrategy({
107
- client,
108
- bucket: requireEnv(cfg, 'SUPABASE_STORAGE_BUCKET'),
109
- });
100
+ try {
101
+ const provider = requireEnv(cfg, 'STORAGE_PROVIDER');
102
+ switch (provider) {
103
+ case 'supabase': {
104
+ const client = createClient(
105
+ requireEnv(cfg, 'SUPABASE_URL'),
106
+ requireEnv(cfg, 'SUPABASE_SERVICE_ROLE_KEY'),
107
+ { auth: { autoRefreshToken: false, persistSession: false } },
108
+ );
109
+ return new SupabaseStorageStrategy({
110
+ client,
111
+ bucket: requireEnv(cfg, 'SUPABASE_STORAGE_BUCKET'),
112
+ });
113
+ }
114
+ case 'firebase':
115
+ return makeFirebaseStorage(cfg);
116
+ case 'cloudinary':
117
+ return makeCloudinaryStorage(cfg);
118
+ default:
119
+ throw new Error(`Unsupported STORAGE_PROVIDER: ${provider}`);
110
120
  }
111
- case 'firebase':
112
- return makeFirebaseStorage(cfg);
113
- case 'cloudinary':
114
- return makeCloudinaryStorage(cfg);
115
- default:
116
- throw new Error(`Unsupported STORAGE_PROVIDER: ${provider}`);
121
+ } catch (err) {
122
+ new Logger('StorageStrategy').warn(
123
+ `Not configured: ${err instanceof Error ? err.message : String(err)}. ` +
124
+ `Requests will fail until apps/microservices/upload/.env is set.`,
125
+ );
126
+ return new FakeStorageStrategy();
117
127
  }
118
128
  },
119
129
  inject: [ConfigService],
@@ -2,9 +2,16 @@
2
2
  import fs from 'node:fs';
3
3
  import { defineConfig } from 'vite';
4
4
  import react from '@vitejs/plugin-react';
5
- import { TanStackRouterVite } from '@tanstack/router-plugin/vite';
5
+ import { tanstackRouter } from '@tanstack/router-plugin/vite';
6
6
  import { nxViteTsPaths } from '@nx/vite/plugins/nx-tsconfig-paths.plugin';
7
7
  import { nxCopyAssetsPlugin } from '@nx/vite/plugins/nx-copy-assets.plugin';
8
+ import {
9
+ commonDefines,
10
+ commonManualChunks,
11
+ commonTestConfig,
12
+ injectAppVersionPlugin,
13
+ noServerModulesPlugin,
14
+ } from '@icore/vite-plugins';
8
15
 
9
16
  const rootPackageJsonPath = new URL('../../../package.json', import.meta.url);
10
17
  const rootPackageJson = JSON.parse(fs.readFileSync(rootPackageJsonPath, 'utf-8')) as {
@@ -14,11 +21,7 @@ const rootPackageJson = JSON.parse(fs.readFileSync(rootPackageJsonPath, 'utf-8')
14
21
  };
15
22
 
16
23
  function depVersion(name: string): string {
17
- return (
18
- rootPackageJson.dependencies?.[name] ??
19
- rootPackageJson.devDependencies?.[name] ??
20
- '?'
21
- );
24
+ return rootPackageJson.dependencies?.[name] ?? rootPackageJson.devDependencies?.[name] ?? '?';
22
25
  }
23
26
 
24
27
  export default defineConfig(() => ({
@@ -33,22 +36,11 @@ export default defineConfig(() => ({
33
36
  host: 'localhost',
34
37
  },
35
38
  define: {
36
- 'import.meta.env.VITE_APP_VERSION': JSON.stringify(rootPackageJson.version),
37
- // Dep versions injected at build time so routes don't need JSON imports
38
- 'import.meta.env.VITE_DEP_REACT': JSON.stringify(depVersion('react')),
39
+ ...commonDefines(rootPackageJson),
39
40
  'import.meta.env.VITE_DEP_ANTD': JSON.stringify(depVersion('antd')),
40
- 'import.meta.env.VITE_DEP_VITE': JSON.stringify(depVersion('vite')),
41
- 'import.meta.env.VITE_DEP_TANSTACK_ROUTER': JSON.stringify(
42
- depVersion('@tanstack/react-router'),
43
- ),
44
- 'import.meta.env.VITE_DEP_TANSTACK_QUERY': JSON.stringify(
45
- depVersion('@tanstack/react-query'),
46
- ),
47
- 'import.meta.env.VITE_DEP_ZUSTAND': JSON.stringify(depVersion('zustand')),
48
- 'import.meta.env.VITE_DEP_CASL': JSON.stringify(depVersion('@casl/ability')),
49
41
  },
50
42
  plugins: [
51
- TanStackRouterVite({
43
+ tanstackRouter({
52
44
  target: 'react',
53
45
  autoCodeSplitting: true,
54
46
  routeFileIgnorePattern: '(__tests__|\\.test\\.(t|j)sx?$)',
@@ -56,12 +48,8 @@ export default defineConfig(() => ({
56
48
  react(),
57
49
  nxViteTsPaths(),
58
50
  nxCopyAssetsPlugin(['*.md']),
59
- {
60
- name: 'inject-app-version-meta',
61
- transformIndexHtml(html: string) {
62
- return html.replace('%APP_VERSION%', rootPackageJson.version);
63
- },
64
- },
51
+ noServerModulesPlugin(),
52
+ injectAppVersionPlugin(rootPackageJson),
65
53
  ],
66
54
  // Uncomment this if you are using workers.
67
55
  // worker: {
@@ -76,32 +64,12 @@ export default defineConfig(() => ({
76
64
  },
77
65
  rolldownOptions: {
78
66
  output: {
79
- manualChunks: (id: string) => {
80
- if (!id.includes('node_modules')) return;
81
- if (id.includes('/react/') || id.includes('/react-dom/') || id.includes('scheduler'))
82
- return 'vendor-react';
83
- if (id.includes('@tanstack')) return 'vendor-tanstack';
84
- if (id.includes('i18next') || id.includes('react-i18next')) return 'vendor-i18n';
85
- if (id.includes('@casl')) return 'vendor-casl';
67
+ manualChunks: commonManualChunks((id) => {
86
68
  if (id.includes('/antd/') || id.includes('@ant-design') || id.includes('rc-'))
87
69
  return 'vendor-antd';
88
- if (id.includes('zustand')) return 'vendor-state';
89
- if (id.includes('@idevconn')) return 'vendor-idevconn';
90
- return 'vendor-core';
91
- },
70
+ }),
92
71
  },
93
72
  },
94
73
  },
95
- test: {
96
- name: 'client-antd',
97
- watch: false,
98
- globals: true,
99
- environment: 'jsdom',
100
- include: ['{src,tests}/**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}'],
101
- reporters: ['default'],
102
- coverage: {
103
- reportsDirectory: '../../../coverage/apps/templates/client-antd',
104
- provider: 'v8' as const,
105
- },
106
- },
74
+ test: commonTestConfig('client-antd', '../../../coverage/apps/templates/client-antd'),
107
75
  }));
@@ -2,9 +2,16 @@
2
2
  import fs from 'node:fs';
3
3
  import { defineConfig } from 'vite';
4
4
  import react from '@vitejs/plugin-react';
5
- import { TanStackRouterVite } from '@tanstack/router-plugin/vite';
5
+ import { tanstackRouter } from '@tanstack/router-plugin/vite';
6
6
  import { nxViteTsPaths } from '@nx/vite/plugins/nx-tsconfig-paths.plugin';
7
7
  import { nxCopyAssetsPlugin } from '@nx/vite/plugins/nx-copy-assets.plugin';
8
+ import {
9
+ commonDefines,
10
+ commonManualChunks,
11
+ commonTestConfig,
12
+ injectAppVersionPlugin,
13
+ noServerModulesPlugin,
14
+ } from '@icore/vite-plugins';
8
15
 
9
16
  const rootPackageJsonPath = new URL('../../../package.json', import.meta.url);
10
17
  const rootPackageJson = JSON.parse(fs.readFileSync(rootPackageJsonPath, 'utf-8')) as {
@@ -14,11 +21,7 @@ const rootPackageJson = JSON.parse(fs.readFileSync(rootPackageJsonPath, 'utf-8')
14
21
  };
15
22
 
16
23
  function depVersion(name: string): string {
17
- return (
18
- rootPackageJson.dependencies?.[name] ??
19
- rootPackageJson.devDependencies?.[name] ??
20
- '?'
21
- );
24
+ return rootPackageJson.dependencies?.[name] ?? rootPackageJson.devDependencies?.[name] ?? '?';
22
25
  }
23
26
 
24
27
  export default defineConfig(() => ({
@@ -33,22 +36,11 @@ export default defineConfig(() => ({
33
36
  host: 'localhost',
34
37
  },
35
38
  define: {
36
- 'import.meta.env.VITE_APP_VERSION': JSON.stringify(rootPackageJson.version),
37
- // Dep versions injected at build time so routes don't need JSON imports
38
- 'import.meta.env.VITE_DEP_REACT': JSON.stringify(depVersion('react')),
39
+ ...commonDefines(rootPackageJson),
39
40
  'import.meta.env.VITE_DEP_MUI': JSON.stringify(depVersion('@mui/material')),
40
- 'import.meta.env.VITE_DEP_VITE': JSON.stringify(depVersion('vite')),
41
- 'import.meta.env.VITE_DEP_TANSTACK_ROUTER': JSON.stringify(
42
- depVersion('@tanstack/react-router'),
43
- ),
44
- 'import.meta.env.VITE_DEP_TANSTACK_QUERY': JSON.stringify(
45
- depVersion('@tanstack/react-query'),
46
- ),
47
- 'import.meta.env.VITE_DEP_ZUSTAND': JSON.stringify(depVersion('zustand')),
48
- 'import.meta.env.VITE_DEP_CASL': JSON.stringify(depVersion('@casl/ability')),
49
41
  },
50
42
  plugins: [
51
- TanStackRouterVite({
43
+ tanstackRouter({
52
44
  target: 'react',
53
45
  autoCodeSplitting: true,
54
46
  routeFileIgnorePattern: '(__tests__|\\.test\\.(t|j)sx?$)',
@@ -56,12 +48,8 @@ export default defineConfig(() => ({
56
48
  react(),
57
49
  nxViteTsPaths(),
58
50
  nxCopyAssetsPlugin(['*.md']),
59
- {
60
- name: 'inject-app-version-meta',
61
- transformIndexHtml(html: string) {
62
- return html.replace('%APP_VERSION%', rootPackageJson.version);
63
- },
64
- },
51
+ noServerModulesPlugin(),
52
+ injectAppVersionPlugin(rootPackageJson),
65
53
  ],
66
54
  // Uncomment this if you are using workers.
67
55
  // worker: {
@@ -76,32 +64,12 @@ export default defineConfig(() => ({
76
64
  },
77
65
  rolldownOptions: {
78
66
  output: {
79
- manualChunks: (id: string) => {
80
- if (!id.includes('node_modules')) return;
81
- if (id.includes('/react/') || id.includes('/react-dom/') || id.includes('scheduler'))
82
- return 'vendor-react';
83
- if (id.includes('@tanstack')) return 'vendor-tanstack';
84
- if (id.includes('i18next') || id.includes('react-i18next')) return 'vendor-i18n';
85
- if (id.includes('@casl')) return 'vendor-casl';
67
+ manualChunks: commonManualChunks((id) => {
86
68
  if (id.includes('@mui')) return 'vendor-mui';
87
69
  if (id.includes('@emotion')) return 'vendor-emotion';
88
- if (id.includes('zustand')) return 'vendor-state';
89
- if (id.includes('@idevconn')) return 'vendor-idevconn';
90
- return 'vendor-core';
91
- },
70
+ }),
92
71
  },
93
72
  },
94
73
  },
95
- test: {
96
- name: 'client-mui',
97
- watch: false,
98
- globals: true,
99
- environment: 'jsdom',
100
- include: ['{src,tests}/**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}'],
101
- reporters: ['default'],
102
- coverage: {
103
- reportsDirectory: '../../../coverage/apps/templates/client-mui',
104
- provider: 'v8' as const,
105
- },
106
- },
74
+ test: commonTestConfig('client-mui', '../../../coverage/apps/templates/client-mui'),
107
75
  }));
@@ -3,9 +3,16 @@ import fs from 'node:fs';
3
3
  import { defineConfig } from 'vite';
4
4
  import react from '@vitejs/plugin-react';
5
5
  import tailwindcss from '@tailwindcss/vite';
6
- import { TanStackRouterVite } from '@tanstack/router-plugin/vite';
6
+ import { tanstackRouter } from '@tanstack/router-plugin/vite';
7
7
  import { nxViteTsPaths } from '@nx/vite/plugins/nx-tsconfig-paths.plugin';
8
8
  import { nxCopyAssetsPlugin } from '@nx/vite/plugins/nx-copy-assets.plugin';
9
+ import {
10
+ commonDefines,
11
+ commonManualChunks,
12
+ commonTestConfig,
13
+ injectAppVersionPlugin,
14
+ noServerModulesPlugin,
15
+ } from '@icore/vite-plugins';
9
16
 
10
17
  const rootPackageJsonPath = new URL('../../../package.json', import.meta.url);
11
18
  const rootPackageJson = JSON.parse(fs.readFileSync(rootPackageJsonPath, 'utf-8')) as {
@@ -15,11 +22,7 @@ const rootPackageJson = JSON.parse(fs.readFileSync(rootPackageJsonPath, 'utf-8')
15
22
  };
16
23
 
17
24
  function depVersion(name: string): string {
18
- return (
19
- rootPackageJson.dependencies?.[name] ??
20
- rootPackageJson.devDependencies?.[name] ??
21
- '?'
22
- );
25
+ return rootPackageJson.dependencies?.[name] ?? rootPackageJson.devDependencies?.[name] ?? '?';
23
26
  }
24
27
 
25
28
  export default defineConfig(() => ({
@@ -34,22 +37,11 @@ export default defineConfig(() => ({
34
37
  host: 'localhost',
35
38
  },
36
39
  define: {
37
- 'import.meta.env.VITE_APP_VERSION': JSON.stringify(rootPackageJson.version),
38
- // Dep versions injected at build time so routes don't need JSON imports
39
- 'import.meta.env.VITE_DEP_REACT': JSON.stringify(depVersion('react')),
40
- 'import.meta.env.VITE_DEP_VITE': JSON.stringify(depVersion('vite')),
40
+ ...commonDefines(rootPackageJson),
41
41
  'import.meta.env.VITE_DEP_TAILWINDCSS': JSON.stringify(depVersion('tailwindcss')),
42
- 'import.meta.env.VITE_DEP_TANSTACK_ROUTER': JSON.stringify(
43
- depVersion('@tanstack/react-router'),
44
- ),
45
- 'import.meta.env.VITE_DEP_TANSTACK_QUERY': JSON.stringify(
46
- depVersion('@tanstack/react-query'),
47
- ),
48
- 'import.meta.env.VITE_DEP_ZUSTAND': JSON.stringify(depVersion('zustand')),
49
- 'import.meta.env.VITE_DEP_CASL': JSON.stringify(depVersion('@casl/ability')),
50
42
  },
51
43
  plugins: [
52
- TanStackRouterVite({
44
+ tanstackRouter({
53
45
  target: 'react',
54
46
  autoCodeSplitting: true,
55
47
  routeFileIgnorePattern: '(__tests__|\\.test\\.(t|j)sx?$)',
@@ -58,12 +50,8 @@ export default defineConfig(() => ({
58
50
  tailwindcss(),
59
51
  nxViteTsPaths(),
60
52
  nxCopyAssetsPlugin(['*.md']),
61
- {
62
- name: 'inject-app-version-meta',
63
- transformIndexHtml(html: string) {
64
- return html.replace('%APP_VERSION%', rootPackageJson.version);
65
- },
66
- },
53
+ noServerModulesPlugin(),
54
+ injectAppVersionPlugin(rootPackageJson),
67
55
  ],
68
56
  // Uncomment this if you are using workers.
69
57
  // worker: {
@@ -78,31 +66,11 @@ export default defineConfig(() => ({
78
66
  },
79
67
  rolldownOptions: {
80
68
  output: {
81
- manualChunks: (id: string) => {
82
- if (!id.includes('node_modules')) return;
83
- if (id.includes('/react/') || id.includes('/react-dom/') || id.includes('scheduler'))
84
- return 'vendor-react';
85
- if (id.includes('@tanstack')) return 'vendor-tanstack';
86
- if (id.includes('i18next') || id.includes('react-i18next')) return 'vendor-i18n';
87
- if (id.includes('@casl')) return 'vendor-casl';
69
+ manualChunks: commonManualChunks((id) => {
88
70
  if (id.includes('lucide-react') || id.includes('@radix-ui')) return 'vendor-ui';
89
- if (id.includes('zustand')) return 'vendor-state';
90
- if (id.includes('@idevconn')) return 'vendor-idevconn';
91
- return 'vendor-core';
92
- },
71
+ }),
93
72
  },
94
73
  },
95
74
  },
96
- test: {
97
- name: 'client-shadcn',
98
- watch: false,
99
- globals: true,
100
- environment: 'jsdom',
101
- include: ['{src,tests}/**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}'],
102
- reporters: ['default'],
103
- coverage: {
104
- reportsDirectory: '../../../coverage/apps/templates/client-shadcn',
105
- provider: 'v8' as const,
106
- },
107
- },
75
+ test: commonTestConfig('client-shadcn', '../../../coverage/apps/templates/client-shadcn'),
108
76
  }));
@@ -5,6 +5,16 @@
5
5
  "type": "commonjs",
6
6
  "main": "./src/index.js",
7
7
  "types": "./src/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "require": "./src/index.js",
11
+ "types": "./src/index.d.ts"
12
+ },
13
+ "./client": {
14
+ "require": "./src/client.js",
15
+ "types": "./src/client.d.ts"
16
+ }
17
+ },
8
18
  "dependencies": {
9
19
  "@casl/ability": "^7.0.0",
10
20
  "@nestjs/microservices": "^11.0.0",
@@ -0,0 +1,121 @@
1
+ /**
2
+ * Cross-boundary dependency guard.
3
+ *
4
+ * Rule 1 — CLIENT boundary: no file reachable from @icore/shared/client
5
+ * may import @nestjs/* (server-only).
6
+ *
7
+ * Rule 2 — SERVER boundary: no file under apps/microservices/** or
8
+ * libs/*-client/** (NestJS modules) may import react or other
9
+ * browser-only packages.
10
+ *
11
+ * These are static source-level checks — they run fast in Vitest without
12
+ * needing a full build.
13
+ */
14
+ import { readdir, readFile, stat } from 'node:fs/promises';
15
+ import { join, resolve } from 'node:path';
16
+ import { describe, expect, it } from 'vitest';
17
+
18
+ const ROOT = resolve(__dirname, '../../../../..');
19
+
20
+ // ── helpers ────────────────────────────────────────────────────────────────
21
+
22
+ async function walkTs(dir: string): Promise<string[]> {
23
+ const files: string[] = [];
24
+ let entries: Awaited<ReturnType<typeof readdir>>;
25
+ try {
26
+ entries = await readdir(dir, { withFileTypes: true });
27
+ } catch {
28
+ return files;
29
+ }
30
+ for (const e of entries) {
31
+ const full = join(dir, e.name);
32
+ if (e.isDirectory() && e.name !== 'node_modules' && e.name !== '__tests__') {
33
+ files.push(...(await walkTs(full)));
34
+ } else if (e.isFile() && /\.(ts|tsx)$/.test(e.name) && !e.name.endsWith('.d.ts')) {
35
+ files.push(full);
36
+ }
37
+ }
38
+ return files;
39
+ }
40
+
41
+ async function importsIn(file: string): Promise<string[]> {
42
+ const src = await readFile(file, 'utf8');
43
+ const matches = [...src.matchAll(/from\s+['"]([^'"]+)['"]/g)];
44
+ return matches.map((m) => m[1] ?? '');
45
+ }
46
+
47
+ // ── Rule 1: client boundary ────────────────────────────────────────────────
48
+
49
+ describe('client boundary — no @nestjs/* in browser-safe exports', () => {
50
+ const CLIENT_ROOTS = [
51
+ join(ROOT, 'libs/shared/src/client.ts'),
52
+ join(ROOT, 'libs/shared/src/abilities'),
53
+ join(ROOT, 'libs/shared/src/types'),
54
+ join(ROOT, 'libs/template-shared/src'),
55
+ ];
56
+
57
+ it('no @nestjs/* import found in client-side source files', async () => {
58
+ const violations: string[] = [];
59
+
60
+ for (const root of CLIENT_ROOTS) {
61
+ const s = await stat(root).catch(() => null);
62
+ if (!s) continue;
63
+ const files = s.isFile() ? [root] : await walkTs(root);
64
+
65
+ for (const file of files) {
66
+ const imports = await importsIn(file);
67
+ for (const imp of imports) {
68
+ if (/^(@nestjs\/|firebase-admin|bullmq|ioredis)/.test(imp)) {
69
+ violations.push(`${file.replace(ROOT + '/', '')} → ${imp}`);
70
+ }
71
+ }
72
+ }
73
+ }
74
+
75
+ expect(
76
+ violations,
77
+ `Server-only imports found in client code:\n${violations.join('\n')}`,
78
+ ).toEqual([]);
79
+ });
80
+ });
81
+
82
+ // ── Rule 2: server boundary ────────────────────────────────────────────────
83
+
84
+ describe('server boundary — no browser-only packages in NestJS modules', () => {
85
+ const SERVER_ROOTS = [
86
+ join(ROOT, 'apps/microservices'),
87
+ join(ROOT, 'libs/auth-client/src'),
88
+ join(ROOT, 'libs/upload-client/src'),
89
+ join(ROOT, 'libs/notes-client/src'),
90
+ join(ROOT, 'libs/payment-client/src'),
91
+ join(ROOT, 'libs/jobs-client/src'),
92
+ ];
93
+
94
+ // Regex matches the start of a bare import specifier
95
+ const BROWSER_ONLY =
96
+ /^(react(-dom|-router)?|@tanstack\/react-|antd|@mui\/|@radix-ui\/|lucide-react|sonner|@ant-design\/)/;
97
+
98
+ it('no browser-only import found in server-side source files', async () => {
99
+ const violations: string[] = [];
100
+
101
+ for (const root of SERVER_ROOTS) {
102
+ const s = await stat(root).catch(() => null);
103
+ if (!s) continue;
104
+ const files = s.isFile() ? [root] : await walkTs(root);
105
+
106
+ for (const file of files) {
107
+ const imports = await importsIn(file);
108
+ for (const imp of imports) {
109
+ if (BROWSER_ONLY.test(imp)) {
110
+ violations.push(`${file.replace(ROOT + '/', '')} → ${imp}`);
111
+ }
112
+ }
113
+ }
114
+ }
115
+
116
+ expect(
117
+ violations,
118
+ `Browser-only imports found in server code:\n${violations.join('\n')}`,
119
+ ).toEqual([]);
120
+ });
121
+ });
@@ -0,0 +1,5 @@
1
+ // Browser-safe subset of @icore/shared.
2
+ // Import from '@icore/shared/client' in client-side code to avoid pulling
3
+ // in NestJS / Node.js-only modules (transport, strategies, contracts).
4
+ export * from './abilities';
5
+ export * from './types';