create-forgeon 0.3.11 → 0.3.14

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.11",
3
+ "version": "0.3.14",
4
4
  "description": "Forgeon project generator CLI",
5
5
  "license": "MIT",
6
6
  "author": "Forgeon",
@@ -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
+
@@ -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
+
@@ -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/);
@@ -752,6 +797,51 @@ describe('addModule', () => {
752
797
  }
753
798
  });
754
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
+
755
845
  it('throws for unknown module id', () => {
756
846
  const targetRoot = mkTmp('forgeon-module-unknown-');
757
847
  try {
@@ -2352,3 +2442,4 @@ describe('addModule', () => {
2352
2442
  }
2353
2443
  });
2354
2444
  });
2445
+