@idevconn/create-icore 0.5.2 → 0.6.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 (73) hide show
  1. package/dist/cli.js +36 -27
  2. package/dist/index.cjs +36 -27
  3. package/dist/index.d.cts +1 -1
  4. package/dist/index.d.ts +1 -1
  5. package/dist/index.js +36 -27
  6. package/package.json +1 -1
  7. package/templates/apps/api/.env.example +20 -0
  8. package/templates/apps/api/tsconfig.json +6 -1
  9. package/templates/apps/microservices/auth/.env.example +5 -0
  10. package/templates/apps/microservices/auth/tsconfig.json +6 -1
  11. package/templates/apps/microservices/jobs/tsconfig.json +6 -1
  12. package/templates/apps/microservices/notes/.env.example +5 -0
  13. package/templates/apps/microservices/notes/tsconfig.json +6 -1
  14. package/templates/apps/microservices/notes-e2e/src/support/global.d.ts +6 -0
  15. package/templates/apps/microservices/payment/.env.example +5 -0
  16. package/templates/apps/microservices/payment/tsconfig.json +6 -1
  17. package/templates/apps/microservices/upload/.env.example +5 -0
  18. package/templates/apps/microservices/upload/tsconfig.json +6 -1
  19. package/templates/apps/templates/client-antd/src/components/AccessDeniedPage.tsx +1 -1
  20. package/templates/apps/templates/client-antd/src/components/layout/LayoutHeader.tsx +1 -1
  21. package/templates/apps/templates/client-antd/src/components/layout/LayoutSider.tsx +3 -3
  22. package/templates/apps/templates/client-antd/src/routes/_dashboard/dashboard.tsx +2 -2
  23. package/templates/apps/templates/client-antd/src/routes/_dashboard/notes.tsx +2 -2
  24. package/templates/apps/templates/client-antd/src/routes/_dashboard/profile.tsx +2 -2
  25. package/templates/apps/templates/client-antd/src/routes/auth.callback.tsx +1 -1
  26. package/templates/apps/templates/client-antd/src/routes/auth.oauth.callback.tsx +1 -1
  27. package/templates/apps/templates/client-antd/src/routes/login.tsx +1 -1
  28. package/templates/apps/templates/client-antd/tsconfig.json +6 -1
  29. package/templates/apps/templates/client-antd-e2e/src/icore.spec.ts +2 -2
  30. package/templates/apps/templates/client-mui/src/components/AccessDeniedPage.tsx +1 -1
  31. package/templates/apps/templates/client-mui/src/components/layout/LayoutHeader.tsx +1 -1
  32. package/templates/apps/templates/client-mui/src/components/layout/LayoutSider.tsx +3 -15
  33. package/templates/apps/templates/client-mui/src/routes/_dashboard/dashboard.tsx +2 -6
  34. package/templates/apps/templates/client-mui/src/routes/_dashboard/notes.tsx +2 -2
  35. package/templates/apps/templates/client-mui/src/routes/_dashboard/profile.tsx +3 -3
  36. package/templates/apps/templates/client-mui/src/routes/auth.callback.tsx +1 -1
  37. package/templates/apps/templates/client-mui/src/routes/auth.oauth.callback.tsx +1 -1
  38. package/templates/apps/templates/client-mui/src/routes/login.tsx +3 -3
  39. package/templates/apps/templates/client-mui/tsconfig.json +6 -1
  40. package/templates/apps/templates/client-mui-e2e/src/icore.spec.ts +2 -2
  41. package/templates/apps/templates/client-shadcn/src/components/AccessDeniedPage.tsx +1 -1
  42. package/templates/apps/templates/client-shadcn/src/components/layout/LayoutSider.tsx +3 -3
  43. package/templates/apps/templates/client-shadcn/src/components/notes/DeleteNoteConfirm.tsx +1 -1
  44. package/templates/apps/templates/client-shadcn/src/components/notes/NoteDialog.tsx +3 -3
  45. package/templates/apps/templates/client-shadcn/src/components/notes/NotesTable.tsx +1 -1
  46. package/templates/apps/templates/client-shadcn/src/components/ui/button.tsx +1 -1
  47. package/templates/apps/templates/client-shadcn/src/components/ui/card.tsx +1 -1
  48. package/templates/apps/templates/client-shadcn/src/components/ui/input.tsx +1 -1
  49. package/templates/apps/templates/client-shadcn/src/components/ui/label.tsx +1 -1
  50. package/templates/apps/templates/client-shadcn/src/routes/_dashboard/dashboard.tsx +3 -9
  51. package/templates/apps/templates/client-shadcn/src/routes/_dashboard/notes.tsx +6 -6
  52. package/templates/apps/templates/client-shadcn/src/routes/_dashboard/profile.tsx +7 -7
  53. package/templates/apps/templates/client-shadcn/src/routes/auth.callback.tsx +1 -1
  54. package/templates/apps/templates/client-shadcn/src/routes/auth.oauth.callback.tsx +1 -1
  55. package/templates/apps/templates/client-shadcn/src/routes/login.tsx +19 -12
  56. package/templates/apps/templates/client-shadcn/tsconfig.json +6 -1
  57. package/templates/libs/auth-strategies/firebase/src/lib/__tests__/firebase-auth.contract.unit.test.ts +6 -3
  58. package/templates/libs/auth-strategies/supabase/src/lib/__tests__/supabase-auth.contract.unit.test.ts +5 -2
  59. package/templates/libs/db-strategies/firestore/src/lib/__tests__/firestore-db.contract.unit.test.ts +1 -2
  60. package/templates/libs/db-strategies/supabase/src/lib/__tests__/supabase-db.contract.unit.test.ts +1 -2
  61. package/templates/libs/shared/src/__tests__/cross-boundary.unit.test.ts +2 -1
  62. package/templates/libs/shared/src/__tests__/transport.unit.test.ts +47 -8
  63. package/templates/libs/shared/src/abilities/subjects.ts +12 -1
  64. package/templates/libs/shared/src/strategies/__tests__/fake-auth.contract.unit.test.ts +2 -2
  65. package/templates/libs/shared/src/strategies/__tests__/fake-db.contract.unit.test.ts +2 -2
  66. package/templates/libs/shared/src/strategies/__tests__/fake-storage.contract.unit.test.ts +2 -2
  67. package/templates/libs/shared/src/transport.ts +41 -0
  68. package/templates/libs/storage-strategies/cloudinary/src/lib/__tests__/cloudinary-storage.contract.unit.test.ts +1 -2
  69. package/templates/libs/storage-strategies/firebase/src/lib/__tests__/firebase-storage.contract.unit.test.ts +1 -2
  70. package/templates/libs/storage-strategies/supabase/src/lib/__tests__/supabase-storage.contract.unit.test.ts +1 -2
  71. package/templates/libs/vite-plugins/src/index.d.mts +5 -7
  72. package/templates/libs/vite-plugins/src/index.mjs +1 -1
  73. package/templates/libs/vite-plugins/tsconfig.json +2 -1
@@ -1,8 +1,11 @@
1
1
  import { randomUUID } from 'node:crypto';
2
2
  import { runAuthContract } from '@icore/shared/testing';
3
- import { FirebaseAuthStrategy } from '../firebase-auth.strategy';
4
- import { createMockIdentityToolkit, type MockHandle } from '../testing/mock-identity-toolkit';
5
- import { createMockAdminAuth } from '../testing/mock-admin-auth';
3
+ import {
4
+ FirebaseAuthStrategy,
5
+ createMockIdentityToolkit,
6
+ type MockHandle,
7
+ createMockAdminAuth,
8
+ } from '@icore/auth-firebase';
6
9
 
7
10
  const toolkits = new WeakMap<FirebaseAuthStrategy, MockHandle>();
8
11
 
@@ -1,6 +1,9 @@
1
1
  import { runAuthContract } from '@icore/shared/testing';
2
- import { SupabaseAuthStrategy } from '../supabase-auth.strategy';
3
- import { createMockSupabaseClient, type MockSupabaseClient } from '../testing/mock-supabase';
2
+ import {
3
+ SupabaseAuthStrategy,
4
+ createMockSupabaseClient,
5
+ type MockSupabaseClient,
6
+ } from '@icore/auth-supabase';
4
7
 
5
8
  const mocks = new WeakMap<SupabaseAuthStrategy, MockSupabaseClient>();
6
9
 
@@ -1,6 +1,5 @@
1
1
  import { runDBContract } from '@icore/shared/testing';
2
- import { FirestoreDBStrategy } from '../firestore-db.strategy';
3
- import { createMockFirestore } from '../testing/mock-firestore';
2
+ import { FirestoreDBStrategy, createMockFirestore } from '@icore/db-firestore';
4
3
 
5
4
  runDBContract('FirestoreDBStrategy', () => {
6
5
  const db = createMockFirestore();
@@ -1,6 +1,5 @@
1
1
  import { runDBContract } from '@icore/shared/testing';
2
- import { SupabaseDBStrategy } from '../supabase-db.strategy';
3
- import { createMockSupabaseDB } from '../testing/mock-supabase-postgres';
2
+ import { SupabaseDBStrategy, createMockSupabaseDB } from '@icore/db-supabase';
4
3
 
5
4
  runDBContract('SupabaseDBStrategy', () => {
6
5
  const client = createMockSupabaseDB();
@@ -12,6 +12,7 @@
12
12
  * needing a full build.
13
13
  */
14
14
  import { readdir, readFile, stat } from 'node:fs/promises';
15
+ import type { Dirent } from 'node:fs';
15
16
  import { join, resolve } from 'node:path';
16
17
  import { describe, expect, it } from 'vitest';
17
18
 
@@ -21,7 +22,7 @@ const ROOT = resolve(__dirname, '../../../../..');
21
22
 
22
23
  async function walkTs(dir: string): Promise<string[]> {
23
24
  const files: string[] = [];
24
- let entries: Awaited<ReturnType<typeof readdir>>;
25
+ let entries: Dirent<string>[];
25
26
  try {
26
27
  entries = await readdir(dir, { withFileTypes: true });
27
28
  } catch {
@@ -19,8 +19,8 @@ describe('buildTransport', () => {
19
19
  });
20
20
 
21
21
  it('defaults to TCP when ${PREFIX}_TRANSPORT is unset', () => {
22
- process.env.AUTH_HOST = '127.0.0.1';
23
- process.env.AUTH_PORT = '4001';
22
+ process.env['AUTH_HOST'] = '127.0.0.1';
23
+ process.env['AUTH_PORT'] = '4001';
24
24
  const opts = buildTransport('AUTH');
25
25
  expect(opts.transport).toBe(Transport.TCP);
26
26
  const tcp = opts.options as { host: string; port: number };
@@ -29,8 +29,8 @@ describe('buildTransport', () => {
29
29
  });
30
30
 
31
31
  it('selects Redis when ${PREFIX}_TRANSPORT=redis', () => {
32
- process.env.AUTH_TRANSPORT = 'redis';
33
- process.env.AUTH_REDIS_URL = 'redis://localhost:6379';
32
+ process.env['AUTH_TRANSPORT'] = 'redis';
33
+ process.env['AUTH_REDIS_URL'] = 'redis://localhost:6379';
34
34
  const opts = buildTransport('AUTH');
35
35
  expect(opts.transport).toBe(Transport.REDIS);
36
36
  const redis = opts.options as { url: string; retryAttempts: number; retryDelay: number };
@@ -42,8 +42,8 @@ describe('buildTransport', () => {
42
42
  });
43
43
 
44
44
  it('selects NATS when ${PREFIX}_TRANSPORT=nats', () => {
45
- process.env.AUTH_TRANSPORT = 'nats';
46
- process.env.AUTH_NATS_URL = 'nats://localhost:4222,nats://localhost:4223';
45
+ process.env['AUTH_TRANSPORT'] = 'nats';
46
+ process.env['AUTH_NATS_URL'] = 'nats://localhost:4222,nats://localhost:4223';
47
47
  const opts = buildTransport('AUTH');
48
48
  expect(opts.transport).toBe(Transport.NATS);
49
49
  const nats = opts.options as {
@@ -58,13 +58,52 @@ describe('buildTransport', () => {
58
58
  expect(nats.maxReconnectAttempts).toBe(-1);
59
59
  });
60
60
 
61
+ it('selects MQTT when ${PREFIX}_TRANSPORT=mqtt', () => {
62
+ process.env.AUTH_TRANSPORT = 'mqtt';
63
+ process.env.AUTH_MQTT_URL = 'mqtt://localhost:1883';
64
+ const opts = buildTransport('AUTH');
65
+ expect(opts.transport).toBe(Transport.MQTT);
66
+ expect((opts.options as { url: string }).url).toBe('mqtt://localhost:1883');
67
+ });
68
+
69
+ it('selects RMQ when ${PREFIX}_TRANSPORT=rmq', () => {
70
+ process.env.AUTH_TRANSPORT = 'rmq';
71
+ process.env.AUTH_RMQ_URL = 'amqp://localhost:5672';
72
+ process.env.AUTH_RMQ_QUEUE = 'auth_queue';
73
+ const opts = buildTransport('AUTH');
74
+ expect(opts.transport).toBe(Transport.RMQ);
75
+ const o = opts.options as { urls: string[]; queue: string };
76
+ expect(o.urls).toEqual(['amqp://localhost:5672']);
77
+ expect(o.queue).toBe('auth_queue');
78
+ });
79
+
80
+ it('selects Kafka when ${PREFIX}_TRANSPORT=kafka (brokers + derived ids)', () => {
81
+ process.env.AUTH_TRANSPORT = 'kafka';
82
+ process.env.AUTH_KAFKA_BROKERS = 'localhost:9092,localhost:9093';
83
+ const opts = buildTransport('AUTH');
84
+ expect(opts.transport).toBe(Transport.KAFKA);
85
+ const o = opts.options as {
86
+ client: { brokers: string[]; clientId: string };
87
+ consumer: { groupId: string };
88
+ };
89
+ expect(o.client.brokers).toEqual(['localhost:9092', 'localhost:9093']);
90
+ expect(o.client.clientId).toBe('auth-client'); // derived from prefix
91
+ expect(o.consumer.groupId).toBe('auth-consumer');
92
+ });
93
+
94
+ it('throws when a broker var is missing (rmq needs queue)', () => {
95
+ process.env.AUTH_TRANSPORT = 'rmq';
96
+ process.env.AUTH_RMQ_URL = 'amqp://localhost:5672';
97
+ expect(() => buildTransport('AUTH')).toThrow(/AUTH_RMQ_QUEUE/);
98
+ });
99
+
61
100
  it('throws on unknown transport', () => {
62
- process.env.AUTH_TRANSPORT = 'sqs';
101
+ process.env['AUTH_TRANSPORT'] = 'sqs';
63
102
  expect(() => buildTransport('AUTH')).toThrow(/sqs/);
64
103
  });
65
104
 
66
105
  it('throws when a required env var is missing', () => {
67
- process.env.AUTH_TRANSPORT = 'redis';
106
+ process.env['AUTH_TRANSPORT'] = 'redis';
68
107
  expect(() => buildTransport('AUTH')).toThrow(/AUTH_REDIS_URL/);
69
108
  });
70
109
  });
@@ -1,2 +1,13 @@
1
+ import type { InferSubjects } from '@casl/ability';
2
+
1
3
  export type AbilityAction = 'manage' | 'create' | 'read' | 'update' | 'delete';
2
- export type AbilitySubject = 'all' | 'User' | 'Profile' | 'Note';
4
+
5
+ // NoteSubject is the shaped object passed to subject('Note', { ... }) in tests
6
+ // and on the frontend <Can>. Included in AbilitySubject so ability.can() accepts
7
+ // tagged instances, not just the string name.
8
+ export interface NoteSubject {
9
+ id: string;
10
+ ownerId: string;
11
+ }
12
+
13
+ export type AbilitySubject = InferSubjects<NoteSubject> | 'all' | 'User' | 'Profile' | 'Note';
@@ -1,5 +1,5 @@
1
- import { FakeAuthStrategy } from '../fakes/fake-auth';
2
- import { runAuthContract } from './auth.contract.unit.test';
1
+ import { FakeAuthStrategy } from '@icore/shared';
2
+ import { runAuthContract } from '@icore/shared/testing';
3
3
 
4
4
  runAuthContract('FakeAuthStrategy', () => new FakeAuthStrategy(), {
5
5
  getMagicLinkToken: (strategy, email) =>
@@ -1,4 +1,4 @@
1
- import { FakeDBStrategy } from '../fakes/fake-db';
2
- import { runDBContract } from './db.contract.unit.test';
1
+ import { FakeDBStrategy } from '@icore/shared';
2
+ import { runDBContract } from '@icore/shared/testing';
3
3
 
4
4
  runDBContract('FakeDBStrategy', () => new FakeDBStrategy());
@@ -1,4 +1,4 @@
1
- import { FakeStorageStrategy } from '../fakes/fake-storage';
2
- import { runStorageContract } from './storage.contract.unit.test';
1
+ import { FakeStorageStrategy } from '@icore/shared';
2
+ import { runStorageContract } from '@icore/shared/testing';
3
3
 
4
4
  runStorageContract('FakeStorageStrategy', () => new FakeStorageStrategy());
@@ -10,6 +10,12 @@ function transportKeys(prefix: string, kind: string): string[] {
10
10
  return [`${prefix}_REDIS_URL`];
11
11
  case 'nats':
12
12
  return [`${prefix}_NATS_URL`];
13
+ case 'mqtt':
14
+ return [`${prefix}_MQTT_URL`];
15
+ case 'rmq':
16
+ return [`${prefix}_RMQ_URL`, `${prefix}_RMQ_QUEUE`];
17
+ case 'kafka':
18
+ return [`${prefix}_KAFKA_BROKERS`];
13
19
  default:
14
20
  return [];
15
21
  }
@@ -98,6 +104,41 @@ export function buildTransport(prefix: string): ClientOptions {
98
104
  reconnectTimeWait: 2000,
99
105
  },
100
106
  } as unknown as ClientOptions;
107
+ case 'mqtt':
108
+ // mqtt.js auto-reconnects (reconnectPeriod default) so a dropped broker
109
+ // re-attaches; a broker that's down on boot is handled by the
110
+ // bootstrapMicroservice() retry, same as nats.
111
+ return {
112
+ transport: Transport.MQTT,
113
+ options: {
114
+ url: required(`${prefix}_MQTT_URL`),
115
+ },
116
+ } as unknown as ClientOptions;
117
+ case 'rmq':
118
+ // amqp-connection-manager reconnects in the background; the initial
119
+ // connect failure is caught by bootstrapMicroservice() and retried.
120
+ return {
121
+ transport: Transport.RMQ,
122
+ options: {
123
+ urls: required(`${prefix}_RMQ_URL`).split(','),
124
+ queue: required(`${prefix}_RMQ_QUEUE`),
125
+ queueOptions: { durable: false },
126
+ },
127
+ } as unknown as ClientOptions;
128
+ case 'kafka':
129
+ // kafkajs retries broker connections internally; clientId defaults from
130
+ // the prefix, and the consumer needs a groupId.
131
+ return {
132
+ transport: Transport.KAFKA,
133
+ options: {
134
+ client: {
135
+ clientId:
136
+ process.env[`${prefix}_KAFKA_CLIENT_ID`]?.trim() || `${prefix.toLowerCase()}-client`,
137
+ brokers: required(`${prefix}_KAFKA_BROKERS`).split(','),
138
+ },
139
+ consumer: { groupId: `${prefix.toLowerCase()}-consumer` },
140
+ },
141
+ } as unknown as ClientOptions;
101
142
  default:
102
143
  throw new Error(`Unknown transport: ${kind}`);
103
144
  }
@@ -1,6 +1,5 @@
1
1
  import { runStorageContract } from '@icore/shared/testing';
2
- import { CloudinaryStorageStrategy } from '../cloudinary-storage.strategy.js';
3
- import { createMockCloudinary } from '../testing/mock-cloudinary.js';
2
+ import { CloudinaryStorageStrategy, createMockCloudinary } from '@icore/storage-cloudinary';
4
3
 
5
4
  runStorageContract('CloudinaryStorageStrategy', () => {
6
5
  const api = createMockCloudinary();
@@ -1,6 +1,5 @@
1
1
  import { runStorageContract } from '@icore/shared/testing';
2
- import { FirebaseStorageStrategy } from '../firebase-storage.strategy.js';
3
- import { createMockFirebaseBucket } from '../testing/mock-firebase-storage.js';
2
+ import { FirebaseStorageStrategy, createMockFirebaseBucket } from '@icore/storage-firebase';
4
3
 
5
4
  runStorageContract('FirebaseStorageStrategy', () => {
6
5
  const bucket = createMockFirebaseBucket('icore-uploads');
@@ -1,6 +1,5 @@
1
1
  import { runStorageContract } from '@icore/shared/testing';
2
- import { SupabaseStorageStrategy } from '../supabase-storage.strategy';
3
- import { createMockSupabaseStorageClient } from '../testing/mock-supabase-storage';
2
+ import { SupabaseStorageStrategy, createMockSupabaseStorageClient } from '@icore/storage-supabase';
4
3
 
5
4
  runStorageContract('SupabaseStorageStrategy', () => {
6
5
  const client = createMockSupabaseStorageClient('icore-uploads');
@@ -1,5 +1,5 @@
1
- import type { Plugin } from 'vite';
2
- import type { UserConfig } from 'vitest/config';
1
+ import type { Plugin, UserConfig } from 'vite';
2
+ import type { TestUserConfig } from 'vitest/config';
3
3
 
4
4
  export declare function noServerModulesPlugin(): Plugin;
5
5
 
@@ -18,10 +18,8 @@ export declare function commonManualChunks(
18
18
  export declare function commonTestConfig(
19
19
  name: string,
20
20
  coverageDir: string,
21
- ): NonNullable<UserConfig['test']>;
21
+ ): NonNullable<TestUserConfig['test']>;
22
22
 
23
- export declare function commonServer(
24
- port: number,
25
- ): NonNullable<import('vite').UserConfig['server']>;
23
+ export declare function commonServer(port: number): NonNullable<UserConfig['server']>;
26
24
 
27
- export declare function apiInfoPlugin(opts?: { proxyTarget?: string }): import('vite').Plugin;
25
+ export declare function apiInfoPlugin(opts?: { proxyTarget?: string }): Plugin;
@@ -88,7 +88,7 @@ export function commonManualChunks(uiChunkFn) {
88
88
  *
89
89
  * @param {string} name - project name (e.g. 'client-shadcn')
90
90
  * @param {string} coverageDir - relative path to coverage output dir
91
- * @returns {import('vitest/config').UserConfig['test']}
91
+ * @returns {import('vitest/config').TestUserConfig['test']}
92
92
  */
93
93
  export function commonTestConfig(name, coverageDir) {
94
94
  return {
@@ -1,7 +1,8 @@
1
1
  {
2
2
  "extends": "../../tsconfig.base.json",
3
3
  "compilerOptions": {
4
- "module": "commonjs",
4
+ "module": "esnext",
5
+ "moduleResolution": "bundler",
5
6
  "forceConsistentCasingInFileNames": true,
6
7
  "strict": true,
7
8
  "importHelpers": true,