@application-kit/testing 0.4.0 → 0.6.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 +21 -0
- package/README.md +82 -4
- package/dist/cjs/index.d.ts +1 -0
- package/dist/cjs/index.js +1 -0
- package/dist/cjs/postgres/index.d.ts +1 -0
- package/dist/cjs/postgres/index.js +1 -0
- package/dist/cjs/postgres/start-postgres-web-application.d.ts +15 -0
- package/dist/cjs/postgres/start-postgres-web-application.js +36 -0
- package/dist/cjs/postgres/test-data-source.d.ts +2 -1
- package/dist/cjs/postgres/test-data-source.js +17 -15
- package/dist/cjs/replace-process-environment.d.ts +1 -0
- package/dist/cjs/replace-process-environment.js +60 -0
- package/dist/cjs/stryker/bin/application-mutation-layer.d.ts +2 -0
- package/dist/cjs/stryker/bin/application-mutation-layer.js +64 -0
- package/dist/cjs/stryker/bin/index.d.ts +1 -0
- package/dist/cjs/stryker/bin/index.js +17 -0
- package/dist/cjs/stryker/code-without-logic.d.ts +1 -0
- package/dist/cjs/stryker/code-without-logic.js +31 -0
- package/dist/cjs/stryker/decide-layer-run.d.ts +12 -0
- package/dist/cjs/stryker/decide-layer-run.js +40 -0
- package/dist/cjs/stryker/define-stryker-config.d.ts +2 -0
- package/dist/cjs/stryker/define-stryker-config.js +24 -0
- package/dist/cjs/stryker/define-stryker-layers.d.ts +2 -0
- package/dist/cjs/stryker/define-stryker-layers.js +113 -0
- package/dist/cjs/stryker/index.d.ts +7 -0
- package/dist/cjs/stryker/index.js +23 -0
- package/dist/cjs/stryker/mutation-layer-cli.d.ts +9 -0
- package/dist/cjs/stryker/mutation-layer-cli.js +64 -0
- package/dist/cjs/stryker/stryker-config.interface.d.ts +23 -0
- package/dist/cjs/stryker/stryker-config.interface.js +2 -0
- package/dist/cjs/stryker/stryker-layers.interface.d.ts +31 -0
- package/dist/cjs/stryker/stryker-layers.interface.js +2 -0
- package/dist/cjs/vitest/setup/fast-bcrypt-cost.d.ts +5 -0
- package/dist/cjs/vitest/setup/fast-bcrypt-cost.js +17 -0
- package/dist/cjs/vitest/setup/fast-bcrypt.d.ts +1 -0
- package/dist/cjs/vitest/setup/fast-bcrypt.js +9 -0
- package/dist/cjs/vitest/setup/index.d.ts +2 -0
- package/dist/cjs/vitest/setup/index.js +18 -0
- package/dist/esm/index.d.ts +1 -0
- package/dist/esm/index.js +1 -0
- package/dist/esm/postgres/index.d.ts +1 -0
- package/dist/esm/postgres/index.js +1 -0
- package/dist/esm/postgres/start-postgres-web-application.d.ts +15 -0
- package/dist/esm/postgres/start-postgres-web-application.js +32 -0
- package/dist/esm/postgres/test-data-source.d.ts +2 -1
- package/dist/esm/postgres/test-data-source.js +15 -14
- package/dist/esm/replace-process-environment.d.ts +1 -0
- package/dist/esm/replace-process-environment.js +53 -0
- package/dist/esm/stryker/bin/application-mutation-layer.d.ts +2 -0
- package/dist/esm/stryker/bin/application-mutation-layer.js +26 -0
- package/dist/esm/stryker/bin/index.d.ts +1 -0
- package/dist/esm/stryker/bin/index.js +1 -0
- package/dist/esm/stryker/code-without-logic.d.ts +1 -0
- package/dist/esm/stryker/code-without-logic.js +28 -0
- package/dist/esm/stryker/decide-layer-run.d.ts +12 -0
- package/dist/esm/stryker/decide-layer-run.js +36 -0
- package/dist/esm/stryker/define-stryker-config.d.ts +2 -0
- package/dist/esm/stryker/define-stryker-config.js +20 -0
- package/dist/esm/stryker/define-stryker-layers.d.ts +2 -0
- package/dist/esm/stryker/define-stryker-layers.js +106 -0
- package/dist/esm/stryker/index.d.ts +7 -0
- package/dist/esm/stryker/index.js +7 -0
- package/dist/esm/stryker/mutation-layer-cli.d.ts +9 -0
- package/dist/esm/stryker/mutation-layer-cli.js +60 -0
- package/dist/esm/stryker/stryker-config.interface.d.ts +23 -0
- package/dist/esm/stryker/stryker-config.interface.js +1 -0
- package/dist/esm/stryker/stryker-layers.interface.d.ts +31 -0
- package/dist/esm/stryker/stryker-layers.interface.js +1 -0
- package/dist/esm/vitest/setup/fast-bcrypt-cost.d.ts +5 -0
- package/dist/esm/vitest/setup/fast-bcrypt-cost.js +13 -0
- package/dist/esm/vitest/setup/fast-bcrypt.d.ts +1 -0
- package/dist/esm/vitest/setup/fast-bcrypt.js +7 -0
- package/dist/esm/vitest/setup/index.d.ts +2 -0
- package/dist/esm/vitest/setup/index.js +2 -0
- package/package.json +32 -4
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.defineStrykerLayers = void 0;
|
|
7
|
+
const node_fs_1 = require("node:fs");
|
|
8
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
9
|
+
const node_process_1 = __importDefault(require("node:process"));
|
|
10
|
+
const DEFAULT_LAYER_VARIABLE = 'TYPESCRIPT_MUTATION_LAYER';
|
|
11
|
+
const TEMPLATE_LAYER_SENTINEL = 'all';
|
|
12
|
+
const MATRIX_PATTERN = (variable) => {
|
|
13
|
+
const source = [variable, ':\\p{White_Space}*\\[([^\\]]*)\\]'].join('');
|
|
14
|
+
return new RegExp(source, 'u');
|
|
15
|
+
};
|
|
16
|
+
const sorted = (values) => [...values].toSorted((left, right) => left.localeCompare(right));
|
|
17
|
+
const isUnderDirectory = (file, directory) => file.startsWith(`${directory}/`);
|
|
18
|
+
const rootsOf = (options) => [
|
|
19
|
+
...options.scope.directories,
|
|
20
|
+
...options.scope.parents.flatMap((parent) => (0, node_fs_1.readdirSync)(node_path_1.default.join(options.root, parent), { withFileTypes: true })
|
|
21
|
+
.filter((entry) => entry.isDirectory())
|
|
22
|
+
.map((entry) => `${parent}/${entry.name}`)),
|
|
23
|
+
];
|
|
24
|
+
const assertLayout = (layers, roots, configName) => {
|
|
25
|
+
const paths = Object.values(layers).flatMap((layer) => layer.paths);
|
|
26
|
+
const unassigned = roots.filter((root) => !paths.includes(root));
|
|
27
|
+
const duplicated = paths.filter((entry, index) => paths.indexOf(entry) !== index);
|
|
28
|
+
const orphaned = paths.filter((entry) => roots.every((root) => !(entry === root || isUnderDirectory(entry, root))));
|
|
29
|
+
if (unassigned.length > 0 || duplicated.length > 0 || orphaned.length > 0) {
|
|
30
|
+
throw new Error(`${configName}: каталог обязан быть ровно в одной группе — без группы: [${unassigned.join(', ')}], `
|
|
31
|
+
+ `в двух: [${duplicated.join(', ')}], вне корней: [${orphaned.join(', ')}]`);
|
|
32
|
+
}
|
|
33
|
+
};
|
|
34
|
+
const assertMatrix = (options, variable, configName) => {
|
|
35
|
+
const matrix = MATRIX_PATTERN(variable).exec((0, node_fs_1.readFileSync)(options.ciFile).toString());
|
|
36
|
+
const matrixLayers = sorted((matrix?.[1] ?? '').split(',').map((name) => name.trim())
|
|
37
|
+
.filter(Boolean));
|
|
38
|
+
const layerNames = sorted(Object.keys(options.layers));
|
|
39
|
+
if (matrixLayers.join(',') !== layerNames.join(',')) {
|
|
40
|
+
throw new Error(`${configName}: матрица [${matrixLayers.join(', ')}] в ${node_path_1.default.basename(options.ciFile)} `
|
|
41
|
+
+ `расходится с группами [${layerNames.join(', ')}]`);
|
|
42
|
+
}
|
|
43
|
+
};
|
|
44
|
+
const carvedOutOf = (allPaths, layer) => {
|
|
45
|
+
const isCarvedOut = (entry) => {
|
|
46
|
+
const isOwn = layer.paths.includes(entry);
|
|
47
|
+
return !isOwn && layer.paths.some((own) => isUnderDirectory(entry, own));
|
|
48
|
+
};
|
|
49
|
+
return allPaths.filter((entry) => isCarvedOut(entry));
|
|
50
|
+
};
|
|
51
|
+
const pruneIncrementalReport = (incrementalFile, isInScope, configName) => {
|
|
52
|
+
if (!(0, node_fs_1.existsSync)(incrementalFile)) {
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
const report = JSON.parse((0, node_fs_1.readFileSync)(incrementalFile, 'utf8'));
|
|
56
|
+
const files = report.files ?? {};
|
|
57
|
+
const foreign = Object.keys(files).filter((file) => !isInScope(file));
|
|
58
|
+
if (foreign.length === 0) {
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
report.files = Object.fromEntries(Object.entries(files).filter(([file]) => isInScope(file)));
|
|
62
|
+
(0, node_fs_1.writeFileSync)(`${incrementalFile}.tmp`, JSON.stringify(report));
|
|
63
|
+
(0, node_fs_1.renameSync)(`${incrementalFile}.tmp`, incrementalFile);
|
|
64
|
+
console.error(`${configName}: из ${incrementalFile} убрано файлов вне области группы: ${foreign.length}`);
|
|
65
|
+
};
|
|
66
|
+
const defineStrykerLayers = (options) => {
|
|
67
|
+
const variable = options.layerVariable ?? DEFAULT_LAYER_VARIABLE;
|
|
68
|
+
const configName = options.configName ?? 'stryker layers';
|
|
69
|
+
const names = Object.keys(options.layers);
|
|
70
|
+
const allPaths = Object.values(options.layers).flatMap((layer) => layer.paths);
|
|
71
|
+
const roots = rootsOf(options);
|
|
72
|
+
assertLayout(options.layers, roots, configName);
|
|
73
|
+
assertMatrix(options, variable, configName);
|
|
74
|
+
const layerOf = (name) => {
|
|
75
|
+
const layer = options.layers[String(name)];
|
|
76
|
+
if (layer === undefined) {
|
|
77
|
+
throw new Error(`${variable}=${String(name)}: ожидается одна из ${names.join(', ')}`);
|
|
78
|
+
}
|
|
79
|
+
return { layer, name: String(name) };
|
|
80
|
+
};
|
|
81
|
+
const pathsOf = (name) => {
|
|
82
|
+
const { layer } = layerOf(name);
|
|
83
|
+
return [...layer.paths, ...carvedOutOf(allPaths, layer).map((entry) => `!${entry}`)];
|
|
84
|
+
};
|
|
85
|
+
const raw = node_process_1.default.env[variable];
|
|
86
|
+
const isLoadedByStryker = node_process_1.default.argv.some((argument) => argument.includes('@stryker-mutator'));
|
|
87
|
+
const isUnset = raw === undefined || (!isLoadedByStryker && raw === TEMPLATE_LAYER_SENTINEL);
|
|
88
|
+
const { layer, name } = layerOf(isUnset ? options.defaultLayer : raw);
|
|
89
|
+
const carvedOut = carvedOutOf(allPaths, layer);
|
|
90
|
+
const incrementalFile = `.stryker-cache/incremental-${name}.json`;
|
|
91
|
+
if (isLoadedByStryker) {
|
|
92
|
+
pruneIncrementalReport(node_path_1.default.join(options.root, incrementalFile), (file) => layer.paths.some((own) => isUnderDirectory(file, own))
|
|
93
|
+
&& carvedOut.every((entry) => !isUnderDirectory(file, entry)), configName);
|
|
94
|
+
}
|
|
95
|
+
const exclusions = options.base.mutate.filter((pattern) => pattern.startsWith('!'));
|
|
96
|
+
return {
|
|
97
|
+
config: {
|
|
98
|
+
...options.base,
|
|
99
|
+
incrementalFile,
|
|
100
|
+
mutate: [
|
|
101
|
+
...layer.paths.map((entry) => `${entry}/**/*.ts`),
|
|
102
|
+
...carvedOut.map((entry) => `!${entry}/**`),
|
|
103
|
+
...exclusions,
|
|
104
|
+
],
|
|
105
|
+
thresholds: { ...options.base.thresholds, break: layer.break },
|
|
106
|
+
},
|
|
107
|
+
names,
|
|
108
|
+
paths: pathsOf,
|
|
109
|
+
roots,
|
|
110
|
+
skip: options.skip,
|
|
111
|
+
};
|
|
112
|
+
};
|
|
113
|
+
exports.defineStrykerLayers = defineStrykerLayers;
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export * from './code-without-logic.js';
|
|
2
|
+
export * from './decide-layer-run.js';
|
|
3
|
+
export * from './define-stryker-config.js';
|
|
4
|
+
export * from './define-stryker-layers.js';
|
|
5
|
+
export * from './mutation-layer-cli.js';
|
|
6
|
+
export * from './stryker-config.interface.js';
|
|
7
|
+
export * from './stryker-layers.interface.js';
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
14
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
|
+
};
|
|
16
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
+
__exportStar(require("./code-without-logic.js"), exports);
|
|
18
|
+
__exportStar(require("./decide-layer-run.js"), exports);
|
|
19
|
+
__exportStar(require("./define-stryker-config.js"), exports);
|
|
20
|
+
__exportStar(require("./define-stryker-layers.js"), exports);
|
|
21
|
+
__exportStar(require("./mutation-layer-cli.js"), exports);
|
|
22
|
+
__exportStar(require("./stryker-config.interface.js"), exports);
|
|
23
|
+
__exportStar(require("./stryker-layers.interface.js"), exports);
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { StrykerLayersInterface } from './stryker-layers.interface.js';
|
|
2
|
+
export interface MutationLayerCliDependenciesInterface {
|
|
3
|
+
changedFiles(base: string): string[];
|
|
4
|
+
env: Readonly<Record<string, string | undefined>>;
|
|
5
|
+
loadLayers(config: string): Promise<StrykerLayersInterface>;
|
|
6
|
+
print(line: string): void;
|
|
7
|
+
run(command: string): number;
|
|
8
|
+
}
|
|
9
|
+
export declare const executeMutationLayerCli: (argv: readonly string[], dependencies: MutationLayerCliDependenciesInterface) => Promise<number>;
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.executeMutationLayerCli = void 0;
|
|
4
|
+
const decide_layer_run_js_1 = require("./decide-layer-run.js");
|
|
5
|
+
const DEFAULT_CONFIG = 'stryker.layer.conf.mjs';
|
|
6
|
+
const DEFAULT_LAYER_VARIABLE = 'TYPESCRIPT_MUTATION_LAYER';
|
|
7
|
+
const defaultCommand = (config) => `yarn stryker run ${config} --logLevel info --concurrency 2`;
|
|
8
|
+
const parse = (argv) => {
|
|
9
|
+
const positional = argv.filter((argument) => !argument.startsWith('--'));
|
|
10
|
+
const flag = argv.find((argument) => argument.startsWith('--config='));
|
|
11
|
+
return { base: positional[1], command: positional[0], config: flag?.slice('--config='.length) ?? DEFAULT_CONFIG };
|
|
12
|
+
};
|
|
13
|
+
const NO_BASE_REASON = 'базы диффа нет (не MR-пайплайн)';
|
|
14
|
+
const changedFilesOf = (dependencies, base) => {
|
|
15
|
+
try {
|
|
16
|
+
return dependencies.changedFiles(base);
|
|
17
|
+
}
|
|
18
|
+
catch (error) {
|
|
19
|
+
return Error.isError(error) ? error : new Error(String(error));
|
|
20
|
+
}
|
|
21
|
+
};
|
|
22
|
+
const labelOf = (cell) => `ts:test:mutation [${String(cell.layer)}]`;
|
|
23
|
+
const launch = (cell, reason) => {
|
|
24
|
+
cell.dependencies.print(`${labelOf(cell)}: прогон — ${reason}`);
|
|
25
|
+
return cell.dependencies.run(cell.dependencies.env.MUTATION_LAYER_COMMAND ?? defaultCommand(cell.config));
|
|
26
|
+
};
|
|
27
|
+
const runCell = (cell) => {
|
|
28
|
+
const { dependencies, layer, layers } = cell;
|
|
29
|
+
const diffBase = cell.base ?? dependencies.env.CI_MERGE_REQUEST_DIFF_BASE_SHA;
|
|
30
|
+
if (diffBase === undefined || diffBase === '') {
|
|
31
|
+
return launch(cell, NO_BASE_REASON);
|
|
32
|
+
}
|
|
33
|
+
const changed = changedFilesOf(dependencies, diffBase);
|
|
34
|
+
if (Error.isError(changed)) {
|
|
35
|
+
return launch(cell, `дифф к ${diffBase} не читается: ${changed.message.replace(/\n[\s\S]*/u, '')}`);
|
|
36
|
+
}
|
|
37
|
+
const decision = (0, decide_layer_run_js_1.decideLayerRun)({ ...layers.skip, changed, layerPaths: layers.paths(layer), roots: layers.roots });
|
|
38
|
+
if (!decision.run) {
|
|
39
|
+
dependencies.print(`${labelOf(cell)}: дифф MR (база ${diffBase}) группу не трогает — прогон пропущен, кеш и score группы без изменений`);
|
|
40
|
+
return 0;
|
|
41
|
+
}
|
|
42
|
+
if (decision.touched.length > 0) {
|
|
43
|
+
dependencies.print(`${labelOf(cell)}: дифф MR трогает группу:\n${decision.touched.map((file) => ` ${file}`).join('\n')}`);
|
|
44
|
+
}
|
|
45
|
+
return launch(cell, decision.reason);
|
|
46
|
+
};
|
|
47
|
+
const executeMutationLayerCli = async (argv, dependencies) => {
|
|
48
|
+
const { base, command, config } = parse(argv);
|
|
49
|
+
const layers = await dependencies.loadLayers(config);
|
|
50
|
+
if (command === 'names') {
|
|
51
|
+
dependencies.print(layers.names.join(' '));
|
|
52
|
+
return 0;
|
|
53
|
+
}
|
|
54
|
+
if (command === 'paths') {
|
|
55
|
+
dependencies.print(layers.paths(dependencies.env[DEFAULT_LAYER_VARIABLE]).join('\n'));
|
|
56
|
+
return 0;
|
|
57
|
+
}
|
|
58
|
+
if (command === 'run') {
|
|
59
|
+
return runCell({ base, config, dependencies, layer: dependencies.env[DEFAULT_LAYER_VARIABLE], layers });
|
|
60
|
+
}
|
|
61
|
+
dependencies.print(`application-mutation-layer: ожидается names | paths | run, получено ${String(command)}`);
|
|
62
|
+
return 2;
|
|
63
|
+
};
|
|
64
|
+
exports.executeMutationLayerCli = executeMutationLayerCli;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
export interface StrykerConfigInterface {
|
|
2
|
+
readonly incrementalFile: string;
|
|
3
|
+
readonly mutate: readonly string[];
|
|
4
|
+
readonly [option: string]: unknown;
|
|
5
|
+
readonly thresholds: StrykerThresholdsInterface;
|
|
6
|
+
}
|
|
7
|
+
export interface StrykerConfigOptionsInterface {
|
|
8
|
+
ignorePatterns?: readonly string[];
|
|
9
|
+
incrementalFile?: string;
|
|
10
|
+
mutate: StrykerMutateScopeInterface;
|
|
11
|
+
tempDirName?: string;
|
|
12
|
+
thresholds: StrykerThresholdsInterface;
|
|
13
|
+
vitestConfigFile: string;
|
|
14
|
+
}
|
|
15
|
+
export interface StrykerMutateScopeInterface {
|
|
16
|
+
exclude?: readonly string[];
|
|
17
|
+
include: readonly string[];
|
|
18
|
+
}
|
|
19
|
+
export interface StrykerThresholdsInterface {
|
|
20
|
+
readonly break: number;
|
|
21
|
+
readonly high: number;
|
|
22
|
+
readonly low: number;
|
|
23
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import type { StrykerConfigInterface } from './stryker-config.interface.js';
|
|
2
|
+
export interface StrykerLayerInterface {
|
|
3
|
+
break: number;
|
|
4
|
+
paths: readonly string[];
|
|
5
|
+
}
|
|
6
|
+
export interface StrykerLayersInterface {
|
|
7
|
+
config: StrykerConfigInterface;
|
|
8
|
+
names: readonly string[];
|
|
9
|
+
paths(name: string | undefined): string[];
|
|
10
|
+
roots: readonly string[];
|
|
11
|
+
skip: StrykerLayerSkipOptionsInterface;
|
|
12
|
+
}
|
|
13
|
+
export interface StrykerLayerSkipOptionsInterface {
|
|
14
|
+
configFiles: RegExp;
|
|
15
|
+
sharedDirectories: readonly string[];
|
|
16
|
+
sourceRoot: string;
|
|
17
|
+
}
|
|
18
|
+
export interface StrykerLayersOptionsInterface {
|
|
19
|
+
base: StrykerConfigInterface;
|
|
20
|
+
ciFile: string;
|
|
21
|
+
configName?: string;
|
|
22
|
+
defaultLayer: string;
|
|
23
|
+
layers: Readonly<Record<string, StrykerLayerInterface>>;
|
|
24
|
+
layerVariable?: string;
|
|
25
|
+
root: string;
|
|
26
|
+
scope: {
|
|
27
|
+
directories: readonly string[];
|
|
28
|
+
parents: readonly string[];
|
|
29
|
+
};
|
|
30
|
+
skip: StrykerLayerSkipOptionsInterface;
|
|
31
|
+
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export interface BcryptInterface {
|
|
2
|
+
compare(data: Buffer | string, encrypted: string): Promise<boolean>;
|
|
3
|
+
hash(data: Buffer | string, saltOrRounds: number | string): Promise<string>;
|
|
4
|
+
}
|
|
5
|
+
export declare const withFastBcryptCost: (bcrypt: BcryptInterface) => BcryptInterface;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.withFastBcryptCost = void 0;
|
|
4
|
+
const FAST_ROUNDS = 4;
|
|
5
|
+
const COST_FIELD_WIDTH = 2;
|
|
6
|
+
const COST_FIELD = /^(?<prefix>\$2[aby]\$)\d{2}\$/u;
|
|
7
|
+
const withCost = (hashed, rounds) => {
|
|
8
|
+
const cost = String(rounds).padStart(COST_FIELD_WIDTH, '0');
|
|
9
|
+
return hashed.replace(COST_FIELD, (_match, prefix) => `${prefix}${cost}$`);
|
|
10
|
+
};
|
|
11
|
+
const withFastBcryptCost = (bcrypt) => ({
|
|
12
|
+
compare: async (data, encrypted) => bcrypt.compare(data, withCost(encrypted, FAST_ROUNDS)),
|
|
13
|
+
hash: async (data, saltOrRounds) => (typeof saltOrRounds === 'number'
|
|
14
|
+
? withCost(await bcrypt.hash(data, Math.min(saltOrRounds, FAST_ROUNDS)), saltOrRounds)
|
|
15
|
+
: bcrypt.hash(data, saltOrRounds)),
|
|
16
|
+
});
|
|
17
|
+
exports.withFastBcryptCost = withFastBcryptCost;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const vitest_1 = require("vitest");
|
|
4
|
+
const fast_bcrypt_cost_js_1 = require("./fast-bcrypt-cost.js");
|
|
5
|
+
vitest_1.vi.mock('bcrypt', async (importOriginal) => {
|
|
6
|
+
const bcrypt = await importOriginal();
|
|
7
|
+
const fast = (0, fast_bcrypt_cost_js_1.withFastBcryptCost)(bcrypt);
|
|
8
|
+
return { ...bcrypt, ...fast, default: { ...bcrypt.default, ...fast } };
|
|
9
|
+
});
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
14
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
|
+
};
|
|
16
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
+
__exportStar(require("./fast-bcrypt.js"), exports);
|
|
18
|
+
__exportStar(require("./fast-bcrypt-cost.js"), exports);
|
package/dist/esm/index.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
export * from './fake-microservice/index.js';
|
|
2
2
|
export * from './postgres/index.js';
|
|
3
3
|
export * from './build-web-application.js';
|
|
4
|
+
export * from './replace-process-environment.js';
|
|
4
5
|
export * from './test-command-bus.js';
|
|
5
6
|
export * from './test-event-bus.js';
|
|
6
7
|
export * from './test-logger.js';
|
package/dist/esm/index.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
export * from './fake-microservice/index.js';
|
|
2
2
|
export * from './postgres/index.js';
|
|
3
3
|
export * from './build-web-application.js';
|
|
4
|
+
export * from './replace-process-environment.js';
|
|
4
5
|
export * from './test-command-bus.js';
|
|
5
6
|
export * from './test-event-bus.js';
|
|
6
7
|
export * from './test-logger.js';
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { InjectionToken, ModuleMetadata } from '@nestjs/common';
|
|
2
|
+
import type { NestFastifyApplication } from '@nestjs/platform-fastify';
|
|
3
|
+
import type { NamingStrategyInterface } from 'typeorm';
|
|
4
|
+
import type { BuildWebApplicationOptionsInterface } from '../build-web-application.js';
|
|
5
|
+
export interface PostgresWebApplicationOptionsInterface extends BuildWebApplicationOptionsInterface {
|
|
6
|
+
databaseConfigToken: InjectionToken;
|
|
7
|
+
image: string;
|
|
8
|
+
imports: NonNullable<ModuleMetadata['imports']>;
|
|
9
|
+
namingStrategy: NamingStrategyInterface;
|
|
10
|
+
}
|
|
11
|
+
export interface StartedWebApplicationInterface {
|
|
12
|
+
app: NestFastifyApplication;
|
|
13
|
+
stop(): Promise<void>;
|
|
14
|
+
}
|
|
15
|
+
export declare const startPostgresWebApplication: (options: PostgresWebApplicationOptionsInterface) => Promise<StartedWebApplicationInterface>;
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { buildWebApplication } from '../build-web-application.js';
|
|
2
|
+
import { postgresContainerDataSourceOptions, startPostgresContainer } from './test-data-source.js';
|
|
3
|
+
export const startPostgresWebApplication = async (options) => {
|
|
4
|
+
const { databaseConfigToken, image, imports, namingStrategy, ...buildOptions } = options;
|
|
5
|
+
const container = await startPostgresContainer(image);
|
|
6
|
+
const dataSourceOptions = {
|
|
7
|
+
...postgresContainerDataSourceOptions(container, namingStrategy),
|
|
8
|
+
migrationsRun: false,
|
|
9
|
+
synchronize: true,
|
|
10
|
+
};
|
|
11
|
+
try {
|
|
12
|
+
const app = await buildWebApplication(imports, [
|
|
13
|
+
{
|
|
14
|
+
provide: databaseConfigToken,
|
|
15
|
+
useValue: { createDataSourceOptions: () => dataSourceOptions },
|
|
16
|
+
},
|
|
17
|
+
], buildOptions);
|
|
18
|
+
await app.getHttpAdapter().getInstance()
|
|
19
|
+
.ready();
|
|
20
|
+
return {
|
|
21
|
+
app,
|
|
22
|
+
stop: async () => {
|
|
23
|
+
await app.close();
|
|
24
|
+
await container.stop();
|
|
25
|
+
},
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
catch (error) {
|
|
29
|
+
await container.stop();
|
|
30
|
+
throw error;
|
|
31
|
+
}
|
|
32
|
+
};
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import type { StartedTestContainer } from 'testcontainers';
|
|
2
|
-
import type { EntitySchema, MixedList, NamingStrategyInterface, QueryRunner } from 'typeorm';
|
|
2
|
+
import type { DataSourceOptions, EntitySchema, MixedList, NamingStrategyInterface, QueryRunner } from 'typeorm';
|
|
3
3
|
import { DataSource } from 'typeorm';
|
|
4
4
|
export declare const TEST_DATABASE_NAME = "db";
|
|
5
5
|
export declare const TEST_DATABASE_PORT = 5432;
|
|
6
6
|
export declare const startPostgresContainer: (image: string) => Promise<StartedTestContainer>;
|
|
7
|
+
export declare const postgresContainerDataSourceOptions: (container: StartedTestContainer, namingStrategy: NamingStrategyInterface) => DataSourceOptions;
|
|
7
8
|
export interface TestDataSourceOptionsInterface {
|
|
8
9
|
entities: MixedList<EntitySchema | Function | string>;
|
|
9
10
|
image: string;
|
|
@@ -12,28 +12,29 @@ export const startPostgresContainer = async (image) => new GenericContainer(imag
|
|
|
12
12
|
})
|
|
13
13
|
.withExposedPorts(TEST_DATABASE_PORT)
|
|
14
14
|
.start();
|
|
15
|
+
export const postgresContainerDataSourceOptions = (container, namingStrategy) => ({
|
|
16
|
+
database: TEST_DATABASE_NAME,
|
|
17
|
+
host: container.getHost(),
|
|
18
|
+
logging: false,
|
|
19
|
+
namingStrategy,
|
|
20
|
+
password: TEST_DATABASE_NAME,
|
|
21
|
+
port: container.getMappedPort(TEST_DATABASE_PORT),
|
|
22
|
+
ssl: false,
|
|
23
|
+
subscribers: [],
|
|
24
|
+
type: 'postgres',
|
|
25
|
+
username: TEST_DATABASE_NAME,
|
|
26
|
+
});
|
|
15
27
|
export const testDataSource = async ({ entities, image, isLogging = false, migrations, namingStrategy, shouldSynchronize = false, }) => {
|
|
16
|
-
const databaseName = TEST_DATABASE_NAME;
|
|
17
|
-
const databasePort = TEST_DATABASE_PORT;
|
|
18
28
|
const container = await startPostgresContainer(image);
|
|
19
|
-
const
|
|
20
|
-
|
|
29
|
+
const dataSource = new DataSource({
|
|
30
|
+
...postgresContainerDataSourceOptions(container, namingStrategy),
|
|
21
31
|
entities,
|
|
22
|
-
host: container.getHost(),
|
|
23
32
|
logging: isLogging,
|
|
24
33
|
maxQueryExecutionTime: 1000,
|
|
25
34
|
migrations,
|
|
26
35
|
migrationsRun: !shouldSynchronize,
|
|
27
|
-
namingStrategy,
|
|
28
|
-
password: databaseName,
|
|
29
|
-
port: container.getMappedPort(databasePort),
|
|
30
|
-
ssl: false,
|
|
31
|
-
subscribers: [],
|
|
32
36
|
synchronize: shouldSynchronize,
|
|
33
|
-
|
|
34
|
-
username: databaseName,
|
|
35
|
-
};
|
|
36
|
-
const dataSource = new DataSource(databaseConfig);
|
|
37
|
+
});
|
|
37
38
|
await dataSource.initialize();
|
|
38
39
|
const queryRunner = dataSource.createQueryRunner();
|
|
39
40
|
Object.assign(dataSource.manager, { queryRunner });
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const replaceProcessEnvironment: (environment: Readonly<Record<string, string>>) => void;
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import process from 'node:process';
|
|
2
|
+
const TOOLCHAIN_NAMES = new Set([
|
|
3
|
+
'_',
|
|
4
|
+
'CI',
|
|
5
|
+
'COLORTERM',
|
|
6
|
+
'FORCE_COLOR',
|
|
7
|
+
'HOME',
|
|
8
|
+
'HOSTNAME',
|
|
9
|
+
'INIT_CWD',
|
|
10
|
+
'LANG',
|
|
11
|
+
'OLDPWD',
|
|
12
|
+
'PATH',
|
|
13
|
+
'PROJECT_CWD',
|
|
14
|
+
'PWD',
|
|
15
|
+
'SHELL',
|
|
16
|
+
'SHLVL',
|
|
17
|
+
'TEMP',
|
|
18
|
+
'TERM',
|
|
19
|
+
'TMP',
|
|
20
|
+
'TMPDIR',
|
|
21
|
+
'TZ',
|
|
22
|
+
'USER',
|
|
23
|
+
]);
|
|
24
|
+
const TOOLCHAIN_PREFIXES = [
|
|
25
|
+
'BERRY_',
|
|
26
|
+
'DOCKER_',
|
|
27
|
+
'HTTP_PROXY',
|
|
28
|
+
'HTTPS_PROXY',
|
|
29
|
+
'LC_',
|
|
30
|
+
'NODE_',
|
|
31
|
+
'NO_PROXY',
|
|
32
|
+
'NPM_',
|
|
33
|
+
'npm_',
|
|
34
|
+
'TESTCONTAINERS_',
|
|
35
|
+
'VITE_',
|
|
36
|
+
'VITEST_',
|
|
37
|
+
'XDG_',
|
|
38
|
+
'YARN_',
|
|
39
|
+
'http_proxy',
|
|
40
|
+
'https_proxy',
|
|
41
|
+
'no_proxy',
|
|
42
|
+
];
|
|
43
|
+
const isToolchainVariable = (name) => TOOLCHAIN_NAMES.has(name)
|
|
44
|
+
|| TOOLCHAIN_PREFIXES.some((prefix) => name.startsWith(prefix));
|
|
45
|
+
export const replaceProcessEnvironment = (environment) => {
|
|
46
|
+
const runtimeEnvironment = process.env;
|
|
47
|
+
for (const name of Object.keys(runtimeEnvironment)) {
|
|
48
|
+
if (!isToolchainVariable(name)) {
|
|
49
|
+
Reflect.deleteProperty(runtimeEnvironment, name);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
Object.assign(runtimeEnvironment, environment);
|
|
53
|
+
};
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { execFileSync, spawnSync } from 'node:child_process';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import process from 'node:process';
|
|
5
|
+
import { pathToFileURL } from 'node:url';
|
|
6
|
+
import { executeMutationLayerCli } from '../mutation-layer-cli.js';
|
|
7
|
+
const changedFiles = (base) => execFileSync('git', ['diff', '--name-only', `${base}...HEAD`], { encoding: 'utf8', stdio: ['ignore', 'pipe', 'pipe'] })
|
|
8
|
+
.split('\n')
|
|
9
|
+
.filter(Boolean);
|
|
10
|
+
const loadLayers = async (config) => {
|
|
11
|
+
const loaded = (await import(pathToFileURL(path.resolve(config)).href));
|
|
12
|
+
return loaded.layers;
|
|
13
|
+
};
|
|
14
|
+
const main = async () => {
|
|
15
|
+
const code = await executeMutationLayerCli(process.argv.slice(2), {
|
|
16
|
+
changedFiles,
|
|
17
|
+
env: process.env,
|
|
18
|
+
loadLayers,
|
|
19
|
+
print: (line) => {
|
|
20
|
+
console.log(line);
|
|
21
|
+
},
|
|
22
|
+
run: (command) => spawnSync(command, { shell: true, stdio: 'inherit' }).status ?? 1,
|
|
23
|
+
});
|
|
24
|
+
process.exitCode = code;
|
|
25
|
+
};
|
|
26
|
+
void main();
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './application-mutation-layer.js';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './application-mutation-layer.js';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const CODE_WITHOUT_LOGIC: readonly string[];
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
export const CODE_WITHOUT_LOGIC = [
|
|
2
|
+
'src/**/*.command.ts',
|
|
3
|
+
'src/**/*.config.ts',
|
|
4
|
+
'src/**/*.d.ts',
|
|
5
|
+
'src/**/*.dto.ts',
|
|
6
|
+
'src/**/*.environment.ts',
|
|
7
|
+
'src/**/*.error.ts',
|
|
8
|
+
'src/**/*.fake.ts',
|
|
9
|
+
'src/**/*.interface.ts',
|
|
10
|
+
'src/**/*.mock.ts',
|
|
11
|
+
'src/**/*.module.ts',
|
|
12
|
+
'src/**/*.query.ts',
|
|
13
|
+
'src/**/*.type.ts',
|
|
14
|
+
'src/**/*constants.ts',
|
|
15
|
+
'src/**/entry.*.ts',
|
|
16
|
+
'src/**/errors/*',
|
|
17
|
+
'src/**/graphql/**/*.input.ts',
|
|
18
|
+
'src/**/graphql/**/*.output.ts',
|
|
19
|
+
'src/**/mappers/*',
|
|
20
|
+
'src/**/module.cli.ts',
|
|
21
|
+
'src/**/module.consumers.ts',
|
|
22
|
+
'src/**/module.manifest.ts',
|
|
23
|
+
'src/**/typeorm/entities/*.ts',
|
|
24
|
+
'src/**/typeorm/migrations/*.ts',
|
|
25
|
+
'src/**/user-interface/client/app/navigation.ts',
|
|
26
|
+
'src/**/user-interface/client/app/plugins/*-widget.plugin.ts',
|
|
27
|
+
'src/**/user-interface/client/app/plugins/container-plugin.ts',
|
|
28
|
+
];
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { StrykerLayerSkipOptionsInterface } from './stryker-layers.interface.js';
|
|
2
|
+
export interface LayerRunDecisionInterface {
|
|
3
|
+
readonly reason: string;
|
|
4
|
+
readonly run: boolean;
|
|
5
|
+
readonly touched: readonly string[];
|
|
6
|
+
}
|
|
7
|
+
export interface LayerRunInputInterface extends StrykerLayerSkipOptionsInterface {
|
|
8
|
+
changed: readonly string[];
|
|
9
|
+
layerPaths: readonly string[];
|
|
10
|
+
roots: readonly string[];
|
|
11
|
+
}
|
|
12
|
+
export declare const decideLayerRun: (input: LayerRunInputInterface) => LayerRunDecisionInterface;
|