@starci/hfs 5.3.0 → 5.5.0
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/CHANGELOG.md +28 -0
- package/README.md +5 -3
- package/cache/test-family.d.mts +1 -1
- package/cache/test-family.mjs +20 -15
- package/codemod/account-doors.mjs +7 -1
- package/codemod/app-prettier.d.mts +5 -0
- package/codemod/app-prettier.mjs +27 -0
- package/codemod/explicit-surface.mjs +1 -25
- package/codemod/main.mjs +14 -5
- package/codemod/module-definition.d.mts +10 -3
- package/codemod/module-definition.mjs +32 -17
- package/codemod/seeds-layout.d.mts +36 -0
- package/codemod/seeds-layout.mjs +113 -0
- package/codemod/sops-rules.d.mts +1 -1
- package/codemod/sops-rules.mjs +8 -3
- package/lint/run.d.mts +5 -4
- package/lint/run.mjs +53 -27
- package/package.json +1 -1
- package/release/lint-report.mjs +4 -3
- package/runtime/knowledge/hfs/canon-pins.yaml +12 -12
- package/runtime/knowledge/hfs/ci-standard.yaml +2 -2
- package/runtime/knowledge/hfs/rules.yaml +15 -2
- package/runtime/knowledge/hfs/slots.yaml +15 -18
- package/runtime/modules/kernel/failure-codes.yaml +64 -3
- package/runtime/scripts/hfs/architecture/root-packages.mjs +1 -1
- package/runtime/scripts/hfs/check.mjs +4 -1
- package/runtime/scripts/hfs/coverage-scope.d.mts +15 -0
- package/runtime/scripts/hfs/coverage-scope.mjs +26 -0
- package/runtime/scripts/hfs/db-squash.mjs +9 -2
- package/runtime/scripts/hfs/declaration-shape.mjs +3 -1
- package/runtime/scripts/hfs/rules/app-root.mjs +4 -1
- package/runtime/scripts/hfs/rules/ci-standard.mjs +5 -4
- package/runtime/scripts/hfs/rules/coverage-below.mjs +12 -4
- package/runtime/scripts/hfs/rules/database-config.mjs +8 -7
- package/runtime/scripts/hfs/rules/database-constants.d.mts +2 -1
- package/runtime/scripts/hfs/rules/database-constants.mjs +2 -1
- package/runtime/scripts/hfs/rules/database.d.mts +2 -2
- package/runtime/scripts/hfs/rules/database.mjs +7 -6
- package/runtime/scripts/hfs/rules/docker.mjs +24 -7
- package/runtime/scripts/hfs/rules/monorepo.d.mts +1 -1
- package/runtime/scripts/hfs/rules/monorepo.mjs +1 -1
- package/runtime/scripts/hfs/rules/seeds.d.mts +21 -0
- package/runtime/scripts/hfs/rules/seeds.mjs +194 -0
- package/runtime/scripts/hfs/rules/stacks.mjs +8 -4
- package/runtime/scripts/hfs/slots.d.mts +2 -0
- package/runtime/scripts/hfs/slots.mjs +1 -0
- package/runtime/scripts/hfs/stack-seeds-config.d.mts +88 -0
- package/runtime/scripts/hfs/stack-seeds-config.mjs +239 -0
- package/runtime/scripts/hfs/view.d.mts +25 -0
- package/runtime/scripts/hfs/view.mjs +41 -0
- package/scaffold/add-app.mjs +2 -2
- package/scaffold/add-cli-lite.mjs +41 -14
- package/scaffold/add-module.mjs +6 -3
- package/scaffold/lite-exports.mjs +1 -1
- package/scaffold/module-registry.mjs +10 -8
- package/src/main.mjs +8 -8
- package/stack/deploy.d.mts +3 -0
- package/stack/deploy.mjs +4 -1
- package/stack/local-up.mjs +25 -3
- package/stack/seeds.d.mts +29 -0
- package/stack/seeds.mjs +197 -0
- package/sync/canon-layout.d.mts +38 -0
- package/sync/canon-layout.mjs +62 -0
- package/sync/ci-commands.d.mts +2 -1
- package/sync/ci-commands.mjs +10 -7
- package/sync/compile-guard.d.mts +36 -0
- package/sync/compile-guard.mjs +189 -0
- package/sync/index.d.mts +22 -3
- package/sync/index.mjs +80 -32
- package/templates/app/ci-workflows/github/workflows/ci.yml +2 -2
- package/templates/app/ci-workflows-lite/github/workflows/ci.yml +2 -2
- package/templates/app/skeleton/.starcistacks/application-stacks.yaml +26 -0
- package/templates/app/skeleton/.starcistacks/dev/README.md +21 -10
- package/templates/app/skeleton/.starcistacks/dev/seeds/data/.gitkeep +0 -0
- package/templates/app/skeleton/.starcistacks/dev/seeds/files/.gitkeep +0 -0
- package/templates/app/skeleton/.starcistacks/dev/seeds/seeds.yaml +3 -0
- package/templates/app/skeleton/.starcistacks/production/seeds/data/.gitkeep +0 -0
- package/templates/app/skeleton/.starcistacks/production/seeds/files/.gitkeep +0 -0
- package/templates/app/skeleton/.starcistacks/production/seeds/seeds.yaml +2 -0
- package/templates/app/skeleton/.starcistacks/production/seeds/sql/.gitkeep +0 -0
- package/templates/app/skeleton/.starcistacks/seeds/data/.gitkeep +0 -0
- package/templates/app/skeleton/.starcistacks/seeds/files/.gitkeep +0 -0
- package/templates/app/skeleton/.starcistacks/seeds/seeds.yaml +2 -0
- package/templates/app/skeleton/.starcistacks/seeds/sql/.gitkeep +0 -0
- package/templates/app/skeleton/README.md +1 -1
- package/templates/app/skeleton-lite/.starcistacks/dev/README.md +2 -2
- package/templates/app/skeleton-lite/.starcistacks/dev/seeds/data/.gitkeep +0 -0
- package/templates/app/skeleton-lite/.starcistacks/dev/seeds/files/.gitkeep +0 -0
- package/templates/app/skeleton-lite/.starcistacks/dev/seeds/seeds.yaml +2 -0
- package/templates/app/skeleton-lite/.starcistacks/dev/seeds/sql/.gitkeep +0 -0
- package/templates/app/skeleton-lite/.starcistacks/production/seeds/data/.gitkeep +0 -0
- package/templates/app/skeleton-lite/.starcistacks/production/seeds/files/.gitkeep +0 -0
- package/templates/app/skeleton-lite/.starcistacks/production/seeds/seeds.yaml +2 -0
- package/templates/app/skeleton-lite/.starcistacks/production/seeds/sql/.gitkeep +0 -0
- package/templates/app/skeleton-lite/.starcistacks/seeds/data/.gitkeep +0 -0
- package/templates/app/skeleton-lite/.starcistacks/seeds/files/.gitkeep +0 -0
- package/templates/app/skeleton-lite/.starcistacks/seeds/seeds.yaml +2 -0
- package/templates/app/skeleton-lite/.starcistacks/seeds/sql/.gitkeep +0 -0
- package/templates/app/skeleton-lite/README.md +2 -2
- package/templates/app/skeleton-lite/db/migrations/__timestamp___baseline.sql +3 -1
- package/templates/be/auth-lite/src/features/api/auth/application/auth.contracts.ts +2 -0
- package/templates/be/auth-lite/src/features/api/auth/application/signup.handler.ts +6 -1
- package/templates/be/auth-lite/src/features/api/auth/auth.module.ts +14 -1
- package/templates/be/auth-lite/src/features/api/auth/transport/http/auth.controller.ts +5 -1
- package/templates/be/auth-lite/src/features/api/auth/transport/http/dto/reset.request.ts +0 -1
- package/templates/be/auth-lite/src/features/api/auth/transport/http/dto/signup.request.ts +7 -0
- package/templates/be/auth-lite/src/modules/domain/identity/account-directory.service.spec.ts +15 -0
- package/templates/be/auth-lite/src/modules/domain/identity/account-directory.service.ts +9 -3
- package/templates/be/auth-lite/src/modules/domain/identity/authenticator.service.spec.ts +79 -16
- package/templates/be/auth-lite/src/modules/domain/identity/authenticator.service.ts +39 -9
- package/templates/be/auth-lite/src/modules/domain/identity/identity-email.service.spec.ts +108 -20
- package/templates/be/auth-lite/src/modules/domain/identity/identity-email.service.ts +34 -7
- package/templates/be/auth-lite/src/modules/domain/identity/persistence/account-doors.port.ts +28 -4
- package/templates/be/auth-lite/src/modules/domain/identity/persistence/entities/identity-refresh-token.entity.ts +4 -0
- package/templates/be/auth-lite/src/modules/domain/identity/persistence/entities/index.ts +6 -1
- package/templates/be/auth-lite/src/modules/domain/identity/rate-counter.service.spec.ts +9 -2
- package/templates/be/auth-lite/src/modules/domain/identity/rate-counter.service.ts +11 -2
- package/templates/be/auth-lite/src/modules/domain/identity/session-store.service.spec.ts +63 -10
- package/templates/be/auth-lite/src/modules/domain/identity/session-store.service.ts +10 -2
- package/templates/be/auth-lite/src/modules/domain/identity/sign-in.service.spec.ts +25 -4
- package/templates/be/auth-lite/src/modules/domain/identity/sign-in.service.ts +8 -1
- package/templates/be/auth-lite/src/tests/fixtures/builders/identity.builder.ts +3 -1
- package/templates/be/skeleton/src/features/cli/seed/subs/run.cli.ts +3 -3
- package/templates/be/skeleton/src/modules/platform/database/database.sql.ts +1 -1
- package/templates/be/skeleton/src/modules/platform/database/seed-connections.client.spec.ts +3 -2
- package/templates/be/skeleton/src/modules/platform/database/seed-connections.client.ts +6 -2
- package/templates/be/skeleton/src/modules/platform/database/seed-runner.service.spec.ts +14 -9
- package/templates/be/skeleton/src/modules/platform/database/seed-runner.service.ts +3 -3
- package/templates/be/skeleton-lite/src/modules/platform/database/seed-runner.service.ts +32 -7
- package/templates/be/tool-config/jest.config.js +1 -1
- package/templates/fe/auth-lite/apps/web/src/modules/auth/email-doors.ts +4 -1
- package/templates/fe/skeleton-lite/apps/web/src/components/blocks/EmailDoorForm/component.tsx +3 -0
- package/templates/fe/skeleton-lite/apps/web/src/components/blocks/EmailDoorForm/index.tsx +14 -6
- package/templates/fe/skeleton-lite/apps/web/src/modules/i18n/messages/en.json +1 -0
- package/templates/fe/skeleton-lite/apps/web/src/modules/i18n/messages/vi.json +1 -0
- package/templates/app/module/jest.config.js.tpl +0 -8
- /package/templates/app/skeleton/.starcistacks/dev/seeds/{primary-notes.sql → sql/primary-notes.sql} +0 -0
package/sync/index.mjs
CHANGED
|
@@ -21,7 +21,7 @@ import fs from 'node:fs';
|
|
|
21
21
|
import path from 'node:path';
|
|
22
22
|
import { fileURLToPath } from 'node:url';
|
|
23
23
|
import { braceVariants } from '../runtime/scripts/lib/glob.mjs';
|
|
24
|
-
import { codecovPaths, coverageComponents, jestCoverage, sonarCoverageExclusions } from '../runtime/scripts/hfs/coverage-scope.mjs';
|
|
24
|
+
import { codecovPaths, coverageComponents, jestCoverage, packagesCoverage, sonarCoverageExclusions } from '../runtime/scripts/hfs/coverage-scope.mjs';
|
|
25
25
|
import { APP_SCOPE, SIDES, litePresenceOf, loadSlotManifest, resolveAppDeclaration, slotInEdition } from '../runtime/scripts/hfs/slots.mjs';
|
|
26
26
|
import { managedGroupOf } from '../runtime/scripts/hfs/edition-slots.mjs';
|
|
27
27
|
import { declaredSlotEnabled } from '../runtime/scripts/hfs/declaration-slots.mjs';
|
|
@@ -29,12 +29,14 @@ import { feAppPackageName } from '../runtime/scripts/hfs/rules/monorepo.mjs';
|
|
|
29
29
|
import { DEFAULT_PORT, NODE_IMAGE, dockerfilePath } from '../runtime/scripts/hfs/rules/docker.mjs';
|
|
30
30
|
import { isFile } from '../runtime/scripts/lib/fs-kind.mjs';
|
|
31
31
|
import { readDeclaredSonarKey } from './sonar-key.mjs';
|
|
32
|
+
import { RETIRED_GROUP, guardWrites, heldLines, isGuardedSource } from './compile-guard.mjs';
|
|
32
33
|
import { readDeclaredStack, stackWorkflowVariables } from './stack-images.mjs';
|
|
33
34
|
import { DB_DOCKERFILE, DB_IMAGE_APP, RETIRED_DB_DOCKERFILE } from '../db/image.mjs';
|
|
34
35
|
import { byCodeUnit } from '../report/order.mjs';
|
|
35
36
|
import { isRuntimeExampleApp } from '../runtime/scripts/lib/runtime-example.mjs';
|
|
36
37
|
import { ciWorkflowCommands } from './ci-commands.mjs';
|
|
37
38
|
import { MODULE_PREFIX, moduleNames, registryFiles } from '../scaffold/module-registry.mjs';
|
|
39
|
+
import { canonImport, canonList } from './canon-layout.mjs';
|
|
38
40
|
import { dependabotUpdates, hasDeclaredServices, readDeclaredQuality, readDeclaredBadges, sonarHostLine } from './quality-services.mjs';
|
|
39
41
|
import { qualityServices } from '../runtime/scripts/hfs/ci-declaration.mjs';
|
|
40
42
|
import { grammarLintFor } from '../runtime/scripts/lib/grammar-lint.mjs';
|
|
@@ -99,7 +101,7 @@ function slotTargets(slot, scope, edition, manifest, hasTemplate, enabled) {
|
|
|
99
101
|
const template = managedTemplate(scope, group, file, hasTemplate);
|
|
100
102
|
if (!template)
|
|
101
103
|
throw new SyncError('HFS_SYNC_TEMPLATE_MISSING', `slot ${slot.id} manages ${file} for ${scope}, but templates/{${scope},common}/${group} has no template for it`);
|
|
102
|
-
return { path: file, template, mode: MODE_OF_GROUP[group] ?? 'file', slot: slot.id };
|
|
104
|
+
return { path: file, template, mode: MODE_OF_GROUP[group] ?? 'file', slot: slot.id, group };
|
|
103
105
|
});
|
|
104
106
|
}
|
|
105
107
|
function unlistedTargets(scope, edition) {
|
|
@@ -248,16 +250,23 @@ const SOURCE_SKIPPED = new Set(['node_modules', 'dist', 'coverage']);
|
|
|
248
250
|
* lite brand tree exists, so sync can preserve its absence. `hasImage` identifies authored optional Dockerfiles without reading
|
|
249
251
|
* their content. An app with no be folder has no source files.
|
|
250
252
|
*/
|
|
251
|
-
/**
|
|
253
|
+
/**
|
|
254
|
+
* The packages of `packages/` that are neither a module package nor the registry: a folder that holds a `package.json` and a `src/` (an install folder
|
|
255
|
+
* the workspaces leave there, `packages/node_modules`, is none).
|
|
256
|
+
*/
|
|
252
257
|
function otherPackages(root) {
|
|
253
258
|
try {
|
|
254
259
|
return fs.readdirSync(path.join(root, 'packages'), { withFileTypes: true })
|
|
255
|
-
.filter(entry => entry.isDirectory() && !entry.name.startsWith(MODULE_PREFIX) && entry.name !== 'modules'
|
|
260
|
+
.filter(entry => entry.isDirectory() && !entry.name.startsWith(MODULE_PREFIX) && entry.name !== 'modules'
|
|
261
|
+
&& isFile(path.join(root, 'packages', entry.name, 'package.json'), { followLinks: false }) && fs.existsSync(path.join(root, 'packages', entry.name, 'src')))
|
|
262
|
+
.map(entry => entry.name).sort(byCodeUnit);
|
|
256
263
|
}
|
|
257
264
|
catch {
|
|
258
265
|
return [];
|
|
259
266
|
}
|
|
260
267
|
}
|
|
268
|
+
/** The app-relative folders of the root packages whose logic the back end's unit run measures: every module package and every other package, never the registry. */
|
|
269
|
+
export const rootPackageFolders = (source) => [...(source?.modules ?? []).map(name => `packages/${MODULE_PREFIX}${name}`), ...(source?.otherPackages ?? []).map(name => `packages/${name}`)];
|
|
261
270
|
export function appSource(root, { parseYaml, notices } = {}) {
|
|
262
271
|
const files = [];
|
|
263
272
|
const walk = (dir) => {
|
|
@@ -282,7 +291,7 @@ export function appSource(root, { parseYaml, notices } = {}) {
|
|
|
282
291
|
walk(dir);
|
|
283
292
|
return { runtimeExample: isRuntimeExampleApp(root), files: files.sort(byCodeUnit), read: (file) => fs.readFileSync(path.join(root, 'be', file), 'utf8'),
|
|
284
293
|
modules: moduleNames(root), otherPackages: otherPackages(root),
|
|
285
|
-
workPresent: fs.existsSync(path.join(root, '.starciwork')), hasImage: (file) => isFile(path.join(root, file), { followLinks: false }),
|
|
294
|
+
workPresent: fs.existsSync(path.join(root, '.starciwork')), hasContracts: fs.existsSync(path.join(root, 'be', 'contracts')), hasImage: (file) => isFile(path.join(root, file), { followLinks: false }),
|
|
286
295
|
stack: readDeclaredStack(root, { parseYaml }), quality: readDeclaredQuality(root, { parseYaml }),
|
|
287
296
|
badges: readDeclaredBadges(root, { parseYaml, notices }),
|
|
288
297
|
qualityDeclared: hasDeclaredServices(root, { parseYaml }) };
|
|
@@ -336,7 +345,7 @@ export function appScripts(app, source) {
|
|
|
336
345
|
}),
|
|
337
346
|
...fe.map(entry => line(`start:${entry.name}`, `npm run start -w ${workspace(entry)}`)),
|
|
338
347
|
...(app.edition !== 'lite' && app.optionalSlots.includes('app.browser') ? [line('test:browser', 'playwright test -c browser/playwright.config.ts')] : []),
|
|
339
|
-
...(source
|
|
348
|
+
...(rootPackageFolders(source).length ? [line('build:modules', 'turbo run build --filter=./packages/*')] : []),
|
|
340
349
|
line('docker:build', images.map(image => `npm run docker:build:${image.entry.name}`).join(' && ')),
|
|
341
350
|
...images.map(image => line(`docker:build:${image.entry.name}`, imageCommand(image))),
|
|
342
351
|
].join('\n ');
|
|
@@ -376,11 +385,13 @@ const IMAGE_TEMPLATE = Object.freeze({ api: 'be/image/api', worker: 'be/image/wo
|
|
|
376
385
|
/**
|
|
377
386
|
* The Dockerfile of every declared app, [{ path, content }] (app-relative): written once by starci app scaffold from templates/<side>/image/<kind>
|
|
378
387
|
* and then the app's own, judged by the docker rules (R187-R191). A Next image copies the fe packages only when the fe side opts into
|
|
379
|
-
* them and the be contracts only when the fe side reads them
|
|
388
|
+
* them and the be contracts only when the fe side reads them and the back end holds them (`source.hasContracts`: a back end that emits no
|
|
389
|
+
* contract has no be/contracts/, and a COPY of a missing folder fails the image build), so each COPY names a folder the app has.
|
|
380
390
|
*/
|
|
381
391
|
export function imageFiles(app, source) {
|
|
382
392
|
const side = app.sides.fe;
|
|
383
|
-
const
|
|
393
|
+
const contracts = (side.reads ?? []).includes('be/contracts/') && source?.hasContracts === true;
|
|
394
|
+
const inputs = [...(opensPackages(side) ? ['fe/packages'] : []), ...(contracts ? ['be/contracts'] : [])];
|
|
384
395
|
const copyInputs = inputs.map(folder => `COPY ${folder} ${folder}\n`).join('');
|
|
385
396
|
// The folders outside be/ that the back end declares it reads (the generated database types of a lite back end, db/types/) are in every one of its images.
|
|
386
397
|
const beFolders = (app.sides.be.reads ?? []).map(folder => folder.replace(/\/$/, ''));
|
|
@@ -393,32 +404,49 @@ export function imageFiles(app, source) {
|
|
|
393
404
|
});
|
|
394
405
|
}
|
|
395
406
|
/** Every value a template of `scope` can name, derived from hfs.json and the presets. */
|
|
396
|
-
/**
|
|
397
|
-
|
|
398
|
-
|
|
407
|
+
/**
|
|
408
|
+
* The `packages` option of the managed be/jest.config.js (`starciJestConfig`), laid out as prettier lays it out (width 120, indent 4): the root packages whose
|
|
409
|
+
* specs the unit run holds, with the files measured in them; an app without one has no such option.
|
|
410
|
+
*/
|
|
411
|
+
export function jestPackagesSource(packages) {
|
|
412
|
+
if (packages.folders.length === 0)
|
|
413
|
+
return '';
|
|
414
|
+
const list = (key, items) => {
|
|
415
|
+
const one = ` ${key}: [${items.map(item => JSON.stringify(item)).join(', ')}],`;
|
|
416
|
+
return one.length <= 120 ? one : [` ${key}: [`, ...items.map(item => ` ${JSON.stringify(item)},`), ' ],'].join('\n');
|
|
417
|
+
};
|
|
418
|
+
return ['', ' packages: {', list('folders', packages.folders), list('files', packages.files), list('excludes', packages.excludes), ' },'].join('\n');
|
|
419
|
+
}
|
|
420
|
+
/**
|
|
421
|
+
* The managed files of the registry package for the module packages the app has (empty when it has none). An app that keeps its modules as data
|
|
422
|
+
* (hfs.json `modules: "data"`) has no rendered registry: a module is a seed there, and packages/modules, if any, is the app's own.
|
|
423
|
+
*/
|
|
399
424
|
function registryTargets(app, source) {
|
|
425
|
+
if (app.modules === 'data')
|
|
426
|
+
return [];
|
|
400
427
|
return registryFiles(app.project, source?.modules ?? []).map((file) => ({ path: file.path, scope: APP_SCOPE, mode: 'file', content: file.content, hash: hashOf(file.content) }));
|
|
401
428
|
}
|
|
402
429
|
/**
|
|
403
|
-
* The root `eslint.config.mjs` of an app that has root workspace packages (packages
|
|
430
|
+
* The root `eslint.config.mjs` of an app that has root workspace packages (packages/<name>: the agent SDK, a block library, a module package, the registry): no side config
|
|
404
431
|
* reaches `packages/`, so this managed file lints their source by subpath. The back-end canon judges everything but the front-end subpaths
|
|
405
432
|
* (`src/workbench/` outside its contract, `src/public/`), the front-end canon and the selected Grammar family those, each over the package view of
|
|
406
433
|
* the app root. An app without a root package has no such file; the family factory for packages is the family's `<name>GrammarPackagesConfig`.
|
|
434
|
+
* It is laid out as the canon Prettier config prints it (`canonList`, `canonImport`), so the app's format check passes it as rendered.
|
|
407
435
|
*/
|
|
408
436
|
function packagesLintTargets(app, source) {
|
|
409
437
|
if (!(source?.modules?.length || source?.otherPackages?.length))
|
|
410
438
|
return [];
|
|
411
439
|
const grammar = grammarLintFor(app.sides.fe.grammar);
|
|
412
|
-
const
|
|
440
|
+
const family = grammar.package ? { name: grammar.package, factory: grammar.factory.replace(/Config$/, 'PackagesConfig') } : null;
|
|
413
441
|
const content = [
|
|
414
442
|
'import { loadPackagesHfs as loadBePackages, starciBePackagesConfig } from "@starci/eslint-canon-be"',
|
|
415
443
|
'import { loadPackagesHfs as loadFePackages, starciFePackagesConfig } from "@starci/eslint-canon-fe"',
|
|
416
|
-
...(
|
|
444
|
+
...(family ? [canonImport({ names: [family.factory], from: family.name })] : []),
|
|
417
445
|
'',
|
|
418
446
|
'const be = loadBePackages(import.meta.url)',
|
|
419
447
|
'const fe = loadFePackages(import.meta.url)',
|
|
420
448
|
'',
|
|
421
|
-
|
|
449
|
+
canonList({ head: 'export default ', items: ['...(await starciBePackagesConfig({ hfs: be }))', '...starciFePackagesConfig({ hfs: fe })', ...(family ? [`...${family.factory}({ hfs: fe })`] : [])] }),
|
|
422
450
|
'',
|
|
423
451
|
].join(LINE);
|
|
424
452
|
return [{ path: PACKAGES_LINT_FILE, scope: APP_SCOPE, mode: 'file', content, hash: hashOf(content) }];
|
|
@@ -430,10 +458,11 @@ export function variables(app, scope, presets, sonarKey, manifest = loadSlotMani
|
|
|
430
458
|
const lite = app.edition === 'lite';
|
|
431
459
|
const providers = (app.sides.be.connections ?? []).map(connection => connection.provider);
|
|
432
460
|
const quality = source.quality ?? qualityServices({});
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
const
|
|
461
|
+
// The root packages (the agent SDK, the module packages and the rest) are built before anything imports them (typecheck, lint, the fe build and the be
|
|
462
|
+
// run), and their logic is measured by the back end's unit run like its own (packagesCoverage, BE-TEST-13).
|
|
463
|
+
const rootPackages = packagesCoverage(rootPackageFolders(source));
|
|
464
|
+
const moduleDirs = rootPackages.folders;
|
|
465
|
+
const modulesFirst = moduleDirs.length ? 'npm run build:modules --silent && ' : '';
|
|
437
466
|
const grammarLint = scope === 'fe' ? grammarLintFor(fe.grammar) : null;
|
|
438
467
|
return {
|
|
439
468
|
header: HEADER(scope),
|
|
@@ -456,9 +485,8 @@ export function variables(app, scope, presets, sonarKey, manifest = loadSlotMani
|
|
|
456
485
|
typecheck: `${modulesFirst}npm run codegen --silent && tsc -p be/tsconfig.json && turbo run typecheck`,
|
|
457
486
|
// The typed lint reads the types the fe packages declare (their dist), so the packages are built first.
|
|
458
487
|
lintPrefix: `${modulesFirst}${packages ? 'npm run codegen --silent && turbo run build --filter=./fe/packages/* && ' : 'npm run codegen --silent && '}`,
|
|
459
|
-
// The unit run, the back-end build and every CI job that imports a
|
|
488
|
+
// The unit run, the back-end build and every CI job that imports a root package find it built (build:modules precedes them); its specs run in the unit run and its shards.
|
|
460
489
|
modulesFirst,
|
|
461
|
-
moduleTestStep: modules.length ? ' - name: module packages\n run: npm run test:modules\n' : '',
|
|
462
490
|
nodeMajor: String(NODE_MAJOR),
|
|
463
491
|
// The deploy server's age identity the restore workflow writes (stack/server-key.mjs owns the path).
|
|
464
492
|
serverAgeIdentity: SERVER_AGE_IDENTITY,
|
|
@@ -471,12 +499,13 @@ export function variables(app, scope, presets, sonarKey, manifest = loadSlotMani
|
|
|
471
499
|
sonarSources: ['be/apps', 'be/src', 'fe/apps', ...(packages ? ['fe/packages'] : []), ...moduleDirs.map(dir => `${dir}/src`)].join(','),
|
|
472
500
|
lcovReport: LCOV_REPORT,
|
|
473
501
|
cpdExclusions: SONAR_CPD_EXCLUSIONS.join(','),
|
|
474
|
-
...ciWorkflowCommands(source, LCOV_REPORT, source.qualityDeclared === false ? undefined : source.quality, { app, packages }),
|
|
502
|
+
...ciWorkflowCommands(source, LCOV_REPORT, source.qualityDeclared === false ? undefined : source.quality, { app, packages, rootPackages: moduleDirs.length > 0 }),
|
|
475
503
|
// The coverage scope is the same in both editions: lite measures exactly what full measures (ruling:R212/case-1, amended by ruling:R212/case-4).
|
|
476
504
|
jestCoverage: scope === 'be' ? jestCoverageSource(jestCoverage(manifest, providers)) : '',
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
505
|
+
jestPackages: scope === 'be' ? jestPackagesSource(rootPackages) : '',
|
|
506
|
+
coverageExclusions: scope === APP_SCOPE ? [...sonarCoverageExclusions(manifest, providers), ...rootPackages.excludes].join(',') : '',
|
|
507
|
+
codecovExclusions: scope === APP_SCOPE ? [...sonarCoverageExclusions(manifest, providers), ...rootPackages.excludes].map(glob => ` - ${JSON.stringify(glob)}`).join('\n') : '',
|
|
508
|
+
codecovPaths: scope === APP_SCOPE ? [...codecovPaths(manifest, providers), ...moduleDirs.map(dir => `${dir}/src/**`)].map(glob => ` - ${JSON.stringify(glob)}`).join('\n') : '',
|
|
480
509
|
codecovComponents: scope === APP_SCOPE ? componentsYaml(coverageComponents(manifest, { ...source, apps: app.sides.be.apps, providers })) : '',
|
|
481
510
|
codecovTarget: quality.codecov.target,
|
|
482
511
|
dependabotUpdates: dependabotUpdates(quality.dependabot),
|
|
@@ -548,7 +577,7 @@ function renderOnce(hfs, presets, { sonarKey, readTemplate = readBundled, manife
|
|
|
548
577
|
const markers = target.markdown ? { begin: '<!-- >>> starci app sync (managed block; do not edit) >>> -->', end: '<!-- <<< starci app sync <<< -->' } : { begin: BLOCK_BEGIN, end: BLOCK_END };
|
|
549
578
|
const spacing = target.markdown ? '\n' : '';
|
|
550
579
|
const managed = target.mode === 'block' ? `${markers.begin}\n${spacing}${trailingNewline(body)}${spacing}${markers.end}\n` : body;
|
|
551
|
-
return { path: where, scope, mode: target.mode, markdown: target.markdown, markers, content: managed, hash: hashOf(managed) };
|
|
580
|
+
return { path: where, scope, mode: target.mode, markdown: target.markdown, markers, content: managed, hash: hashOf(managed), ...(target.group === undefined ? {} : { group: target.group }) };
|
|
552
581
|
})];
|
|
553
582
|
});
|
|
554
583
|
}
|
|
@@ -714,6 +743,16 @@ export function retiredFiles(root, hfs) {
|
|
|
714
743
|
walk(file);
|
|
715
744
|
return found;
|
|
716
745
|
}
|
|
746
|
+
/** The writes of `targets` and the deletions of `retired` the compile guard judges: every file sync would change, with its managed group. */
|
|
747
|
+
export function guardedWrites(root, targets, retired) {
|
|
748
|
+
const changed = checkTargets(root, targets).filter(result => result.status !== 'ok' && isGuardedSource(result.path));
|
|
749
|
+
const writes = changed.map((result) => {
|
|
750
|
+
const target = targets.find(candidate => candidate.path === result.path);
|
|
751
|
+
const file = path.join(root, target.path);
|
|
752
|
+
return { path: target.path, group: target.group ?? target.path, text: writtenContent(target, fs.existsSync(file) ? lf(fs.readFileSync(file, 'utf8')) : '') };
|
|
753
|
+
});
|
|
754
|
+
return [...writes, ...retired.map((file) => ({ path: file, group: RETIRED_GROUP, text: null }))];
|
|
755
|
+
}
|
|
717
756
|
/** `starci app sync [--execute] [--cwd <dir>]` at the app root: the dry run reports drift, --execute writes; returns 0 clean, 1 drift, 2 usage. */
|
|
718
757
|
function syncOptions(argv) {
|
|
719
758
|
const write = argv.includes('--execute');
|
|
@@ -738,7 +777,7 @@ function reportSyncCheck(root, targets, out, retired = []) {
|
|
|
738
777
|
out(`starci app sync: ${results.length - bad.length} of ${results.length} in sync${bad.length ? '; run "npx --no-install starci app sync --execute"' : ''}`);
|
|
739
778
|
return bad.length || retired.length ? 1 : 0;
|
|
740
779
|
}
|
|
741
|
-
export async function runSync(argv, { cwd = process.cwd(), out = (line) => process.stdout.write(`${line}\n`), presets, parseYaml } = {}) {
|
|
780
|
+
export async function runSync(argv, { cwd = process.cwd(), out = (line) => process.stdout.write(`${line}\n`), presets, parseYaml, loadTs } = {}) {
|
|
742
781
|
const options = syncOptions(argv);
|
|
743
782
|
if (!options) {
|
|
744
783
|
out('usage: starci app sync [--execute] [--cwd <dir>]');
|
|
@@ -754,18 +793,27 @@ export async function runSync(argv, { cwd = process.cwd(), out = (line) => proce
|
|
|
754
793
|
for (const notice of new Set(notices))
|
|
755
794
|
out(notice);
|
|
756
795
|
if (write) {
|
|
757
|
-
const
|
|
796
|
+
const { held, unchecked } = guardWrites(root, guardedWrites(root, targets, retired), loadTs);
|
|
797
|
+
const holds = new Set(held.flatMap(group => group.files));
|
|
798
|
+
const free = targets.filter(target => !holds.has(target.path));
|
|
799
|
+
const written = writeTargets(root, free);
|
|
800
|
+
const removed = retired.filter(file => !holds.has(file));
|
|
758
801
|
for (const file of written)
|
|
759
802
|
out(`wrote ${file}`);
|
|
760
|
-
for (const file of
|
|
803
|
+
for (const file of removed) {
|
|
761
804
|
fs.rmSync(path.join(root, file));
|
|
762
805
|
out(`removed ${file} (retired)`);
|
|
763
806
|
}
|
|
764
807
|
for (const dir of LITE_RETIRED_DIRS)
|
|
765
|
-
if (
|
|
808
|
+
if (removed.length && fs.existsSync(path.join(root, dir)) && !fs.readdirSync(path.join(root, dir)).length)
|
|
766
809
|
fs.rmdirSync(path.join(root, dir));
|
|
767
|
-
|
|
768
|
-
|
|
810
|
+
for (const project of unchecked)
|
|
811
|
+
out(`notice: the writes compiled by ${project} were not type-checked: its TypeScript is not installed (run npm install, then "npx --no-install starci app check")`);
|
|
812
|
+
for (const group of held)
|
|
813
|
+
for (const line of heldLines(group))
|
|
814
|
+
out(line);
|
|
815
|
+
out(`starci app sync: ${written.length} written, ${free.length - written.length} already in sync${holds.size ? `, ${holds.size} held` : ''}`);
|
|
816
|
+
return held.length ? 1 : 0;
|
|
769
817
|
}
|
|
770
818
|
return reportSyncCheck(root, targets, out, retired);
|
|
771
819
|
}
|
|
@@ -92,7 +92,7 @@ jobs:
|
|
|
92
92
|
run: npm run typecheck
|
|
93
93
|
env:
|
|
94
94
|
NODE_OPTIONS: {{nodeOptions}}
|
|
95
|
-
|
|
95
|
+
|
|
96
96
|
lint:
|
|
97
97
|
needs: plan
|
|
98
98
|
if: ${{ needs.plan.outputs.checks == 'true' }}
|
|
@@ -101,7 +101,7 @@ jobs:
|
|
|
101
101
|
strategy:
|
|
102
102
|
fail-fast: false
|
|
103
103
|
matrix:
|
|
104
|
-
side:
|
|
104
|
+
side: {{lintSides}}
|
|
105
105
|
steps:
|
|
106
106
|
{{> app/ci-workflows/install-steps.yml}}
|
|
107
107
|
{{> app/ci-workflows/lint-base.yml}}
|
|
@@ -92,7 +92,7 @@ jobs:
|
|
|
92
92
|
run: npm run typecheck
|
|
93
93
|
env:
|
|
94
94
|
NODE_OPTIONS: {{nodeOptions}}
|
|
95
|
-
|
|
95
|
+
|
|
96
96
|
lint:
|
|
97
97
|
needs: plan
|
|
98
98
|
if: ${{ needs.plan.outputs.checks == 'true' }}
|
|
@@ -101,7 +101,7 @@ jobs:
|
|
|
101
101
|
strategy:
|
|
102
102
|
fail-fast: false
|
|
103
103
|
matrix:
|
|
104
|
-
side:
|
|
104
|
+
side: {{lintSides}}
|
|
105
105
|
steps:
|
|
106
106
|
{{> app/ci-workflows/install-steps.yml}}
|
|
107
107
|
{{> app/ci-workflows/lint-base.yml}}
|
|
@@ -40,6 +40,32 @@ environments:
|
|
|
40
40
|
runbook: README.md
|
|
41
41
|
# Custody starts empty; add real sealed members after owner provisioning.
|
|
42
42
|
secrets: []
|
|
43
|
+
# `starci app stack up --env dev --execute` reads this block (PROJECT-STACKS-6): the compose services in dependency order, each
|
|
44
|
+
# awaited on its healthcheck, then readiness and the origins. The app images come from `starci app stack prepare --env dev --execute`.
|
|
45
|
+
local:
|
|
46
|
+
ports:
|
|
47
|
+
core: 3001
|
|
48
|
+
app: 3000
|
|
49
|
+
landing: 3002
|
|
50
|
+
services:
|
|
51
|
+
- {name: postgres, wait: healthy}
|
|
52
|
+
- {name: core, wait: healthy, port: core}
|
|
53
|
+
- {name: app, wait: healthy, port: app}
|
|
54
|
+
- {name: landing, wait: healthy, port: landing}
|
|
55
|
+
ready:
|
|
56
|
+
- {name: core-live, url: "http://127.0.0.1:${port.core}/health/live"}
|
|
57
|
+
- {name: core-ready, url: "http://127.0.0.1:${port.core}/health/ready"}
|
|
58
|
+
- {name: app-live, url: "http://127.0.0.1:${port.app}/health/live"}
|
|
59
|
+
- {name: landing-live, url: "http://127.0.0.1:${port.landing}/health/live"}
|
|
60
|
+
verify:
|
|
61
|
+
- {name: core-live, url: "http://127.0.0.1:${port.core}/health/live"}
|
|
62
|
+
- {name: core-ready, url: "http://127.0.0.1:${port.core}/health/ready"}
|
|
63
|
+
- {name: app-live, url: "http://127.0.0.1:${port.app}/health/live"}
|
|
64
|
+
- {name: landing-live, url: "http://127.0.0.1:${port.landing}/health/live"}
|
|
65
|
+
origins:
|
|
66
|
+
- {name: app, url: "http://127.0.0.1:${port.app}"}
|
|
67
|
+
- {name: landing, url: "http://127.0.0.1:${port.landing}"}
|
|
68
|
+
- {name: core, url: "http://127.0.0.1:${port.core}"}
|
|
43
69
|
production:
|
|
44
70
|
status: supported
|
|
45
71
|
runtime: docker-swarm
|
|
@@ -1,16 +1,27 @@
|
|
|
1
1
|
# Development stack
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
3
|
+
The dev environment is one command: `starci app stack up --env dev --execute`. It checks the machine, opens the dev custody, starts the
|
|
4
|
+
compose services of `infra/compose/stack.yaml` in Docker Linux containers (the pinned local PostgreSQL first, then the core API and the product
|
|
5
|
+
and landing front ends), waits for each healthcheck and for readiness, and prints the origins. What it runs is the `local` block of
|
|
6
|
+
`environments.dev` in `.starcistacks/application-stacks.yaml` (PROJECT-STACKS-6 in contexts/project/stacks.md); this runbook never carries a
|
|
7
|
+
docker command, an agent never runs docker by hand. Trust authentication is confined to this local, unpublished database.
|
|
8
|
+
|
|
9
|
+
**A new machine.** Install what `starci app stack doctor --env dev` reports missing (it prints the exact winget, brew or apt step per tool; Node
|
|
10
|
+
from `.nvmrc`, npm, git, Docker with Compose v2 running Linux containers, WSL2 on Windows, sops and age), then run `prepare` and `up`.
|
|
11
|
+
|
|
12
|
+
**Custody.** The API reads its database connection from the owner-controlled file `/run/starci-secrets/{{project}}/dev/primary-db-url`, which
|
|
13
|
+
the compose model mounts into the API; materialize its real sealed member there before `up`. The scaffold creates no fake secret or
|
|
14
|
+
successful proof.
|
|
5
15
|
|
|
6
16
|
| Command | Operation |
|
|
7
17
|
| --- | --- |
|
|
8
|
-
| prepare |
|
|
9
|
-
| doctor |
|
|
10
|
-
| up |
|
|
11
|
-
| status |
|
|
12
|
-
| logs |
|
|
13
|
-
| down |
|
|
14
|
-
| verification |
|
|
18
|
+
| prepare | `starci app stack prepare --env dev --execute`: checks the machine, opens the custody and builds the app images. |
|
|
19
|
+
| doctor | `starci app stack doctor --env dev`: read-only table of host prerequisites with the owner-run install step per OS, custody, compose render and ports. |
|
|
20
|
+
| up | `starci app stack up --env dev --execute`: prepare, then the services in order and readiness; prints the origins. |
|
|
21
|
+
| status | `starci app stack status --env dev`: desired against running and every readiness probe. |
|
|
22
|
+
| logs | `starci app stack logs --env dev --service core` (a compose service, bounded and redacted; never prints mounted credentials). |
|
|
23
|
+
| down | `starci app stack down --env dev --execute`: stops the compose project; volumes and sealed values stay. |
|
|
24
|
+
| verification | `starci app stack verify --env dev`: the declared liveness and readiness probes after a real start, then `npm run lint` from the app root. |
|
|
15
25
|
|
|
16
|
-
Keep real sealed members under `.starcistacks/dev/secrets/*.enc`, add them to the manifest after sealing and inventory their key names in
|
|
26
|
+
Keep real sealed members under `.starcistacks/dev/secrets/*.enc.yaml`, add them to the manifest after sealing and inventory their key names in
|
|
27
|
+
`.starcistacks/dev/runtime/env/KEYS.md`.
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
@@ -36,7 +36,7 @@ npm run build:fe
|
|
|
36
36
|
The core api app reads `PORT`, `HTTP_SECURITY_ALLOWED_ORIGINS` (comma-separated origins allowed to send state-changing browser
|
|
37
37
|
requests) and `PRIMARY_DB_URL` at boot; every door is closed until it is marked public or sign-in is designed. The cli app reads
|
|
38
38
|
`PRIMARY_DB_URL`: `npm run migrate` applies the migrations of the primary database before the api starts, and
|
|
39
|
-
`npm run cli -- seed run` runs the
|
|
39
|
+
`npm run cli -- seed run` runs the SQL seeds of `.starcistacks/seeds/sql/`, then those of `.starcistacks/dev/seeds/sql/` (`--env <env>` names another environment; run both from the app root). The product app reads
|
|
40
40
|
`NEXT_PUBLIC_SITE_URL` and `NEXT_PUBLIC_GRAPHQL_URL` (the back end it calls through `fe/apps/app/src/modules/api`, read once the first GraphQL operation exists), the landing `NEXT_PUBLIC_APP_URL` (the product app it hands the reader over to). A missing key stops the process with an error that names it.
|
|
41
41
|
|
|
42
42
|
Production browser build values live in `.starcistacks/application-stacks.yaml` under `environments.production.deploy.images.*.buildArgs.values`. Replace the placeholder origins for the product's `NEXT_PUBLIC_SITE_URL` and the landing's `NEXT_PUBLIC_APP_URL` with your product app's public origin before building deployment images.
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
The dev environment is one command: `starci app stack up --env dev --execute`. It checks the machine, opens the dev custody, runs the setup
|
|
4
4
|
commands, starts the dependencies in Docker Linux containers (the `db` service: the official postgres image plus pgvector, built from
|
|
5
|
-
`.starcistacks/images/db/Dockerfile`; the `mail` service: Mailpit, the dev mail sink), gives the environment its own database role, migrates,
|
|
5
|
+
`.starcistacks/images/db/Dockerfile`; the `mail` service: Mailpit, the dev mail sink), gives the environment its own database role, migrates, applies the seed levels `.starcistacks/seeds/` then `.starcistacks/dev/seeds/`, starts the api and web
|
|
6
6
|
processes natively and waits for readiness. What it runs is the `local` block of `environments.dev` in `.starcistacks/application-stacks.yaml`
|
|
7
7
|
(PROJECT-STACKS-6 in contexts/project/stacks.md); this runbook never carries a docker command, an agent never runs docker by hand.
|
|
8
8
|
|
|
@@ -20,7 +20,7 @@ another environment.
|
|
|
20
20
|
| --- | --- |
|
|
21
21
|
| prepare | `starci app stack prepare --env dev --execute`: checks the machine, opens the custody, runs the setup commands and builds the images. |
|
|
22
22
|
| doctor | `starci app stack doctor --env dev`: read-only table of host prerequisites with the owner-run install step per OS, custody, compose render and ports. |
|
|
23
|
-
| up | `starci app stack up --env dev --execute`: prepare, then the services, the database role, migrations with a receipt, the seed, the processes and readiness; prints the origins. |
|
|
23
|
+
| up | `starci app stack up --env dev --execute`: prepare, then the services, the database role, migrations with a receipt, the seed levels `.starcistacks/seeds/` then `.starcistacks/dev/seeds/`, each entry once per content hash, the processes and readiness; prints the origins. |
|
|
24
24
|
| status | `starci app stack status --env dev`: desired against running, the migration receipt and every readiness probe. |
|
|
25
25
|
| logs | `starci app stack logs --env dev --service api` (a process or a compose service, bounded and redacted). |
|
|
26
26
|
| down | `starci app stack down --env dev --execute`: ends the processes and stops the compose project; volumes and sealed values stay. |
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
@@ -12,10 +12,10 @@ TypeScript everywhere, NestJS in `be/`, Next.js with next-intl in `fe/`, Postgre
|
|
|
12
12
|
|
|
13
13
|
## Repository layout
|
|
14
14
|
|
|
15
|
-
- `db/`: forward-only SQL migrations
|
|
15
|
+
- `db/`: forward-only SQL migrations and generated database types.
|
|
16
16
|
- `be/`: the API, shared platform capabilities, verified identity admission, and the passport and storage integrations.
|
|
17
17
|
- `fe/`: the web app; it holds no credential, signs in through `fe/apps/web/src/modules/auth` and reaches data through `fe/apps/web/src/modules/api`.
|
|
18
|
-
- `.starcistacks/`: value-free environment metadata
|
|
18
|
+
- `.starcistacks/`: value-free environment metadata, encrypted-secret custody and the seed levels (`seeds/` for every environment, `<env>/seeds/` for one, each a `seeds.yaml` manifest of `sql/`, `data/` and `files/`).
|
|
19
19
|
- `.starciwork/`: `.gitignore`, `.starciwork/workspace.yaml` project metadata and repo roles, and optional brand drawing records, references, and assets.
|
|
20
20
|
|
|
21
21
|
## Status badges
|
|
@@ -25,7 +25,8 @@ end;
|
|
|
25
25
|
$$;
|
|
26
26
|
|
|
27
27
|
-- The device sessions and rotating refresh tokens of @starci/auth. A token is stored by its sha256 only; a rotated token stays,
|
|
28
|
-
-- marked used, so a replay is detected
|
|
28
|
+
-- marked used, so a replay is detected: a replay inside the grace window gets a successor of its own (raced_count bounds how many),
|
|
29
|
+
-- an older one ends the session and every token of it.
|
|
29
30
|
-- identity_attempts counts the sign-in attempts of one address in a window every replica shares (the budget of failures).
|
|
30
31
|
-- The app owns auth.users (db/bootstrap.sql) and the role app_be.
|
|
31
32
|
|
|
@@ -48,6 +49,7 @@ create table public.identity_refresh_tokens (
|
|
|
48
49
|
created_at timestamptz not null default now(),
|
|
49
50
|
expires_at timestamptz not null,
|
|
50
51
|
used_at timestamptz,
|
|
52
|
+
raced_count integer not null default 0,
|
|
51
53
|
constraint identity_refresh_tokens_hash_key unique (token_hash)
|
|
52
54
|
);
|
|
53
55
|
|
|
@@ -17,6 +17,8 @@ export interface SignupData {
|
|
|
17
17
|
readonly email: string
|
|
18
18
|
/** The chosen password. */
|
|
19
19
|
readonly password: string
|
|
20
|
+
/** The person's full name, when the request names one. */
|
|
21
|
+
readonly name: string | undefined
|
|
20
22
|
/** The language of the confirmation mail, when the request names one. */
|
|
21
23
|
readonly language: MailLanguage | undefined
|
|
22
24
|
}
|
|
@@ -17,6 +17,11 @@ export class SignupHandler extends ICQRSHandler<SignupCommand, void> {
|
|
|
17
17
|
}
|
|
18
18
|
|
|
19
19
|
protected override process(command: SignupCommand): Promise<void> {
|
|
20
|
-
return this.email.signup(
|
|
20
|
+
return this.email.signup(
|
|
21
|
+
command.params.request.email,
|
|
22
|
+
command.params.request.password,
|
|
23
|
+
command.params.request.language,
|
|
24
|
+
command.params.request.name,
|
|
25
|
+
)
|
|
21
26
|
}
|
|
22
27
|
}
|
|
@@ -15,7 +15,20 @@ import { ResetHandler } from "./application/reset.handler"
|
|
|
15
15
|
import { ConfigurableModuleClass } from "./auth.module-definition"
|
|
16
16
|
|
|
17
17
|
@Module({
|
|
18
|
-
providers: [
|
|
18
|
+
providers: [
|
|
19
|
+
LoginHandler,
|
|
20
|
+
RefreshHandler,
|
|
21
|
+
SessionHandler,
|
|
22
|
+
LogoutHandler,
|
|
23
|
+
GoogleBeginHandler,
|
|
24
|
+
GoogleCompleteHandler,
|
|
25
|
+
GetJwksHandler,
|
|
26
|
+
SignupHandler,
|
|
27
|
+
ResendHandler,
|
|
28
|
+
ConfirmHandler,
|
|
29
|
+
ForgotHandler,
|
|
30
|
+
ResetHandler,
|
|
31
|
+
],
|
|
19
32
|
})
|
|
20
33
|
/** The auth feature: the handlers behind the sign-in doors. */
|
|
21
34
|
export class AuthApiModule extends ConfigurableModuleClass {}
|
|
@@ -96,7 +96,11 @@ export class AuthController {
|
|
|
96
96
|
@Public({ reason: PublicReason.AuthHandshake })
|
|
97
97
|
@RateLimit(RateTier.Strict)
|
|
98
98
|
signup(@Body() body: SignupRequest): Promise<void> {
|
|
99
|
-
return this.commandBus.execute(
|
|
99
|
+
return this.commandBus.execute(
|
|
100
|
+
new SignupCommand({
|
|
101
|
+
request: { email: body.email, password: body.password, name: body.name, language: body.language },
|
|
102
|
+
}),
|
|
103
|
+
)
|
|
100
104
|
}
|
|
101
105
|
|
|
102
106
|
/** Mails a new confirmation link; always answers 204. */
|
|
@@ -15,6 +15,13 @@ export class SignupRequest {
|
|
|
15
15
|
@MaxLength(128)
|
|
16
16
|
password!: string
|
|
17
17
|
|
|
18
|
+
/** The person's full name, kept on the account's profile keys; left out, the account has none. */
|
|
19
|
+
@IsOptional()
|
|
20
|
+
@IsString()
|
|
21
|
+
@MinLength(1)
|
|
22
|
+
@MaxLength(200)
|
|
23
|
+
name?: string
|
|
24
|
+
|
|
18
25
|
/** The language of the mail; the app's default when left out. */
|
|
19
26
|
@IsOptional()
|
|
20
27
|
@IsIn(["vi", "en"])
|