@idevconn/create-icore 0.4.1 → 0.5.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 +221 -0
- package/dist/index.cjs +221 -0
- package/dist/index.js +221 -0
- package/package.json +1 -1
- package/templates/apps/api/src/app/app.module.ts +5 -1
- package/templates/apps/api/src/main.ts +12 -6
- package/templates/apps/microservices/auth/project.json +2 -1
- package/templates/apps/microservices/auth/src/app/app.module.ts +47 -23
- package/templates/apps/microservices/jobs/project.json +2 -1
- package/templates/apps/microservices/notes/project.json +2 -1
- package/templates/apps/microservices/notes/src/app/app.module.ts +44 -25
- package/templates/apps/microservices/payment/project.json +2 -1
- package/templates/apps/microservices/payment/src/app/app.module.ts +35 -12
- package/templates/apps/microservices/upload/project.json +2 -1
- package/templates/apps/microservices/upload/src/app/app.module.ts +48 -28
- package/templates/apps/templates/client-antd/.env.example +7 -0
- package/templates/apps/templates/client-antd/vite.config.mts +4 -4
- package/templates/apps/templates/client-mui/.env.example +7 -0
- package/templates/apps/templates/client-mui/vite.config.mts +4 -4
- package/templates/apps/templates/client-shadcn/.env.example +6 -1
- package/templates/apps/templates/client-shadcn/vite.config.mts +4 -4
- package/templates/libs/auth-client/src/index.ts +1 -0
- package/templates/libs/auth-client/src/lib/auth-client.module.ts +1 -1
- package/templates/libs/auth-client/src/lib/auth-client.service.ts +1 -1
- package/templates/libs/auth-client/src/lib/auth-client.tokens.ts +4 -0
- package/templates/libs/jobs-client/src/index.ts +1 -0
- package/templates/libs/jobs-client/src/lib/jobs-client.module.ts +1 -1
- package/templates/libs/jobs-client/src/lib/jobs-client.service.ts +1 -1
- package/templates/libs/jobs-client/src/lib/jobs-client.tokens.ts +4 -0
- package/templates/libs/notes-client/src/index.ts +1 -0
- package/templates/libs/notes-client/src/lib/notes-client.module.ts +1 -1
- package/templates/libs/notes-client/src/lib/notes-client.service.ts +1 -1
- package/templates/libs/notes-client/src/lib/notes-client.tokens.ts +4 -0
- package/templates/libs/payment-client/src/index.ts +1 -0
- package/templates/libs/payment-client/src/lib/payment-client.module.ts +1 -1
- package/templates/libs/payment-client/src/lib/payment-client.service.ts +1 -1
- package/templates/libs/payment-client/src/lib/payment-client.tokens.ts +4 -0
- package/templates/libs/shared/src/env.ts +88 -0
- package/templates/libs/shared/src/index.ts +1 -0
- package/templates/libs/shared/src/transport.ts +37 -0
- package/templates/libs/upload-client/src/index.ts +1 -0
- package/templates/libs/upload-client/src/lib/upload-client.module.ts +1 -1
- package/templates/libs/upload-client/src/lib/upload-client.service.ts +1 -1
- package/templates/libs/upload-client/src/lib/upload-client.tokens.ts +4 -0
- package/templates/libs/vite-plugins/src/index.d.mts +6 -0
- package/templates/libs/vite-plugins/src/index.mjs +50 -0
- package/templates/package.json +1 -0
- package/templates/tools/create-icore/_template-shell/package.json +1 -0
|
@@ -7,15 +7,26 @@ 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
|
+
import { FakeStorageStrategy, missingEnv, formatEnvBanner } from '@icore/shared';
|
|
11
11
|
import type { StorageStrategy } from '@icore/shared';
|
|
12
12
|
import { Logger } from '@nestjs/common';
|
|
13
13
|
import { StorageController } from './storage.controller';
|
|
14
14
|
|
|
15
|
+
const ENV_PATH = 'apps/microservices/upload/.env';
|
|
16
|
+
|
|
17
|
+
const REQUIRED_ENV: Record<string, string[]> = {
|
|
18
|
+
supabase: ['SUPABASE_URL', 'SUPABASE_SERVICE_ROLE_KEY', 'SUPABASE_STORAGE_BUCKET'],
|
|
19
|
+
firebase: [
|
|
20
|
+
'FIREBASE_STORAGE_BUCKET',
|
|
21
|
+
'FB_ADMIN_PROJECT_ID',
|
|
22
|
+
'FB_ADMIN_CLIENT_EMAIL',
|
|
23
|
+
'FB_ADMIN_PRIVATE_KEY',
|
|
24
|
+
],
|
|
25
|
+
cloudinary: ['CLOUDINARY_CLOUD_NAME', 'CLOUDINARY_API_KEY', 'CLOUDINARY_API_SECRET'],
|
|
26
|
+
};
|
|
27
|
+
|
|
15
28
|
function requireEnv(cfg: ConfigService, key: string): string {
|
|
16
|
-
|
|
17
|
-
if (!val) throw new Error(`${key} is not set — check apps/microservices/upload/.env`);
|
|
18
|
-
return val;
|
|
29
|
+
return cfg.getOrThrow<string>(key);
|
|
19
30
|
}
|
|
20
31
|
|
|
21
32
|
function makeFirebaseStorage(cfg: ConfigService): StorageStrategy {
|
|
@@ -97,33 +108,42 @@ function makeCloudinaryStorage(cfg: ConfigService): StorageStrategy {
|
|
|
97
108
|
{
|
|
98
109
|
provide: 'StorageStrategy',
|
|
99
110
|
useFactory: (cfg: ConfigService): StorageStrategy => {
|
|
111
|
+
const logger = new Logger('StorageStrategy');
|
|
112
|
+
const provider = cfg.get<string>('STORAGE_PROVIDER')?.trim();
|
|
113
|
+
const keys = provider ? REQUIRED_ENV[provider] : undefined;
|
|
114
|
+
const missing = keys ? missingEnv((k) => cfg.get<string>(k), keys) : [];
|
|
115
|
+
|
|
116
|
+
const fallback = (reason?: string): StorageStrategy => {
|
|
117
|
+
const banner = formatEnvBanner({
|
|
118
|
+
service: 'upload MS',
|
|
119
|
+
provider,
|
|
120
|
+
missing,
|
|
121
|
+
envPath: ENV_PATH,
|
|
122
|
+
reason,
|
|
123
|
+
});
|
|
124
|
+
if (process.env.NODE_ENV === 'production') throw new Error(banner);
|
|
125
|
+
logger.warn(banner);
|
|
126
|
+
return new FakeStorageStrategy();
|
|
127
|
+
};
|
|
128
|
+
|
|
129
|
+
if (!keys || missing.length > 0) return fallback();
|
|
130
|
+
|
|
100
131
|
try {
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
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}`);
|
|
132
|
+
if (provider === 'supabase') {
|
|
133
|
+
const client = createClient(
|
|
134
|
+
requireEnv(cfg, 'SUPABASE_URL'),
|
|
135
|
+
requireEnv(cfg, 'SUPABASE_SERVICE_ROLE_KEY'),
|
|
136
|
+
{ auth: { autoRefreshToken: false, persistSession: false } },
|
|
137
|
+
);
|
|
138
|
+
return new SupabaseStorageStrategy({
|
|
139
|
+
client,
|
|
140
|
+
bucket: requireEnv(cfg, 'SUPABASE_STORAGE_BUCKET'),
|
|
141
|
+
});
|
|
120
142
|
}
|
|
143
|
+
if (provider === 'firebase') return makeFirebaseStorage(cfg);
|
|
144
|
+
return makeCloudinaryStorage(cfg);
|
|
121
145
|
} catch (err) {
|
|
122
|
-
|
|
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();
|
|
146
|
+
return fallback(err instanceof Error ? err.message : String(err));
|
|
127
147
|
}
|
|
128
148
|
},
|
|
129
149
|
inject: [ConfigService],
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
# ─── Client environment variables ────────────────────────────────────────
|
|
2
|
+
# API gateway base URL.
|
|
3
|
+
# /api — default; dev-server proxies it to http://localhost:3001 (see vite.config.mts)
|
|
4
|
+
# http://localhost:3001/api — set explicitly if you want to bypass the proxy
|
|
5
|
+
# https://your-domain.com/api — production override when building a standalone SPA
|
|
6
|
+
VITE_API_URL=/api
|
|
7
|
+
|
|
@@ -6,8 +6,10 @@ 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
8
|
import {
|
|
9
|
+
apiInfoPlugin,
|
|
9
10
|
commonDefines,
|
|
10
11
|
commonManualChunks,
|
|
12
|
+
commonServer,
|
|
11
13
|
commonTestConfig,
|
|
12
14
|
injectAppVersionPlugin,
|
|
13
15
|
noServerModulesPlugin,
|
|
@@ -27,10 +29,7 @@ function depVersion(name: string): string {
|
|
|
27
29
|
export default defineConfig(() => ({
|
|
28
30
|
root: import.meta.dirname,
|
|
29
31
|
cacheDir: '../../../node_modules/.vite/apps/templates/client-antd',
|
|
30
|
-
server:
|
|
31
|
-
port: 4201,
|
|
32
|
-
host: 'localhost',
|
|
33
|
-
},
|
|
32
|
+
server: commonServer(4201),
|
|
34
33
|
preview: {
|
|
35
34
|
port: 4201,
|
|
36
35
|
host: 'localhost',
|
|
@@ -49,6 +48,7 @@ export default defineConfig(() => ({
|
|
|
49
48
|
nxViteTsPaths(),
|
|
50
49
|
nxCopyAssetsPlugin(['*.md']),
|
|
51
50
|
noServerModulesPlugin(),
|
|
51
|
+
apiInfoPlugin(),
|
|
52
52
|
injectAppVersionPlugin(rootPackageJson),
|
|
53
53
|
],
|
|
54
54
|
// Uncomment this if you are using workers.
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
# ─── Client environment variables ────────────────────────────────────────
|
|
2
|
+
# API gateway base URL.
|
|
3
|
+
# /api — default; dev-server proxies it to http://localhost:3001 (see vite.config.mts)
|
|
4
|
+
# http://localhost:3001/api — set explicitly if you want to bypass the proxy
|
|
5
|
+
# https://your-domain.com/api — production override when building a standalone SPA
|
|
6
|
+
VITE_API_URL=/api
|
|
7
|
+
|
|
@@ -6,8 +6,10 @@ 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
8
|
import {
|
|
9
|
+
apiInfoPlugin,
|
|
9
10
|
commonDefines,
|
|
10
11
|
commonManualChunks,
|
|
12
|
+
commonServer,
|
|
11
13
|
commonTestConfig,
|
|
12
14
|
injectAppVersionPlugin,
|
|
13
15
|
noServerModulesPlugin,
|
|
@@ -27,10 +29,7 @@ function depVersion(name: string): string {
|
|
|
27
29
|
export default defineConfig(() => ({
|
|
28
30
|
root: import.meta.dirname,
|
|
29
31
|
cacheDir: '../../../node_modules/.vite/client-mui',
|
|
30
|
-
server:
|
|
31
|
-
port: 4202,
|
|
32
|
-
host: 'localhost',
|
|
33
|
-
},
|
|
32
|
+
server: commonServer(4202),
|
|
34
33
|
preview: {
|
|
35
34
|
port: 4202,
|
|
36
35
|
host: 'localhost',
|
|
@@ -49,6 +48,7 @@ export default defineConfig(() => ({
|
|
|
49
48
|
nxViteTsPaths(),
|
|
50
49
|
nxCopyAssetsPlugin(['*.md']),
|
|
51
50
|
noServerModulesPlugin(),
|
|
51
|
+
apiInfoPlugin(),
|
|
52
52
|
injectAppVersionPlugin(rootPackageJson),
|
|
53
53
|
],
|
|
54
54
|
// Uncomment this if you are using workers.
|
|
@@ -1,2 +1,7 @@
|
|
|
1
|
-
#
|
|
1
|
+
# ─── Client environment variables ────────────────────────────────────────
|
|
2
|
+
# API gateway base URL.
|
|
3
|
+
# /api — default; dev-server proxies it to http://localhost:3001 (see vite.config.mts)
|
|
4
|
+
# http://localhost:3001/api — set explicitly if you want to bypass the proxy
|
|
5
|
+
# https://your-domain.com/api — production override when building a standalone SPA
|
|
2
6
|
VITE_API_URL=/api
|
|
7
|
+
|
|
@@ -7,8 +7,10 @@ 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
9
|
import {
|
|
10
|
+
apiInfoPlugin,
|
|
10
11
|
commonDefines,
|
|
11
12
|
commonManualChunks,
|
|
13
|
+
commonServer,
|
|
12
14
|
commonTestConfig,
|
|
13
15
|
injectAppVersionPlugin,
|
|
14
16
|
noServerModulesPlugin,
|
|
@@ -28,10 +30,7 @@ function depVersion(name: string): string {
|
|
|
28
30
|
export default defineConfig(() => ({
|
|
29
31
|
root: import.meta.dirname,
|
|
30
32
|
cacheDir: '../../../node_modules/.vite/apps/templates/client-shadcn',
|
|
31
|
-
server:
|
|
32
|
-
port: 4200,
|
|
33
|
-
host: 'localhost',
|
|
34
|
-
},
|
|
33
|
+
server: commonServer(4200),
|
|
35
34
|
preview: {
|
|
36
35
|
port: 4200,
|
|
37
36
|
host: 'localhost',
|
|
@@ -51,6 +50,7 @@ export default defineConfig(() => ({
|
|
|
51
50
|
nxViteTsPaths(),
|
|
52
51
|
nxCopyAssetsPlugin(['*.md']),
|
|
53
52
|
noServerModulesPlugin(),
|
|
53
|
+
apiInfoPlugin(),
|
|
54
54
|
injectAppVersionPlugin(rootPackageJson),
|
|
55
55
|
],
|
|
56
56
|
// Uncomment this if you are using workers.
|
|
@@ -3,7 +3,7 @@ import { ClientsModule } from '@nestjs/microservices';
|
|
|
3
3
|
import { buildTransport } from '@icore/shared';
|
|
4
4
|
import { AuthClientService } from './auth-client.service';
|
|
5
5
|
|
|
6
|
-
|
|
6
|
+
import { AUTH_CLIENT } from './auth-client.tokens';
|
|
7
7
|
|
|
8
8
|
@Module({})
|
|
9
9
|
export class AuthClientModule {
|
|
@@ -2,7 +2,7 @@ import { Inject, Injectable } from '@nestjs/common';
|
|
|
2
2
|
import { ClientProxy } from '@nestjs/microservices';
|
|
3
3
|
import { firstValueFrom } from 'rxjs';
|
|
4
4
|
import type { AuthSession, OAuthProvider, OAuthStartResult, VerifiedToken } from '@icore/shared';
|
|
5
|
-
import { AUTH_CLIENT } from './auth-client.
|
|
5
|
+
import { AUTH_CLIENT } from './auth-client.tokens';
|
|
6
6
|
|
|
7
7
|
@Injectable()
|
|
8
8
|
export class AuthClientService {
|
|
@@ -2,7 +2,7 @@ import { DynamicModule, Module } from '@nestjs/common';
|
|
|
2
2
|
import { ConfigService } from '@nestjs/config';
|
|
3
3
|
import { JobsClientService } from './jobs-client.service';
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
import { JOBS_REDIS_URL } from './jobs-client.tokens';
|
|
6
6
|
|
|
7
7
|
@Module({})
|
|
8
8
|
export class JobsClientModule {
|
|
@@ -2,7 +2,7 @@ import { Inject, Injectable, OnModuleDestroy } from '@nestjs/common';
|
|
|
2
2
|
import { Queue, type JobsOptions } from 'bullmq';
|
|
3
3
|
import IORedis from 'ioredis';
|
|
4
4
|
import { ICORE_QUEUES, type JobsMap } from '@icore/shared';
|
|
5
|
-
import { JOBS_REDIS_URL } from './jobs-client.
|
|
5
|
+
import { JOBS_REDIS_URL } from './jobs-client.tokens';
|
|
6
6
|
|
|
7
7
|
@Injectable()
|
|
8
8
|
export class JobsClientService implements OnModuleDestroy {
|
|
@@ -3,7 +3,7 @@ import { ClientsModule } from '@nestjs/microservices';
|
|
|
3
3
|
import { buildTransport } from '@icore/shared';
|
|
4
4
|
import { NotesClientService } from './notes-client.service';
|
|
5
5
|
|
|
6
|
-
|
|
6
|
+
import { NOTES_CLIENT } from './notes-client.tokens';
|
|
7
7
|
|
|
8
8
|
@Module({})
|
|
9
9
|
export class NotesClientModule {
|
|
@@ -2,7 +2,7 @@ import { Inject, Injectable } from '@nestjs/common';
|
|
|
2
2
|
import { ClientProxy } from '@nestjs/microservices';
|
|
3
3
|
import { firstValueFrom } from 'rxjs';
|
|
4
4
|
import type { ListNotesOptions, Note } from '@icore/shared';
|
|
5
|
-
import { NOTES_CLIENT } from './notes-client.
|
|
5
|
+
import { NOTES_CLIENT } from './notes-client.tokens';
|
|
6
6
|
|
|
7
7
|
@Injectable()
|
|
8
8
|
export class NotesClientService {
|
|
@@ -3,7 +3,7 @@ import { ClientsModule } from '@nestjs/microservices';
|
|
|
3
3
|
import { buildTransport } from '@icore/shared';
|
|
4
4
|
import { PaymentClientService } from './payment-client.service';
|
|
5
5
|
|
|
6
|
-
|
|
6
|
+
import { PAYMENT_CLIENT } from './payment-client.tokens';
|
|
7
7
|
|
|
8
8
|
@Module({})
|
|
9
9
|
export class PaymentClientModule {
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
// Server-side env helpers. NOT exported from ./client (browser-safe entry) —
|
|
2
|
+
// generated microservices import these to report which provider credentials
|
|
3
|
+
// are missing on startup instead of crashing.
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Returns the keys whose value is missing or blank.
|
|
7
|
+
* @param get accessor (e.g. ConfigService.get bound to the MS config)
|
|
8
|
+
* @param keys env var names required for the selected provider
|
|
9
|
+
*/
|
|
10
|
+
export function missingEnv(get: (key: string) => string | undefined, keys: string[]): string[] {
|
|
11
|
+
return keys.filter((k) => !get(k)?.trim());
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Builds an eye-catching boxed banner listing the env vars a microservice
|
|
16
|
+
* needs but is missing, so it stands out in the `yarn dev` log noise.
|
|
17
|
+
*/
|
|
18
|
+
export function formatEnvBanner(opts: {
|
|
19
|
+
service: string;
|
|
20
|
+
provider: string | undefined;
|
|
21
|
+
missing: string[];
|
|
22
|
+
envPath: string;
|
|
23
|
+
reason?: string;
|
|
24
|
+
/** Override the first line. Defaults to the in-memory-fake warning. */
|
|
25
|
+
headline?: string;
|
|
26
|
+
}): string {
|
|
27
|
+
const { service, provider, missing, envPath, reason, headline } = opts;
|
|
28
|
+
const lines: string[] = [];
|
|
29
|
+
lines.push(headline ?? `⚠ ${service} — running with an IN-MEMORY FAKE (requests will fail)`);
|
|
30
|
+
lines.push('');
|
|
31
|
+
if (!provider) {
|
|
32
|
+
lines.push(`Provider env var is not set.`);
|
|
33
|
+
} else if (missing.length > 0) {
|
|
34
|
+
lines.push(`"${provider}" needs these env vars, currently missing:`);
|
|
35
|
+
for (const k of missing) lines.push(` • ${k}`);
|
|
36
|
+
} else if (reason) {
|
|
37
|
+
// Vars are present but invalid (e.g. placeholder URL the SDK rejected).
|
|
38
|
+
lines.push(`"${provider}" failed to initialise:`);
|
|
39
|
+
lines.push(` ${reason}`);
|
|
40
|
+
}
|
|
41
|
+
lines.push('');
|
|
42
|
+
lines.push(`Set real values in: ${envPath}`);
|
|
43
|
+
|
|
44
|
+
const width = Math.max(...lines.map((l) => l.length), 50);
|
|
45
|
+
const top = `╔═${'═'.repeat(width)}═╗`;
|
|
46
|
+
const bot = `╚═${'═'.repeat(width)}═╝`;
|
|
47
|
+
const body = lines.map((l) => `║ ${l.padEnd(width)} ║`).join('\n');
|
|
48
|
+
return `\n${top}\n${body}\n${bot}`;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Returns a boxed startup info banner listing every MS the gateway will
|
|
53
|
+
* connect to, reading the current transport env vars.
|
|
54
|
+
*/
|
|
55
|
+
export function formatGatewayBanner(opts: {
|
|
56
|
+
port: number;
|
|
57
|
+
origin: string;
|
|
58
|
+
services: Array<{ name: string; prefix: string }>;
|
|
59
|
+
}): string {
|
|
60
|
+
const { port, origin, services } = opts;
|
|
61
|
+
const lines: string[] = [];
|
|
62
|
+
lines.push(`Gateway listening on ${origin}:${port}/api`);
|
|
63
|
+
lines.push(`Swagger UI ${origin}:${port}/api/docs`);
|
|
64
|
+
lines.push('');
|
|
65
|
+
lines.push('Microservice transports:');
|
|
66
|
+
for (const { name, prefix } of services) {
|
|
67
|
+
const kind = (process.env[`${prefix}_TRANSPORT`] ?? 'tcp').toLowerCase();
|
|
68
|
+
let target: string;
|
|
69
|
+
if (kind === 'tcp') {
|
|
70
|
+
const host = process.env[`${prefix}_HOST`] ?? '127.0.0.1';
|
|
71
|
+
const port_ = process.env[`${prefix}_PORT`] ?? '?';
|
|
72
|
+
target = `tcp ${host}:${port_}`;
|
|
73
|
+
} else if (kind === 'redis') {
|
|
74
|
+
target = `redis ${process.env[`${prefix}_REDIS_URL`] ?? '(REDIS_URL not set)'}`;
|
|
75
|
+
} else if (kind === 'nats') {
|
|
76
|
+
target = `nats ${process.env[`${prefix}_NATS_URL`] ?? '(NATS_URL not set)'}`;
|
|
77
|
+
} else {
|
|
78
|
+
target = kind;
|
|
79
|
+
}
|
|
80
|
+
lines.push(` ${name.padEnd(10)} → ${target}`);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const width = Math.max(...lines.map((l) => l.length), 50);
|
|
84
|
+
const top = `╔═${'═'.repeat(width)}═╗`;
|
|
85
|
+
const bot = `╚═${'═'.repeat(width)}═╝`;
|
|
86
|
+
const body = lines.map((l) => `║ ${l.padEnd(width)} ║`).join('\n');
|
|
87
|
+
return `\n${top}\n${body}\n${bot}`;
|
|
88
|
+
}
|
|
@@ -1,4 +1,40 @@
|
|
|
1
1
|
import { Transport, type ClientOptions, type MicroserviceOptions } from '@nestjs/microservices';
|
|
2
|
+
import { formatEnvBanner } from './env';
|
|
3
|
+
|
|
4
|
+
// Transport vars each kind needs (besides ${prefix}_TRANSPORT itself).
|
|
5
|
+
function transportKeys(prefix: string, kind: string): string[] {
|
|
6
|
+
switch (kind) {
|
|
7
|
+
case 'tcp':
|
|
8
|
+
return [`${prefix}_HOST`, `${prefix}_PORT`];
|
|
9
|
+
case 'redis':
|
|
10
|
+
return [`${prefix}_REDIS_URL`];
|
|
11
|
+
case 'nats':
|
|
12
|
+
return [`${prefix}_NATS_URL`];
|
|
13
|
+
default:
|
|
14
|
+
return [];
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Throws an eye-catching banner if any transport var for `prefix` is missing.
|
|
20
|
+
* Used by both the gateway client modules and each MS bootstrap, so transport
|
|
21
|
+
* misconfiguration surfaces clearly instead of a raw "Missing env var".
|
|
22
|
+
*/
|
|
23
|
+
function assertTransportEnv(prefix: string, kind: string): void {
|
|
24
|
+
const keys = transportKeys(prefix, kind);
|
|
25
|
+
const missing = keys.filter((k) => !process.env[k]?.trim());
|
|
26
|
+
if (missing.length > 0) {
|
|
27
|
+
throw new Error(
|
|
28
|
+
formatEnvBanner({
|
|
29
|
+
service: `${prefix} transport`,
|
|
30
|
+
provider: kind,
|
|
31
|
+
missing,
|
|
32
|
+
envPath: `the service .env (${prefix}_* transport vars)`,
|
|
33
|
+
headline: `⚠ ${prefix} transport (${kind}) not configured — cannot reach the microservice`,
|
|
34
|
+
}),
|
|
35
|
+
);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
2
38
|
|
|
3
39
|
function required(name: string): string {
|
|
4
40
|
const value = process.env[name];
|
|
@@ -17,6 +53,7 @@ function requiredPort(name: string): number {
|
|
|
17
53
|
|
|
18
54
|
export function buildTransport(prefix: string): ClientOptions {
|
|
19
55
|
const kind = (process.env[`${prefix}_TRANSPORT`] ?? 'tcp').toLowerCase();
|
|
56
|
+
assertTransportEnv(prefix, kind);
|
|
20
57
|
switch (kind) {
|
|
21
58
|
case 'tcp':
|
|
22
59
|
return {
|
|
@@ -3,7 +3,7 @@ import { ClientsModule } from '@nestjs/microservices';
|
|
|
3
3
|
import { buildTransport } from '@icore/shared';
|
|
4
4
|
import { UploadClientService } from './upload-client.service';
|
|
5
5
|
|
|
6
|
-
|
|
6
|
+
import { UPLOAD_CLIENT } from './upload-client.tokens';
|
|
7
7
|
|
|
8
8
|
@Module({})
|
|
9
9
|
export class UploadClientModule {
|
|
@@ -2,7 +2,7 @@ import { Inject, Injectable } from '@nestjs/common';
|
|
|
2
2
|
import { ClientProxy } from '@nestjs/microservices';
|
|
3
3
|
import { firstValueFrom } from 'rxjs';
|
|
4
4
|
import type { StorageRef } from '@icore/shared';
|
|
5
|
-
import { UPLOAD_CLIENT } from './upload-client.
|
|
5
|
+
import { UPLOAD_CLIENT } from './upload-client.tokens';
|
|
6
6
|
|
|
7
7
|
@Injectable()
|
|
8
8
|
export class UploadClientService {
|
|
@@ -19,3 +19,9 @@ export declare function commonTestConfig(
|
|
|
19
19
|
name: string,
|
|
20
20
|
coverageDir: string,
|
|
21
21
|
): NonNullable<UserConfig['test']>;
|
|
22
|
+
|
|
23
|
+
export declare function commonServer(
|
|
24
|
+
port: number,
|
|
25
|
+
): NonNullable<import('vite').UserConfig['server']>;
|
|
26
|
+
|
|
27
|
+
export declare function apiInfoPlugin(opts?: { proxyTarget?: string }): import('vite').Plugin;
|
|
@@ -104,3 +104,53 @@ export function commonTestConfig(name, coverageDir) {
|
|
|
104
104
|
},
|
|
105
105
|
};
|
|
106
106
|
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Shared Vite dev-server config: binds the given port and proxies `/api`
|
|
110
|
+
* to the gateway (:3001) so the client's relative API base works in dev.
|
|
111
|
+
* @param {number} port
|
|
112
|
+
* @returns {import('vite').UserConfig['server']}
|
|
113
|
+
*/
|
|
114
|
+
export function commonServer(port) {
|
|
115
|
+
return {
|
|
116
|
+
port,
|
|
117
|
+
host: 'localhost',
|
|
118
|
+
proxy: { '/api': { target: 'http://localhost:3001', changeOrigin: true } },
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function box(lines) {
|
|
123
|
+
const width = Math.max(...lines.map((l) => l.length), 48);
|
|
124
|
+
const top = `╔═${'═'.repeat(width)}═╗`;
|
|
125
|
+
const bot = `╚═${'═'.repeat(width)}═╝`;
|
|
126
|
+
const body = lines.map((l) => `║ ${l.padEnd(width)} ║`).join('\n');
|
|
127
|
+
return `\n${top}\n${body}\n${bot}`;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Prints a terminal banner on dev-server start showing the API base the client
|
|
132
|
+
* will use and the gateway proxy target, so misconfiguration is obvious.
|
|
133
|
+
* @param {{ proxyTarget?: string }} [opts]
|
|
134
|
+
* @returns {import('vite').Plugin}
|
|
135
|
+
*/
|
|
136
|
+
export function apiInfoPlugin(opts = {}) {
|
|
137
|
+
const target = opts.proxyTarget ?? 'http://localhost:3001';
|
|
138
|
+
return {
|
|
139
|
+
name: 'icore-api-info',
|
|
140
|
+
apply: 'serve',
|
|
141
|
+
configureServer(server) {
|
|
142
|
+
server.httpServer?.once('listening', () => {
|
|
143
|
+
const explicit = process.env.VITE_API_URL;
|
|
144
|
+
const lines = explicit
|
|
145
|
+
? [`API base: VITE_API_URL = ${explicit}`, '', `(gateway dev-proxy is bypassed)`]
|
|
146
|
+
: [
|
|
147
|
+
`API base: /api → proxied to ${target}`,
|
|
148
|
+
'',
|
|
149
|
+
`Gateway must be running on ${target}.`,
|
|
150
|
+
`Override with VITE_API_URL in the client .env.`,
|
|
151
|
+
];
|
|
152
|
+
server.config.logger.info(box([`ℹ iCore client API wiring`, '', ...lines]));
|
|
153
|
+
});
|
|
154
|
+
},
|
|
155
|
+
};
|
|
156
|
+
}
|
package/templates/package.json
CHANGED