@idevconn/create-icore 0.3.1 → 0.4.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.
- package/dist/cli.js +14 -0
- package/dist/index.cjs +14 -0
- package/dist/index.js +14 -0
- package/package.json +1 -1
- package/templates/apps/microservices/auth/src/app/app.module.ts +23 -13
- package/templates/apps/microservices/notes/src/app/app.module.ts +33 -23
- package/templates/apps/microservices/upload/src/app/app.module.ts +28 -18
- package/templates/apps/templates/client-antd/vite.config.mts +16 -48
- package/templates/apps/templates/client-mui/vite.config.mts +16 -48
- package/templates/apps/templates/client-shadcn/vite.config.mts +16 -48
- package/templates/libs/shared/package.json +10 -0
- package/templates/libs/shared/src/__tests__/cross-boundary.unit.test.ts +121 -0
- package/templates/libs/shared/src/client.ts +5 -0
- package/templates/libs/template-shared/src/lib/abilities/ability-provider.tsx +1 -1
- package/templates/libs/vite-plugins/README.md +7 -0
- package/templates/libs/vite-plugins/eslint.config.mjs +19 -0
- package/templates/libs/vite-plugins/package.json +18 -0
- package/templates/libs/vite-plugins/project.json +19 -0
- package/templates/libs/vite-plugins/src/index.d.mts +21 -0
- package/templates/libs/vite-plugins/src/index.mjs +106 -0
- package/templates/libs/vite-plugins/tsconfig.json +20 -0
- package/templates/libs/vite-plugins/tsconfig.lib.json +9 -0
- package/templates/tsconfig.base.json +3 -1
package/dist/cli.js
CHANGED
|
@@ -283,6 +283,19 @@ async function writeUploadEnv(targetDir, opts) {
|
|
|
283
283
|
}
|
|
284
284
|
await writeFile(join2(targetDir, "apps/microservices/upload/.env"), next);
|
|
285
285
|
}
|
|
286
|
+
async function writeNotesEnv(targetDir, opts) {
|
|
287
|
+
if (opts.example === "none") return;
|
|
288
|
+
const envExample = join2(targetDir, "apps/microservices/notes/.env.example");
|
|
289
|
+
try {
|
|
290
|
+
const env = await readFile2(envExample, "utf8");
|
|
291
|
+
let next = env.replace(/^NOTES_TRANSPORT=.*$/m, `NOTES_TRANSPORT=${opts.transport}`);
|
|
292
|
+
if (opts.transport !== "tcp") {
|
|
293
|
+
next = next.replace(/^# (NOTES_(?:REDIS|NATS)_URL=)/m, "$1");
|
|
294
|
+
}
|
|
295
|
+
await writeFile(join2(targetDir, "apps/microservices/notes/.env"), next);
|
|
296
|
+
} catch {
|
|
297
|
+
}
|
|
298
|
+
}
|
|
286
299
|
async function writeGatewayEnv(targetDir, opts) {
|
|
287
300
|
const envExample = join2(targetDir, "apps/api/.env.example");
|
|
288
301
|
const env = await readFile2(envExample, "utf8");
|
|
@@ -658,6 +671,7 @@ async function scaffold(opts, templatesDir2) {
|
|
|
658
671
|
await rewriteRootPackageJson(opts.targetDir, opts);
|
|
659
672
|
await writeAuthEnv(opts.targetDir, opts);
|
|
660
673
|
await writeUploadEnv(opts.targetDir, opts);
|
|
674
|
+
await writeNotesEnv(opts.targetDir, opts);
|
|
661
675
|
await writePaymentEnv(opts.targetDir, opts);
|
|
662
676
|
await writeGatewayEnv(opts.targetDir, opts);
|
|
663
677
|
await writeRootEnv(opts.targetDir, opts);
|
package/dist/index.cjs
CHANGED
|
@@ -99,6 +99,19 @@ async function writeUploadEnv(targetDir, opts) {
|
|
|
99
99
|
}
|
|
100
100
|
await (0, import_promises.writeFile)((0, import_node_path.join)(targetDir, "apps/microservices/upload/.env"), next);
|
|
101
101
|
}
|
|
102
|
+
async function writeNotesEnv(targetDir, opts) {
|
|
103
|
+
if (opts.example === "none") return;
|
|
104
|
+
const envExample = (0, import_node_path.join)(targetDir, "apps/microservices/notes/.env.example");
|
|
105
|
+
try {
|
|
106
|
+
const env = await (0, import_promises.readFile)(envExample, "utf8");
|
|
107
|
+
let next = env.replace(/^NOTES_TRANSPORT=.*$/m, `NOTES_TRANSPORT=${opts.transport}`);
|
|
108
|
+
if (opts.transport !== "tcp") {
|
|
109
|
+
next = next.replace(/^# (NOTES_(?:REDIS|NATS)_URL=)/m, "$1");
|
|
110
|
+
}
|
|
111
|
+
await (0, import_promises.writeFile)((0, import_node_path.join)(targetDir, "apps/microservices/notes/.env"), next);
|
|
112
|
+
} catch {
|
|
113
|
+
}
|
|
114
|
+
}
|
|
102
115
|
async function writeGatewayEnv(targetDir, opts) {
|
|
103
116
|
const envExample = (0, import_node_path.join)(targetDir, "apps/api/.env.example");
|
|
104
117
|
const env = await (0, import_promises.readFile)(envExample, "utf8");
|
|
@@ -474,6 +487,7 @@ async function scaffold(opts, templatesDir) {
|
|
|
474
487
|
await rewriteRootPackageJson(opts.targetDir, opts);
|
|
475
488
|
await writeAuthEnv(opts.targetDir, opts);
|
|
476
489
|
await writeUploadEnv(opts.targetDir, opts);
|
|
490
|
+
await writeNotesEnv(opts.targetDir, opts);
|
|
477
491
|
await writePaymentEnv(opts.targetDir, opts);
|
|
478
492
|
await writeGatewayEnv(opts.targetDir, opts);
|
|
479
493
|
await writeRootEnv(opts.targetDir, opts);
|
package/dist/index.js
CHANGED
|
@@ -58,6 +58,19 @@ async function writeUploadEnv(targetDir, opts) {
|
|
|
58
58
|
}
|
|
59
59
|
await writeFile(join(targetDir, "apps/microservices/upload/.env"), next);
|
|
60
60
|
}
|
|
61
|
+
async function writeNotesEnv(targetDir, opts) {
|
|
62
|
+
if (opts.example === "none") return;
|
|
63
|
+
const envExample = join(targetDir, "apps/microservices/notes/.env.example");
|
|
64
|
+
try {
|
|
65
|
+
const env = await readFile(envExample, "utf8");
|
|
66
|
+
let next = env.replace(/^NOTES_TRANSPORT=.*$/m, `NOTES_TRANSPORT=${opts.transport}`);
|
|
67
|
+
if (opts.transport !== "tcp") {
|
|
68
|
+
next = next.replace(/^# (NOTES_(?:REDIS|NATS)_URL=)/m, "$1");
|
|
69
|
+
}
|
|
70
|
+
await writeFile(join(targetDir, "apps/microservices/notes/.env"), next);
|
|
71
|
+
} catch {
|
|
72
|
+
}
|
|
73
|
+
}
|
|
61
74
|
async function writeGatewayEnv(targetDir, opts) {
|
|
62
75
|
const envExample = join(targetDir, "apps/api/.env.example");
|
|
63
76
|
const env = await readFile(envExample, "utf8");
|
|
@@ -433,6 +446,7 @@ async function scaffold(opts, templatesDir) {
|
|
|
433
446
|
await rewriteRootPackageJson(opts.targetDir, opts);
|
|
434
447
|
await writeAuthEnv(opts.targetDir, opts);
|
|
435
448
|
await writeUploadEnv(opts.targetDir, opts);
|
|
449
|
+
await writeNotesEnv(opts.targetDir, opts);
|
|
436
450
|
await writePaymentEnv(opts.targetDir, opts);
|
|
437
451
|
await writeGatewayEnv(opts.targetDir, opts);
|
|
438
452
|
await writeRootEnv(opts.targetDir, opts);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@idevconn/create-icore",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"description": "Bootstrap a new project from the iCore scaffold (Nx + NestJS + React + Vite + shadcn/Tailwind, swappable auth + storage providers).",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"author": "iDEVconn",
|
|
@@ -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
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
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
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
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
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
if (
|
|
43
|
-
admin.
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
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
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
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
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
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
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
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 {
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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 {
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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 {
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
+
});
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { AbilityProvider as CaslAbilityProvider, Can } from '@casl/react';
|
|
2
2
|
import type { ReactNode } from 'react';
|
|
3
3
|
import { useMemo } from 'react';
|
|
4
|
-
import { defineAbilitiesFor, type AppAbility } from '@icore/shared';
|
|
4
|
+
import { defineAbilitiesFor, type AppAbility } from '@icore/shared/client';
|
|
5
5
|
import { useAuthStore } from '../stores/auth.store.js';
|
|
6
6
|
|
|
7
7
|
export { Can };
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import baseConfig from '../../eslint.config.mjs';
|
|
2
|
+
|
|
3
|
+
export default [
|
|
4
|
+
...baseConfig,
|
|
5
|
+
{
|
|
6
|
+
files: ['**/*.json'],
|
|
7
|
+
rules: {
|
|
8
|
+
'@nx/dependency-checks': [
|
|
9
|
+
'error',
|
|
10
|
+
{
|
|
11
|
+
ignoredFiles: ['{projectRoot}/eslint.config.{js,cjs,mjs,ts,cts,mts}'],
|
|
12
|
+
},
|
|
13
|
+
],
|
|
14
|
+
},
|
|
15
|
+
languageOptions: {
|
|
16
|
+
parser: await import('jsonc-eslint-parser'),
|
|
17
|
+
},
|
|
18
|
+
},
|
|
19
|
+
];
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@icore/vite-plugins",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"private": true,
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./src/index.mjs",
|
|
7
|
+
"types": "./src/index.d.mts",
|
|
8
|
+
"peerDependencies": {
|
|
9
|
+
"tslib": "^2.3.0",
|
|
10
|
+
"vite": "^8.0.0",
|
|
11
|
+
"vitest": "~4.1.0"
|
|
12
|
+
},
|
|
13
|
+
"peerDependenciesMeta": {
|
|
14
|
+
"vite": {
|
|
15
|
+
"optional": false
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "vite-plugins",
|
|
3
|
+
"$schema": "../../node_modules/nx/schemas/project-schema.json",
|
|
4
|
+
"sourceRoot": "libs/vite-plugins/src",
|
|
5
|
+
"projectType": "library",
|
|
6
|
+
"tags": [],
|
|
7
|
+
"targets": {
|
|
8
|
+
"build": {
|
|
9
|
+
"executor": "@nx/js:tsc",
|
|
10
|
+
"outputs": ["{options.outputPath}"],
|
|
11
|
+
"options": {
|
|
12
|
+
"outputPath": "dist/libs/vite-plugins",
|
|
13
|
+
"main": "libs/vite-plugins/src/index.ts",
|
|
14
|
+
"tsConfig": "libs/vite-plugins/tsconfig.lib.json",
|
|
15
|
+
"assets": ["libs/vite-plugins/*.md"]
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { Plugin } from 'vite';
|
|
2
|
+
import type { UserConfig } from 'vitest/config';
|
|
3
|
+
|
|
4
|
+
export declare function noServerModulesPlugin(): Plugin;
|
|
5
|
+
|
|
6
|
+
export declare function injectAppVersionPlugin(pkg: { version: string }): Plugin;
|
|
7
|
+
|
|
8
|
+
export declare function commonDefines(pkg: {
|
|
9
|
+
version: string;
|
|
10
|
+
dependencies?: Record<string, string>;
|
|
11
|
+
devDependencies?: Record<string, string>;
|
|
12
|
+
}): Record<string, string>;
|
|
13
|
+
|
|
14
|
+
export declare function commonManualChunks(
|
|
15
|
+
uiChunkFn?: (id: string) => string | undefined,
|
|
16
|
+
): (id: string) => string | undefined;
|
|
17
|
+
|
|
18
|
+
export declare function commonTestConfig(
|
|
19
|
+
name: string,
|
|
20
|
+
coverageDir: string,
|
|
21
|
+
): NonNullable<UserConfig['test']>;
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
// @icore/vite-plugins — shared Vite plugin helpers for iCore client templates.
|
|
2
|
+
// Plain ESM (no TypeScript syntax) so vite.config.mts can import it directly.
|
|
3
|
+
|
|
4
|
+
const SERVER_ONLY_RE = /^(@nestjs\/|firebase-admin$|bullmq$|ioredis$)/;
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Fails the Vite build if server-only modules are imported in client code.
|
|
8
|
+
* @returns {import('vite').Plugin}
|
|
9
|
+
*/
|
|
10
|
+
export function noServerModulesPlugin() {
|
|
11
|
+
return {
|
|
12
|
+
name: 'no-server-modules',
|
|
13
|
+
enforce: 'pre',
|
|
14
|
+
resolveId(id, importer) {
|
|
15
|
+
if (SERVER_ONLY_RE.test(id)) {
|
|
16
|
+
throw new Error(
|
|
17
|
+
`Server-only module "${id}" imported in client code` +
|
|
18
|
+
(importer ? ` (from ${importer})` : '') +
|
|
19
|
+
`. Use @icore/shared/client instead of @icore/shared for browser-safe imports.`,
|
|
20
|
+
);
|
|
21
|
+
}
|
|
22
|
+
},
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Replaces %APP_VERSION% in index.html with the root package.json version.
|
|
28
|
+
* @param {{ version: string }} pkg
|
|
29
|
+
* @returns {import('vite').Plugin}
|
|
30
|
+
*/
|
|
31
|
+
export function injectAppVersionPlugin(pkg) {
|
|
32
|
+
return {
|
|
33
|
+
name: 'inject-app-version-meta',
|
|
34
|
+
transformIndexHtml(html) {
|
|
35
|
+
return html.replace('%APP_VERSION%', pkg.version);
|
|
36
|
+
},
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Returns Vite `define` entries shared by all iCore client templates.
|
|
42
|
+
* Each template spreads this and adds its own UI-lib-specific entry.
|
|
43
|
+
*
|
|
44
|
+
* @param {{ version: string, dependencies?: Record<string,string>, devDependencies?: Record<string,string> }} pkg
|
|
45
|
+
* @returns {Record<string, string>}
|
|
46
|
+
*/
|
|
47
|
+
export function commonDefines(pkg) {
|
|
48
|
+
const dep = (name) =>
|
|
49
|
+
JSON.stringify(pkg.dependencies?.[name] ?? pkg.devDependencies?.[name] ?? '?');
|
|
50
|
+
return {
|
|
51
|
+
'import.meta.env.VITE_APP_VERSION': JSON.stringify(pkg.version),
|
|
52
|
+
'import.meta.env.VITE_DEP_REACT': dep('react'),
|
|
53
|
+
'import.meta.env.VITE_DEP_VITE': dep('vite'),
|
|
54
|
+
'import.meta.env.VITE_DEP_TANSTACK_ROUTER': dep('@tanstack/react-router'),
|
|
55
|
+
'import.meta.env.VITE_DEP_TANSTACK_QUERY': dep('@tanstack/react-query'),
|
|
56
|
+
'import.meta.env.VITE_DEP_ZUSTAND': dep('zustand'),
|
|
57
|
+
'import.meta.env.VITE_DEP_CASL': dep('@casl/ability'),
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Returns a manualChunks function with the common vendor splits pre-applied.
|
|
63
|
+
* Pass a `uiChunkFn` to add UI-library-specific splits before the fallback.
|
|
64
|
+
*
|
|
65
|
+
* @param {(id: string) => string | undefined} [uiChunkFn]
|
|
66
|
+
* @returns {(id: string) => string | undefined}
|
|
67
|
+
*/
|
|
68
|
+
export function commonManualChunks(uiChunkFn) {
|
|
69
|
+
return (id) => {
|
|
70
|
+
if (!id.includes('node_modules')) return undefined;
|
|
71
|
+
if (id.includes('/react/') || id.includes('/react-dom/') || id.includes('scheduler'))
|
|
72
|
+
return 'vendor-react';
|
|
73
|
+
if (id.includes('@tanstack')) return 'vendor-tanstack';
|
|
74
|
+
if (id.includes('i18next') || id.includes('react-i18next')) return 'vendor-i18n';
|
|
75
|
+
if (id.includes('@casl')) return 'vendor-casl';
|
|
76
|
+
if (uiChunkFn) {
|
|
77
|
+
const chunk = uiChunkFn(id);
|
|
78
|
+
if (chunk) return chunk;
|
|
79
|
+
}
|
|
80
|
+
if (id.includes('zustand')) return 'vendor-state';
|
|
81
|
+
if (id.includes('@idevconn')) return 'vendor-idevconn';
|
|
82
|
+
return 'vendor-core';
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Returns a vitest `test` configuration block shared by all iCore client templates.
|
|
88
|
+
*
|
|
89
|
+
* @param {string} name - project name (e.g. 'client-shadcn')
|
|
90
|
+
* @param {string} coverageDir - relative path to coverage output dir
|
|
91
|
+
* @returns {import('vitest/config').UserConfig['test']}
|
|
92
|
+
*/
|
|
93
|
+
export function commonTestConfig(name, coverageDir) {
|
|
94
|
+
return {
|
|
95
|
+
name,
|
|
96
|
+
watch: false,
|
|
97
|
+
globals: true,
|
|
98
|
+
environment: 'jsdom',
|
|
99
|
+
include: ['{src,tests}/**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}'],
|
|
100
|
+
reporters: ['default'],
|
|
101
|
+
coverage: {
|
|
102
|
+
reportsDirectory: coverageDir,
|
|
103
|
+
provider: 'v8',
|
|
104
|
+
},
|
|
105
|
+
};
|
|
106
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
{
|
|
2
|
+
"extends": "../../tsconfig.base.json",
|
|
3
|
+
"compilerOptions": {
|
|
4
|
+
"module": "commonjs",
|
|
5
|
+
"forceConsistentCasingInFileNames": true,
|
|
6
|
+
"strict": true,
|
|
7
|
+
"importHelpers": true,
|
|
8
|
+
"noImplicitOverride": true,
|
|
9
|
+
"noImplicitReturns": true,
|
|
10
|
+
"noFallthroughCasesInSwitch": true,
|
|
11
|
+
"noPropertyAccessFromIndexSignature": true
|
|
12
|
+
},
|
|
13
|
+
"files": [],
|
|
14
|
+
"include": [],
|
|
15
|
+
"references": [
|
|
16
|
+
{
|
|
17
|
+
"path": "./tsconfig.lib.json"
|
|
18
|
+
}
|
|
19
|
+
]
|
|
20
|
+
}
|
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
"baseUrl": ".",
|
|
14
14
|
"paths": {
|
|
15
15
|
"@icore/shared": ["./libs/shared/src/index.ts"],
|
|
16
|
+
"@icore/shared/client": ["./libs/shared/src/client.ts"],
|
|
16
17
|
"@icore/auth-supabase": ["./libs/auth-strategies/supabase/src/index.ts"],
|
|
17
18
|
"@icore/auth-client": ["./libs/auth-client/src/index.ts"],
|
|
18
19
|
"@icore/package.json": ["./package.json"],
|
|
@@ -27,7 +28,8 @@
|
|
|
27
28
|
"@icore/db-firestore": ["./libs/db-strategies/firestore/src/index.ts"],
|
|
28
29
|
"@icore/payment-client": ["./libs/payment-client/src/index.ts"],
|
|
29
30
|
"@icore/notes-client": ["./libs/notes-client/src/index.ts"],
|
|
30
|
-
"@icore/jobs-client": ["./libs/jobs-client/src/index.ts"]
|
|
31
|
+
"@icore/jobs-client": ["./libs/jobs-client/src/index.ts"],
|
|
32
|
+
"@icore/vite-plugins": ["./libs/vite-plugins/src/index.d.mts"]
|
|
31
33
|
}
|
|
32
34
|
},
|
|
33
35
|
"exclude": ["node_modules", "dist", ".nx"]
|