nebula-starter-kit 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (81) hide show
  1. package/README.md +107 -0
  2. package/dist/index.js +8 -0
  3. package/dist/run.js +112 -0
  4. package/dist/utils/addService.js +204 -0
  5. package/dist/utils/appName.js +75 -0
  6. package/dist/utils/deployNow.js +18 -0
  7. package/dist/utils/generateFiles.js +326 -0
  8. package/dist/utils/generateSchemas.js +58 -0
  9. package/dist/utils/listServices.js +24 -0
  10. package/dist/utils/replaceServiceNames.js +42 -0
  11. package/dist/utils/telemetryAddon.js +91 -0
  12. package/package.json +31 -0
  13. package/templates/core/audit/audit.controller.ts +125 -0
  14. package/templates/core/audit/audit.module.ts +10 -0
  15. package/templates/core/audit/audit.schema.ts +14 -0
  16. package/templates/core/audit/audit.service.ts +47 -0
  17. package/templates/core/auth/auth.controller.ts +207 -0
  18. package/templates/core/auth/auth.dto.ts +50 -0
  19. package/templates/core/auth/auth.module.ts +10 -0
  20. package/templates/core/auth/auth.service.ts +178 -0
  21. package/templates/core/auth/utils.ts +160 -0
  22. package/templates/core/constants/environment.db.ts +27 -0
  23. package/templates/core/constants/environment.module.ts +9 -0
  24. package/templates/core/constants/environment.service.ts +69 -0
  25. package/templates/core/core.controller.ts +35 -0
  26. package/templates/core/core.module.ts +22 -0
  27. package/templates/core/database/database.module.ts +20 -0
  28. package/templates/core/database/database.provider.ts +32 -0
  29. package/templates/core/database/database.service.ts +168 -0
  30. package/templates/core/database/database.types.ts +13 -0
  31. package/templates/core/filters/audit.decorator.ts +5 -0
  32. package/templates/core/filters/audit.interceptor.ts +74 -0
  33. package/templates/core/filters/http-exception.filter.ts +43 -0
  34. package/templates/core/filters/success-message.decorator.ts +5 -0
  35. package/templates/core/filters/success-response.interceptor.ts +35 -0
  36. package/templates/core/summarize/summarize.controller.ts +74 -0
  37. package/templates/core/summarize/summarize.dto.ts +13 -0
  38. package/templates/core/summarize/summarize.module.ts +9 -0
  39. package/templates/core/summarize/summarize.service.ts +54 -0
  40. package/templates/nest-cli.json +8 -0
  41. package/templates/package.json +52 -0
  42. package/templates/service/src/__name__.controller.ts +15 -0
  43. package/templates/service/src/__name__.module.ts +11 -0
  44. package/templates/service/src/__name__.schema.ts +15 -0
  45. package/templates/service/src/__name__.service.ts +12 -0
  46. package/templates/service/src/lambda.ts +60 -0
  47. package/templates/tsconfig.json +28 -0
  48. package/templates/ui/README.md +36 -0
  49. package/templates/ui/eslint.config.mjs +18 -0
  50. package/templates/ui/next.config.ts +8 -0
  51. package/templates/ui/package.json +33 -0
  52. package/templates/ui/postcss.config.mjs +7 -0
  53. package/templates/ui/public/file.svg +1 -0
  54. package/templates/ui/public/globe.svg +1 -0
  55. package/templates/ui/public/next.svg +1 -0
  56. package/templates/ui/public/vercel.svg +1 -0
  57. package/templates/ui/public/window.svg +1 -0
  58. package/templates/ui/src/app/LandingPage.tsx +98 -0
  59. package/templates/ui/src/app/ai/summarize/page.tsx +115 -0
  60. package/templates/ui/src/app/context/AuthContext.tsx +48 -0
  61. package/templates/ui/src/app/favicon.ico +0 -0
  62. package/templates/ui/src/app/globals.css +26 -0
  63. package/templates/ui/src/app/layout.tsx +37 -0
  64. package/templates/ui/src/app/page.tsx +7 -0
  65. package/templates/ui/src/app/services/page.tsx +99 -0
  66. package/templates/ui/src/components/Auth.css +252 -0
  67. package/templates/ui/src/components/Auth.tsx +455 -0
  68. package/templates/ui/src/components/Error.tsx +32 -0
  69. package/templates/ui/src/components/FormInput.tsx +77 -0
  70. package/templates/ui/src/components/Loading.tsx +10 -0
  71. package/templates/ui/src/components/Login.tsx +171 -0
  72. package/templates/ui/src/components/Popup.css +90 -0
  73. package/templates/ui/src/components/Signup.tsx +155 -0
  74. package/templates/ui/src/utils/axiosInstance.ts +37 -0
  75. package/templates/ui/src/utils/axiosRawInstance.ts +33 -0
  76. package/templates/ui/src/utils/util.constant.ts +0 -0
  77. package/templates/ui/src/utils/util.function.ts +165 -0
  78. package/templates/ui/src/utils/util.type.ts +64 -0
  79. package/templates/ui/src/utils/variables.ts +6 -0
  80. package/templates/ui/tailwind.config.js +8 -0
  81. package/templates/ui/tsconfig.json +43 -0
@@ -0,0 +1,326 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.generateNextJSEnv = exports.generateRootEnv = exports.generateServerlessFiles = exports.generateInfraCognitoFile = exports.generateInfraBucketFile = exports.generateInfraApiFile = exports.generateNebulaJson = exports.generateRootLambda = void 0;
4
+ const fs = require('fs-extra');
5
+ const path = require('path');
6
+ const addService_1 = require("./addService");
7
+ const replaceServiceNames_1 = require("./replaceServiceNames");
8
+ const telemetryAddon_1 = require("./telemetryAddon");
9
+ const region = (0, telemetryAddon_1.getAwsRegion)();
10
+ const generateRootLambda = (apiName = 'API', services = [], directory) => {
11
+ console.log(services, apiName);
12
+ apiName = apiName.charAt(0).toUpperCase() + apiName.slice(1);
13
+ const fileContent = `export const handler = async () => {
14
+
15
+ const services = ${JSON.stringify(services)};
16
+
17
+ return {
18
+ statusCode: 200,
19
+ headers: {
20
+ "Content-Type": "application/json",
21
+ 'Access-Control-Allow-Origin': '*', // match your cors config
22
+ 'Access-Control-Allow-Headers': 'Content-Type,Authorization',
23
+ 'Access-Control-Allow-Methods': 'OPTIONS,POST,GET,PUT,DELETE',
24
+ },
25
+ body: JSON.stringify({
26
+ name: "${apiName} API",
27
+ services,
28
+ message: "Welcome to the ${apiName} API"
29
+ })
30
+ }
31
+ }
32
+ `;
33
+ const outputPath = path.join(directory, 'infra/api/src/root.ts');
34
+ fs.mkdirSync(path.dirname(outputPath), { recursive: true });
35
+ fs.writeFileSync(outputPath, fileContent);
36
+ console.log('✅ Root API lambda generated from nebula.json');
37
+ };
38
+ exports.generateRootLambda = generateRootLambda;
39
+ const generateNebulaJson = (appName, selectedServices, OUTPUT) => {
40
+ const nebulaConfig = {
41
+ name: appName,
42
+ runtime: 'nestjs',
43
+ architecture: 'multi-lambda',
44
+ deployment: 'serverless-compose',
45
+ services: selectedServices,
46
+ region,
47
+ };
48
+ fs.writeJsonSync(path.join(OUTPUT, 'nebula.json'), nebulaConfig, {
49
+ spaces: 2,
50
+ });
51
+ };
52
+ exports.generateNebulaJson = generateNebulaJson;
53
+ const generateInfraApiFile = async (appName, OUTPUT) => {
54
+ const infraDir = path.join(OUTPUT, 'infra/api');
55
+ await fs.ensureDir(infraDir);
56
+ const infraServerlessTemplate = `service: ${appName}-shared-api
57
+
58
+ provider:
59
+ name: aws
60
+ runtime: nodejs20.x
61
+ stage: \${opt:stage, 'dev'}
62
+ region: ${region}
63
+
64
+ functions:
65
+ root:
66
+ handler: src/root.handler
67
+ events:
68
+ - http:
69
+ path: /
70
+ method: get
71
+ cors: true
72
+
73
+ resources:
74
+ Resources:
75
+ ApiGatewayRestApi:
76
+ Type: AWS::ApiGateway::RestApi
77
+ Properties:
78
+ Name: ${appName}-shared-api
79
+
80
+ Outputs:
81
+ ApiGatewayRestApiId:
82
+ Value: !Ref ApiGatewayRestApi
83
+
84
+ ApiGatewayRootResourceId:
85
+ Value: !GetAtt ApiGatewayRestApi.RootResourceId
86
+ `;
87
+ await fs.writeFile(path.join(infraDir, 'serverless.yml'), infraServerlessTemplate);
88
+ };
89
+ exports.generateInfraApiFile = generateInfraApiFile;
90
+ const generateInfraBucketFile = async (appName, OUTPUT) => {
91
+ const bucketDir = path.join(OUTPUT, 'infra/buckets');
92
+ await fs.ensureDir(bucketDir);
93
+ const bucketServerlessTemplate = `service: ${appName}-buckets
94
+
95
+ plugins:
96
+ - serverless-s3-sync
97
+
98
+ custom:
99
+ # cloudfrontDistributionId: E2OW0GLR4XXOBS
100
+ # scripts:
101
+ # hooks:
102
+ # after:deploy:deploy: >
103
+ # aws cloudfront create-invalidation
104
+ # --distribution-id \${self:custom.cloudfrontDistributionId}
105
+ # --paths "/*"
106
+ # scripts:
107
+ # hooks:
108
+ # # Run BEFORE upload to S3
109
+ # before:deploy:deploy: node scripts/generate-env.js
110
+
111
+ s3Sync:
112
+ - bucketName: ${appName}-bucket-\${aws:accountId}-\${self:provider.stage}
113
+ localDir: ../../ui/out
114
+ deleteRemoved: true
115
+
116
+ params:
117
+ dev:
118
+ region: ${region}
119
+ disableLogs: true
120
+ # publicCloudfrontSubdomain: "${appName}.nebulalogix.io"
121
+
122
+ provider:
123
+ name: aws
124
+ stage: \${opt:stage, 'dev'}
125
+ region: ${region}
126
+ environment:
127
+ DEPLOYMENT_TIME: \${sls:instanceId}
128
+
129
+ resources:
130
+ Resources:
131
+ S3WebsiteBucket:
132
+ Type: AWS::S3::Bucket
133
+ Properties:
134
+ BucketName: ${appName}-bucket-\${aws:accountId}-\${self:provider.stage}
135
+ WebsiteConfiguration:
136
+ IndexDocument: index.html
137
+ ErrorDocument: index.html
138
+ PublicAccessBlockConfiguration:
139
+ BlockPublicAcls: false
140
+ IgnorePublicAcls: false
141
+ BlockPublicPolicy: false
142
+ RestrictPublicBuckets: false
143
+
144
+ S3BucketPolicy:
145
+ Type: AWS::S3::BucketPolicy
146
+ Properties:
147
+ Bucket: !Ref S3WebsiteBucket
148
+ PolicyDocument:
149
+ Version: "2012-10-17"
150
+ Statement:
151
+ - Effect: Allow
152
+ Principal: "*"
153
+ Action:
154
+ - s3:GetObject
155
+ Resource: !Sub "\${S3WebsiteBucket.Arn}/*"
156
+
157
+ # Allow your AWS account to deploy files
158
+ - Effect: Allow
159
+ Principal:
160
+ AWS: !Sub "arn:aws:iam::\${AWS::AccountId}:root"
161
+ Action:
162
+ - s3:PutObject
163
+ - s3:DeleteObject
164
+ - s3:ListBucket
165
+ Resource:
166
+ - !Sub "\${S3WebsiteBucket.Arn}"
167
+ - !Sub "\${S3WebsiteBucket.Arn}/*"
168
+
169
+
170
+ Outputs:
171
+ WebsiteURL:
172
+ Value: !GetAtt S3WebsiteBucket.WebsiteURL
173
+ Description: Public website URL
174
+ `;
175
+ await fs.writeFile(path.join(bucketDir, 'serverless.yml'), bucketServerlessTemplate);
176
+ };
177
+ exports.generateInfraBucketFile = generateInfraBucketFile;
178
+ const generateInfraCognitoFile = async (appName, OUTPUT) => {
179
+ const cognitoDir = path.join(OUTPUT, 'infra/cognito');
180
+ await fs.ensureDir(cognitoDir);
181
+ const cognitoServerlessTemplate = `service: ${appName}-cognito
182
+
183
+ provider:
184
+ name: aws
185
+ runtime: nodejs20.x
186
+ stage: \${opt:stage, 'dev'}
187
+ region: us-east-1
188
+
189
+ resources:
190
+ Resources:
191
+ # Cognito User Pool
192
+ UserPool:
193
+ Type: AWS::Cognito::UserPool
194
+ Properties:
195
+ UserPoolName: ${appName}-userpool
196
+ AutoVerifiedAttributes:
197
+ - email
198
+ UsernameAttributes:
199
+ - email
200
+ Policies:
201
+ PasswordPolicy:
202
+ MinimumLength: 8
203
+ RequireLowercase: true
204
+ RequireUppercase: true
205
+ RequireNumbers: true
206
+
207
+ # Cognito User Pool Client
208
+ UserPoolClient:
209
+ Type: AWS::Cognito::UserPoolClient
210
+ Properties:
211
+ ClientName: ${appName}-userpoolclient
212
+ UserPoolId: !Ref UserPool
213
+ ExplicitAuthFlows:
214
+ - ALLOW_USER_PASSWORD_AUTH
215
+ - ALLOW_REFRESH_TOKEN_AUTH
216
+ GenerateSecret: false
217
+
218
+ # Cognito User Pool Domain
219
+ UserPoolDomain:
220
+ Type: AWS::Cognito::UserPoolDomain
221
+ Properties:
222
+ Domain: ${appName}-userpooldomain
223
+ UserPoolId: !Ref UserPool
224
+
225
+ # 4️⃣ Lambda Role with Cognito permissions
226
+ LambdaCognitoRole:
227
+ Type: AWS::IAM::Role
228
+ Properties:
229
+ RoleName: ${appName}-lambda-cognito-role-\${self:provider.stage}
230
+ AssumeRolePolicyDocument:
231
+ Version: '2012-10-17'
232
+ Statement:
233
+ - Effect: Allow
234
+ Principal:
235
+ Service:
236
+ - lambda.amazonaws.com
237
+ Action:
238
+ - sts:AssumeRole
239
+ Policies:
240
+ - PolicyName: CognitoAccessPolicy
241
+ PolicyDocument:
242
+ Version: '2012-10-17'
243
+ Statement:
244
+ - Effect: Allow
245
+ Action:
246
+ - cognito-idp:AdminCreateUser
247
+ - cognito-idp:AdminDeleteUser
248
+ - cognito-idp:AdminGetUser
249
+ - cognito-idp:AdminUpdateUserAttributes
250
+ - cognito-idp:ListUsers
251
+ - cognito-idp:AdminInitiateAuth
252
+ Resource: !GetAtt UserPool.Arn
253
+
254
+ Outputs:
255
+ # Export the User Pool ID
256
+ CognitoUserPoolId:
257
+ Value: !Ref UserPool
258
+
259
+ # Export the User Pool Client ID
260
+ CognitoClientId:
261
+ Value: !Ref UserPoolClient
262
+
263
+ # Export the User Pool Domain
264
+ CognitoUserPoolDomain:
265
+ Value: !Ref UserPoolDomain
266
+ `;
267
+ await fs.writeFile(path.join(cognitoDir, 'serverless.yml'), cognitoServerlessTemplate);
268
+ };
269
+ exports.generateInfraCognitoFile = generateInfraCognitoFile;
270
+ const generateServerlessFiles = async (selectedServices, ROOT, OUTPUT, appNamePlaceHolder) => {
271
+ for (const service of selectedServices) {
272
+ const serviceOutput = path.join(OUTPUT, 'services', service);
273
+ await fs.ensureDir(serviceOutput);
274
+ // copy base nest template
275
+ fs.copySync(path.join(ROOT, 'templates/service'), serviceOutput);
276
+ // update service names
277
+ await (0, replaceServiceNames_1.replaceServiceNames)(serviceOutput, service, appNamePlaceHolder);
278
+ console.log(`✅ Service ${service} created\n`);
279
+ // Generate serverless.yml per service
280
+ await fs.writeFile(path.join(serviceOutput, 'serverless.yml'), (0, addService_1.serverlessTemplate)(service, appNamePlaceHolder));
281
+ // Generate root serverless-compose.yml
282
+ let composeContent = `
283
+ services:
284
+ api:
285
+ path: infra/api
286
+
287
+ cognito:
288
+ path: infra/cognito
289
+
290
+ buckets:
291
+ path: infra/buckets
292
+ `;
293
+ for (const service of selectedServices) {
294
+ composeContent += `
295
+ ${service}:
296
+ path: services/${service}
297
+ params:
298
+ ApiGatewayRestApiId: \${api.ApiGatewayRestApiId}
299
+ ApiGatewayRootResourceId: \${api.ApiGatewayRootResourceId}
300
+ CognitoClientId: \${cognito.CognitoClientId}
301
+ CognitoUserPoolId: \${cognito.CognitoUserPoolId}
302
+ `;
303
+ }
304
+ await fs.writeFile(path.join(OUTPUT, 'serverless-compose.yml'), composeContent);
305
+ }
306
+ };
307
+ exports.generateServerlessFiles = generateServerlessFiles;
308
+ const generateRootEnv = (OUTPUT) => {
309
+ const envRootContent = `COGNITO_USER_POOL_ID=
310
+ COGNITO_CLIENT_ID=
311
+ `;
312
+ fs.writeFileSync(path.join(OUTPUT, '.env'), envRootContent.trim());
313
+ console.log('✅ Root env generated:');
314
+ console.log(envRootContent);
315
+ };
316
+ exports.generateRootEnv = generateRootEnv;
317
+ const generateNextJSEnv = (appName, uiDir, selectedServices) => {
318
+ const envContent = `NEXT_PUBLIC_APP_NAME=${appName.charAt(0).toUpperCase() + appName.slice(1)}
319
+ NEXT_PUBLIC_API_URL=
320
+ NEXT_PUBLIC_SERVICES=${selectedServices.join(',')}
321
+ `;
322
+ fs.writeFileSync(path.join(uiDir, '.env.local'), envContent.trim());
323
+ console.log('✅ Next.js env generated:');
324
+ console.log(envContent);
325
+ };
326
+ exports.generateNextJSEnv = generateNextJSEnv;
@@ -0,0 +1,58 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.generateSchemas = void 0;
37
+ const fs = __importStar(require("fs-extra"));
38
+ const path = __importStar(require("path"));
39
+ const generateSchemas = (OUTPUT) => {
40
+ const nebula = JSON.parse(fs.readFileSync(path.join(OUTPUT, 'nebula.json'), 'utf-8'));
41
+ let imports = '';
42
+ let exportsArr = [];
43
+ for (const service of nebula.services) {
44
+ const importName = `${service.toUpperCase()}_TABLE_SCHEMA`;
45
+ imports += `import { ${importName} } from "@services/${service}/src/${service}.schema";\n`;
46
+ exportsArr.push(importName);
47
+ }
48
+ const content = `
49
+ ${imports}
50
+
51
+ export const GENERATED_SCHEMAS = [
52
+ ${exportsArr.join(',\n ')}
53
+ ];
54
+ `;
55
+ fs.writeFileSync(path.join(OUTPUT, 'core/database/generated-schemas.ts'), content);
56
+ console.log('✅ Schemas generated');
57
+ };
58
+ exports.generateSchemas = generateSchemas;
@@ -0,0 +1,24 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.listServices = listServices;
7
+ const fs_extra_1 = __importDefault(require("fs-extra"));
8
+ const path_1 = __importDefault(require("path"));
9
+ function listServices() {
10
+ const ROOT = process.cwd();
11
+ const nebulaPath = path_1.default.join(ROOT, 'nebula.json');
12
+ if (!fs_extra_1.default.existsSync(nebulaPath)) {
13
+ throw new Error('Not a Nebula project');
14
+ }
15
+ const config = fs_extra_1.default.readJsonSync(nebulaPath);
16
+ if (config.services.length) {
17
+ console.log('Services:');
18
+ config.services.forEach((s) => console.log(`- ${s}`));
19
+ console.log('\n');
20
+ }
21
+ else {
22
+ console.log('No services present');
23
+ }
24
+ }
@@ -0,0 +1,42 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.replaceServiceNames = replaceServiceNames;
7
+ const fs_extra_1 = __importDefault(require("fs-extra"));
8
+ const path_1 = __importDefault(require("path"));
9
+ async function replaceServiceNames(dir, service, appName) {
10
+ const name = service.toLowerCase();
11
+ const NAME = service.toUpperCase();
12
+ const Name = name.charAt(0).toUpperCase() + name.slice(1);
13
+ const files = await fs_extra_1.default.readdir(dir);
14
+ for (const file of files) {
15
+ const oldPath = path_1.default.join(dir, file);
16
+ const stat = await fs_extra_1.default.stat(oldPath);
17
+ if (stat.isDirectory()) {
18
+ await replaceServiceNames(oldPath, service, appName);
19
+ }
20
+ else {
21
+ // ✅ rename file if contains placeholder
22
+ const newFile = file
23
+ .replace(/__name__/g, name)
24
+ .replace(/__Name__/g, Name)
25
+ .replace(/__NAME__/g, NAME);
26
+ const newPath = path_1.default.join(dir, newFile);
27
+ if (newFile !== file) {
28
+ await fs_extra_1.default.rename(oldPath, newPath);
29
+ }
30
+ // ✅ replace content
31
+ let content = await fs_extra_1.default.readFile(newPath, 'utf8');
32
+ if (appName) {
33
+ content = content.replace(/__appName__/g, appName);
34
+ }
35
+ content = content
36
+ .replace(/__name__/g, name)
37
+ .replace(/__Name__/g, Name)
38
+ .replace(/__NAME__/g, NAME);
39
+ await fs_extra_1.default.writeFile(newPath, content);
40
+ }
41
+ }
42
+ }
@@ -0,0 +1,91 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.getAnonymousId = getAnonymousId;
7
+ exports.sendTelemetry = sendTelemetry;
8
+ exports.promptTelemetry = promptTelemetry;
9
+ exports.getAwsRegion = getAwsRegion;
10
+ const https_1 = __importDefault(require("https"));
11
+ const fs_extra_1 = __importDefault(require("fs-extra"));
12
+ const os_1 = __importDefault(require("os"));
13
+ const path_1 = __importDefault(require("path"));
14
+ const inquirer_1 = __importDefault(require("inquirer"));
15
+ const package_json_1 = __importDefault(require("../../package.json"));
16
+ const CONFIG_PATH = path_1.default.join(os_1.default.homedir(), '.nebula', 'config.json');
17
+ async function getAnonymousId() {
18
+ if (await fs_extra_1.default.pathExists(CONFIG_PATH)) {
19
+ return (await fs_extra_1.default.readJson(CONFIG_PATH)).anonymousId;
20
+ }
21
+ const anonymousId = crypto.randomUUID();
22
+ await fs_extra_1.default.ensureDir(path_1.default.dirname(CONFIG_PATH));
23
+ await fs_extra_1.default.writeJson(CONFIG_PATH, { anonymousId });
24
+ console.log('✅ Created anonymous ID for telemetry:', anonymousId);
25
+ return anonymousId;
26
+ }
27
+ function sendTelemetry(payload) {
28
+ const data = JSON.stringify(payload, null, 2);
29
+ console.log(`✅ Sending telemetry: ${data}`);
30
+ const req = https_1.default.request('https://telemetry.yourdomain.com/events', {
31
+ method: 'POST',
32
+ headers: {
33
+ 'Content-Type': 'application/json',
34
+ 'Content-Length': data.length,
35
+ },
36
+ });
37
+ req.on('error', () => { }); // NEVER crash CLI
38
+ req.write(data);
39
+ req.end();
40
+ }
41
+ async function promptTelemetry(appName, selectedServices) {
42
+ const { collectTelemetry } = await inquirer_1.default.prompt([
43
+ {
44
+ type: 'confirm',
45
+ name: 'collectTelemetry',
46
+ message: 'Would you like to share anonymous usage data to improve NebulaLogix StarterKit?',
47
+ default: false,
48
+ },
49
+ ]);
50
+ const anonymousId = await getAnonymousId();
51
+ if (!collectTelemetry) {
52
+ sendTelemetry({
53
+ event: 'telemetry_opt_out',
54
+ timestamp: new Date().toISOString(),
55
+ anonymousId,
56
+ });
57
+ console.log('📊 Telemetry disabled');
58
+ }
59
+ else {
60
+ const systemPayload = {
61
+ cliVersion: package_json_1.default.version,
62
+ projectName: appName,
63
+ selectedServices,
64
+ anonymousId,
65
+ environment: {
66
+ os: os_1.default.platform(),
67
+ arch: os_1.default.arch(),
68
+ node: process.version,
69
+ },
70
+ };
71
+ sendTelemetry({
72
+ event: 'telemetry_opt_in',
73
+ timestamp: new Date().toISOString(),
74
+ ...systemPayload,
75
+ });
76
+ console.log('📊 Telemetry enabled. Thank you for helping us improve!');
77
+ }
78
+ }
79
+ function getAwsRegion() {
80
+ try {
81
+ const configPath = path_1.default.join(os_1.default.homedir(), '.aws', 'config');
82
+ if (!fs_extra_1.default.existsSync(configPath))
83
+ return null;
84
+ const content = fs_extra_1.default.readFileSync(configPath, 'utf8');
85
+ const match = content.match(/region\s*=\s*(.+)/);
86
+ return match ? match[1].trim() : null;
87
+ }
88
+ catch {
89
+ return null;
90
+ }
91
+ }
package/package.json ADDED
@@ -0,0 +1,31 @@
1
+ {
2
+ "name": "nebula-starter-kit",
3
+ "version": "0.0.1",
4
+ "bin": {
5
+ "nebula": "./dist/index.js"
6
+ },
7
+ "type": "commonjs",
8
+ "scripts": {
9
+ "build": "tsc && chmod +x dist/index.js",
10
+ "dev": "ts-node ./src/index.ts",
11
+ "test": "jest",
12
+ "format": "prettier --write \"../**/*.ts\""
13
+ },
14
+ "files": ["dist", "templates"],
15
+ "dependencies": {
16
+ "fs-extra": "^11.1.0",
17
+ "inquirer": "^9.0.0",
18
+ "yaml": "^2.8.2"
19
+ },
20
+ "devDependencies": {
21
+ "@types/fs-extra": "^11.0.4",
22
+ "@types/inquirer": "^9.0.9",
23
+ "@types/jest": "^30.0.0",
24
+ "@types/node": "^25.0.3",
25
+ "jest": "^30.3.0",
26
+ "prettier": "^2.3.2",
27
+ "ts-jest": "^29.4.6",
28
+ "ts-node": "^10.9.2",
29
+ "typescript": "^5.3.3"
30
+ }
31
+ }
@@ -0,0 +1,125 @@
1
+ import { BadRequestException, Controller, Get, Query } from '@nestjs/common';
2
+ import * as dotenv from 'dotenv';
3
+ import { EnvironmentService } from '@core/constants/environment.service';
4
+ import { AuditService } from './audit.service';
5
+ import { createDynamoClient } from '@core/constants/environment.db';
6
+ import { AUDIT_TABLE } from './audit.schema';
7
+ import { QueryCommand, ScanCommand } from '@aws-sdk/lib-dynamodb';
8
+ import { SkipAuditLog } from '@core/filters/audit.decorator';
9
+ dotenv.config();
10
+
11
+ let cached: { db: any } | undefined;
12
+
13
+ @SkipAuditLog()
14
+ @Controller('audit-logs')
15
+ export class AuditController {
16
+ constructor(
17
+ private readonly auditTable = AUDIT_TABLE,
18
+ private readonly auditService: AuditService,
19
+ private readonly config: EnvironmentService,
20
+ ) {}
21
+
22
+ public repository(): { db: any } {
23
+ try {
24
+ if (!cached) {
25
+ const db = createDynamoClient(this.config);
26
+
27
+ cached = { db };
28
+ }
29
+ return cached!;
30
+ } catch (error) {
31
+ throw error;
32
+ }
33
+ }
34
+
35
+ async getAllAudits({ limit: parsedLimit, nextToken }: any): Promise<any> {
36
+ let userId: string = 'anonymous';
37
+ try {
38
+ const params: any = {
39
+ TableName: this.auditTable,
40
+ KeyConditionExpression: 'userId = :userId',
41
+ ExpressionAttributeValues: {
42
+ ':userId': `${userId}`,
43
+ },
44
+ Limit: parsedLimit,
45
+ ScanIndexForward: false,
46
+ };
47
+ if (nextToken) {
48
+ params.ExclusiveStartKey = nextToken;
49
+ }
50
+ const { db } = this.repository();
51
+ return await db.send(new QueryCommand(params));
52
+ } catch (error) {
53
+ throw error;
54
+ }
55
+ }
56
+
57
+ async getAuditsCount(): Promise<number> {
58
+ try {
59
+ const { db } = this.repository();
60
+ const { Count } = await db.send(
61
+ new ScanCommand({
62
+ TableName: this.auditTable,
63
+ Select: 'COUNT',
64
+ }),
65
+ );
66
+ return Count ?? 0;
67
+ } catch (error) {
68
+ throw error;
69
+ }
70
+ }
71
+
72
+ @Get()
73
+ async getAudits(@Query() query: any): Promise<any> {
74
+ try {
75
+ let { limit, nextToken } = query;
76
+ let prevPage: any = 1;
77
+ try {
78
+ const validToken = (val: string) =>
79
+ val && val !== 'null' && val !== 'undefined';
80
+ if (validToken(nextToken)) {
81
+ const splitToken: any = nextToken?.split(':');
82
+ nextToken = splitToken[0];
83
+ prevPage = splitToken[1];
84
+ nextToken = JSON.parse(
85
+ Buffer.from(nextToken as string, 'base64').toString('utf-8'),
86
+ );
87
+ } else {
88
+ nextToken = undefined;
89
+ }
90
+ } catch (err) {
91
+ throw new BadRequestException('Invalid pagination token');
92
+ }
93
+
94
+ let audits;
95
+
96
+ const parsedLimit = Number(limit ?? '10');
97
+ const total = await this.getAuditsCount();
98
+ const result = await this.getAllAudits({
99
+ limit: parsedLimit,
100
+ nextToken,
101
+ });
102
+
103
+ const page = !nextToken ? prevPage : Number(prevPage) + 1;
104
+ nextToken = result.LastEvaluatedKey
105
+ ? Buffer.from(JSON.stringify(result.LastEvaluatedKey)).toString(
106
+ 'base64',
107
+ )
108
+ : undefined;
109
+ const response = {
110
+ logs: result.Items,
111
+ count: result.Items?.length,
112
+ total,
113
+ page,
114
+ pageSize: parsedLimit,
115
+ nextToken:
116
+ !nextToken || page * Number(limit) === total
117
+ ? null
118
+ : `${nextToken}:${page}`,
119
+ };
120
+ return response;
121
+ } catch (error) {
122
+ throw error;
123
+ }
124
+ }
125
+ }