create-forgeon 0.3.21 → 0.3.23

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-forgeon",
3
- "version": "0.3.21",
3
+ "version": "0.3.23",
4
4
  "description": "Forgeon project generator CLI",
5
5
  "license": "MIT",
6
6
  "author": "Forgeon",
@@ -506,6 +506,16 @@ function assertFilesAccessWiring(projectRoot) {
506
506
  assert.match(filesController, /@Req\(\) req: any/);
507
507
  assert.match(filesController, /openDownload\(publicId,\s*variant\)/);
508
508
 
509
+ const filesModule = fs.readFileSync(
510
+ path.join(projectRoot, 'packages', 'files', 'src', 'forgeon-files.module.ts'),
511
+ 'utf8',
512
+ );
513
+ assert.match(filesModule, /import \{ ForgeonFilesAccessModule \} from '@forgeon\/files-access';/);
514
+ assert.match(
515
+ filesModule,
516
+ /imports: \[FilesConfigModule, ForgeonFilesAccessModule, (?:ForgeonFilesImageModule, )?DbPrismaModule, \.\.\.\(options\.imports \?\? \[\]\)\],/,
517
+ );
518
+
509
519
  const healthController = fs.readFileSync(
510
520
  path.join(projectRoot, 'apps', 'api', 'src', 'health', 'health.controller.ts'),
511
521
  'utf8',
@@ -532,14 +542,14 @@ function assertFilesQuotasWiring(projectRoot) {
532
542
  assert.match(appModule, /filesQuotasEnvSchema/);
533
543
  assert.match(appModule, /ForgeonFilesQuotasModule/);
534
544
 
535
- const apiPackage = fs.readFileSync(path.join(projectRoot, 'apps', 'api', 'package.json'), 'utf8');
536
- assert.match(apiPackage, /@forgeon\/files-quotas/);
537
- assert.match(apiPackage, /pnpm --filter @forgeon\/files-quotas build/);
538
- assert.equal(
539
- apiPackage.indexOf('pnpm --filter @forgeon/files-quotas build') >
540
- apiPackage.indexOf('pnpm --filter @forgeon/files build'),
541
- true,
542
- );
545
+ const apiPackage = fs.readFileSync(path.join(projectRoot, 'apps', 'api', 'package.json'), 'utf8');
546
+ assert.match(apiPackage, /@forgeon\/files-quotas/);
547
+ assert.match(apiPackage, /pnpm --filter @forgeon\/files-quotas build/);
548
+ assert.equal(
549
+ apiPackage.indexOf('pnpm --filter @forgeon/files-quotas build') >
550
+ apiPackage.indexOf('pnpm --filter @forgeon/files build'),
551
+ true,
552
+ );
543
553
 
544
554
  const filesPackage = fs.readFileSync(path.join(projectRoot, 'packages', 'files', 'package.json'), 'utf8');
545
555
  assert.doesNotMatch(filesPackage, /@forgeon\/files-quotas/);
@@ -550,13 +560,13 @@ function assertFilesQuotasWiring(projectRoot) {
550
560
  apiDockerfile,
551
561
  /COPY packages\/files-quotas\/package\.json packages\/files-quotas\/package\.json/,
552
562
  );
553
- assert.match(apiDockerfile, /COPY packages\/files-quotas packages\/files-quotas/);
554
- assert.match(apiDockerfile, /RUN pnpm --filter @forgeon\/files-quotas build/);
555
- assert.equal(
556
- apiDockerfile.indexOf('RUN pnpm --filter @forgeon/files-quotas build') >
557
- apiDockerfile.indexOf('RUN pnpm --filter @forgeon/files build'),
558
- true,
559
- );
563
+ assert.match(apiDockerfile, /COPY packages\/files-quotas packages\/files-quotas/);
564
+ assert.match(apiDockerfile, /RUN pnpm --filter @forgeon\/files-quotas build/);
565
+ assert.equal(
566
+ apiDockerfile.indexOf('RUN pnpm --filter @forgeon/files-quotas build') >
567
+ apiDockerfile.indexOf('RUN pnpm --filter @forgeon/files build'),
568
+ true,
569
+ );
560
570
 
561
571
  const filesController = fs.readFileSync(
562
572
  path.join(projectRoot, 'packages', 'files', 'src', 'files.controller.ts'),
@@ -591,6 +601,22 @@ function assertFilesQuotasWiring(projectRoot) {
591
601
  const readme = fs.readFileSync(path.join(projectRoot, 'README.md'), 'utf8');
592
602
  assert.match(readme, /## Files Quotas Module/);
593
603
  assert.match(readme, /owner-based limits/i);
604
+
605
+ const filesQuotasService = fs.readFileSync(
606
+ path.join(projectRoot, 'packages', 'files-quotas', 'src', 'files-quotas.service.ts'),
607
+ 'utf8',
608
+ );
609
+ assert.match(filesQuotasService, /FilesStore/);
610
+ assert.match(filesQuotasService, /filesStore\.countOwnerUsage/);
611
+ assert.doesNotMatch(filesQuotasService, /FilesService/);
612
+
613
+ const filesQuotasModule = fs.readFileSync(
614
+ path.join(projectRoot, 'packages', 'files-quotas', 'src', 'forgeon-files-quotas.module.ts'),
615
+ 'utf8',
616
+ );
617
+ assert.match(filesQuotasModule, /DbPrismaModule/);
618
+ assert.match(filesQuotasModule, /FilesStore/);
619
+ assert.doesNotMatch(filesQuotasModule, /ForgeonFilesModule/);
594
620
  }
595
621
 
596
622
  function assertFilesImageWiring(projectRoot) {
@@ -616,6 +642,10 @@ function assertFilesImageWiring(projectRoot) {
616
642
  'utf8',
617
643
  );
618
644
  assert.match(filesModule, /ForgeonFilesImageModule/);
645
+ assert.match(
646
+ filesModule,
647
+ /imports: \[FilesConfigModule, (?:ForgeonFilesAccessModule, )?ForgeonFilesImageModule, DbPrismaModule, \.\.\.\(options\.imports \?\? \[\]\)\],/,
648
+ );
619
649
 
620
650
  const filesService = fs.readFileSync(
621
651
  path.join(projectRoot, 'packages', 'files', 'src', 'files.service.ts'),
@@ -1,4 +1,4 @@
1
- import fs from 'node:fs';
1
+ import fs from 'node:fs';
2
2
  import path from 'node:path';
3
3
  import { copyRecursive, writeJson } from '../utils/fs.mjs';
4
4
  import {
@@ -51,6 +51,29 @@ function patchFilesPackage(targetRoot) {
51
51
  writeJson(packagePath, packageJson);
52
52
  }
53
53
 
54
+ function patchFilesModule(targetRoot) {
55
+ const filePath = path.join(targetRoot, 'packages', 'files', 'src', 'forgeon-files.module.ts');
56
+ if (!fs.existsSync(filePath)) {
57
+ return;
58
+ }
59
+
60
+ let content = fs.readFileSync(filePath, 'utf8').replace(/\r\n/g, '\n');
61
+ content = ensureImportLine(content, "import { ForgeonFilesAccessModule } from '@forgeon/files-access';");
62
+
63
+ if (!content.includes('imports: [FilesConfigModule, ForgeonFilesAccessModule,')) {
64
+ content = content.replace(
65
+ 'imports: [FilesConfigModule, ForgeonFilesImageModule, DbPrismaModule, ...(options.imports ?? [])],',
66
+ 'imports: [FilesConfigModule, ForgeonFilesAccessModule, ForgeonFilesImageModule, DbPrismaModule, ...(options.imports ?? [])],',
67
+ );
68
+ content = content.replace(
69
+ 'imports: [FilesConfigModule, DbPrismaModule, ...(options.imports ?? [])],',
70
+ 'imports: [FilesConfigModule, ForgeonFilesAccessModule, DbPrismaModule, ...(options.imports ?? [])],',
71
+ );
72
+ }
73
+
74
+ fs.writeFileSync(filePath, `${content.trimEnd()}\n`, 'utf8');
75
+ }
76
+
54
77
  function patchAppModule(targetRoot) {
55
78
  const filePath = path.join(targetRoot, 'apps', 'api', 'src', 'app.module.ts');
56
79
  if (!fs.existsSync(filePath)) {
@@ -366,6 +389,7 @@ export function applyFilesAccessModule({ packageRoot, targetRoot }) {
366
389
 
367
390
  patchApiPackage(targetRoot);
368
391
  patchFilesPackage(targetRoot);
392
+ patchFilesModule(targetRoot);
369
393
  patchAppModule(targetRoot);
370
394
  patchFilesController(targetRoot);
371
395
  patchHealthController(targetRoot, probeTargets);
@@ -115,21 +115,27 @@ function patchAppModule(targetRoot) {
115
115
  fs.writeFileSync(filePath, `${content.trimEnd()}\n`, 'utf8');
116
116
  }
117
117
 
118
- function patchFilesModule(targetRoot) {
119
- const filePath = path.join(targetRoot, 'packages', 'files', 'src', 'forgeon-files.module.ts');
120
- if (!fs.existsSync(filePath)) {
121
- return;
122
- }
123
-
124
- let content = fs.readFileSync(filePath, 'utf8').replace(/\r\n/g, '\n');
125
- content = ensureImportLine(content, "import { ForgeonFilesImageModule } from '@forgeon/files-image';");
126
- content = content.replace(
127
- 'imports: [FilesConfigModule],',
128
- 'imports: [FilesConfigModule, ForgeonFilesImageModule],',
129
- );
130
-
131
- fs.writeFileSync(filePath, `${content.trimEnd()}\n`, 'utf8');
132
- }
118
+ function patchFilesModule(targetRoot) {
119
+ const filePath = path.join(targetRoot, 'packages', 'files', 'src', 'forgeon-files.module.ts');
120
+ if (!fs.existsSync(filePath)) {
121
+ return;
122
+ }
123
+
124
+ let content = fs.readFileSync(filePath, 'utf8').replace(/\r\n/g, '\n');
125
+ content = ensureImportLine(content, "import { ForgeonFilesImageModule } from '@forgeon/files-image';");
126
+ if (!content.includes('imports: [FilesConfigModule, ForgeonFilesImageModule,')) {
127
+ content = content.replace(
128
+ 'imports: [FilesConfigModule, DbPrismaModule, ...(options.imports ?? [])],',
129
+ 'imports: [FilesConfigModule, ForgeonFilesImageModule, DbPrismaModule, ...(options.imports ?? [])],',
130
+ );
131
+ content = content.replace(
132
+ 'imports: [FilesConfigModule, ForgeonFilesAccessModule, DbPrismaModule, ...(options.imports ?? [])],',
133
+ 'imports: [FilesConfigModule, ForgeonFilesAccessModule, ForgeonFilesImageModule, DbPrismaModule, ...(options.imports ?? [])],',
134
+ );
135
+ }
136
+
137
+ fs.writeFileSync(filePath, `${content.trimEnd()}\n`, 'utf8');
138
+ }
133
139
 
134
140
  function patchFilesController(targetRoot) {
135
141
  const filePath = path.join(targetRoot, 'packages', 'files', 'src', 'files.controller.ts');
@@ -1,20 +1,21 @@
1
- {
2
- "name": "@forgeon/files-quotas",
3
- "version": "0.1.0",
4
- "private": true,
5
- "main": "dist/index.js",
6
- "types": "dist/index.d.ts",
7
- "scripts": {
8
- "build": "tsc -p tsconfig.json"
9
- },
10
- "dependencies": {
11
- "@forgeon/files": "workspace:*",
12
- "@nestjs/common": "^11.0.1",
13
- "@nestjs/config": "^4.0.0",
14
- "zod": "^3.24.2"
15
- },
16
- "devDependencies": {
17
- "@types/node": "^22.10.7",
18
- "typescript": "^5.7.3"
19
- }
20
- }
1
+ {
2
+ "name": "@forgeon/files-quotas",
3
+ "version": "0.1.0",
4
+ "private": true,
5
+ "main": "dist/index.js",
6
+ "types": "dist/index.d.ts",
7
+ "scripts": {
8
+ "build": "tsc -p tsconfig.json"
9
+ },
10
+ "dependencies": {
11
+ "@forgeon/db-prisma": "workspace:*",
12
+ "@forgeon/files": "workspace:*",
13
+ "@nestjs/common": "^11.0.1",
14
+ "@nestjs/config": "^4.0.0",
15
+ "zod": "^3.24.2"
16
+ },
17
+ "devDependencies": {
18
+ "@types/node": "^22.10.7",
19
+ "typescript": "^5.7.3"
20
+ }
21
+ }
@@ -1,118 +1,118 @@
1
- import { ConflictException, Injectable } from '@nestjs/common';
2
- import { FilesService } from '@forgeon/files';
3
- import { FilesQuotasConfigService } from './files-quotas-config.service';
4
- import type { FilesQuotaCheckInput, FilesQuotaCheckResult } from './files-quotas.types';
5
-
6
- @Injectable()
7
- export class FilesQuotasService {
8
- constructor(
9
- private readonly filesService: FilesService,
10
- private readonly configService: FilesQuotasConfigService,
11
- ) {}
12
-
13
- get enabled(): boolean {
14
- return this.configService.enabled;
15
- }
16
-
17
- get limits() {
18
- return {
19
- maxFilesPerOwner: this.configService.maxFilesPerOwner,
20
- maxBytesPerOwner: this.configService.maxBytesPerOwner,
21
- };
22
- }
23
-
24
- async evaluateUpload(input: FilesQuotaCheckInput): Promise<FilesQuotaCheckResult> {
25
- const limits = this.limits;
26
- const emptyUsage = {
27
- filesCount: 0,
28
- totalBytes: 0,
29
- };
30
- const baseResult = {
31
- limits,
32
- current: emptyUsage,
33
- next: {
34
- filesCount: emptyUsage.filesCount + 1,
35
- totalBytes: emptyUsage.totalBytes + input.fileSize,
36
- },
37
- };
38
-
39
- if (!this.enabled) {
40
- return {
41
- allowed: true,
42
- reason: 'disabled',
43
- ...baseResult,
44
- };
45
- }
46
-
47
- if (!input.ownerId) {
48
- return {
49
- allowed: true,
50
- reason: 'owner-missing',
51
- ...baseResult,
52
- };
53
- }
54
-
55
- const usage = await this.filesService.getOwnerUsage(input.ownerType, input.ownerId);
56
- const next = {
57
- filesCount: usage.filesCount + 1,
58
- totalBytes: usage.totalBytes + input.fileSize,
59
- };
60
-
61
- if (next.filesCount > limits.maxFilesPerOwner) {
62
- return {
63
- allowed: false,
64
- reason: 'max-files',
65
- limits,
66
- current: usage,
67
- next,
68
- };
69
- }
70
- if (next.totalBytes > limits.maxBytesPerOwner) {
71
- return {
72
- allowed: false,
73
- reason: 'max-bytes',
74
- limits,
75
- current: usage,
76
- next,
77
- };
78
- }
79
-
80
- return {
81
- allowed: true,
82
- reason: 'ok',
83
- limits,
84
- current: usage,
85
- next,
86
- };
87
- }
88
-
89
- async assertUploadAllowed(input: FilesQuotaCheckInput): Promise<void> {
90
- const result = await this.evaluateUpload(input);
91
- if (result.allowed) {
92
- return;
93
- }
94
-
95
- throw new ConflictException({
96
- message: 'File quota exceeded for owner',
97
- details: {
98
- reason: result.reason,
99
- limits: result.limits,
100
- current: result.current,
101
- next: result.next,
102
- },
103
- });
104
- }
105
-
106
- async getProbeStatus(input: FilesQuotaCheckInput): Promise<{
107
- status: 'ok';
108
- feature: 'files-quotas';
109
- result: FilesQuotaCheckResult;
110
- }> {
111
- const result = await this.evaluateUpload(input);
112
- return {
113
- status: 'ok',
114
- feature: 'files-quotas',
115
- result,
116
- };
117
- }
118
- }
1
+ import { ConflictException, Injectable } from '@nestjs/common';
2
+ import { FilesStore } from '@forgeon/files';
3
+ import { FilesQuotasConfigService } from './files-quotas-config.service';
4
+ import type { FilesQuotaCheckInput, FilesQuotaCheckResult } from './files-quotas.types';
5
+
6
+ @Injectable()
7
+ export class FilesQuotasService {
8
+ constructor(
9
+ private readonly filesStore: FilesStore,
10
+ private readonly configService: FilesQuotasConfigService,
11
+ ) {}
12
+
13
+ get enabled(): boolean {
14
+ return this.configService.enabled;
15
+ }
16
+
17
+ get limits() {
18
+ return {
19
+ maxFilesPerOwner: this.configService.maxFilesPerOwner,
20
+ maxBytesPerOwner: this.configService.maxBytesPerOwner,
21
+ };
22
+ }
23
+
24
+ async evaluateUpload(input: FilesQuotaCheckInput): Promise<FilesQuotaCheckResult> {
25
+ const limits = this.limits;
26
+ const emptyUsage = {
27
+ filesCount: 0,
28
+ totalBytes: 0,
29
+ };
30
+ const baseResult = {
31
+ limits,
32
+ current: emptyUsage,
33
+ next: {
34
+ filesCount: emptyUsage.filesCount + 1,
35
+ totalBytes: emptyUsage.totalBytes + input.fileSize,
36
+ },
37
+ };
38
+
39
+ if (!this.enabled) {
40
+ return {
41
+ allowed: true,
42
+ reason: 'disabled',
43
+ ...baseResult,
44
+ };
45
+ }
46
+
47
+ if (!input.ownerId) {
48
+ return {
49
+ allowed: true,
50
+ reason: 'owner-missing',
51
+ ...baseResult,
52
+ };
53
+ }
54
+
55
+ const usage = await this.filesStore.countOwnerUsage(input.ownerType, input.ownerId);
56
+ const next = {
57
+ filesCount: usage.filesCount + 1,
58
+ totalBytes: usage.totalBytes + input.fileSize,
59
+ };
60
+
61
+ if (next.filesCount > limits.maxFilesPerOwner) {
62
+ return {
63
+ allowed: false,
64
+ reason: 'max-files',
65
+ limits,
66
+ current: usage,
67
+ next,
68
+ };
69
+ }
70
+ if (next.totalBytes > limits.maxBytesPerOwner) {
71
+ return {
72
+ allowed: false,
73
+ reason: 'max-bytes',
74
+ limits,
75
+ current: usage,
76
+ next,
77
+ };
78
+ }
79
+
80
+ return {
81
+ allowed: true,
82
+ reason: 'ok',
83
+ limits,
84
+ current: usage,
85
+ next,
86
+ };
87
+ }
88
+
89
+ async assertUploadAllowed(input: FilesQuotaCheckInput): Promise<void> {
90
+ const result = await this.evaluateUpload(input);
91
+ if (result.allowed) {
92
+ return;
93
+ }
94
+
95
+ throw new ConflictException({
96
+ message: 'File quota exceeded for owner',
97
+ details: {
98
+ reason: result.reason,
99
+ limits: result.limits,
100
+ current: result.current,
101
+ next: result.next,
102
+ },
103
+ });
104
+ }
105
+
106
+ async getProbeStatus(input: FilesQuotaCheckInput): Promise<{
107
+ status: 'ok';
108
+ feature: 'files-quotas';
109
+ result: FilesQuotaCheckResult;
110
+ }> {
111
+ const result = await this.evaluateUpload(input);
112
+ return {
113
+ status: 'ok',
114
+ feature: 'files-quotas',
115
+ result,
116
+ };
117
+ }
118
+ }
@@ -1,19 +1,21 @@
1
- import { Module } from '@nestjs/common';
2
- import { ForgeonFilesModule } from '@forgeon/files';
3
- import { FilesQuotasConfigModule } from './files-quotas-config.module';
4
- import { FilesQuotasService } from './files-quotas.service';
5
-
6
- const FORGEON_FILES_UPLOAD_QUOTA_SERVICE = 'FORGEON_FILES_UPLOAD_QUOTA_SERVICE';
7
-
8
- @Module({
9
- imports: [ForgeonFilesModule, FilesQuotasConfigModule],
10
- providers: [
11
- FilesQuotasService,
12
- {
13
- provide: FORGEON_FILES_UPLOAD_QUOTA_SERVICE,
14
- useExisting: FilesQuotasService,
15
- },
16
- ],
17
- exports: [FilesQuotasConfigModule, FilesQuotasService, FORGEON_FILES_UPLOAD_QUOTA_SERVICE],
18
- })
19
- export class ForgeonFilesQuotasModule {}
1
+ import { Module } from '@nestjs/common';
2
+ import { DbPrismaModule } from '@forgeon/db-prisma';
3
+ import { FilesStore } from '@forgeon/files';
4
+ import { FilesQuotasConfigModule } from './files-quotas-config.module';
5
+ import { FilesQuotasService } from './files-quotas.service';
6
+
7
+ const FORGEON_FILES_UPLOAD_QUOTA_SERVICE = 'FORGEON_FILES_UPLOAD_QUOTA_SERVICE';
8
+
9
+ @Module({
10
+ imports: [DbPrismaModule, FilesQuotasConfigModule],
11
+ providers: [
12
+ FilesStore,
13
+ FilesQuotasService,
14
+ {
15
+ provide: FORGEON_FILES_UPLOAD_QUOTA_SERVICE,
16
+ useExisting: FilesQuotasService,
17
+ },
18
+ ],
19
+ exports: [FilesQuotasConfigModule, FilesQuotasService, FORGEON_FILES_UPLOAD_QUOTA_SERVICE],
20
+ })
21
+ export class ForgeonFilesQuotasModule {}