create-forgeon 0.3.11 → 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.
- package/package.json +1 -1
- package/src/modules/dependencies.test.mjs +42 -1
- package/src/modules/executor.mjs +4 -1
- package/src/modules/executor.test.mjs +92 -1
- package/src/modules/queue.mjs +412 -410
- package/src/modules/registry.mjs +16 -1
- package/src/modules/scheduler.mjs +368 -0
- package/templates/module-fragments/scheduler/00_title.md +1 -0
- package/templates/module-fragments/scheduler/10_overview.md +6 -0
- package/templates/module-fragments/scheduler/20_scope.md +8 -0
- package/templates/module-fragments/scheduler/90_status_implemented.md +3 -0
- package/templates/module-presets/scheduler/packages/scheduler/package.json +24 -0
- package/templates/module-presets/scheduler/packages/scheduler/src/forgeon-scheduler.module.ts +12 -0
- package/templates/module-presets/scheduler/packages/scheduler/src/index.ts +6 -0
- package/templates/module-presets/scheduler/packages/scheduler/src/scheduler-config.loader.ts +23 -0
- package/templates/module-presets/scheduler/packages/scheduler/src/scheduler-config.module.ts +11 -0
- package/templates/module-presets/scheduler/packages/scheduler/src/scheduler-config.service.ts +29 -0
- package/templates/module-presets/scheduler/packages/scheduler/src/scheduler-env.schema.ts +15 -0
- package/templates/module-presets/scheduler/packages/scheduler/src/scheduler.service.ts +123 -0
- package/templates/module-presets/scheduler/packages/scheduler/tsconfig.json +9 -0
package/package.json
CHANGED
|
@@ -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
|
+
|
package/src/modules/executor.mjs
CHANGED
|
@@ -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/);
|
|
@@ -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
|
+
|