@starci/hfs 4.7.17 → 4.7.19
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 +13 -2
- package/coverage/cli.mjs +29 -4
- package/lint/run.mjs +32 -16
- package/package.json +1 -1
- package/release/gate.mjs +10 -4
- package/release/index.mjs +1 -1
- package/release/push.mjs +12 -6
- package/runtime/knowledge/hfs/canon-pins.yaml +5 -5
- package/runtime/knowledge/hfs/ci-standard.yaml +5 -3
- package/runtime/modules/kernel/failure-codes.yaml +1 -1
- package/runtime/packages/cli/src/catalog.generated.mjs +13 -7
- package/runtime/packages/jest-preset/sonar-exclusions.cjs +8 -0
- package/runtime/scripts/hfs/rules/ci-standard.mjs +5 -2
- package/runtime/scripts/hfs/rules/staged-specs.mjs +25 -5
- package/scaffold/app.mjs +1 -1
- package/sync/ci-commands.mjs +276 -275
- package/sync/hygiene.mjs +8 -2
- package/sync/index.mjs +7 -6
- package/templates/be/skeleton/src/modules/platform/database/migrate-connections.client.spec.ts +60 -0
- package/runtime/packages/jest-preset/sonar-exclusions.json +0 -1
package/sync/index.mjs
CHANGED
|
@@ -19,6 +19,7 @@ import { createHash } from 'node:crypto';
|
|
|
19
19
|
import { createRequire } from 'node:module';
|
|
20
20
|
import fs from 'node:fs';
|
|
21
21
|
import path from 'node:path';
|
|
22
|
+
import { fileURLToPath } from 'node:url';
|
|
22
23
|
import { braceVariants } from '../runtime/scripts/lib/glob.mjs';
|
|
23
24
|
import { codecovPaths, coverageComponents, jestCoverage, sonarCoverageExclusions } from '../runtime/scripts/hfs/coverage-scope.mjs';
|
|
24
25
|
import { APP_SCOPE, SIDES, litePresenceOf, loadSlotManifest, resolveRepoDeclaration, slotInEdition } from '../runtime/scripts/hfs/slots.mjs';
|
|
@@ -159,19 +160,19 @@ export async function loadPresets(root) {
|
|
|
159
160
|
}
|
|
160
161
|
|
|
161
162
|
/**
|
|
162
|
-
* The jest preset's Sonar exclusions as @starci/hfs ships them: `@starci/jest-preset/sonar-exclusions.
|
|
163
|
-
* packages/hfs/runtime by scripts/hfs/sync-runtime.mjs, so a fresh `starci app scaffold` (nothing installed
|
|
164
|
-
* same list the app's installed preset answers. A missing copy is a refusal, never a render without it.
|
|
163
|
+
* The jest preset's Sonar exclusions as @starci/hfs ships them: `@starci/jest-preset/sonar-exclusions.cjs` (a require-free module),
|
|
164
|
+
* copied byte for byte into packages/hfs/runtime by scripts/hfs/sync-runtime.mjs, so a fresh `starci app scaffold` (nothing installed
|
|
165
|
+
* yet) and an upgrade render the same list the app's installed preset answers. A missing copy is a refusal, never a render without it.
|
|
165
166
|
*/
|
|
166
|
-
const BUNDLED_SONAR_EXCLUSIONS = new URL('../runtime/packages/jest-preset/sonar-exclusions.
|
|
167
|
+
const BUNDLED_SONAR_EXCLUSIONS = new URL('../runtime/packages/jest-preset/sonar-exclusions.cjs', import.meta.url);
|
|
167
168
|
|
|
168
169
|
/** The presets ({ sonarExclusions }) of the jest preset version this @starci/hfs pins, read from its bundled copy. */
|
|
169
170
|
export function bundledPresets() {
|
|
170
171
|
let list;
|
|
171
172
|
try {
|
|
172
|
-
list =
|
|
173
|
+
list = createRequire(import.meta.url)(fileURLToPath(BUNDLED_SONAR_EXCLUSIONS));
|
|
173
174
|
} catch {
|
|
174
|
-
throw new SyncError('HFS_SYNC_PRESET_MISSING', '@starci/hfs ships no runtime/packages/jest-preset/sonar-exclusions.
|
|
175
|
+
throw new SyncError('HFS_SYNC_PRESET_MISSING', '@starci/hfs ships no runtime/packages/jest-preset/sonar-exclusions.cjs; reinstall @starci/hfs at its published version');
|
|
175
176
|
}
|
|
176
177
|
return { sonarExclusions: list.join(',') };
|
|
177
178
|
}
|
package/templates/be/skeleton/src/modules/platform/database/migrate-connections.client.spec.ts
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { Test } from "@nestjs/testing"
|
|
2
|
+
import { mock } from "@starci/jest-preset"
|
|
3
|
+
import { Secret } from "@modules/platform/config"
|
|
4
|
+
import type { DatabaseConnectionOptions } from "./database.options"
|
|
5
|
+
import type { ConnectionOpener, ConnectionSource } from "./database.port"
|
|
6
|
+
import { migrateConnections } from "./migrate-connections.client"
|
|
7
|
+
|
|
8
|
+
const MIGRATE_CLIENT = Symbol("migrate-connections")
|
|
9
|
+
|
|
10
|
+
const setup = async () => {
|
|
11
|
+
const moduleRef = await Test.createTestingModule({
|
|
12
|
+
providers: [{ provide: MIGRATE_CLIENT, useFactory: () => ({ migrateConnections }) }],
|
|
13
|
+
}).compile()
|
|
14
|
+
return moduleRef.get<{ readonly migrateConnections: typeof migrateConnections }>(MIGRATE_CLIENT)
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const connection = (name: string): DatabaseConnectionOptions => ({
|
|
18
|
+
name,
|
|
19
|
+
url: new Secret(`postgres://localhost:5432/${name}`),
|
|
20
|
+
entities: [],
|
|
21
|
+
migrations: [],
|
|
22
|
+
})
|
|
23
|
+
|
|
24
|
+
describe("migrateConnections", () => {
|
|
25
|
+
it("runs the pending migrations of each connection in order and answers their names by connection", async () => {
|
|
26
|
+
const client = await setup()
|
|
27
|
+
const primary = mock<ConnectionSource>()
|
|
28
|
+
primary.runMigrations.mockResolvedValue([{ name: "Init1" }])
|
|
29
|
+
const archive = mock<ConnectionSource>()
|
|
30
|
+
archive.runMigrations.mockResolvedValue([])
|
|
31
|
+
const sources = new Map([
|
|
32
|
+
["primary", primary],
|
|
33
|
+
["archive", archive],
|
|
34
|
+
])
|
|
35
|
+
const opener = mock<ConnectionOpener>()
|
|
36
|
+
opener.open.mockImplementation(
|
|
37
|
+
(target: DatabaseConnectionOptions) => sources.get(target.name) ?? mock<ConnectionSource>(),
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
const applied = await client.migrateConnections([connection("primary"), connection("archive")], opener)
|
|
41
|
+
|
|
42
|
+
expect(applied).toEqual({ primary: ["Init1"], archive: [] })
|
|
43
|
+
expect(primary.initialize).toHaveBeenCalledTimes(1)
|
|
44
|
+
expect(primary.destroy).toHaveBeenCalledTimes(1)
|
|
45
|
+
expect(archive.destroy).toHaveBeenCalledTimes(1)
|
|
46
|
+
})
|
|
47
|
+
|
|
48
|
+
it("destroys the data source of a connection whose migration fails, and fails", async () => {
|
|
49
|
+
const client = await setup()
|
|
50
|
+
const failing = mock<ConnectionSource>()
|
|
51
|
+
failing.runMigrations.mockRejectedValue(new TypeError("relation already exists"))
|
|
52
|
+
const opener = mock<ConnectionOpener>()
|
|
53
|
+
opener.open.mockReturnValue(failing)
|
|
54
|
+
|
|
55
|
+
await expect(client.migrateConnections([connection("primary")], opener)).rejects.toThrow(
|
|
56
|
+
"relation already exists",
|
|
57
|
+
)
|
|
58
|
+
expect(failing.destroy).toHaveBeenCalledTimes(1)
|
|
59
|
+
})
|
|
60
|
+
})
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
["**/*.spec.ts", "**/*.e2e-spec.ts", "**/dist/**", "**/coverage/**"]
|