@idevconn/create-icore 0.4.0 → 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.
Files changed (50) hide show
  1. package/dist/cli.js +238 -2
  2. package/dist/index.cjs +238 -2
  3. package/dist/index.js +238 -2
  4. package/package.json +1 -1
  5. package/templates/.yarn/releases/yarn-4.15.0.cjs +940 -0
  6. package/templates/.yarnrc.yml +6 -1
  7. package/templates/apps/api/src/app/app.module.ts +5 -1
  8. package/templates/apps/api/src/main.ts +12 -6
  9. package/templates/apps/microservices/auth/project.json +2 -1
  10. package/templates/apps/microservices/auth/src/app/app.module.ts +47 -23
  11. package/templates/apps/microservices/jobs/project.json +2 -1
  12. package/templates/apps/microservices/notes/project.json +2 -1
  13. package/templates/apps/microservices/notes/src/app/app.module.ts +44 -25
  14. package/templates/apps/microservices/payment/project.json +2 -1
  15. package/templates/apps/microservices/payment/src/app/app.module.ts +35 -12
  16. package/templates/apps/microservices/upload/project.json +2 -1
  17. package/templates/apps/microservices/upload/src/app/app.module.ts +48 -28
  18. package/templates/apps/templates/client-antd/.env.example +7 -0
  19. package/templates/apps/templates/client-antd/vite.config.mts +4 -4
  20. package/templates/apps/templates/client-mui/.env.example +7 -0
  21. package/templates/apps/templates/client-mui/vite.config.mts +4 -4
  22. package/templates/apps/templates/client-shadcn/.env.example +6 -1
  23. package/templates/apps/templates/client-shadcn/vite.config.mts +4 -4
  24. package/templates/libs/auth-client/src/index.ts +1 -0
  25. package/templates/libs/auth-client/src/lib/auth-client.module.ts +1 -1
  26. package/templates/libs/auth-client/src/lib/auth-client.service.ts +1 -1
  27. package/templates/libs/auth-client/src/lib/auth-client.tokens.ts +4 -0
  28. package/templates/libs/jobs-client/src/index.ts +1 -0
  29. package/templates/libs/jobs-client/src/lib/jobs-client.module.ts +1 -1
  30. package/templates/libs/jobs-client/src/lib/jobs-client.service.ts +1 -1
  31. package/templates/libs/jobs-client/src/lib/jobs-client.tokens.ts +4 -0
  32. package/templates/libs/notes-client/src/index.ts +1 -0
  33. package/templates/libs/notes-client/src/lib/notes-client.module.ts +1 -1
  34. package/templates/libs/notes-client/src/lib/notes-client.service.ts +1 -1
  35. package/templates/libs/notes-client/src/lib/notes-client.tokens.ts +4 -0
  36. package/templates/libs/payment-client/src/index.ts +1 -0
  37. package/templates/libs/payment-client/src/lib/payment-client.module.ts +1 -1
  38. package/templates/libs/payment-client/src/lib/payment-client.service.ts +1 -1
  39. package/templates/libs/payment-client/src/lib/payment-client.tokens.ts +4 -0
  40. package/templates/libs/shared/src/env.ts +88 -0
  41. package/templates/libs/shared/src/index.ts +1 -0
  42. package/templates/libs/shared/src/transport.ts +37 -0
  43. package/templates/libs/upload-client/src/index.ts +1 -0
  44. package/templates/libs/upload-client/src/lib/upload-client.module.ts +1 -1
  45. package/templates/libs/upload-client/src/lib/upload-client.service.ts +1 -1
  46. package/templates/libs/upload-client/src/lib/upload-client.tokens.ts +4 -0
  47. package/templates/libs/vite-plugins/src/index.d.mts +6 -0
  48. package/templates/libs/vite-plugins/src/index.mjs +50 -0
  49. package/templates/package.json +1 -0
  50. package/templates/tools/create-icore/_template-shell/package.json +1 -0
@@ -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.module';
5
+ import { JOBS_REDIS_URL } from './jobs-client.tokens';
6
6
 
7
7
  @Injectable()
8
8
  export class JobsClientService implements OnModuleDestroy {
@@ -0,0 +1,4 @@
1
+ // Injection token — kept in its own file so module and service can both
2
+ // import it without creating a circular dependency (which breaks DI in
3
+ // webpack-bundled NestJS apps).
4
+ export const JOBS_REDIS_URL = 'JOBS_REDIS_URL';
@@ -1,2 +1,3 @@
1
+ export * from './lib/notes-client.tokens';
1
2
  export * from './lib/notes-client.module';
2
3
  export * from './lib/notes-client.service';
@@ -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
- export const NOTES_CLIENT = 'NOTES_CLIENT';
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.module';
5
+ import { NOTES_CLIENT } from './notes-client.tokens';
6
6
 
7
7
  @Injectable()
8
8
  export class NotesClientService {
@@ -0,0 +1,4 @@
1
+ // Injection token — kept in its own file so module and service can both
2
+ // import it without creating a circular dependency (which breaks DI in
3
+ // webpack-bundled NestJS apps).
4
+ export const NOTES_CLIENT = 'NOTES_CLIENT';
@@ -1,2 +1,3 @@
1
+ export * from './lib/payment-client.tokens';
1
2
  export * from './lib/payment-client.module';
2
3
  export * from './lib/payment-client.service';
@@ -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
- export const PAYMENT_CLIENT = 'PAYMENT_CLIENT';
6
+ import { PAYMENT_CLIENT } from './payment-client.tokens';
7
7
 
8
8
  @Module({})
9
9
  export class PaymentClientModule {
@@ -7,7 +7,7 @@ import type {
7
7
  OrderResult,
8
8
  RequestOptions,
9
9
  } from '@idevconn/payment';
10
- import { PAYMENT_CLIENT } from './payment-client.module';
10
+ import { PAYMENT_CLIENT } from './payment-client.tokens';
11
11
 
12
12
  @Injectable()
13
13
  export class PaymentClientService {
@@ -0,0 +1,4 @@
1
+ // Injection token — kept in its own file so module and service can both
2
+ // import it without creating a circular dependency (which breaks DI in
3
+ // webpack-bundled NestJS apps).
4
+ export const PAYMENT_CLIENT = 'PAYMENT_CLIENT';
@@ -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,3 +1,4 @@
1
+ export * from './env';
1
2
  export * from './abilities';
2
3
  export * from './jobs';
3
4
  export * from './strategies';
@@ -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 {
@@ -1,2 +1,3 @@
1
+ export * from './lib/upload-client.tokens';
1
2
  export * from './lib/upload-client.module';
2
3
  export * from './lib/upload-client.service';
@@ -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
- export const UPLOAD_CLIENT = 'UPLOAD_CLIENT';
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.module';
5
+ import { UPLOAD_CLIENT } from './upload-client.tokens';
6
6
 
7
7
  @Injectable()
8
8
  export class UploadClientService {
@@ -0,0 +1,4 @@
1
+ // Injection token — kept in its own file so module and service can both
2
+ // import it without creating a circular dependency (which breaks DI in
3
+ // webpack-bundled NestJS apps).
4
+ export const UPLOAD_CLIENT = 'UPLOAD_CLIENT';
@@ -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
+ }
@@ -23,6 +23,7 @@
23
23
  "prepare": "husky"
24
24
  },
25
25
  "devDependencies": {
26
+ "@icore/vite-plugins": "workspace:*",
26
27
  "@eslint/js": "^9.18.0",
27
28
  "@nestjs/schematics": "^11.0.0",
28
29
  "@nestjs/testing": "^11.0.0",
@@ -23,6 +23,7 @@
23
23
  "prepare": "husky"
24
24
  },
25
25
  "devDependencies": {
26
+ "@icore/vite-plugins": "workspace:*",
26
27
  "@eslint/js": "^9.18.0",
27
28
  "@nestjs/schematics": "^11.0.0",
28
29
  "@nestjs/testing": "^11.0.0",