create-forgeon 0.3.10 → 0.3.12

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 (21) hide show
  1. package/package.json +1 -1
  2. package/src/modules/dependencies.test.mjs +42 -1
  3. package/src/modules/executor.mjs +4 -1
  4. package/src/modules/executor.test.mjs +99 -1
  5. package/src/modules/queue.mjs +412 -410
  6. package/src/modules/registry.mjs +16 -1
  7. package/src/modules/scheduler.mjs +368 -0
  8. package/templates/module-fragments/scheduler/00_title.md +1 -0
  9. package/templates/module-fragments/scheduler/10_overview.md +6 -0
  10. package/templates/module-fragments/scheduler/20_scope.md +8 -0
  11. package/templates/module-fragments/scheduler/90_status_implemented.md +3 -0
  12. package/templates/module-presets/files-image/packages/files-image/src/files-image.service.ts +12 -1
  13. package/templates/module-presets/scheduler/packages/scheduler/package.json +24 -0
  14. package/templates/module-presets/scheduler/packages/scheduler/src/forgeon-scheduler.module.ts +12 -0
  15. package/templates/module-presets/scheduler/packages/scheduler/src/index.ts +6 -0
  16. package/templates/module-presets/scheduler/packages/scheduler/src/scheduler-config.loader.ts +23 -0
  17. package/templates/module-presets/scheduler/packages/scheduler/src/scheduler-config.module.ts +11 -0
  18. package/templates/module-presets/scheduler/packages/scheduler/src/scheduler-config.service.ts +29 -0
  19. package/templates/module-presets/scheduler/packages/scheduler/src/scheduler-env.schema.ts +15 -0
  20. package/templates/module-presets/scheduler/packages/scheduler/src/scheduler.service.ts +123 -0
  21. package/templates/module-presets/scheduler/packages/scheduler/tsconfig.json +9 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-forgeon",
3
- "version": "0.3.10",
3
+ "version": "0.3.12",
4
4
  "description": "Forgeon project generator CLI",
5
5
  "license": "MIT",
6
6
  "author": "Forgeon",
@@ -1,4 +1,4 @@
1
- import { describe, it } from 'node:test';
1
+ import { describe, it } from 'node:test';
2
2
  import assert from 'node:assert/strict';
3
3
  import fs from 'node:fs';
4
4
  import os from 'node:os';
@@ -109,6 +109,24 @@ const TEST_PRESETS = [
109
109
  },
110
110
  ],
111
111
  },
112
+ {
113
+ id: 'queue',
114
+ label: 'Queue',
115
+ implemented: true,
116
+ detectionPaths: ['packages/queue/package.json'],
117
+ provides: ['queue-runtime'],
118
+ requires: [],
119
+ optionalIntegrations: [],
120
+ },
121
+ {
122
+ id: 'scheduler',
123
+ label: 'Scheduler',
124
+ implemented: true,
125
+ detectionPaths: ['packages/scheduler/package.json'],
126
+ provides: ['scheduler-runtime'],
127
+ requires: [{ type: 'capability', id: 'queue-runtime' }],
128
+ optionalIntegrations: [],
129
+ },
112
130
  ];
113
131
 
114
132
  describe('module dependency helpers', () => {
@@ -278,6 +296,28 @@ describe('module dependency helpers', () => {
278
296
  }
279
297
  });
280
298
 
299
+ it('resolves scheduler plan through queue-runtime capability chain', async () => {
300
+ const targetRoot = mkTmp('forgeon-deps-scheduler-plan-');
301
+
302
+ try {
303
+ const result = await resolveModuleInstallPlan({
304
+ moduleId: 'scheduler',
305
+ targetRoot,
306
+ presets: TEST_PRESETS,
307
+ withRequired: true,
308
+ isInteractive: false,
309
+ });
310
+
311
+ assert.equal(result.cancelled, false);
312
+ assert.deepEqual(result.moduleSequence, ['queue', 'scheduler']);
313
+ assert.deepEqual(result.selectedProviders, {
314
+ 'queue-runtime': 'queue',
315
+ });
316
+ } finally {
317
+ fs.rmSync(targetRoot, { recursive: true, force: true });
318
+ }
319
+ });
320
+
281
321
  it('reports missing optional integrations for the installed module', () => {
282
322
  const targetRoot = mkTmp('forgeon-deps-optional-');
283
323
  try {
@@ -358,3 +398,4 @@ describe('module dependency helpers', () => {
358
398
  }
359
399
  });
360
400
  });
401
+
@@ -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 { ensureModuleExists } from './registry.mjs';
4
4
  import { writeModuleDocs } from './docs.mjs';
@@ -15,6 +15,7 @@ import { applyLoggerModule } from './logger.mjs';
15
15
  import { applyRateLimitModule } from './rate-limit.mjs';
16
16
  import { applyRbacModule } from './rbac.mjs';
17
17
  import { applyQueueModule } from './queue.mjs';
18
+ import { applySchedulerModule } from './scheduler.mjs';
18
19
  import { applySwaggerModule } from './swagger.mjs';
19
20
 
20
21
  function ensureForgeonLikeProject(targetRoot) {
@@ -46,6 +47,7 @@ const MODULE_APPLIERS = {
46
47
  logger: applyLoggerModule,
47
48
  queue: applyQueueModule,
48
49
  'rate-limit': applyRateLimitModule,
50
+ scheduler: applySchedulerModule,
49
51
  rbac: applyRbacModule,
50
52
  swagger: applySwaggerModule,
51
53
  };
@@ -82,3 +84,4 @@ export function addModule({ moduleId, targetRoot, packageRoot, writeDocs = true
82
84
  : `Module "${preset.id}" is planned; docs note created only.`,
83
85
  };
84
86
  }
87
+
@@ -1,4 +1,4 @@
1
- import { describe, it } from 'node:test';
1
+ import { describe, it } from 'node:test';
2
2
  import assert from 'node:assert/strict';
3
3
  import fs from 'node:fs';
4
4
  import os from 'node:os';
@@ -132,6 +132,51 @@ function assertQueueWiring(projectRoot) {
132
132
  assert.match(readme, /runtime baseline backed by Redis/i);
133
133
  }
134
134
 
135
+ function assertSchedulerWiring(projectRoot) {
136
+ const appModule = fs.readFileSync(path.join(projectRoot, 'apps', 'api', 'src', 'app.module.ts'), 'utf8');
137
+ assert.match(appModule, /schedulerConfig/);
138
+ assert.match(appModule, /schedulerEnvSchema/);
139
+ assert.match(appModule, /ForgeonSchedulerModule/);
140
+
141
+ const apiPackage = fs.readFileSync(path.join(projectRoot, 'apps', 'api', 'package.json'), 'utf8');
142
+ assert.match(apiPackage, /@forgeon\/scheduler/);
143
+ assert.match(apiPackage, /pnpm --filter @forgeon\/scheduler build/);
144
+
145
+ const apiDockerfile = fs.readFileSync(path.join(projectRoot, 'apps', 'api', 'Dockerfile'), 'utf8');
146
+ assert.match(apiDockerfile, /COPY packages\/scheduler\/package\.json packages\/scheduler\/package\.json/);
147
+ assert.match(apiDockerfile, /COPY packages\/scheduler packages\/scheduler/);
148
+ assert.match(apiDockerfile, /RUN pnpm --filter @forgeon\/scheduler build/);
149
+
150
+ const healthController = fs.readFileSync(
151
+ path.join(projectRoot, 'apps', 'api', 'src', 'health', 'health.controller.ts'),
152
+ 'utf8',
153
+ );
154
+ assert.match(healthController, /ForgeonSchedulerService/);
155
+ assert.match(healthController, /@Get\('scheduler'\)/);
156
+ assert.match(healthController, /schedulerService\.getProbeStatus/);
157
+
158
+ const webApp = fs.readFileSync(path.join(projectRoot, 'apps', 'web', 'src', 'App.tsx'), 'utf8');
159
+ assert.match(webApp, /Check scheduler health/);
160
+ assert.match(webApp, /Scheduler probe response/);
161
+
162
+ const apiEnv = fs.readFileSync(path.join(projectRoot, 'apps', 'api', '.env.example'), 'utf8');
163
+ assert.match(apiEnv, /SCHEDULER_ENABLED=true/);
164
+ assert.match(apiEnv, /SCHEDULER_TIMEZONE=UTC/);
165
+ assert.match(apiEnv, /SCHEDULER_HEARTBEAT_CRON=\*\/5 \* \* \* \*/);
166
+
167
+ const dockerEnv = fs.readFileSync(path.join(projectRoot, 'infra', 'docker', '.env.example'), 'utf8');
168
+ assert.match(dockerEnv, /SCHEDULER_TIMEZONE=UTC/);
169
+
170
+ const compose = fs.readFileSync(path.join(projectRoot, 'infra', 'docker', 'compose.yml'), 'utf8');
171
+ assert.match(compose, /SCHEDULER_ENABLED: \$\{SCHEDULER_ENABLED\}/);
172
+ assert.match(compose, /SCHEDULER_HEARTBEAT_CRON: \$\{SCHEDULER_HEARTBEAT_CRON\}/);
173
+ assert.doesNotMatch(compose, /^\s{2}scheduler:\s*$/m);
174
+
175
+ const readme = fs.readFileSync(path.join(projectRoot, 'README.md'), 'utf8');
176
+ assert.match(readme, /## Scheduler Module/);
177
+ assert.match(readme, /cron-based orchestration/i);
178
+ }
179
+
135
180
  function assertRbacWiring(projectRoot) {
136
181
  const appModule = fs.readFileSync(path.join(projectRoot, 'apps', 'api', 'src', 'app.module.ts'), 'utf8');
137
182
  assert.match(appModule, /ForgeonRbacModule/);
@@ -479,6 +524,13 @@ function assertFilesImageWiring(projectRoot) {
479
524
  const filesTypes = fs.readFileSync(path.join(projectRoot, 'packages', 'files', 'src', 'files.types.ts'), 'utf8');
480
525
  assert.match(filesTypes, /auditContext\?: \{/);
481
526
 
527
+ const filesImageService = fs.readFileSync(
528
+ path.join(projectRoot, 'packages', 'files-image', 'src', 'files-image.service.ts'),
529
+ 'utf8',
530
+ );
531
+ assert.match(filesImageService, /loadFileTypeModule/);
532
+ assert.match(filesImageService, /new Function\('specifier', 'return import\(specifier\)'\)/);
533
+
482
534
  const apiDockerfile = fs.readFileSync(path.join(projectRoot, 'apps', 'api', 'Dockerfile'), 'utf8');
483
535
  assert.match(
484
536
  apiDockerfile,
@@ -745,6 +797,51 @@ describe('addModule', () => {
745
797
  }
746
798
  });
747
799
 
800
+ it('applies scheduler module on top of project with queue installed', () => {
801
+ const targetRoot = mkTmp('forgeon-module-scheduler-');
802
+ const projectRoot = path.join(targetRoot, 'demo-scheduler');
803
+ const templateRoot = path.join(packageRoot, 'templates', 'base');
804
+
805
+ try {
806
+ scaffoldProject({
807
+ templateRoot,
808
+ packageRoot,
809
+ targetRoot: projectRoot,
810
+ projectName: 'demo-scheduler',
811
+ frontend: 'react',
812
+ db: 'prisma',
813
+ dbPrismaEnabled: false,
814
+ i18nEnabled: false,
815
+ proxy: 'caddy',
816
+ });
817
+
818
+ addModule({
819
+ moduleId: 'queue',
820
+ targetRoot: projectRoot,
821
+ packageRoot,
822
+ });
823
+
824
+ const result = addModule({
825
+ moduleId: 'scheduler',
826
+ targetRoot: projectRoot,
827
+ packageRoot,
828
+ });
829
+
830
+ assert.equal(result.applied, true);
831
+ assert.match(result.message, /applied/);
832
+ assert.equal(fs.existsSync(result.docsPath), true);
833
+
834
+ assertQueueWiring(projectRoot);
835
+ assertSchedulerWiring(projectRoot);
836
+
837
+ const note = fs.readFileSync(result.docsPath, 'utf8');
838
+ assert.match(note, /Scheduler/);
839
+ assert.match(note, /Status: implemented/);
840
+ } finally {
841
+ fs.rmSync(targetRoot, { recursive: true, force: true });
842
+ }
843
+ });
844
+
748
845
  it('throws for unknown module id', () => {
749
846
  const targetRoot = mkTmp('forgeon-module-unknown-');
750
847
  try {
@@ -2345,3 +2442,4 @@ describe('addModule', () => {
2345
2442
  }
2346
2443
  });
2347
2444
  });
2445
+