@opencrvs/toolkit 2.0.0-rc.fdc585a → 2.0.0-rc.ff04b30

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.
Files changed (44) hide show
  1. package/create-countryconfig/index.js +73 -0
  2. package/create-countryconfig/package.json +11 -0
  3. package/dist/application-config/index.js +27 -28
  4. package/dist/cli.js +392 -128
  5. package/dist/commons/api/router.d.ts +3301 -247
  6. package/dist/commons/conditionals/validate.d.ts +1 -0
  7. package/dist/commons/events/ActionInput.d.ts +108 -0
  8. package/dist/commons/events/Draft.d.ts +3 -0
  9. package/dist/commons/events/EventIndex.d.ts +0 -3
  10. package/dist/commons/events/EventMetadata.d.ts +0 -7
  11. package/dist/commons/events/WorkqueueConfig.d.ts +146 -146
  12. package/dist/commons/events/locations.d.ts +21 -0
  13. package/dist/commons/events/mocks.test.utils.d.ts +19 -0
  14. package/dist/commons/events/scopes.d.ts +24 -1
  15. package/dist/commons/events/state/index.d.ts +0 -4
  16. package/dist/commons/events/state/utils.d.ts +0 -2
  17. package/dist/commons/events/utils.d.ts +1 -1
  18. package/dist/conditionals/index.js +3 -1
  19. package/dist/events/index.js +109 -76
  20. package/dist/migrations/v2.0/checkout-upstream-files.d.ts +1 -1
  21. package/dist/migrations/v2.0/checkout-upstream-files.d.ts.map +1 -1
  22. package/dist/migrations/v2.0/checkout-upstream-files.js +1 -2
  23. package/dist/migrations/v2.0/delete-infrastructure-directory.d.ts +9 -1
  24. package/dist/migrations/v2.0/delete-infrastructure-directory.d.ts.map +1 -1
  25. package/dist/migrations/v2.0/delete-infrastructure-directory.js +66 -20
  26. package/dist/migrations/v2.0/fix-accept-requested-registration-function-type.d.ts +3 -0
  27. package/dist/migrations/v2.0/fix-accept-requested-registration-function-type.d.ts.map +1 -0
  28. package/dist/migrations/v2.0/fix-accept-requested-registration-function-type.js +110 -0
  29. package/dist/migrations/v2.0/index.d.ts +5 -3
  30. package/dist/migrations/v2.0/index.d.ts.map +1 -1
  31. package/dist/migrations/v2.0/index.js +390 -126
  32. package/dist/migrations/v2.0/merge-infrastructure-directory.d.ts +11 -1
  33. package/dist/migrations/v2.0/merge-infrastructure-directory.d.ts.map +1 -1
  34. package/dist/migrations/v2.0/merge-infrastructure-directory.js +17 -16
  35. package/dist/migrations/v2.0/migrate-scopes.d.ts.map +1 -1
  36. package/dist/migrations/v2.0/migrate-scopes.js +54 -30
  37. package/dist/migrations/v2.0/migrate-workqueue-configs.d.ts.map +1 -1
  38. package/dist/migrations/v2.0/migrate-workqueue-configs.js +140 -59
  39. package/dist/notification/index.js +41 -46
  40. package/dist/scopes/index.js +3 -1
  41. package/opencrvs-toolkit-2.0.0-rc.ff04b30.tgz +0 -0
  42. package/package.json +1 -1
  43. package/tsconfig.tsbuildinfo +1 -1
  44. package/opencrvs-toolkit-2.0.0-rc.fdc585a.tgz +0 -0
package/dist/cli.js CHANGED
@@ -1883,9 +1883,9 @@ function updateRouteHandlers(sourceFiles, group, srcDir, relCwd) {
1883
1883
  }
1884
1884
  return replacedCount;
1885
1885
  }
1886
- function resolveConfigChain(configObj, path23) {
1886
+ function resolveConfigChain(configObj, path24) {
1887
1887
  let current = configObj;
1888
- for (const key of path23) {
1888
+ for (const key of path24) {
1889
1889
  if (!import_ts_morph10.Node.isObjectLiteralExpression(current)) return null;
1890
1890
  const prop = current.getProperty(key);
1891
1891
  if (!prop || !import_ts_morph10.Node.isPropertyAssignment(prop)) return null;
@@ -2120,6 +2120,10 @@ Done. Converted ${convertedCount} config file(s), replaced ${totalReplaced} rout
2120
2120
  var import_ts_morph11 = require("ts-morph");
2121
2121
  var import_path11 = __toESM(require("path"));
2122
2122
 
2123
+ // ../commons/src/scopes.deprecated.do-not-use.ts
2124
+ var z4 = __toESM(require("zod/v4"));
2125
+ var import_lodash = require("lodash");
2126
+
2123
2127
  // ../commons/src/scopes.ts
2124
2128
  var z3 = __toESM(require("zod/v4"));
2125
2129
  var qs = __toESM(require("qs"));
@@ -2389,11 +2393,12 @@ var decodeScope = (query) => {
2389
2393
  };
2390
2394
  var DEFAULT_SCOPE_OPTIONS = {
2391
2395
  placeOfEvent: JurisdictionFilter.enum.all,
2392
- accessLevel: JurisdictionFilter.enum.all
2396
+ accessLevel: JurisdictionFilter.enum.all,
2397
+ registeredIn: JurisdictionFilter.enum.all,
2398
+ declaredIn: JurisdictionFilter.enum.all
2393
2399
  };
2394
2400
 
2395
2401
  // ../commons/src/scopes.deprecated.do-not-use.ts
2396
- var z4 = __toESM(require("zod/v4"));
2397
2402
  var rawConfigurableScopeRegex = /^([a-zA-Z][a-zA-Z0-9.-]*(?:\.[a-zA-Z0-9.-]+)*)\[((?:\w+=[\w.-]+(?:\|[\w.-]+)*)(?:,[\w]+=[\w.-]+(?:\|[\w.-]+)*)*)\]$/;
2398
2403
  var rawConfigurableScope = z4.string().regex(rawConfigurableScopeRegex);
2399
2404
  var SearchScope = z4.object({
@@ -2627,6 +2632,74 @@ var legacyScopeToV2Scope = (v1Scope) => {
2627
2632
  }
2628
2633
  throw new Error(`Unsupported V1 scope type: ${v1Scope}`);
2629
2634
  };
2635
+ var MERGEABLE_USER_SCOPE_TYPES = ["user.create", "user.edit"];
2636
+ function isMergeableUserScopeType(type) {
2637
+ return MERGEABLE_USER_SCOPE_TYPES.includes(type);
2638
+ }
2639
+ function decodeLegacyScope(legacy) {
2640
+ const encoded = legacyScopeToV2Scope(legacy);
2641
+ const scope = decodeScope(encoded);
2642
+ if (!scope) {
2643
+ throw new Error(
2644
+ `Could not decode migrated scope from legacy '${legacy}' (encoded: '${encoded}').`
2645
+ );
2646
+ }
2647
+ return { legacy, scope };
2648
+ }
2649
+ function groupByType(entries) {
2650
+ return entries.reduce(
2651
+ (groups, entry) => ({
2652
+ ...groups,
2653
+ [entry.scope.type]: [...groups[entry.scope.type] ?? [], entry]
2654
+ }),
2655
+ {}
2656
+ );
2657
+ }
2658
+ function deduplicateEntries(entries) {
2659
+ return entries.filter(
2660
+ (entry, index) => entries.findIndex((e) => (0, import_lodash.isEqual)(e.scope, entry.scope)) === index
2661
+ );
2662
+ }
2663
+ function mergeEntries(type, entries) {
2664
+ const optionsList = entries.map(
2665
+ (e) => "options" in e.scope && e.scope.options ? e.scope.options : {}
2666
+ );
2667
+ const allKeys = optionsList.flatMap(
2668
+ (opts) => Object.keys(opts).filter((k) => opts[k] !== void 0)
2669
+ );
2670
+ const conflictKey = allKeys.find((key, i) => allKeys.indexOf(key) !== i);
2671
+ if (conflictKey) {
2672
+ const conflicting = entries.filter(
2673
+ (e) => conflictKey in ("options" in e.scope && e.scope.options || {})
2674
+ ).map((e) => JSON.stringify(e.legacy));
2675
+ throw new Error(
2676
+ `Cannot auto-merge two '${type}' scopes that both define '${conflictKey}'. Conflicting legacy scopes: [${conflicting.join(", ")}]. Resolve manually in roles.ts and re-run.`
2677
+ );
2678
+ }
2679
+ const mergedOptions = Object.assign({}, ...optionsList);
2680
+ const merged = Object.keys(mergedOptions).length > 0 ? { type, options: mergedOptions } : { type };
2681
+ const parsed = Scope2.safeParse(merged);
2682
+ if (!parsed.success) {
2683
+ throw new Error(
2684
+ `Merged '${type}' scope failed schema validation: ${parsed.error.message}. Legacy scopes: [${entries.map((e) => JSON.stringify(e.legacy)).join(", ")}].`
2685
+ );
2686
+ }
2687
+ return parsed.data;
2688
+ }
2689
+ function migrateLegacyScopesArrayToV2Scopes(legacyScopes) {
2690
+ const groups = groupByType(legacyScopes.map(decodeLegacyScope));
2691
+ return Object.entries(groups).flatMap(([type, entries]) => {
2692
+ const deduped = deduplicateEntries(entries);
2693
+ if (deduped.length <= 1) return deduped.map((e) => e.scope);
2694
+ if (!isMergeableUserScopeType(type)) {
2695
+ console.warn(
2696
+ `Multiple non-mergeable '${type}' scopes detected after dedup \u2014 kept all as-is. Legacy scopes: [${deduped.map((e) => JSON.stringify(e.legacy)).join(", ")}]. Review whether the duplication is intentional.`
2697
+ );
2698
+ return deduped.map((e) => e.scope);
2699
+ }
2700
+ return [mergeEntries(type, deduped)];
2701
+ });
2702
+ }
2630
2703
 
2631
2704
  // src/migrations/v2.0/migrate-scopes.ts
2632
2705
  var ROLES_FILE_RELATIVE_PATH = "src/data-seeding/roles/roles.ts";
@@ -2664,57 +2737,65 @@ function resolveLegacyScopeFromElement(element) {
2664
2737
  }
2665
2738
  function migrateScopesArrays(rolesFilePath, project) {
2666
2739
  const sourceFile = project.getSourceFile(rolesFilePath);
2667
- if (!sourceFile) return { migratedCount: 0, removedCount: 0 };
2740
+ if (!sourceFile) {
2741
+ return { migratedCount: 0, removedCount: 0, mergedCount: 0 };
2742
+ }
2668
2743
  let migratedCount = 0;
2669
2744
  let removedCount = 0;
2745
+ let mergedCount = 0;
2670
2746
  const scopesProperties = sourceFile.getDescendantsOfKind(import_ts_morph11.SyntaxKind.PropertyAssignment).filter((prop) => prop.getName() === SCOPES_PROPERTY_NAME);
2747
+ const relPath = import_path11.default.relative(process.cwd(), rolesFilePath);
2671
2748
  for (const scopesProp of scopesProperties) {
2672
2749
  const initializer = scopesProp.getInitializer();
2673
2750
  if (!initializer || !import_ts_morph11.Node.isArrayLiteralExpression(initializer)) continue;
2674
- const elements = initializer.getElements();
2675
- for (let index = elements.length - 1; index >= 0; index--) {
2676
- const element = elements[index];
2677
- if (!import_ts_morph11.Node.isExpression(element)) continue;
2751
+ const rawTexts = [];
2752
+ const legacyStrings = [];
2753
+ let anyChange = false;
2754
+ for (const element of initializer.getElements()) {
2678
2755
  const scopeKey = resolveScopeKeyFromElementAccess(element);
2679
2756
  if (scopeKey && !(scopeKey in SCOPES)) {
2680
2757
  console.warn(
2681
- ` [${import_path11.default.relative(process.cwd(), rolesFilePath)}] Removing unknown scope reference: SCOPES.${scopeKey}`
2758
+ ` [${relPath}] Removing unknown scope reference: SCOPES.${scopeKey}`
2682
2759
  );
2683
- initializer.removeElement(index);
2684
2760
  removedCount++;
2761
+ anyChange = true;
2685
2762
  continue;
2686
2763
  }
2687
2764
  const legacyScope = resolveLegacyScopeFromElement(element);
2688
2765
  if (!legacyScope) {
2689
2766
  console.warn(
2690
- ` [${import_path11.default.relative(process.cwd(), rolesFilePath)}] Skipping non-literal scope expression: ${element.getText()}`
2767
+ ` [${relPath}] Preserving non-literal scope expression verbatim: ${element.getText()}`
2691
2768
  );
2769
+ rawTexts.push(element.getText());
2692
2770
  continue;
2693
2771
  }
2694
- try {
2695
- const migratedScope = legacyScopeToV2Scope(legacyScope);
2696
- const scope = decodeScope(migratedScope);
2697
- if (!scope) {
2698
- console.warn(
2699
- ` [${import_path11.default.relative(process.cwd(), rolesFilePath)}] Could not decode migrated scope '${migratedScope}'.`
2700
- );
2701
- continue;
2702
- }
2703
- element.replaceWithText(JSON.stringify(scope));
2704
- migratedCount++;
2705
- } catch (error) {
2706
- console.warn(
2707
- ` [${import_path11.default.relative(process.cwd(), rolesFilePath)}] Could not migrate scope '${legacyScope}': ${error}`
2708
- );
2709
- }
2772
+ legacyStrings.push(legacyScope);
2773
+ anyChange = true;
2774
+ }
2775
+ if (!anyChange) continue;
2776
+ let mergedScopes;
2777
+ try {
2778
+ mergedScopes = migrateLegacyScopesArrayToV2Scopes(legacyStrings);
2779
+ } catch (error) {
2780
+ throw new Error(
2781
+ `[${relPath}] Failed to migrate scopes for role: ${error instanceof Error ? error.message : String(error)}`,
2782
+ { cause: error }
2783
+ );
2710
2784
  }
2785
+ migratedCount += legacyStrings.length;
2786
+ mergedCount += legacyStrings.length - mergedScopes.length;
2787
+ const outputElements = [
2788
+ ...rawTexts,
2789
+ ...mergedScopes.map((s) => JSON.stringify(s))
2790
+ ];
2791
+ scopesProp.setInitializer(`[${outputElements.join(", ")}]`);
2711
2792
  }
2712
2793
  if (removedCount > 0) {
2713
2794
  console.log(
2714
- ` Removed ${removedCount} unknown SCOPES reference(s) from '${import_path11.default.relative(process.cwd(), rolesFilePath)}'.`
2795
+ ` Removed ${removedCount} unknown SCOPES reference(s) from '${relPath}'.`
2715
2796
  );
2716
2797
  }
2717
- return { migratedCount, removedCount };
2798
+ return { migratedCount, removedCount, mergedCount };
2718
2799
  }
2719
2800
  function ensureScopesHelpersImport(rolesFilePath, project) {
2720
2801
  const sourceFile = project.getSourceFile(rolesFilePath);
@@ -2849,7 +2930,7 @@ async function main11() {
2849
2930
  return;
2850
2931
  }
2851
2932
  const importChanged = ensureScopesHelpersImport(rolesFilePath, project);
2852
- const { migratedCount, removedCount } = migrateScopesArrays(
2933
+ const { migratedCount, removedCount, mergedCount } = migrateScopesArrays(
2853
2934
  rolesFilePath,
2854
2935
  project
2855
2936
  );
@@ -2868,7 +2949,7 @@ async function main11() {
2868
2949
  }
2869
2950
  await sourceFile.save();
2870
2951
  console.log(
2871
- `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." : "."}`
2952
+ `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." : "."}`
2872
2953
  );
2873
2954
  }
2874
2955
 
@@ -3674,6 +3755,10 @@ var ACTION_PROPERTY_NAME2 = "action";
3674
3755
  var CONDITIONALS_PROPERTY_NAME = "conditionals";
3675
3756
  var TYPE_PROPERTY_NAME7 = "type";
3676
3757
  var SLUG_PROPERTY_NAME = "slug";
3758
+ var READ_ACTION_TYPE = "READ";
3759
+ var ACTION_TYPE_IMPORT_NAME = "ActionType";
3760
+ var TOOLKIT_EVENTS_MODULE2 = "@opencrvs/toolkit/events";
3761
+ var READ_ACTION_FALLBACK_INITIALIZER = `{ type: ${ACTION_TYPE_IMPORT_NAME}.${READ_ACTION_TYPE} }`;
3677
3762
  var SUPPORTED_WORKQUEUE_ACTION_TYPES = /* @__PURE__ */ new Set([
3678
3763
  "READ",
3679
3764
  "DELETE",
@@ -3721,91 +3806,137 @@ function isUnsupportedActionType(action) {
3721
3806
  }
3722
3807
  return !SUPPORTED_WORKQUEUE_ACTION_TYPES.has(typeName);
3723
3808
  }
3724
- function migrateWorkqueueActions(workqueue, relPath) {
3725
- const actionsProperty = workqueue.getProperty(ACTIONS_PROPERTY_NAME5);
3726
- if (!actionsProperty || !import_ts_morph17.Node.isPropertyAssignment(actionsProperty)) {
3727
- return 0;
3728
- }
3729
- const actionsInitializer = actionsProperty.getInitializer();
3730
- if (!actionsInitializer || !import_ts_morph17.Node.isArrayLiteralExpression(actionsInitializer)) {
3731
- return 0;
3809
+ function ensureValidAction(workqueue, relPath) {
3810
+ const workqueueLabel = getWorkqueueLabel(workqueue);
3811
+ const actionProperty = workqueue.getProperty(ACTION_PROPERTY_NAME2);
3812
+ if (actionProperty && import_ts_morph17.Node.isPropertyAssignment(actionProperty)) {
3813
+ const initializer = actionProperty.getInitializer();
3814
+ if (initializer && import_ts_morph17.Node.isObjectLiteralExpression(initializer)) {
3815
+ if (!isUnsupportedActionType(initializer)) {
3816
+ return { changed: false, usedReadFallback: false };
3817
+ }
3818
+ actionProperty.set({
3819
+ name: ACTION_PROPERTY_NAME2,
3820
+ initializer: READ_ACTION_FALLBACK_INITIALIZER
3821
+ });
3822
+ console.log(
3823
+ ` [${relPath}] Replaced unsupported 'action' type with '${ACTION_TYPE_IMPORT_NAME}.${READ_ACTION_TYPE}' on workqueue '${workqueueLabel}'`
3824
+ );
3825
+ return { changed: true, usedReadFallback: true };
3826
+ }
3732
3827
  }
3828
+ workqueue.addPropertyAssignment({
3829
+ name: ACTION_PROPERTY_NAME2,
3830
+ initializer: READ_ACTION_FALLBACK_INITIALIZER
3831
+ });
3832
+ console.log(
3833
+ ` [${relPath}] Added default 'action: ${READ_ACTION_FALLBACK_INITIALIZER}' to workqueue '${workqueueLabel}'`
3834
+ );
3835
+ return { changed: true, usedReadFallback: true };
3836
+ }
3837
+ function migrateWorkqueueActions(workqueue, relPath) {
3733
3838
  const workqueueLabel = getWorkqueueLabel(workqueue);
3734
- const existingActionProperty = workqueue.getProperty(ACTION_PROPERTY_NAME2);
3735
- if (existingActionProperty) {
3736
- if (import_ts_morph17.Node.isPropertyAssignment(existingActionProperty)) {
3737
- const existingActionInitializer = existingActionProperty.getInitializer();
3738
- if (existingActionInitializer && import_ts_morph17.Node.isObjectLiteralExpression(existingActionInitializer)) {
3739
- const conditionalsProperty2 = existingActionInitializer.getProperty(
3740
- CONDITIONALS_PROPERTY_NAME
3839
+ const actionsProperty = workqueue.getProperty(ACTIONS_PROPERTY_NAME5);
3840
+ let actionsArrayMigrated = false;
3841
+ if (actionsProperty && import_ts_morph17.Node.isPropertyAssignment(actionsProperty)) {
3842
+ const actionsInitializer = actionsProperty.getInitializer();
3843
+ if (actionsInitializer && import_ts_morph17.Node.isArrayLiteralExpression(actionsInitializer)) {
3844
+ const existingActionProperty = workqueue.getProperty(ACTION_PROPERTY_NAME2);
3845
+ if (existingActionProperty) {
3846
+ if (import_ts_morph17.Node.isPropertyAssignment(existingActionProperty)) {
3847
+ const existingActionInitializer = existingActionProperty.getInitializer();
3848
+ if (existingActionInitializer && import_ts_morph17.Node.isObjectLiteralExpression(existingActionInitializer)) {
3849
+ const conditionalsProperty = existingActionInitializer.getProperty(
3850
+ CONDITIONALS_PROPERTY_NAME
3851
+ );
3852
+ if (conditionalsProperty) {
3853
+ conditionalsProperty.remove();
3854
+ console.log(
3855
+ ` [${relPath}] Removed deprecated 'conditionals' from 'action' on workqueue '${workqueueLabel}'`
3856
+ );
3857
+ }
3858
+ }
3859
+ }
3860
+ actionsProperty.remove();
3861
+ console.log(
3862
+ ` [${relPath}] Removed deprecated 'actions' from workqueue '${workqueueLabel}' (existing 'action' kept)`
3863
+ );
3864
+ actionsArrayMigrated = true;
3865
+ } else {
3866
+ const actionCandidates = actionsInitializer.getElements().filter(
3867
+ (element) => import_ts_morph17.Node.isObjectLiteralExpression(element)
3741
3868
  );
3742
- if (conditionalsProperty2) {
3743
- conditionalsProperty2.remove();
3869
+ if (actionCandidates.length === 0) {
3870
+ actionsProperty.remove();
3744
3871
  console.log(
3745
- ` [${relPath}] Removed deprecated 'conditionals' from 'action' on workqueue '${workqueueLabel}'`
3872
+ ` [${relPath}] Removed empty 'actions' from workqueue '${workqueueLabel}'`
3746
3873
  );
3747
- }
3748
- if (isUnsupportedActionType(existingActionInitializer)) {
3749
- existingActionProperty.remove();
3750
- console.log(
3751
- ` [${relPath}] Removed unsupported 'action' type on workqueue '${workqueueLabel}'`
3874
+ actionsArrayMigrated = true;
3875
+ } else {
3876
+ if (actionCandidates.length > 1) {
3877
+ const droppedCount = actionCandidates.length - 1;
3878
+ console.warn(
3879
+ ` [${relPath}] Workqueue '${workqueueLabel}' has ${actionCandidates.length} actions; keeping first as 'action' and dropping ${droppedCount} extra action(s)`
3880
+ );
3881
+ }
3882
+ const conditionalsProperty = actionCandidates[0].getProperty(
3883
+ CONDITIONALS_PROPERTY_NAME
3752
3884
  );
3885
+ if (conditionalsProperty) {
3886
+ conditionalsProperty.remove();
3887
+ console.log(
3888
+ ` [${relPath}] Removed deprecated 'conditionals' from migrated 'action' on workqueue '${workqueueLabel}'`
3889
+ );
3890
+ }
3891
+ if (isUnsupportedActionType(actionCandidates[0])) {
3892
+ actionsProperty.remove();
3893
+ console.log(
3894
+ ` [${relPath}] Removed deprecated 'actions' from workqueue '${workqueueLabel}' because action type is unsupported`
3895
+ );
3896
+ actionsArrayMigrated = true;
3897
+ } else {
3898
+ const firstActionText = actionCandidates[0].getText();
3899
+ workqueue.addPropertyAssignment({
3900
+ name: ACTION_PROPERTY_NAME2,
3901
+ initializer: firstActionText
3902
+ });
3903
+ actionsProperty.remove();
3904
+ console.log(
3905
+ ` [${relPath}] Replaced 'actions' with 'action' on workqueue '${workqueueLabel}'`
3906
+ );
3907
+ actionsArrayMigrated = true;
3908
+ }
3753
3909
  }
3754
3910
  }
3755
3911
  }
3756
- actionsProperty.remove();
3757
- console.log(
3758
- ` [${relPath}] Removed deprecated 'actions' from workqueue '${workqueueLabel}' (existing 'action' kept)`
3759
- );
3760
- return 1;
3761
- }
3762
- const actionCandidates = actionsInitializer.getElements().filter(
3763
- (element) => import_ts_morph17.Node.isObjectLiteralExpression(element)
3764
- );
3765
- if (actionCandidates.length === 0) {
3766
- actionsProperty.remove();
3767
- console.log(
3768
- ` [${relPath}] Removed empty 'actions' from workqueue '${workqueueLabel}'`
3769
- );
3770
- return 1;
3771
3912
  }
3772
- if (actionCandidates.length > 1) {
3773
- const droppedCount = actionCandidates.length - 1;
3774
- console.warn(
3775
- ` [${relPath}] Workqueue '${workqueueLabel}' has ${actionCandidates.length} actions; keeping first as 'action' and dropping ${droppedCount} extra action(s)`
3776
- );
3777
- }
3778
- const conditionalsProperty = actionCandidates[0].getProperty(
3779
- CONDITIONALS_PROPERTY_NAME
3913
+ const fallback = ensureValidAction(workqueue, relPath);
3914
+ return {
3915
+ changed: actionsArrayMigrated || fallback.changed,
3916
+ usedReadFallback: fallback.usedReadFallback
3917
+ };
3918
+ }
3919
+ function ensureActionTypeImport(sourceFile) {
3920
+ const existingImport = sourceFile.getImportDeclaration(
3921
+ (decl) => decl.getModuleSpecifierValue() === TOOLKIT_EVENTS_MODULE2
3780
3922
  );
3781
- if (conditionalsProperty) {
3782
- conditionalsProperty.remove();
3783
- console.log(
3784
- ` [${relPath}] Removed deprecated 'conditionals' from migrated 'action' on workqueue '${workqueueLabel}'`
3785
- );
3786
- }
3787
- if (isUnsupportedActionType(actionCandidates[0])) {
3788
- actionsProperty.remove();
3789
- console.log(
3790
- ` [${relPath}] Removed deprecated 'actions' from workqueue '${workqueueLabel}' because action type is unsupported`
3791
- );
3792
- return 1;
3923
+ if (existingImport) {
3924
+ const alreadyImported = existingImport.getNamedImports().some((ni) => ni.getName() === ACTION_TYPE_IMPORT_NAME);
3925
+ if (!alreadyImported) {
3926
+ existingImport.addNamedImport(ACTION_TYPE_IMPORT_NAME);
3927
+ }
3928
+ return;
3793
3929
  }
3794
- const firstActionText = actionCandidates[0].getText();
3795
- workqueue.addPropertyAssignment({
3796
- name: ACTION_PROPERTY_NAME2,
3797
- initializer: firstActionText
3930
+ sourceFile.addImportDeclaration({
3931
+ moduleSpecifier: TOOLKIT_EVENTS_MODULE2,
3932
+ namedImports: [ACTION_TYPE_IMPORT_NAME]
3798
3933
  });
3799
- actionsProperty.remove();
3800
- console.log(
3801
- ` [${relPath}] Replaced 'actions' with 'action' on workqueue '${workqueueLabel}'`
3802
- );
3803
- return 1;
3804
3934
  }
3805
3935
  function processFile13(filePath, project) {
3806
3936
  const sourceFile = project.getSourceFile(filePath);
3807
3937
  if (!sourceFile) return 0;
3808
3938
  let migratedWorkqueues = 0;
3939
+ let needsActionTypeImport = false;
3809
3940
  const relPath = import_path18.default.relative(process.cwd(), filePath);
3810
3941
  const callExpressions = sourceFile.getDescendantsOfKind(
3811
3942
  import_ts_morph17.SyntaxKind.CallExpression
@@ -3821,9 +3952,14 @@ function processFile13(filePath, project) {
3821
3952
  if (!import_ts_morph17.Node.isArrayLiteralExpression(workqueuesArg)) continue;
3822
3953
  for (const element of workqueuesArg.getElements()) {
3823
3954
  if (!import_ts_morph17.Node.isObjectLiteralExpression(element)) continue;
3824
- migratedWorkqueues += migrateWorkqueueActions(element, relPath);
3955
+ const result = migrateWorkqueueActions(element, relPath);
3956
+ if (result.changed) migratedWorkqueues++;
3957
+ if (result.usedReadFallback) needsActionTypeImport = true;
3825
3958
  }
3826
3959
  }
3960
+ if (needsActionTypeImport) {
3961
+ ensureActionTypeImport(sourceFile);
3962
+ }
3827
3963
  return migratedWorkqueues;
3828
3964
  }
3829
3965
  async function main18() {
@@ -4158,7 +4294,7 @@ Saved ${handlerRelPath}.`);
4158
4294
  // src/migrations/v2.0/checkout-upstream-files.ts
4159
4295
  var import_child_process = require("child_process");
4160
4296
  var UPSTREAM_URL = "https://github.com/opencrvs/opencrvs-countryconfig.git";
4161
- var UPSTREAM_BRANCH = "develop";
4297
+ var UPSTREAM_BRANCH = "release-v2.0.0";
4162
4298
  var TEMP_REMOTE = "opencrvs-upgrade-v19-v20-codemod";
4163
4299
  var FILES_TO_CHECKOUT = [
4164
4300
  "src/analytics/analytics.ts",
@@ -4697,9 +4833,9 @@ var import_path24 = require("path");
4697
4833
  var TEMP_REMOTE2 = "opencrvs-upgrade-v19-v20-codemod-merge-infra";
4698
4834
  var INFRASTRUCTURE_DIR = "infrastructure";
4699
4835
  var BASE_BRANCH = "release-v1.9";
4700
- function gitShowBytes(ref, path23) {
4836
+ function gitShowBytes(ref, path24) {
4701
4837
  try {
4702
- const buf = (0, import_child_process2.execFileSync)("git", ["show", `${ref}:${path23}`], {
4838
+ const buf = (0, import_child_process2.execFileSync)("git", ["show", `${ref}:${path24}`], {
4703
4839
  cwd: process.cwd(),
4704
4840
  stdio: ["ignore", "pipe", "pipe"]
4705
4841
  });
@@ -4708,9 +4844,13 @@ function gitShowBytes(ref, path23) {
4708
4844
  return null;
4709
4845
  }
4710
4846
  }
4711
- async function main25() {
4847
+ async function main25({
4848
+ subdirs = []
4849
+ } = {}) {
4850
+ const pathspecs = subdirs.length > 0 ? subdirs.map((s) => `${INFRASTRUCTURE_DIR}/${s}/`) : [`${INFRASTRUCTURE_DIR}/`];
4851
+ const scopeLabel = pathspecs.map((p) => `'${p}'`).join(", ");
4712
4852
  console.log(
4713
- `Merging '${INFRASTRUCTURE_DIR}/' from ${UPSTREAM_URL}@${UPSTREAM_BRANCH} (base: ${BASE_BRANCH})...
4853
+ `Merging ${scopeLabel} from ${UPSTREAM_URL}@${UPSTREAM_BRANCH} (base: ${BASE_BRANCH})...
4714
4854
  `
4715
4855
  );
4716
4856
  assertIsGitRepo();
@@ -4725,20 +4865,12 @@ async function main25() {
4725
4865
  const ref = `${TEMP_REMOTE2}/${UPSTREAM_BRANCH}`;
4726
4866
  const baseRef = `${TEMP_REMOTE2}/${BASE_BRANCH}`;
4727
4867
  const upstreamFiles = runGit(
4728
- [
4729
- "ls-tree",
4730
- "-r",
4731
- "--name-only",
4732
- "-z",
4733
- ref,
4734
- "--",
4735
- `${INFRASTRUCTURE_DIR}/`
4736
- ],
4868
+ ["ls-tree", "-r", "--name-only", "-z", ref, "--", ...pathspecs],
4737
4869
  { silent: true }
4738
4870
  ).split("\0").filter(Boolean);
4739
4871
  if (upstreamFiles.length === 0) {
4740
4872
  console.log(
4741
- ` No files found under '${INFRASTRUCTURE_DIR}/' on ${ref}. Nothing to merge.`
4873
+ ` No files found under ${scopeLabel} on ${ref}. Nothing to merge.`
4742
4874
  );
4743
4875
  return;
4744
4876
  }
@@ -4813,7 +4945,9 @@ async function main25() {
4813
4945
  var import_fs6 = require("fs");
4814
4946
  var import_path25 = require("path");
4815
4947
  var INFRASTRUCTURE_DIR2 = "infrastructure";
4816
- async function main26() {
4948
+ async function main26({
4949
+ keep = []
4950
+ } = {}) {
4817
4951
  const fullPath = (0, import_path25.join)(process.cwd(), INFRASTRUCTURE_DIR2);
4818
4952
  if (!(0, import_fs6.existsSync)(fullPath)) {
4819
4953
  console.log(
@@ -4821,24 +4955,152 @@ async function main26() {
4821
4955
  );
4822
4956
  return;
4823
4957
  }
4824
- console.log(`Deleting '${INFRASTRUCTURE_DIR2}/'...
4825
- `);
4826
4958
  assertIsGitRepo();
4827
- runGit(["rm", "-rf", "--ignore-unmatch", "--", `${INFRASTRUCTURE_DIR2}/`], {
4828
- silent: true
4959
+ if (keep.length === 0) {
4960
+ console.log(`Deleting '${INFRASTRUCTURE_DIR2}/'...
4961
+ `);
4962
+ runGit(["rm", "-rf", "--ignore-unmatch", "--", `${INFRASTRUCTURE_DIR2}/`], {
4963
+ silent: true
4964
+ });
4965
+ console.log(
4966
+ ` Staged deletion of tracked files under '${INFRASTRUCTURE_DIR2}/'.`
4967
+ );
4968
+ if ((0, import_fs6.existsSync)(fullPath)) {
4969
+ (0, import_fs6.rmSync)(fullPath, { recursive: true, force: true });
4970
+ console.log(
4971
+ ` Removed remaining untracked files under '${INFRASTRUCTURE_DIR2}/'.`
4972
+ );
4973
+ }
4974
+ console.log(
4975
+ `
4976
+ Done. Review the staged deletion with: git diff --cached -- '${INFRASTRUCTURE_DIR2}/'`
4977
+ );
4978
+ return;
4979
+ }
4980
+ const keepSet = new Set(keep);
4981
+ console.log(
4982
+ `Deleting contents of '${INFRASTRUCTURE_DIR2}/' except: ${[...keepSet].map((name) => `'${name}'`).join(", ")}
4983
+ `
4984
+ );
4985
+ const topLevelEntries = (0, import_fs6.readdirSync)(fullPath, { withFileTypes: true });
4986
+ let deleted = 0;
4987
+ for (const entry of topLevelEntries) {
4988
+ if (keepSet.has(entry.name)) {
4989
+ console.log(` Keeping '${INFRASTRUCTURE_DIR2}/${entry.name}'.`);
4990
+ continue;
4991
+ }
4992
+ const relPath = `${INFRASTRUCTURE_DIR2}/${entry.name}${entry.isDirectory() ? "/" : ""}`;
4993
+ runGit(["rm", "-rf", "--ignore-unmatch", "--", relPath], { silent: true });
4994
+ const fullEntryPath = (0, import_path25.join)(fullPath, entry.name);
4995
+ if ((0, import_fs6.existsSync)(fullEntryPath)) {
4996
+ (0, import_fs6.rmSync)(fullEntryPath, { recursive: true, force: true });
4997
+ }
4998
+ console.log(` Deleted '${relPath}'.`);
4999
+ deleted++;
5000
+ }
5001
+ const presentNames = new Set(topLevelEntries.map((e) => e.name));
5002
+ const missingKeeps = [...keepSet].filter((name) => !presentNames.has(name));
5003
+ if (missingKeeps.length > 0) {
5004
+ console.warn(
5005
+ ` [warn] Requested to keep ${missingKeeps.map((n) => `'${n}'`).join(", ")} but they don't exist under '${INFRASTRUCTURE_DIR2}/'.`
5006
+ );
5007
+ }
5008
+ console.log(
5009
+ `
5010
+ Done. Deleted ${deleted} top-level entr${deleted === 1 ? "y" : "ies"}. Review the staged deletions with: git diff --cached -- '${INFRASTRUCTURE_DIR2}/'`
5011
+ );
5012
+ }
5013
+
5014
+ // src/migrations/v2.0/fix-accept-requested-registration-function-type.ts
5015
+ var import_ts_morph22 = require("ts-morph");
5016
+ var import_path26 = __toESM(require("path"));
5017
+ var TARGET_FUNCTION_NAME = "acceptRequestedRegistration";
5018
+ var ACTION_PARAMETER_NAME = "action";
5019
+ var OLD_TYPE = "ActionInput";
5020
+ var NEW_TYPE = "Extract<ActionInput, { type?: 'REGISTER' }>";
5021
+ function isAcceptRequestedRegistrationParameter(parameter) {
5022
+ const functionDeclaration = parameter.getFirstAncestorByKind(
5023
+ import_ts_morph22.SyntaxKind.FunctionDeclaration
5024
+ );
5025
+ if (functionDeclaration?.getName() === TARGET_FUNCTION_NAME) {
5026
+ return true;
5027
+ }
5028
+ const arrowFunction = parameter.getFirstAncestorByKind(
5029
+ import_ts_morph22.SyntaxKind.ArrowFunction
5030
+ );
5031
+ const variableDeclaration = arrowFunction?.getFirstAncestorByKind(
5032
+ import_ts_morph22.SyntaxKind.VariableDeclaration
5033
+ );
5034
+ return variableDeclaration?.getName() === TARGET_FUNCTION_NAME;
5035
+ }
5036
+ function processFile14(filePath, project) {
5037
+ const sourceFile = project.getSourceFile(filePath);
5038
+ if (!sourceFile) return 0;
5039
+ let updatedCount = 0;
5040
+ for (const parameter of sourceFile.getDescendantsOfKind(
5041
+ import_ts_morph22.SyntaxKind.Parameter
5042
+ )) {
5043
+ if (parameter.getName() !== ACTION_PARAMETER_NAME) {
5044
+ continue;
5045
+ }
5046
+ if (!isAcceptRequestedRegistrationParameter(parameter)) {
5047
+ continue;
5048
+ }
5049
+ const typeNode = parameter.getTypeNode();
5050
+ if (!typeNode || typeNode.getText() !== OLD_TYPE) {
5051
+ continue;
5052
+ }
5053
+ parameter.setType(NEW_TYPE);
5054
+ updatedCount++;
5055
+ console.log(
5056
+ ` [${import_path26.default.relative(process.cwd(), filePath)}] Updated '${ACTION_PARAMETER_NAME}' in ${TARGET_FUNCTION_NAME} to ${NEW_TYPE}`
5057
+ );
5058
+ }
5059
+ return updatedCount;
5060
+ }
5061
+ async function main27() {
5062
+ const srcDir = import_path26.default.join(process.cwd(), "src");
5063
+ console.log(`Scanning for ${TARGET_FUNCTION_NAME} in: ${srcDir}
5064
+ `);
5065
+ const project = new import_ts_morph22.Project({
5066
+ tsConfigFilePath: import_path26.default.resolve(srcDir, "../tsconfig.json"),
5067
+ skipAddingFilesFromTsConfig: false
4829
5068
  });
4830
- console.log(` Staged deletion of tracked files under '${INFRASTRUCTURE_DIR2}/'.`);
4831
- if ((0, import_fs6.existsSync)(fullPath)) {
4832
- (0, import_fs6.rmSync)(fullPath, { recursive: true, force: true });
4833
- console.log(` Removed remaining untracked files under '${INFRASTRUCTURE_DIR2}/'.`);
5069
+ const sourceFiles = project.getSourceFiles().filter((sf) => {
5070
+ const filePath = sf.getFilePath();
5071
+ return filePath.includes("/src/") && !filePath.includes("/node_modules/");
5072
+ });
5073
+ let totalUpdated = 0;
5074
+ const modifiedFiles = [];
5075
+ for (const sourceFile of sourceFiles) {
5076
+ const filePath = sourceFile.getFilePath();
5077
+ const updated = processFile14(filePath, project);
5078
+ if (updated > 0) {
5079
+ totalUpdated += updated;
5080
+ modifiedFiles.push(filePath);
5081
+ }
5082
+ }
5083
+ if (modifiedFiles.length === 0) {
5084
+ console.log(
5085
+ `No '${ACTION_PARAMETER_NAME}: ${OLD_TYPE}' parameter in ${TARGET_FUNCTION_NAME} found. Nothing to do.`
5086
+ );
5087
+ return;
5088
+ }
5089
+ console.log(`
5090
+ Saving ${modifiedFiles.length} modified file(s)...`);
5091
+ for (const filePath of modifiedFiles) {
5092
+ const sourceFile = project.getSourceFileOrThrow(filePath);
5093
+ await sourceFile.save();
5094
+ console.log(` Saved: ${import_path26.default.relative(process.cwd(), filePath)}`);
4834
5095
  }
4835
5096
  console.log(
4836
5097
  `
4837
- Done. Review the staged deletion with: git diff --cached -- '${INFRASTRUCTURE_DIR2}/'`
5098
+ Done. Updated ${totalUpdated} parameter type annotation(s) in ${TARGET_FUNCTION_NAME}.`
4838
5099
  );
4839
5100
  }
4840
5101
 
4841
5102
  // src/migrations/v2.0/index.ts
5103
+ var NON_SWARM_INFRA_SUBDIRS = ["postgres", "metabase"];
4842
5104
  async function runUpgrade(dockerSwarm) {
4843
5105
  await main18();
4844
5106
  await main();
@@ -4863,11 +5125,13 @@ async function runUpgrade(dockerSwarm) {
4863
5125
  await main22();
4864
5126
  await main23();
4865
5127
  await main24();
5128
+ await main27();
4866
5129
  await main17();
4867
5130
  if (dockerSwarm) {
4868
5131
  await main25();
4869
5132
  } else {
4870
- await main26();
5133
+ await main26({ keep: NON_SWARM_INFRA_SUBDIRS });
5134
+ await main25({ subdirs: NON_SWARM_INFRA_SUBDIRS });
4871
5135
  }
4872
5136
  }
4873
5137
 
@@ -4896,7 +5160,7 @@ Options:
4896
5160
  'infrastructure/' directory is deleted (default).
4897
5161
  -h, --help Show this message.
4898
5162
  `.trim();
4899
- function main27() {
5163
+ function main28() {
4900
5164
  const command = args[0];
4901
5165
  if (!command || command === "--help" || command === "-h") {
4902
5166
  console.log(USAGE);
@@ -4971,4 +5235,4 @@ function handleCheckTranslations() {
4971
5235
  console.warn("This command is not implemented yet!");
4972
5236
  process.exit(1);
4973
5237
  }
4974
- main27();
5238
+ main28();