create-forgeon 0.3.9 → 0.3.11

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.9",
3
+ "version": "0.3.11",
4
4
  "description": "Forgeon project generator CLI",
5
5
  "license": "MIT",
6
6
  "author": "Forgeon",
@@ -479,6 +479,13 @@ function assertFilesImageWiring(projectRoot) {
479
479
  const filesTypes = fs.readFileSync(path.join(projectRoot, 'packages', 'files', 'src', 'files.types.ts'), 'utf8');
480
480
  assert.match(filesTypes, /auditContext\?: \{/);
481
481
 
482
+ const filesImageService = fs.readFileSync(
483
+ path.join(projectRoot, 'packages', 'files-image', 'src', 'files-image.service.ts'),
484
+ 'utf8',
485
+ );
486
+ assert.match(filesImageService, /loadFileTypeModule/);
487
+ assert.match(filesImageService, /new Function\('specifier', 'return import\(specifier\)'\)/);
488
+
482
489
  const apiDockerfile = fs.readFileSync(path.join(projectRoot, 'apps', 'api', 'Dockerfile'), 'utf8');
483
490
  assert.match(
484
491
  apiDockerfile,
@@ -529,6 +536,10 @@ function assertFilesImageWiring(projectRoot) {
529
536
  assert.match(filesImagePackage, /"sharp":/);
530
537
  assert.match(filesImagePackage, /"file-type":/);
531
538
 
539
+ const rootPackage = fs.readFileSync(path.join(projectRoot, 'package.json'), 'utf8');
540
+ assert.match(rootPackage, /"onlyBuiltDependencies"/);
541
+ assert.match(rootPackage, /"sharp"/);
542
+
532
543
  const readme = fs.readFileSync(path.join(projectRoot, 'README.md'), 'utf8');
533
544
  assert.match(readme, /## Files Image Module/);
534
545
  assert.match(readme, /metadata is stripped before storage/i);
@@ -53,6 +53,29 @@ function patchFilesPackage(targetRoot) {
53
53
  writeJson(packagePath, packageJson);
54
54
  }
55
55
 
56
+ function patchRootPackage(targetRoot) {
57
+ const packagePath = path.join(targetRoot, 'package.json');
58
+ if (!fs.existsSync(packagePath)) {
59
+ return;
60
+ }
61
+
62
+ const packageJson = JSON.parse(fs.readFileSync(packagePath, 'utf8'));
63
+ if (!packageJson.pnpm || typeof packageJson.pnpm !== 'object' || Array.isArray(packageJson.pnpm)) {
64
+ packageJson.pnpm = {};
65
+ }
66
+
67
+ const onlyBuiltDependencies = Array.isArray(packageJson.pnpm.onlyBuiltDependencies)
68
+ ? packageJson.pnpm.onlyBuiltDependencies
69
+ : [];
70
+
71
+ if (!onlyBuiltDependencies.includes('sharp')) {
72
+ onlyBuiltDependencies.push('sharp');
73
+ }
74
+
75
+ packageJson.pnpm.onlyBuiltDependencies = onlyBuiltDependencies;
76
+ writeJson(packagePath, packageJson);
77
+ }
78
+
56
79
  function patchFilesTypes(targetRoot) {
57
80
  const filePath = path.join(targetRoot, 'packages', 'files', 'src', 'files.types.ts');
58
81
  if (!fs.existsSync(filePath)) {
@@ -526,6 +549,7 @@ export function applyFilesImageModule({ packageRoot, targetRoot }) {
526
549
  copyFromPreset(packageRoot, targetRoot, path.join('packages', 'files-image'));
527
550
  patchApiPackage(targetRoot);
528
551
  patchFilesPackage(targetRoot);
552
+ patchRootPackage(targetRoot);
529
553
  patchFilesTypes(targetRoot);
530
554
  patchAppModule(targetRoot);
531
555
  patchFilesModule(targetRoot);
@@ -15,6 +15,10 @@ const MIME_TO_ALLOWED_EXTS: Record<string, string[]> = {
15
15
  'image/webp': ['.webp'],
16
16
  };
17
17
 
18
+ interface FileTypeModuleLike {
19
+ fileTypeFromBuffer(buffer: Buffer): Promise<{ mime: string; ext: string } | undefined>;
20
+ }
21
+
18
22
  @Injectable()
19
23
  export class FilesImageService {
20
24
  private readonly logger = new Logger(FilesImageService.name);
@@ -146,54 +150,89 @@ export class FilesImageService {
146
150
  return this.configService.enabled;
147
151
  }
148
152
 
149
- async getProbeStatus(): Promise<{
150
- status: 'ok';
151
- feature: 'files-image';
152
- stripMetadata: boolean;
153
- maxWidth: number;
154
- maxHeight: number;
155
- maxPixels: number;
156
- maxFrames: number;
157
- inputBytes: number;
158
- outputBytes: number;
159
- outputMimeType: string;
160
- transformed: boolean;
161
- }> {
162
- const sample = await sharp({
163
- create: {
164
- width: 4,
165
- height: 4,
166
- channels: 3,
167
- background: { r: 120, g: 80, b: 40 },
168
- },
169
- })
170
- .jpeg({ quality: 90 })
171
- .withMetadata()
172
- .toBuffer();
173
-
174
- const result = await this.sanitizeForStorage({
175
- buffer: sample,
176
- declaredMimeType: 'image/jpeg',
177
- originalName: 'probe.jpg',
178
- });
153
+ async getProbeStatus(): Promise<
154
+ | {
155
+ status: 'ok';
156
+ feature: 'files-image';
157
+ stripMetadata: boolean;
158
+ maxWidth: number;
159
+ maxHeight: number;
160
+ maxPixels: number;
161
+ maxFrames: number;
162
+ inputBytes: number;
163
+ outputBytes: number;
164
+ outputMimeType: string;
165
+ transformed: boolean;
166
+ }
167
+ | {
168
+ status: 'error';
169
+ feature: 'files-image';
170
+ stripMetadata: boolean;
171
+ maxWidth: number;
172
+ maxHeight: number;
173
+ maxPixels: number;
174
+ maxFrames: number;
175
+ errorCode: string;
176
+ errorMessage: string;
177
+ }
178
+ > {
179
+ try {
180
+ const sample = await sharp({
181
+ create: {
182
+ width: 4,
183
+ height: 4,
184
+ channels: 3,
185
+ background: { r: 120, g: 80, b: 40 },
186
+ },
187
+ })
188
+ .jpeg({ quality: 90 })
189
+ .withMetadata()
190
+ .toBuffer();
191
+
192
+ const result = await this.sanitizeForStorage({
193
+ buffer: sample,
194
+ declaredMimeType: 'image/jpeg',
195
+ originalName: 'probe.jpg',
196
+ });
179
197
 
180
- return {
181
- status: 'ok',
182
- feature: 'files-image',
183
- stripMetadata: this.configService.stripMetadata,
184
- maxWidth: this.configService.maxWidth,
185
- maxHeight: this.configService.maxHeight,
186
- maxPixels: this.configService.maxPixels,
187
- maxFrames: this.configService.maxFrames,
188
- inputBytes: sample.byteLength,
189
- outputBytes: result.buffer.byteLength,
190
- outputMimeType: result.mimeType,
191
- transformed: result.transformed,
192
- };
198
+ return {
199
+ status: 'ok',
200
+ feature: 'files-image',
201
+ stripMetadata: this.configService.stripMetadata,
202
+ maxWidth: this.configService.maxWidth,
203
+ maxHeight: this.configService.maxHeight,
204
+ maxPixels: this.configService.maxPixels,
205
+ maxFrames: this.configService.maxFrames,
206
+ inputBytes: sample.byteLength,
207
+ outputBytes: result.buffer.byteLength,
208
+ outputMimeType: result.mimeType,
209
+ transformed: result.transformed,
210
+ };
211
+ } catch (error) {
212
+ const errorMessage = error instanceof Error ? error.message : 'Unknown files-image probe error';
213
+ this.logger.error(
214
+ JSON.stringify({
215
+ event: 'files.image.probe_failed',
216
+ errorMessage,
217
+ }),
218
+ );
219
+
220
+ return {
221
+ status: 'error',
222
+ feature: 'files-image',
223
+ stripMetadata: this.configService.stripMetadata,
224
+ maxWidth: this.configService.maxWidth,
225
+ maxHeight: this.configService.maxHeight,
226
+ maxPixels: this.configService.maxPixels,
227
+ maxFrames: this.configService.maxFrames,
228
+ errorCode: 'FILES_IMAGE_PROBE_FAILED',
229
+ errorMessage,
230
+ };
231
+ }
193
232
  }
194
233
 
195
234
  private async detectFileType(buffer: Buffer): Promise<{ mime: string; ext: string } | null> {
196
- const { fileTypeFromBuffer } = await import('file-type');
235
+ const { fileTypeFromBuffer } = await this.loadFileTypeModule();
197
236
  const detected = await fileTypeFromBuffer(buffer);
198
237
  if (!detected) {
199
238
  return null;
@@ -267,6 +306,13 @@ export class FilesImageService {
267
306
  }
268
307
  }
269
308
 
309
+ private async loadFileTypeModule(): Promise<FileTypeModuleLike> {
310
+ const importModule = new Function('specifier', 'return import(specifier)') as (
311
+ specifier: string,
312
+ ) => Promise<FileTypeModuleLike>;
313
+ return importModule('file-type');
314
+ }
315
+
270
316
  private async validateImageShape(
271
317
  buffer: Buffer,
272
318
  detectedMimeType: string,