@opencrvs/toolkit 2.0.0-rc.fef9d21 → 2.0.0-rc.ff99998
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/create-countryconfig/index.js +103 -0
- package/create-countryconfig/package.json +21 -0
- package/dist/application-config/index.js +305 -129
- package/dist/cli.js +548 -178
- package/dist/commons/api/router.d.ts +3379 -239
- package/dist/commons/application-config/index.d.ts +17 -4
- package/dist/commons/conditionals/conditionals.d.ts +360 -10
- package/dist/commons/conditionals/validate.d.ts +52 -2
- package/dist/commons/events/ActionDocument.d.ts +6 -0
- package/dist/commons/events/ActionInput.d.ts +114 -0
- package/dist/commons/events/AdvancedSearchConfig.d.ts +342 -0
- package/dist/commons/events/Draft.d.ts +4 -2
- package/dist/commons/events/EventDocument.d.ts +3 -0
- package/dist/commons/events/EventIndex.d.ts +0 -3
- package/dist/commons/events/EventMetadata.d.ts +0 -7
- package/dist/commons/events/FieldConfig.d.ts +387 -50
- package/dist/commons/events/FieldValue.d.ts +6 -0
- package/dist/commons/events/WorkqueueConfig.d.ts +150 -150
- package/dist/commons/events/field.d.ts +53 -1
- package/dist/commons/events/index.d.ts +2 -0
- package/dist/commons/events/locations.d.ts +38 -1
- package/dist/commons/events/mocks.test.utils.d.ts +19 -0
- package/dist/commons/events/scopes.d.ts +24 -1
- package/dist/commons/events/state/flags.d.ts +16 -0
- package/dist/commons/events/state/index.d.ts +0 -4
- package/dist/commons/events/state/utils.d.ts +12 -289
- package/dist/commons/events/utils.d.ts +13 -2
- package/dist/conditionals/index.js +92 -9
- package/dist/events/index.js +1084 -261
- package/dist/migrations/v2.0/add-birth-certificate-issuance-flag.d.ts.map +1 -1
- package/dist/migrations/v2.0/add-birth-certificate-issuance-flag.js +60 -35
- package/dist/migrations/v2.0/checkout-upstream-files.d.ts +1 -1
- package/dist/migrations/v2.0/checkout-upstream-files.d.ts.map +1 -1
- package/dist/migrations/v2.0/checkout-upstream-files.js +1 -2
- package/dist/migrations/v2.0/delete-infrastructure-directory.d.ts +9 -1
- package/dist/migrations/v2.0/delete-infrastructure-directory.d.ts.map +1 -1
- package/dist/migrations/v2.0/delete-infrastructure-directory.js +66 -20
- package/dist/migrations/v2.0/fix-accept-requested-registration-function-type.d.ts +3 -0
- package/dist/migrations/v2.0/fix-accept-requested-registration-function-type.d.ts.map +1 -0
- package/dist/migrations/v2.0/fix-accept-requested-registration-function-type.js +110 -0
- package/dist/migrations/v2.0/index.d.ts +5 -3
- package/dist/migrations/v2.0/index.d.ts.map +1 -1
- package/dist/migrations/v2.0/index.js +546 -176
- package/dist/migrations/v2.0/make-built-in-validate-actions-custom.d.ts.map +1 -1
- package/dist/migrations/v2.0/make-built-in-validate-actions-custom.js +90 -1
- package/dist/migrations/v2.0/merge-infrastructure-directory.d.ts +11 -1
- package/dist/migrations/v2.0/merge-infrastructure-directory.d.ts.map +1 -1
- package/dist/migrations/v2.0/merge-infrastructure-directory.js +17 -16
- package/dist/migrations/v2.0/migrate-scopes.d.ts.map +1 -1
- package/dist/migrations/v2.0/migrate-scopes.js +54 -30
- package/dist/migrations/v2.0/migrate-workqueue-configs.d.ts.map +1 -1
- package/dist/migrations/v2.0/migrate-workqueue-configs.js +140 -59
- package/dist/notification/index.js +841 -160
- package/dist/scopes/index.d.ts +91 -16
- package/dist/scopes/index.js +55 -12
- package/opencrvs-toolkit-2.0.0-rc.ff99998.tgz +0 -0
- package/package.json +6 -2
- package/tsconfig.tsbuildinfo +1 -1
- package/opencrvs-toolkit-2.0.0-rc.fef9d21.tgz +0 -0
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"make-built-in-validate-actions-custom.d.ts","sourceRoot":"","sources":["../../../src/migrations/v2.0/make-built-in-validate-actions-custom.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"make-built-in-validate-actions-custom.d.ts","sourceRoot":"","sources":["../../../src/migrations/v2.0/make-built-in-validate-actions-custom.ts"],"names":[],"mappings":"AAodA,iBAAe,IAAI,kBAiDlB;AAED,OAAO,EAAE,IAAI,EAAE,CAAA"}
|
|
@@ -41,6 +41,10 @@ exports.main = main;
|
|
|
41
41
|
* - Removes the `deduplication` property from those objects
|
|
42
42
|
* - Replaces `review: { fields: <value> }` with `form: <value>`, or
|
|
43
43
|
* `review: <expr>` with `form: <expr>.fields` when the value is not an inline object
|
|
44
|
+
* - Adds the top-level `validated` flag definition and `{ id: 'validated',
|
|
45
|
+
* operation: 'add' }` to the (now custom) validate action
|
|
46
|
+
* - Adds `{ id: 'validated', operation: 'remove' }` to every `REGISTER` action
|
|
47
|
+
* in the same config, closing the flag's lifecycle when registration happens
|
|
44
48
|
* - Saves the modified files in-place
|
|
45
49
|
*/
|
|
46
50
|
const ts_morph_1 = require("ts-morph");
|
|
@@ -74,6 +78,8 @@ function ensureImports(sourceFile) {
|
|
|
74
78
|
const DEFINE_CONFIG_NAME = 'defineConfig';
|
|
75
79
|
const ACTIONS_PROPERTY_NAME = 'actions';
|
|
76
80
|
const TYPE_PROPERTY_NAME = 'type';
|
|
81
|
+
const ID_PROPERTY_NAME = 'id';
|
|
82
|
+
const FLAGS_PROPERTY_NAME = 'flags';
|
|
77
83
|
const CUSTOM_ACTION_TYPE_PROPERTY_NAME = 'customActionType';
|
|
78
84
|
const AUDIT_HISTORY_LABEL_PROPERTY_NAME = 'auditHistoryLabel';
|
|
79
85
|
const DEDUPLICATION_PROPERTY_NAME = 'deduplication';
|
|
@@ -83,9 +89,12 @@ const FORM_PROPERTY_NAME = 'form';
|
|
|
83
89
|
const ACTION_TYPE_ENUM_NAME = 'ActionType';
|
|
84
90
|
const VALIDATE_MEMBER_NAME = 'VALIDATE';
|
|
85
91
|
const CUSTOM_MEMBER_NAME = 'CUSTOM';
|
|
92
|
+
const REGISTER_MEMBER_NAME = 'REGISTER';
|
|
86
93
|
const VALIDATE_STRING_LITERAL = 'VALIDATE';
|
|
87
94
|
const CUSTOM_STRING_LITERAL = 'CUSTOM';
|
|
95
|
+
const REGISTER_STRING_LITERAL = 'REGISTER';
|
|
88
96
|
const VALIDATE_DECLARATION_VALUE = 'VALIDATE_DECLARATION';
|
|
97
|
+
const VALIDATED_FLAG_VALUE = 'validated';
|
|
89
98
|
function isValidateType(typeInitializer) {
|
|
90
99
|
// Matches: ActionType.VALIDATE
|
|
91
100
|
if (typeInitializer.getKind() === ts_morph_1.SyntaxKind.PropertyAccessExpression) {
|
|
@@ -230,6 +239,72 @@ function transformValidateActionToCustom(obj, eventType) {
|
|
|
230
239
|
}
|
|
231
240
|
return true;
|
|
232
241
|
}
|
|
242
|
+
function isRegisterAction(action) {
|
|
243
|
+
const typeProperty = action.getProperty(TYPE_PROPERTY_NAME);
|
|
244
|
+
if (!typeProperty || !ts_morph_1.Node.isPropertyAssignment(typeProperty))
|
|
245
|
+
return false;
|
|
246
|
+
const typeInit = typeProperty.getInitializer();
|
|
247
|
+
if (!typeInit)
|
|
248
|
+
return false;
|
|
249
|
+
// Matches: ActionType.REGISTER
|
|
250
|
+
if (ts_morph_1.Node.isPropertyAccessExpression(typeInit)) {
|
|
251
|
+
const enumExpr = typeInit.getExpression();
|
|
252
|
+
return (ts_morph_1.Node.isIdentifier(enumExpr) &&
|
|
253
|
+
enumExpr.getText() === ACTION_TYPE_ENUM_NAME &&
|
|
254
|
+
typeInit.getName() === REGISTER_MEMBER_NAME);
|
|
255
|
+
}
|
|
256
|
+
// Matches: "REGISTER"
|
|
257
|
+
if (ts_morph_1.Node.isStringLiteral(typeInit)) {
|
|
258
|
+
return typeInit.getLiteralValue() === REGISTER_STRING_LITERAL;
|
|
259
|
+
}
|
|
260
|
+
return false;
|
|
261
|
+
}
|
|
262
|
+
/**
|
|
263
|
+
* Returns true if an array literal contains an object literal with `id: <id>`.
|
|
264
|
+
*/
|
|
265
|
+
function arrayHasElementWithId(array, id) {
|
|
266
|
+
for (const el of array.getElements()) {
|
|
267
|
+
if (!ts_morph_1.Node.isObjectLiteralExpression(el))
|
|
268
|
+
continue;
|
|
269
|
+
const idProp = el.getProperty(ID_PROPERTY_NAME);
|
|
270
|
+
if (!idProp || !ts_morph_1.Node.isPropertyAssignment(idProp))
|
|
271
|
+
continue;
|
|
272
|
+
const idInit = idProp.getInitializer();
|
|
273
|
+
if (idInit &&
|
|
274
|
+
ts_morph_1.Node.isStringLiteral(idInit) &&
|
|
275
|
+
idInit.getLiteralValue() === id) {
|
|
276
|
+
return true;
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
return false;
|
|
280
|
+
}
|
|
281
|
+
/**
|
|
282
|
+
* Adds `{ id: 'validated', operation: 'remove' }` to a REGISTER action's
|
|
283
|
+
* `flags` array. Skips if any `validated` flag entry is already present —
|
|
284
|
+
* this keeps the codemod idempotent and avoids stomping on an intentional
|
|
285
|
+
* `add` op a maintainer may have authored manually.
|
|
286
|
+
*
|
|
287
|
+
* Returns true if a change was made.
|
|
288
|
+
*/
|
|
289
|
+
function ensureRegisterRemovesValidatedFlag(action) {
|
|
290
|
+
const entry = `{ id: '${VALIDATED_FLAG_VALUE}', operation: 'remove' }`;
|
|
291
|
+
const flagsProp = action.getProperty(FLAGS_PROPERTY_NAME);
|
|
292
|
+
if (flagsProp && ts_morph_1.Node.isPropertyAssignment(flagsProp)) {
|
|
293
|
+
const flagsInit = flagsProp.getInitializer();
|
|
294
|
+
if (flagsInit && ts_morph_1.Node.isArrayLiteralExpression(flagsInit)) {
|
|
295
|
+
if (arrayHasElementWithId(flagsInit, VALIDATED_FLAG_VALUE))
|
|
296
|
+
return false;
|
|
297
|
+
flagsInit.addElement(entry);
|
|
298
|
+
return true;
|
|
299
|
+
}
|
|
300
|
+
return false;
|
|
301
|
+
}
|
|
302
|
+
action.addPropertyAssignment({
|
|
303
|
+
name: FLAGS_PROPERTY_NAME,
|
|
304
|
+
initializer: `[${entry}]`
|
|
305
|
+
});
|
|
306
|
+
return true;
|
|
307
|
+
}
|
|
233
308
|
function processFile(filePath, project) {
|
|
234
309
|
const sourceFile = project.getSourceFile(filePath);
|
|
235
310
|
if (!sourceFile)
|
|
@@ -297,6 +372,20 @@ function processFile(filePath, project) {
|
|
|
297
372
|
});
|
|
298
373
|
console.log(` [${path_1.default.relative(process.cwd(), filePath)}] Added top-level 'flags' array to defineConfig`);
|
|
299
374
|
}
|
|
375
|
+
// Close the `validated` flag's lifecycle: when the validate action adds the
|
|
376
|
+
// flag, the matching REGISTER action(s) must remove it on registration.
|
|
377
|
+
if (configHadValidateAction) {
|
|
378
|
+
for (const element of actionsInitializer.getElements()) {
|
|
379
|
+
if (!ts_morph_1.Node.isObjectLiteralExpression(element))
|
|
380
|
+
continue;
|
|
381
|
+
if (!isRegisterAction(element))
|
|
382
|
+
continue;
|
|
383
|
+
if (ensureRegisterRemovesValidatedFlag(element)) {
|
|
384
|
+
transformedCount++;
|
|
385
|
+
console.log(` [${path_1.default.relative(process.cwd(), filePath)}] Added '${VALIDATED_FLAG_VALUE}' remove-flag to REGISTER action`);
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
}
|
|
300
389
|
if (fileNeedsImports) {
|
|
301
390
|
ensureImports(sourceFile);
|
|
302
391
|
}
|
|
@@ -338,6 +427,6 @@ function main() {
|
|
|
338
427
|
yield sourceFile.save();
|
|
339
428
|
console.log(` Saved: ${path_1.default.relative(process.cwd(), filePath)}`);
|
|
340
429
|
}
|
|
341
|
-
console.log(`\nDone.
|
|
430
|
+
console.log(`\nDone. Applied ${totalTransformed} change(s): VALIDATE → CUSTOM (${VALIDATE_DECLARATION_VALUE}) and '${VALIDATED_FLAG_VALUE}' remove-flag on matching REGISTER actions.`);
|
|
342
431
|
});
|
|
343
432
|
}
|
|
@@ -1,3 +1,13 @@
|
|
|
1
|
-
|
|
1
|
+
interface MergeInfrastructureOptions {
|
|
2
|
+
/**
|
|
3
|
+
* Top-level subdirectories of `infrastructure/` to scope the merge to.
|
|
4
|
+
* When omitted or empty, the entire `infrastructure/` tree is merged
|
|
5
|
+
* (legacy behavior). Each name is relative to `infrastructure/` —
|
|
6
|
+
* e.g. `['postgres', 'metabase']` merges only
|
|
7
|
+
* `infrastructure/postgres/` and `infrastructure/metabase/`.
|
|
8
|
+
*/
|
|
9
|
+
subdirs?: string[];
|
|
10
|
+
}
|
|
11
|
+
declare function main({ subdirs }?: MergeInfrastructureOptions): Promise<void>;
|
|
2
12
|
export { main };
|
|
3
13
|
//# sourceMappingURL=merge-infrastructure-directory.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"merge-infrastructure-directory.d.ts","sourceRoot":"","sources":["../../../src/migrations/v2.0/merge-infrastructure-directory.ts"],"names":[],"mappings":"AAsHA,iBAAe,IAAI,
|
|
1
|
+
{"version":3,"file":"merge-infrastructure-directory.d.ts","sourceRoot":"","sources":["../../../src/migrations/v2.0/merge-infrastructure-directory.ts"],"names":[],"mappings":"AAsHA,UAAU,0BAA0B;IAClC;;;;;;OAMG;IACH,OAAO,CAAC,EAAE,MAAM,EAAE,CAAA;CACnB;AAID,iBAAe,IAAI,CAAC,EAClB,OAAY,EACb,GAAE,0BAA+B,GAAG,OAAO,CAAC,IAAI,CAAC,CAoIjD;AAED,OAAO,EAAE,IAAI,EAAE,CAAA"}
|
|
@@ -22,7 +22,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
22
22
|
exports.main = main;
|
|
23
23
|
/**
|
|
24
24
|
* Codemod: Merge upstream changes into the local `infrastructure/` directory
|
|
25
|
-
* from opencrvs/opencrvs-countryconfig.
|
|
25
|
+
* (or selected subdirectories of it) from opencrvs/opencrvs-countryconfig.
|
|
26
26
|
*
|
|
27
27
|
* Usage:
|
|
28
28
|
* ts-node -r tsconfig-paths/register src/migrations/v2.0/merge-infrastructure-directory.ts
|
|
@@ -33,8 +33,10 @@ exports.main = main;
|
|
|
33
33
|
* - the upstream "theirs" branch (v2.0) → `UPSTREAM_BRANCH`
|
|
34
34
|
* - the synthetic merge-base branch (latest 1.9 release line)
|
|
35
35
|
* → `BASE_BRANCH`
|
|
36
|
-
* 2. For every file under `infrastructure/` on upstream "theirs",
|
|
37
|
-
*
|
|
36
|
+
* 2. For every file under `infrastructure/` on upstream "theirs" (or, when
|
|
37
|
+
* `subdirs` is provided, only files under
|
|
38
|
+
* `infrastructure/<subdir>/` for each requested subdir), performs a
|
|
39
|
+
* file-scoped 3-way merge:
|
|
38
40
|
* - new file on upstream → checked out and staged
|
|
39
41
|
* - existing file → 3-way merged in place via `git merge-file`,
|
|
40
42
|
* using the BASE_BRANCH version of the file
|
|
@@ -116,8 +118,14 @@ function gitShowBytes(ref, path) {
|
|
|
116
118
|
}
|
|
117
119
|
// ─── Entry point ─────────────────────────────────────────────────────────────
|
|
118
120
|
function main() {
|
|
119
|
-
return __awaiter(this,
|
|
120
|
-
|
|
121
|
+
return __awaiter(this, arguments, void 0, function* ({ subdirs = [] } = {}) {
|
|
122
|
+
// Build the pathspec list passed to `git ls-tree`. Trailing slashes make
|
|
123
|
+
// git treat them as tree paths (recurse into them).
|
|
124
|
+
const pathspecs = subdirs.length > 0
|
|
125
|
+
? subdirs.map((s) => `${INFRASTRUCTURE_DIR}/${s}/`)
|
|
126
|
+
: [`${INFRASTRUCTURE_DIR}/`];
|
|
127
|
+
const scopeLabel = pathspecs.map((p) => `'${p}'`).join(', ');
|
|
128
|
+
console.log(`Merging ${scopeLabel} from ${checkout_upstream_files_1.UPSTREAM_URL}@${checkout_upstream_files_1.UPSTREAM_BRANCH} (base: ${BASE_BRANCH})...\n`);
|
|
121
129
|
(0, checkout_upstream_files_1.assertIsGitRepo)();
|
|
122
130
|
// Best-effort cleanup of a stale temp remote from a previous aborted run.
|
|
123
131
|
(0, checkout_upstream_files_1.tryGit)(['remote', 'remove', TEMP_REMOTE]);
|
|
@@ -131,20 +139,13 @@ function main() {
|
|
|
131
139
|
const ref = `${TEMP_REMOTE}/${checkout_upstream_files_1.UPSTREAM_BRANCH}`;
|
|
132
140
|
const baseRef = `${TEMP_REMOTE}/${BASE_BRANCH}`;
|
|
133
141
|
// -z gives NUL-separated paths so filenames with spaces or newlines
|
|
134
|
-
// survive the round-trip.
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
'-r',
|
|
138
|
-
'--name-only',
|
|
139
|
-
'-z',
|
|
140
|
-
ref,
|
|
141
|
-
'--',
|
|
142
|
-
`${INFRASTRUCTURE_DIR}/`
|
|
143
|
-
], { silent: true })
|
|
142
|
+
// survive the round-trip. Multiple pathspecs after `--` are unioned
|
|
143
|
+
// by git, which scopes the listing to the requested subdirectories.
|
|
144
|
+
const upstreamFiles = (0, checkout_upstream_files_1.runGit)(['ls-tree', '-r', '--name-only', '-z', ref, '--', ...pathspecs], { silent: true })
|
|
144
145
|
.split('\0')
|
|
145
146
|
.filter(Boolean);
|
|
146
147
|
if (upstreamFiles.length === 0) {
|
|
147
|
-
console.log(` No files found under
|
|
148
|
+
console.log(` No files found under ${scopeLabel} on ${ref}. Nothing to merge.`);
|
|
148
149
|
return;
|
|
149
150
|
}
|
|
150
151
|
const tmpDir = (0, fs_1.mkdtempSync)((0, path_1.join)((0, os_1.tmpdir)(), 'opencrvs-merge-infra-'));
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"migrate-scopes.d.ts","sourceRoot":"","sources":["../../../src/migrations/v2.0/migrate-scopes.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"migrate-scopes.d.ts","sourceRoot":"","sources":["../../../src/migrations/v2.0/migrate-scopes.ts"],"names":[],"mappings":"AAoXA,iBAAe,IAAI,kBAmDlB;AAED,OAAO,EAAE,IAAI,EAAE,CAAA"}
|
|
@@ -25,7 +25,6 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
25
25
|
exports.main = main;
|
|
26
26
|
const ts_morph_1 = require("ts-morph");
|
|
27
27
|
const path_1 = __importDefault(require("path"));
|
|
28
|
-
const scopes_1 = require("../commons/scopes");
|
|
29
28
|
const scopes_deprecated_do_not_use_1 = require("../commons/scopes.deprecated.do-not-use");
|
|
30
29
|
const ROLES_FILE_RELATIVE_PATH = 'src/data-seeding/roles/roles.ts';
|
|
31
30
|
const SCOPES_PROPERTY_NAME = 'scopes';
|
|
@@ -65,60 +64,85 @@ function resolveLegacyScopeFromElement(element) {
|
|
|
65
64
|
}
|
|
66
65
|
return null;
|
|
67
66
|
}
|
|
67
|
+
/**
|
|
68
|
+
* Walks each `scopes: [...]` array in the roles file, classifies elements,
|
|
69
|
+
* and rewrites the whole array using `migrateLegacyScopesArrayToV2Scopes`
|
|
70
|
+
* (which preserves v1.9 AND semantics by merging pairs of legacy scopes
|
|
71
|
+
* that targeted the same v2 type). See issue #12489.
|
|
72
|
+
*
|
|
73
|
+
* Elements are partitioned into three kinds:
|
|
74
|
+
* - drop: `SCOPES.X` references where X is not in the SCOPES map (warned
|
|
75
|
+
* and removed, matching pre-refactor behavior).
|
|
76
|
+
* - raw: non-literal expressions we cannot statically resolve (e.g.
|
|
77
|
+
* `...sharedScopes` spreads). Preserved verbatim in output so authoring
|
|
78
|
+
* intent isn't lost.
|
|
79
|
+
* - legacy: literal strings or resolvable `SCOPES.X` references.
|
|
80
|
+
* Collected and passed as a batch to the merge function.
|
|
81
|
+
*
|
|
82
|
+
* The output array text is `[...rawTexts, ...mergedScopesAsJson]`. Order of
|
|
83
|
+
* legacy entries relative to raw expressions is not preserved — scopes in a
|
|
84
|
+
* v2 array compose with OR, so position is semantically irrelevant.
|
|
85
|
+
*/
|
|
68
86
|
function migrateScopesArrays(rolesFilePath, project) {
|
|
69
87
|
const sourceFile = project.getSourceFile(rolesFilePath);
|
|
70
|
-
if (!sourceFile)
|
|
71
|
-
return { migratedCount: 0, removedCount: 0 };
|
|
88
|
+
if (!sourceFile) {
|
|
89
|
+
return { migratedCount: 0, removedCount: 0, mergedCount: 0 };
|
|
90
|
+
}
|
|
72
91
|
let migratedCount = 0;
|
|
73
92
|
let removedCount = 0;
|
|
93
|
+
let mergedCount = 0;
|
|
74
94
|
const scopesProperties = sourceFile
|
|
75
95
|
.getDescendantsOfKind(ts_morph_1.SyntaxKind.PropertyAssignment)
|
|
76
96
|
.filter((prop) => prop.getName() === SCOPES_PROPERTY_NAME);
|
|
97
|
+
const relPath = path_1.default.relative(process.cwd(), rolesFilePath);
|
|
77
98
|
for (const scopesProp of scopesProperties) {
|
|
78
99
|
const initializer = scopesProp.getInitializer();
|
|
79
100
|
if (!initializer || !ts_morph_1.Node.isArrayLiteralExpression(initializer))
|
|
80
101
|
continue;
|
|
81
|
-
const
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
continue;
|
|
102
|
+
const rawTexts = [];
|
|
103
|
+
const legacyStrings = [];
|
|
104
|
+
let anyChange = false;
|
|
105
|
+
for (const element of initializer.getElements()) {
|
|
86
106
|
const scopeKey = resolveScopeKeyFromElementAccess(element);
|
|
87
107
|
if (scopeKey && !(scopeKey in scopes_deprecated_do_not_use_1.SCOPES)) {
|
|
88
108
|
// eslint-disable-next-line no-console
|
|
89
|
-
console.warn(` [${
|
|
90
|
-
initializer.removeElement(index);
|
|
109
|
+
console.warn(` [${relPath}] Removing unknown scope reference: SCOPES.${scopeKey}`);
|
|
91
110
|
removedCount++;
|
|
111
|
+
anyChange = true;
|
|
92
112
|
continue;
|
|
93
113
|
}
|
|
94
114
|
const legacyScope = resolveLegacyScopeFromElement(element);
|
|
95
115
|
if (!legacyScope) {
|
|
96
116
|
// eslint-disable-next-line no-console
|
|
97
|
-
console.warn(` [${
|
|
117
|
+
console.warn(` [${relPath}] Preserving non-literal scope expression verbatim: ${element.getText()}`);
|
|
118
|
+
rawTexts.push(element.getText());
|
|
98
119
|
continue;
|
|
99
120
|
}
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
}
|
|
111
|
-
catch (error) {
|
|
112
|
-
// eslint-disable-next-line no-console
|
|
113
|
-
console.warn(` [${path_1.default.relative(process.cwd(), rolesFilePath)}] Could not migrate scope '${legacyScope}': ${error}`);
|
|
114
|
-
}
|
|
121
|
+
legacyStrings.push(legacyScope);
|
|
122
|
+
anyChange = true;
|
|
123
|
+
}
|
|
124
|
+
if (!anyChange)
|
|
125
|
+
continue;
|
|
126
|
+
let mergedScopes;
|
|
127
|
+
try {
|
|
128
|
+
mergedScopes = (0, scopes_deprecated_do_not_use_1.migrateLegacyScopesArrayToV2Scopes)(legacyStrings);
|
|
129
|
+
}
|
|
130
|
+
catch (error) {
|
|
131
|
+
throw new Error(`[${relPath}] Failed to migrate scopes for role: ${error instanceof Error ? error.message : String(error)}`, { cause: error });
|
|
115
132
|
}
|
|
133
|
+
migratedCount += legacyStrings.length;
|
|
134
|
+
mergedCount += legacyStrings.length - mergedScopes.length;
|
|
135
|
+
const outputElements = [
|
|
136
|
+
...rawTexts,
|
|
137
|
+
...mergedScopes.map((s) => JSON.stringify(s))
|
|
138
|
+
];
|
|
139
|
+
scopesProp.setInitializer(`[${outputElements.join(', ')}]`);
|
|
116
140
|
}
|
|
117
141
|
if (removedCount > 0) {
|
|
118
142
|
// eslint-disable-next-line no-console
|
|
119
|
-
console.log(` Removed ${removedCount} unknown SCOPES reference(s) from '${
|
|
143
|
+
console.log(` Removed ${removedCount} unknown SCOPES reference(s) from '${relPath}'.`);
|
|
120
144
|
}
|
|
121
|
-
return { migratedCount, removedCount };
|
|
145
|
+
return { migratedCount, removedCount, mergedCount };
|
|
122
146
|
}
|
|
123
147
|
function ensureScopesHelpersImport(rolesFilePath, project) {
|
|
124
148
|
const sourceFile = project.getSourceFile(rolesFilePath);
|
|
@@ -262,7 +286,7 @@ function main() {
|
|
|
262
286
|
return;
|
|
263
287
|
}
|
|
264
288
|
const importChanged = ensureScopesHelpersImport(rolesFilePath, project);
|
|
265
|
-
const { migratedCount, removedCount } = migrateScopesArrays(rolesFilePath, project);
|
|
289
|
+
const { migratedCount, removedCount, mergedCount } = migrateScopesArrays(rolesFilePath, project);
|
|
266
290
|
const wrappedScopesCount = wrapScopesArraysWithDefineScopes(rolesFilePath, project);
|
|
267
291
|
const wrappedRoles = wrapRolesWithDefineRoles(rolesFilePath, project);
|
|
268
292
|
const removedRoleType = removeRoleTypeDefinitionAndUnusedImports(rolesFilePath, project);
|
|
@@ -276,6 +300,6 @@ function main() {
|
|
|
276
300
|
return;
|
|
277
301
|
}
|
|
278
302
|
yield sourceFile.save();
|
|
279
|
-
console.log(`Done. Migrated ${migratedCount} legacy scope(s), removed ${removedCount} unknown SCOPES reference(s), wrapped ${wrappedScopesCount} scope array(s) with defineScopes()${wrappedRoles ? ', wrapped roles with defineRoles()' : ''}${removedRoleType ? ', and removed Role type definition' : ''}${importChanged ? '. Updated toolkit/scopes imports.' : '.'}`);
|
|
303
|
+
console.log(`Done. Migrated ${migratedCount} legacy scope(s), merged ${mergedCount} AND-paired scope(s) into single v2 objects (privilege-escalation fix, see issue #12489), removed ${removedCount} unknown SCOPES reference(s), wrapped ${wrappedScopesCount} scope array(s) with defineScopes()${wrappedRoles ? ', wrapped roles with defineRoles()' : ''}${removedRoleType ? ', and removed Role type definition' : ''}${importChanged ? '. Updated toolkit/scopes imports.' : '.'}`);
|
|
280
304
|
});
|
|
281
305
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"migrate-workqueue-configs.d.ts","sourceRoot":"","sources":["../../../src/migrations/v2.0/migrate-workqueue-configs.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"migrate-workqueue-configs.d.ts","sourceRoot":"","sources":["../../../src/migrations/v2.0/migrate-workqueue-configs.ts"],"names":[],"mappings":"AAgVA,iBAAe,IAAI,kBAqDlB;AAED,OAAO,EAAE,IAAI,EAAE,CAAA"}
|
|
@@ -39,6 +39,10 @@ exports.main = main;
|
|
|
39
39
|
* - `actions: [{...}, {...}, ...]` -> keeps first as `action`, warns about dropped extras
|
|
40
40
|
* - If `action` already exists, removes deprecated `actions` and keeps `action`
|
|
41
41
|
* - Removes unsupported `action.type` values (`DEFAULT`, `VALIDATE`, etc.)
|
|
42
|
+
* - Ensures every workqueue ends up with a valid `action`. When none is
|
|
43
|
+
* present (or the existing one is unsupported), falls back to
|
|
44
|
+
* `action: { type: ActionType.READ }` and ensures `ActionType` is imported
|
|
45
|
+
* from `@opencrvs/toolkit/events` in the file.
|
|
42
46
|
* - Saves modified files in-place
|
|
43
47
|
*/
|
|
44
48
|
const ts_morph_1 = require("ts-morph");
|
|
@@ -49,6 +53,10 @@ const ACTION_PROPERTY_NAME = 'action';
|
|
|
49
53
|
const CONDITIONALS_PROPERTY_NAME = 'conditionals';
|
|
50
54
|
const TYPE_PROPERTY_NAME = 'type';
|
|
51
55
|
const SLUG_PROPERTY_NAME = 'slug';
|
|
56
|
+
const READ_ACTION_TYPE = 'READ';
|
|
57
|
+
const ACTION_TYPE_IMPORT_NAME = 'ActionType';
|
|
58
|
+
const TOOLKIT_EVENTS_MODULE = '@opencrvs/toolkit/events';
|
|
59
|
+
const READ_ACTION_FALLBACK_INITIALIZER = `{ type: ${ACTION_TYPE_IMPORT_NAME}.${READ_ACTION_TYPE} }`;
|
|
52
60
|
const SUPPORTED_WORKQUEUE_ACTION_TYPES = new Set([
|
|
53
61
|
'READ',
|
|
54
62
|
'DELETE',
|
|
@@ -96,80 +104,146 @@ function isUnsupportedActionType(action) {
|
|
|
96
104
|
}
|
|
97
105
|
return !SUPPORTED_WORKQUEUE_ACTION_TYPES.has(typeName);
|
|
98
106
|
}
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
+
/**
|
|
108
|
+
* Ensures the workqueue has a valid singular `action` property. When the
|
|
109
|
+
* existing `action` is missing or has an unsupported type, replaces it with
|
|
110
|
+
* `action: { type: ActionType.READ }`.
|
|
111
|
+
*
|
|
112
|
+
* Returns whether anything was changed and whether the READ fallback was
|
|
113
|
+
* applied (so the caller can ensure `ActionType` is imported in the file).
|
|
114
|
+
*/
|
|
115
|
+
function ensureValidAction(workqueue, relPath) {
|
|
116
|
+
const workqueueLabel = getWorkqueueLabel(workqueue);
|
|
117
|
+
const actionProperty = workqueue.getProperty(ACTION_PROPERTY_NAME);
|
|
118
|
+
if (actionProperty && ts_morph_1.Node.isPropertyAssignment(actionProperty)) {
|
|
119
|
+
const initializer = actionProperty.getInitializer();
|
|
120
|
+
if (initializer && ts_morph_1.Node.isObjectLiteralExpression(initializer)) {
|
|
121
|
+
if (!isUnsupportedActionType(initializer)) {
|
|
122
|
+
return { changed: false, usedReadFallback: false };
|
|
123
|
+
}
|
|
124
|
+
actionProperty.set({
|
|
125
|
+
name: ACTION_PROPERTY_NAME,
|
|
126
|
+
initializer: READ_ACTION_FALLBACK_INITIALIZER
|
|
127
|
+
});
|
|
128
|
+
// eslint-disable-next-line no-console
|
|
129
|
+
console.log(` [${relPath}] Replaced unsupported 'action' type with '${ACTION_TYPE_IMPORT_NAME}.${READ_ACTION_TYPE}' on workqueue '${workqueueLabel}'`);
|
|
130
|
+
return { changed: true, usedReadFallback: true };
|
|
131
|
+
}
|
|
107
132
|
}
|
|
133
|
+
workqueue.addPropertyAssignment({
|
|
134
|
+
name: ACTION_PROPERTY_NAME,
|
|
135
|
+
initializer: READ_ACTION_FALLBACK_INITIALIZER
|
|
136
|
+
});
|
|
137
|
+
// eslint-disable-next-line no-console
|
|
138
|
+
console.log(` [${relPath}] Added default 'action: ${READ_ACTION_FALLBACK_INITIALIZER}' to workqueue '${workqueueLabel}'`);
|
|
139
|
+
return { changed: true, usedReadFallback: true };
|
|
140
|
+
}
|
|
141
|
+
function migrateWorkqueueActions(workqueue, relPath) {
|
|
108
142
|
const workqueueLabel = getWorkqueueLabel(workqueue);
|
|
109
|
-
const
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
143
|
+
const actionsProperty = workqueue.getProperty(ACTIONS_PROPERTY_NAME);
|
|
144
|
+
let actionsArrayMigrated = false;
|
|
145
|
+
if (actionsProperty && ts_morph_1.Node.isPropertyAssignment(actionsProperty)) {
|
|
146
|
+
const actionsInitializer = actionsProperty.getInitializer();
|
|
147
|
+
if (actionsInitializer &&
|
|
148
|
+
ts_morph_1.Node.isArrayLiteralExpression(actionsInitializer)) {
|
|
149
|
+
const existingActionProperty = workqueue.getProperty(ACTION_PROPERTY_NAME);
|
|
150
|
+
if (existingActionProperty) {
|
|
151
|
+
if (ts_morph_1.Node.isPropertyAssignment(existingActionProperty)) {
|
|
152
|
+
const existingActionInitializer = existingActionProperty.getInitializer();
|
|
153
|
+
if (existingActionInitializer &&
|
|
154
|
+
ts_morph_1.Node.isObjectLiteralExpression(existingActionInitializer)) {
|
|
155
|
+
const conditionalsProperty = existingActionInitializer.getProperty(CONDITIONALS_PROPERTY_NAME);
|
|
156
|
+
if (conditionalsProperty) {
|
|
157
|
+
conditionalsProperty.remove();
|
|
158
|
+
// eslint-disable-next-line no-console
|
|
159
|
+
console.log(` [${relPath}] Removed deprecated 'conditionals' from 'action' on workqueue '${workqueueLabel}'`);
|
|
160
|
+
}
|
|
161
|
+
}
|
|
119
162
|
}
|
|
120
|
-
|
|
121
|
-
|
|
163
|
+
actionsProperty.remove();
|
|
164
|
+
// eslint-disable-next-line no-console
|
|
165
|
+
console.log(` [${relPath}] Removed deprecated 'actions' from workqueue '${workqueueLabel}' (existing 'action' kept)`);
|
|
166
|
+
actionsArrayMigrated = true;
|
|
167
|
+
}
|
|
168
|
+
else {
|
|
169
|
+
const actionCandidates = actionsInitializer
|
|
170
|
+
.getElements()
|
|
171
|
+
.filter((element) => ts_morph_1.Node.isObjectLiteralExpression(element));
|
|
172
|
+
if (actionCandidates.length === 0) {
|
|
173
|
+
actionsProperty.remove();
|
|
122
174
|
// eslint-disable-next-line no-console
|
|
123
|
-
console.log(` [${relPath}] Removed
|
|
175
|
+
console.log(` [${relPath}] Removed empty 'actions' from workqueue '${workqueueLabel}'`);
|
|
176
|
+
actionsArrayMigrated = true;
|
|
177
|
+
}
|
|
178
|
+
else {
|
|
179
|
+
if (actionCandidates.length > 1) {
|
|
180
|
+
const droppedCount = actionCandidates.length - 1;
|
|
181
|
+
// eslint-disable-next-line no-console
|
|
182
|
+
console.warn(` [${relPath}] Workqueue '${workqueueLabel}' has ${actionCandidates.length} actions; keeping first as 'action' and dropping ${droppedCount} extra action(s)`);
|
|
183
|
+
}
|
|
184
|
+
const conditionalsProperty = actionCandidates[0].getProperty(CONDITIONALS_PROPERTY_NAME);
|
|
185
|
+
if (conditionalsProperty) {
|
|
186
|
+
conditionalsProperty.remove();
|
|
187
|
+
// eslint-disable-next-line no-console
|
|
188
|
+
console.log(` [${relPath}] Removed deprecated 'conditionals' from migrated 'action' on workqueue '${workqueueLabel}'`);
|
|
189
|
+
}
|
|
190
|
+
if (isUnsupportedActionType(actionCandidates[0])) {
|
|
191
|
+
actionsProperty.remove();
|
|
192
|
+
// eslint-disable-next-line no-console
|
|
193
|
+
console.log(` [${relPath}] Removed deprecated 'actions' from workqueue '${workqueueLabel}' because action type is unsupported`);
|
|
194
|
+
actionsArrayMigrated = true;
|
|
195
|
+
}
|
|
196
|
+
else {
|
|
197
|
+
const firstActionText = actionCandidates[0].getText();
|
|
198
|
+
workqueue.addPropertyAssignment({
|
|
199
|
+
name: ACTION_PROPERTY_NAME,
|
|
200
|
+
initializer: firstActionText
|
|
201
|
+
});
|
|
202
|
+
actionsProperty.remove();
|
|
203
|
+
// eslint-disable-next-line no-console
|
|
204
|
+
console.log(` [${relPath}] Replaced 'actions' with 'action' on workqueue '${workqueueLabel}'`);
|
|
205
|
+
actionsArrayMigrated = true;
|
|
206
|
+
}
|
|
124
207
|
}
|
|
125
208
|
}
|
|
126
209
|
}
|
|
127
|
-
actionsProperty.remove();
|
|
128
|
-
// eslint-disable-next-line no-console
|
|
129
|
-
console.log(` [${relPath}] Removed deprecated 'actions' from workqueue '${workqueueLabel}' (existing 'action' kept)`);
|
|
130
|
-
return 1;
|
|
131
|
-
}
|
|
132
|
-
const actionCandidates = actionsInitializer
|
|
133
|
-
.getElements()
|
|
134
|
-
.filter((element) => ts_morph_1.Node.isObjectLiteralExpression(element));
|
|
135
|
-
if (actionCandidates.length === 0) {
|
|
136
|
-
actionsProperty.remove();
|
|
137
|
-
// eslint-disable-next-line no-console
|
|
138
|
-
console.log(` [${relPath}] Removed empty 'actions' from workqueue '${workqueueLabel}'`);
|
|
139
|
-
return 1;
|
|
140
|
-
}
|
|
141
|
-
if (actionCandidates.length > 1) {
|
|
142
|
-
const droppedCount = actionCandidates.length - 1;
|
|
143
|
-
// eslint-disable-next-line no-console
|
|
144
|
-
console.warn(` [${relPath}] Workqueue '${workqueueLabel}' has ${actionCandidates.length} actions; keeping first as 'action' and dropping ${droppedCount} extra action(s)`);
|
|
145
|
-
}
|
|
146
|
-
const conditionalsProperty = actionCandidates[0].getProperty(CONDITIONALS_PROPERTY_NAME);
|
|
147
|
-
if (conditionalsProperty) {
|
|
148
|
-
conditionalsProperty.remove();
|
|
149
|
-
// eslint-disable-next-line no-console
|
|
150
|
-
console.log(` [${relPath}] Removed deprecated 'conditionals' from migrated 'action' on workqueue '${workqueueLabel}'`);
|
|
151
210
|
}
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
211
|
+
// Always ensure the workqueue ends up with a valid `action`. This both
|
|
212
|
+
// covers workqueues that never had an `actions` array, and patches up
|
|
213
|
+
// workqueues whose existing/migrated action type is unsupported.
|
|
214
|
+
const fallback = ensureValidAction(workqueue, relPath);
|
|
215
|
+
return {
|
|
216
|
+
changed: actionsArrayMigrated || fallback.changed,
|
|
217
|
+
usedReadFallback: fallback.usedReadFallback
|
|
218
|
+
};
|
|
219
|
+
}
|
|
220
|
+
/**
|
|
221
|
+
* Ensures `import { ActionType } from '@opencrvs/toolkit/events'` exists in
|
|
222
|
+
* the source file, merging into any existing import declaration from the
|
|
223
|
+
* same module specifier.
|
|
224
|
+
*/
|
|
225
|
+
function ensureActionTypeImport(sourceFile) {
|
|
226
|
+
const existingImport = sourceFile.getImportDeclaration((decl) => decl.getModuleSpecifierValue() === TOOLKIT_EVENTS_MODULE);
|
|
227
|
+
if (existingImport) {
|
|
228
|
+
const alreadyImported = existingImport
|
|
229
|
+
.getNamedImports()
|
|
230
|
+
.some((ni) => ni.getName() === ACTION_TYPE_IMPORT_NAME);
|
|
231
|
+
if (!alreadyImported) {
|
|
232
|
+
existingImport.addNamedImport(ACTION_TYPE_IMPORT_NAME);
|
|
233
|
+
}
|
|
234
|
+
return;
|
|
157
235
|
}
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
initializer: firstActionText
|
|
236
|
+
sourceFile.addImportDeclaration({
|
|
237
|
+
moduleSpecifier: TOOLKIT_EVENTS_MODULE,
|
|
238
|
+
namedImports: [ACTION_TYPE_IMPORT_NAME]
|
|
162
239
|
});
|
|
163
|
-
actionsProperty.remove();
|
|
164
|
-
// eslint-disable-next-line no-console
|
|
165
|
-
console.log(` [${relPath}] Replaced 'actions' with 'action' on workqueue '${workqueueLabel}'`);
|
|
166
|
-
return 1;
|
|
167
240
|
}
|
|
168
241
|
function processFile(filePath, project) {
|
|
169
242
|
const sourceFile = project.getSourceFile(filePath);
|
|
170
243
|
if (!sourceFile)
|
|
171
244
|
return 0;
|
|
172
245
|
let migratedWorkqueues = 0;
|
|
246
|
+
let needsActionTypeImport = false;
|
|
173
247
|
const relPath = path_1.default.relative(process.cwd(), filePath);
|
|
174
248
|
const callExpressions = sourceFile.getDescendantsOfKind(ts_morph_1.SyntaxKind.CallExpression);
|
|
175
249
|
for (const call of callExpressions) {
|
|
@@ -187,9 +261,16 @@ function processFile(filePath, project) {
|
|
|
187
261
|
for (const element of workqueuesArg.getElements()) {
|
|
188
262
|
if (!ts_morph_1.Node.isObjectLiteralExpression(element))
|
|
189
263
|
continue;
|
|
190
|
-
|
|
264
|
+
const result = migrateWorkqueueActions(element, relPath);
|
|
265
|
+
if (result.changed)
|
|
266
|
+
migratedWorkqueues++;
|
|
267
|
+
if (result.usedReadFallback)
|
|
268
|
+
needsActionTypeImport = true;
|
|
191
269
|
}
|
|
192
270
|
}
|
|
271
|
+
if (needsActionTypeImport) {
|
|
272
|
+
ensureActionTypeImport(sourceFile);
|
|
273
|
+
}
|
|
193
274
|
return migratedWorkqueues;
|
|
194
275
|
}
|
|
195
276
|
function main() {
|