@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
@@ -1895,9 +1895,9 @@ function updateRouteHandlers(sourceFiles, group, srcDir, relCwd) {
1895
1895
  }
1896
1896
  return replacedCount;
1897
1897
  }
1898
- function resolveConfigChain(configObj, path23) {
1898
+ function resolveConfigChain(configObj, path24) {
1899
1899
  let current = configObj;
1900
- for (const key of path23) {
1900
+ for (const key of path24) {
1901
1901
  if (!import_ts_morph10.Node.isObjectLiteralExpression(current)) return null;
1902
1902
  const prop = current.getProperty(key);
1903
1903
  if (!prop || !import_ts_morph10.Node.isPropertyAssignment(prop)) return null;
@@ -2132,6 +2132,10 @@ Done. Converted ${convertedCount} config file(s), replaced ${totalReplaced} rout
2132
2132
  var import_ts_morph11 = require("ts-morph");
2133
2133
  var import_path11 = __toESM(require("path"));
2134
2134
 
2135
+ // ../commons/src/scopes.deprecated.do-not-use.ts
2136
+ var z4 = __toESM(require("zod/v4"));
2137
+ var import_lodash = require("lodash");
2138
+
2135
2139
  // ../commons/src/scopes.ts
2136
2140
  var z3 = __toESM(require("zod/v4"));
2137
2141
  var qs = __toESM(require("qs"));
@@ -2401,11 +2405,12 @@ var decodeScope = (query) => {
2401
2405
  };
2402
2406
  var DEFAULT_SCOPE_OPTIONS = {
2403
2407
  placeOfEvent: JurisdictionFilter.enum.all,
2404
- accessLevel: JurisdictionFilter.enum.all
2408
+ accessLevel: JurisdictionFilter.enum.all,
2409
+ registeredIn: JurisdictionFilter.enum.all,
2410
+ declaredIn: JurisdictionFilter.enum.all
2405
2411
  };
2406
2412
 
2407
2413
  // ../commons/src/scopes.deprecated.do-not-use.ts
2408
- var z4 = __toESM(require("zod/v4"));
2409
2414
  var rawConfigurableScopeRegex = /^([a-zA-Z][a-zA-Z0-9.-]*(?:\.[a-zA-Z0-9.-]+)*)\[((?:\w+=[\w.-]+(?:\|[\w.-]+)*)(?:,[\w]+=[\w.-]+(?:\|[\w.-]+)*)*)\]$/;
2410
2415
  var rawConfigurableScope = z4.string().regex(rawConfigurableScopeRegex);
2411
2416
  var SearchScope = z4.object({
@@ -2639,6 +2644,74 @@ var legacyScopeToV2Scope = (v1Scope) => {
2639
2644
  }
2640
2645
  throw new Error(`Unsupported V1 scope type: ${v1Scope}`);
2641
2646
  };
2647
+ var MERGEABLE_USER_SCOPE_TYPES = ["user.create", "user.edit"];
2648
+ function isMergeableUserScopeType(type) {
2649
+ return MERGEABLE_USER_SCOPE_TYPES.includes(type);
2650
+ }
2651
+ function decodeLegacyScope(legacy) {
2652
+ const encoded = legacyScopeToV2Scope(legacy);
2653
+ const scope = decodeScope(encoded);
2654
+ if (!scope) {
2655
+ throw new Error(
2656
+ `Could not decode migrated scope from legacy '${legacy}' (encoded: '${encoded}').`
2657
+ );
2658
+ }
2659
+ return { legacy, scope };
2660
+ }
2661
+ function groupByType(entries) {
2662
+ return entries.reduce(
2663
+ (groups, entry) => ({
2664
+ ...groups,
2665
+ [entry.scope.type]: [...groups[entry.scope.type] ?? [], entry]
2666
+ }),
2667
+ {}
2668
+ );
2669
+ }
2670
+ function deduplicateEntries(entries) {
2671
+ return entries.filter(
2672
+ (entry, index) => entries.findIndex((e) => (0, import_lodash.isEqual)(e.scope, entry.scope)) === index
2673
+ );
2674
+ }
2675
+ function mergeEntries(type, entries) {
2676
+ const optionsList = entries.map(
2677
+ (e) => "options" in e.scope && e.scope.options ? e.scope.options : {}
2678
+ );
2679
+ const allKeys = optionsList.flatMap(
2680
+ (opts) => Object.keys(opts).filter((k) => opts[k] !== void 0)
2681
+ );
2682
+ const conflictKey = allKeys.find((key, i) => allKeys.indexOf(key) !== i);
2683
+ if (conflictKey) {
2684
+ const conflicting = entries.filter(
2685
+ (e) => conflictKey in ("options" in e.scope && e.scope.options || {})
2686
+ ).map((e) => JSON.stringify(e.legacy));
2687
+ throw new Error(
2688
+ `Cannot auto-merge two '${type}' scopes that both define '${conflictKey}'. Conflicting legacy scopes: [${conflicting.join(", ")}]. Resolve manually in roles.ts and re-run.`
2689
+ );
2690
+ }
2691
+ const mergedOptions = Object.assign({}, ...optionsList);
2692
+ const merged = Object.keys(mergedOptions).length > 0 ? { type, options: mergedOptions } : { type };
2693
+ const parsed = Scope2.safeParse(merged);
2694
+ if (!parsed.success) {
2695
+ throw new Error(
2696
+ `Merged '${type}' scope failed schema validation: ${parsed.error.message}. Legacy scopes: [${entries.map((e) => JSON.stringify(e.legacy)).join(", ")}].`
2697
+ );
2698
+ }
2699
+ return parsed.data;
2700
+ }
2701
+ function migrateLegacyScopesArrayToV2Scopes(legacyScopes) {
2702
+ const groups = groupByType(legacyScopes.map(decodeLegacyScope));
2703
+ return Object.entries(groups).flatMap(([type, entries]) => {
2704
+ const deduped = deduplicateEntries(entries);
2705
+ if (deduped.length <= 1) return deduped.map((e) => e.scope);
2706
+ if (!isMergeableUserScopeType(type)) {
2707
+ console.warn(
2708
+ `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.`
2709
+ );
2710
+ return deduped.map((e) => e.scope);
2711
+ }
2712
+ return [mergeEntries(type, deduped)];
2713
+ });
2714
+ }
2642
2715
 
2643
2716
  // src/migrations/v2.0/migrate-scopes.ts
2644
2717
  var ROLES_FILE_RELATIVE_PATH = "src/data-seeding/roles/roles.ts";
@@ -2676,57 +2749,65 @@ function resolveLegacyScopeFromElement(element) {
2676
2749
  }
2677
2750
  function migrateScopesArrays(rolesFilePath, project) {
2678
2751
  const sourceFile = project.getSourceFile(rolesFilePath);
2679
- if (!sourceFile) return { migratedCount: 0, removedCount: 0 };
2752
+ if (!sourceFile) {
2753
+ return { migratedCount: 0, removedCount: 0, mergedCount: 0 };
2754
+ }
2680
2755
  let migratedCount = 0;
2681
2756
  let removedCount = 0;
2757
+ let mergedCount = 0;
2682
2758
  const scopesProperties = sourceFile.getDescendantsOfKind(import_ts_morph11.SyntaxKind.PropertyAssignment).filter((prop) => prop.getName() === SCOPES_PROPERTY_NAME);
2759
+ const relPath = import_path11.default.relative(process.cwd(), rolesFilePath);
2683
2760
  for (const scopesProp of scopesProperties) {
2684
2761
  const initializer = scopesProp.getInitializer();
2685
2762
  if (!initializer || !import_ts_morph11.Node.isArrayLiteralExpression(initializer)) continue;
2686
- const elements = initializer.getElements();
2687
- for (let index = elements.length - 1; index >= 0; index--) {
2688
- const element = elements[index];
2689
- if (!import_ts_morph11.Node.isExpression(element)) continue;
2763
+ const rawTexts = [];
2764
+ const legacyStrings = [];
2765
+ let anyChange = false;
2766
+ for (const element of initializer.getElements()) {
2690
2767
  const scopeKey = resolveScopeKeyFromElementAccess(element);
2691
2768
  if (scopeKey && !(scopeKey in SCOPES)) {
2692
2769
  console.warn(
2693
- ` [${import_path11.default.relative(process.cwd(), rolesFilePath)}] Removing unknown scope reference: SCOPES.${scopeKey}`
2770
+ ` [${relPath}] Removing unknown scope reference: SCOPES.${scopeKey}`
2694
2771
  );
2695
- initializer.removeElement(index);
2696
2772
  removedCount++;
2773
+ anyChange = true;
2697
2774
  continue;
2698
2775
  }
2699
2776
  const legacyScope = resolveLegacyScopeFromElement(element);
2700
2777
  if (!legacyScope) {
2701
2778
  console.warn(
2702
- ` [${import_path11.default.relative(process.cwd(), rolesFilePath)}] Skipping non-literal scope expression: ${element.getText()}`
2779
+ ` [${relPath}] Preserving non-literal scope expression verbatim: ${element.getText()}`
2703
2780
  );
2781
+ rawTexts.push(element.getText());
2704
2782
  continue;
2705
2783
  }
2706
- try {
2707
- const migratedScope = legacyScopeToV2Scope(legacyScope);
2708
- const scope = decodeScope(migratedScope);
2709
- if (!scope) {
2710
- console.warn(
2711
- ` [${import_path11.default.relative(process.cwd(), rolesFilePath)}] Could not decode migrated scope '${migratedScope}'.`
2712
- );
2713
- continue;
2714
- }
2715
- element.replaceWithText(JSON.stringify(scope));
2716
- migratedCount++;
2717
- } catch (error) {
2718
- console.warn(
2719
- ` [${import_path11.default.relative(process.cwd(), rolesFilePath)}] Could not migrate scope '${legacyScope}': ${error}`
2720
- );
2721
- }
2784
+ legacyStrings.push(legacyScope);
2785
+ anyChange = true;
2786
+ }
2787
+ if (!anyChange) continue;
2788
+ let mergedScopes;
2789
+ try {
2790
+ mergedScopes = migrateLegacyScopesArrayToV2Scopes(legacyStrings);
2791
+ } catch (error) {
2792
+ throw new Error(
2793
+ `[${relPath}] Failed to migrate scopes for role: ${error instanceof Error ? error.message : String(error)}`,
2794
+ { cause: error }
2795
+ );
2722
2796
  }
2797
+ migratedCount += legacyStrings.length;
2798
+ mergedCount += legacyStrings.length - mergedScopes.length;
2799
+ const outputElements = [
2800
+ ...rawTexts,
2801
+ ...mergedScopes.map((s) => JSON.stringify(s))
2802
+ ];
2803
+ scopesProp.setInitializer(`[${outputElements.join(", ")}]`);
2723
2804
  }
2724
2805
  if (removedCount > 0) {
2725
2806
  console.log(
2726
- ` Removed ${removedCount} unknown SCOPES reference(s) from '${import_path11.default.relative(process.cwd(), rolesFilePath)}'.`
2807
+ ` Removed ${removedCount} unknown SCOPES reference(s) from '${relPath}'.`
2727
2808
  );
2728
2809
  }
2729
- return { migratedCount, removedCount };
2810
+ return { migratedCount, removedCount, mergedCount };
2730
2811
  }
2731
2812
  function ensureScopesHelpersImport(rolesFilePath, project) {
2732
2813
  const sourceFile = project.getSourceFile(rolesFilePath);
@@ -2861,7 +2942,7 @@ async function main11() {
2861
2942
  return;
2862
2943
  }
2863
2944
  const importChanged = ensureScopesHelpersImport(rolesFilePath, project);
2864
- const { migratedCount, removedCount } = migrateScopesArrays(
2945
+ const { migratedCount, removedCount, mergedCount } = migrateScopesArrays(
2865
2946
  rolesFilePath,
2866
2947
  project
2867
2948
  );
@@ -2880,7 +2961,7 @@ async function main11() {
2880
2961
  }
2881
2962
  await sourceFile.save();
2882
2963
  console.log(
2883
- `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." : "."}`
2964
+ `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." : "."}`
2884
2965
  );
2885
2966
  }
2886
2967
 
@@ -3686,6 +3767,10 @@ var ACTION_PROPERTY_NAME2 = "action";
3686
3767
  var CONDITIONALS_PROPERTY_NAME = "conditionals";
3687
3768
  var TYPE_PROPERTY_NAME7 = "type";
3688
3769
  var SLUG_PROPERTY_NAME = "slug";
3770
+ var READ_ACTION_TYPE = "READ";
3771
+ var ACTION_TYPE_IMPORT_NAME = "ActionType";
3772
+ var TOOLKIT_EVENTS_MODULE2 = "@opencrvs/toolkit/events";
3773
+ var READ_ACTION_FALLBACK_INITIALIZER = `{ type: ${ACTION_TYPE_IMPORT_NAME}.${READ_ACTION_TYPE} }`;
3689
3774
  var SUPPORTED_WORKQUEUE_ACTION_TYPES = /* @__PURE__ */ new Set([
3690
3775
  "READ",
3691
3776
  "DELETE",
@@ -3733,91 +3818,137 @@ function isUnsupportedActionType(action) {
3733
3818
  }
3734
3819
  return !SUPPORTED_WORKQUEUE_ACTION_TYPES.has(typeName);
3735
3820
  }
3736
- function migrateWorkqueueActions(workqueue, relPath) {
3737
- const actionsProperty = workqueue.getProperty(ACTIONS_PROPERTY_NAME5);
3738
- if (!actionsProperty || !import_ts_morph17.Node.isPropertyAssignment(actionsProperty)) {
3739
- return 0;
3740
- }
3741
- const actionsInitializer = actionsProperty.getInitializer();
3742
- if (!actionsInitializer || !import_ts_morph17.Node.isArrayLiteralExpression(actionsInitializer)) {
3743
- return 0;
3821
+ function ensureValidAction(workqueue, relPath) {
3822
+ const workqueueLabel = getWorkqueueLabel(workqueue);
3823
+ const actionProperty = workqueue.getProperty(ACTION_PROPERTY_NAME2);
3824
+ if (actionProperty && import_ts_morph17.Node.isPropertyAssignment(actionProperty)) {
3825
+ const initializer = actionProperty.getInitializer();
3826
+ if (initializer && import_ts_morph17.Node.isObjectLiteralExpression(initializer)) {
3827
+ if (!isUnsupportedActionType(initializer)) {
3828
+ return { changed: false, usedReadFallback: false };
3829
+ }
3830
+ actionProperty.set({
3831
+ name: ACTION_PROPERTY_NAME2,
3832
+ initializer: READ_ACTION_FALLBACK_INITIALIZER
3833
+ });
3834
+ console.log(
3835
+ ` [${relPath}] Replaced unsupported 'action' type with '${ACTION_TYPE_IMPORT_NAME}.${READ_ACTION_TYPE}' on workqueue '${workqueueLabel}'`
3836
+ );
3837
+ return { changed: true, usedReadFallback: true };
3838
+ }
3744
3839
  }
3840
+ workqueue.addPropertyAssignment({
3841
+ name: ACTION_PROPERTY_NAME2,
3842
+ initializer: READ_ACTION_FALLBACK_INITIALIZER
3843
+ });
3844
+ console.log(
3845
+ ` [${relPath}] Added default 'action: ${READ_ACTION_FALLBACK_INITIALIZER}' to workqueue '${workqueueLabel}'`
3846
+ );
3847
+ return { changed: true, usedReadFallback: true };
3848
+ }
3849
+ function migrateWorkqueueActions(workqueue, relPath) {
3745
3850
  const workqueueLabel = getWorkqueueLabel(workqueue);
3746
- const existingActionProperty = workqueue.getProperty(ACTION_PROPERTY_NAME2);
3747
- if (existingActionProperty) {
3748
- if (import_ts_morph17.Node.isPropertyAssignment(existingActionProperty)) {
3749
- const existingActionInitializer = existingActionProperty.getInitializer();
3750
- if (existingActionInitializer && import_ts_morph17.Node.isObjectLiteralExpression(existingActionInitializer)) {
3751
- const conditionalsProperty2 = existingActionInitializer.getProperty(
3752
- CONDITIONALS_PROPERTY_NAME
3851
+ const actionsProperty = workqueue.getProperty(ACTIONS_PROPERTY_NAME5);
3852
+ let actionsArrayMigrated = false;
3853
+ if (actionsProperty && import_ts_morph17.Node.isPropertyAssignment(actionsProperty)) {
3854
+ const actionsInitializer = actionsProperty.getInitializer();
3855
+ if (actionsInitializer && import_ts_morph17.Node.isArrayLiteralExpression(actionsInitializer)) {
3856
+ const existingActionProperty = workqueue.getProperty(ACTION_PROPERTY_NAME2);
3857
+ if (existingActionProperty) {
3858
+ if (import_ts_morph17.Node.isPropertyAssignment(existingActionProperty)) {
3859
+ const existingActionInitializer = existingActionProperty.getInitializer();
3860
+ if (existingActionInitializer && import_ts_morph17.Node.isObjectLiteralExpression(existingActionInitializer)) {
3861
+ const conditionalsProperty = existingActionInitializer.getProperty(
3862
+ CONDITIONALS_PROPERTY_NAME
3863
+ );
3864
+ if (conditionalsProperty) {
3865
+ conditionalsProperty.remove();
3866
+ console.log(
3867
+ ` [${relPath}] Removed deprecated 'conditionals' from 'action' on workqueue '${workqueueLabel}'`
3868
+ );
3869
+ }
3870
+ }
3871
+ }
3872
+ actionsProperty.remove();
3873
+ console.log(
3874
+ ` [${relPath}] Removed deprecated 'actions' from workqueue '${workqueueLabel}' (existing 'action' kept)`
3875
+ );
3876
+ actionsArrayMigrated = true;
3877
+ } else {
3878
+ const actionCandidates = actionsInitializer.getElements().filter(
3879
+ (element) => import_ts_morph17.Node.isObjectLiteralExpression(element)
3753
3880
  );
3754
- if (conditionalsProperty2) {
3755
- conditionalsProperty2.remove();
3881
+ if (actionCandidates.length === 0) {
3882
+ actionsProperty.remove();
3756
3883
  console.log(
3757
- ` [${relPath}] Removed deprecated 'conditionals' from 'action' on workqueue '${workqueueLabel}'`
3884
+ ` [${relPath}] Removed empty 'actions' from workqueue '${workqueueLabel}'`
3758
3885
  );
3759
- }
3760
- if (isUnsupportedActionType(existingActionInitializer)) {
3761
- existingActionProperty.remove();
3762
- console.log(
3763
- ` [${relPath}] Removed unsupported 'action' type on workqueue '${workqueueLabel}'`
3886
+ actionsArrayMigrated = true;
3887
+ } else {
3888
+ if (actionCandidates.length > 1) {
3889
+ const droppedCount = actionCandidates.length - 1;
3890
+ console.warn(
3891
+ ` [${relPath}] Workqueue '${workqueueLabel}' has ${actionCandidates.length} actions; keeping first as 'action' and dropping ${droppedCount} extra action(s)`
3892
+ );
3893
+ }
3894
+ const conditionalsProperty = actionCandidates[0].getProperty(
3895
+ CONDITIONALS_PROPERTY_NAME
3764
3896
  );
3897
+ if (conditionalsProperty) {
3898
+ conditionalsProperty.remove();
3899
+ console.log(
3900
+ ` [${relPath}] Removed deprecated 'conditionals' from migrated 'action' on workqueue '${workqueueLabel}'`
3901
+ );
3902
+ }
3903
+ if (isUnsupportedActionType(actionCandidates[0])) {
3904
+ actionsProperty.remove();
3905
+ console.log(
3906
+ ` [${relPath}] Removed deprecated 'actions' from workqueue '${workqueueLabel}' because action type is unsupported`
3907
+ );
3908
+ actionsArrayMigrated = true;
3909
+ } else {
3910
+ const firstActionText = actionCandidates[0].getText();
3911
+ workqueue.addPropertyAssignment({
3912
+ name: ACTION_PROPERTY_NAME2,
3913
+ initializer: firstActionText
3914
+ });
3915
+ actionsProperty.remove();
3916
+ console.log(
3917
+ ` [${relPath}] Replaced 'actions' with 'action' on workqueue '${workqueueLabel}'`
3918
+ );
3919
+ actionsArrayMigrated = true;
3920
+ }
3765
3921
  }
3766
3922
  }
3767
3923
  }
3768
- actionsProperty.remove();
3769
- console.log(
3770
- ` [${relPath}] Removed deprecated 'actions' from workqueue '${workqueueLabel}' (existing 'action' kept)`
3771
- );
3772
- return 1;
3773
- }
3774
- const actionCandidates = actionsInitializer.getElements().filter(
3775
- (element) => import_ts_morph17.Node.isObjectLiteralExpression(element)
3776
- );
3777
- if (actionCandidates.length === 0) {
3778
- actionsProperty.remove();
3779
- console.log(
3780
- ` [${relPath}] Removed empty 'actions' from workqueue '${workqueueLabel}'`
3781
- );
3782
- return 1;
3783
3924
  }
3784
- if (actionCandidates.length > 1) {
3785
- const droppedCount = actionCandidates.length - 1;
3786
- console.warn(
3787
- ` [${relPath}] Workqueue '${workqueueLabel}' has ${actionCandidates.length} actions; keeping first as 'action' and dropping ${droppedCount} extra action(s)`
3788
- );
3789
- }
3790
- const conditionalsProperty = actionCandidates[0].getProperty(
3791
- CONDITIONALS_PROPERTY_NAME
3925
+ const fallback = ensureValidAction(workqueue, relPath);
3926
+ return {
3927
+ changed: actionsArrayMigrated || fallback.changed,
3928
+ usedReadFallback: fallback.usedReadFallback
3929
+ };
3930
+ }
3931
+ function ensureActionTypeImport(sourceFile) {
3932
+ const existingImport = sourceFile.getImportDeclaration(
3933
+ (decl) => decl.getModuleSpecifierValue() === TOOLKIT_EVENTS_MODULE2
3792
3934
  );
3793
- if (conditionalsProperty) {
3794
- conditionalsProperty.remove();
3795
- console.log(
3796
- ` [${relPath}] Removed deprecated 'conditionals' from migrated 'action' on workqueue '${workqueueLabel}'`
3797
- );
3798
- }
3799
- if (isUnsupportedActionType(actionCandidates[0])) {
3800
- actionsProperty.remove();
3801
- console.log(
3802
- ` [${relPath}] Removed deprecated 'actions' from workqueue '${workqueueLabel}' because action type is unsupported`
3803
- );
3804
- return 1;
3935
+ if (existingImport) {
3936
+ const alreadyImported = existingImport.getNamedImports().some((ni) => ni.getName() === ACTION_TYPE_IMPORT_NAME);
3937
+ if (!alreadyImported) {
3938
+ existingImport.addNamedImport(ACTION_TYPE_IMPORT_NAME);
3939
+ }
3940
+ return;
3805
3941
  }
3806
- const firstActionText = actionCandidates[0].getText();
3807
- workqueue.addPropertyAssignment({
3808
- name: ACTION_PROPERTY_NAME2,
3809
- initializer: firstActionText
3942
+ sourceFile.addImportDeclaration({
3943
+ moduleSpecifier: TOOLKIT_EVENTS_MODULE2,
3944
+ namedImports: [ACTION_TYPE_IMPORT_NAME]
3810
3945
  });
3811
- actionsProperty.remove();
3812
- console.log(
3813
- ` [${relPath}] Replaced 'actions' with 'action' on workqueue '${workqueueLabel}'`
3814
- );
3815
- return 1;
3816
3946
  }
3817
3947
  function processFile13(filePath, project) {
3818
3948
  const sourceFile = project.getSourceFile(filePath);
3819
3949
  if (!sourceFile) return 0;
3820
3950
  let migratedWorkqueues = 0;
3951
+ let needsActionTypeImport = false;
3821
3952
  const relPath = import_path18.default.relative(process.cwd(), filePath);
3822
3953
  const callExpressions = sourceFile.getDescendantsOfKind(
3823
3954
  import_ts_morph17.SyntaxKind.CallExpression
@@ -3833,9 +3964,14 @@ function processFile13(filePath, project) {
3833
3964
  if (!import_ts_morph17.Node.isArrayLiteralExpression(workqueuesArg)) continue;
3834
3965
  for (const element of workqueuesArg.getElements()) {
3835
3966
  if (!import_ts_morph17.Node.isObjectLiteralExpression(element)) continue;
3836
- migratedWorkqueues += migrateWorkqueueActions(element, relPath);
3967
+ const result = migrateWorkqueueActions(element, relPath);
3968
+ if (result.changed) migratedWorkqueues++;
3969
+ if (result.usedReadFallback) needsActionTypeImport = true;
3837
3970
  }
3838
3971
  }
3972
+ if (needsActionTypeImport) {
3973
+ ensureActionTypeImport(sourceFile);
3974
+ }
3839
3975
  return migratedWorkqueues;
3840
3976
  }
3841
3977
  async function main18() {
@@ -4170,7 +4306,7 @@ Saved ${handlerRelPath}.`);
4170
4306
  // src/migrations/v2.0/checkout-upstream-files.ts
4171
4307
  var import_child_process = require("child_process");
4172
4308
  var UPSTREAM_URL = "https://github.com/opencrvs/opencrvs-countryconfig.git";
4173
- var UPSTREAM_BRANCH = "develop";
4309
+ var UPSTREAM_BRANCH = "release-v2.0.0";
4174
4310
  var TEMP_REMOTE = "opencrvs-upgrade-v19-v20-codemod";
4175
4311
  var FILES_TO_CHECKOUT = [
4176
4312
  "src/analytics/analytics.ts",
@@ -4709,9 +4845,9 @@ var import_path24 = require("path");
4709
4845
  var TEMP_REMOTE2 = "opencrvs-upgrade-v19-v20-codemod-merge-infra";
4710
4846
  var INFRASTRUCTURE_DIR = "infrastructure";
4711
4847
  var BASE_BRANCH = "release-v1.9";
4712
- function gitShowBytes(ref, path23) {
4848
+ function gitShowBytes(ref, path24) {
4713
4849
  try {
4714
- const buf = (0, import_child_process2.execFileSync)("git", ["show", `${ref}:${path23}`], {
4850
+ const buf = (0, import_child_process2.execFileSync)("git", ["show", `${ref}:${path24}`], {
4715
4851
  cwd: process.cwd(),
4716
4852
  stdio: ["ignore", "pipe", "pipe"]
4717
4853
  });
@@ -4720,9 +4856,13 @@ function gitShowBytes(ref, path23) {
4720
4856
  return null;
4721
4857
  }
4722
4858
  }
4723
- async function main25() {
4859
+ async function main25({
4860
+ subdirs = []
4861
+ } = {}) {
4862
+ const pathspecs = subdirs.length > 0 ? subdirs.map((s) => `${INFRASTRUCTURE_DIR}/${s}/`) : [`${INFRASTRUCTURE_DIR}/`];
4863
+ const scopeLabel = pathspecs.map((p) => `'${p}'`).join(", ");
4724
4864
  console.log(
4725
- `Merging '${INFRASTRUCTURE_DIR}/' from ${UPSTREAM_URL}@${UPSTREAM_BRANCH} (base: ${BASE_BRANCH})...
4865
+ `Merging ${scopeLabel} from ${UPSTREAM_URL}@${UPSTREAM_BRANCH} (base: ${BASE_BRANCH})...
4726
4866
  `
4727
4867
  );
4728
4868
  assertIsGitRepo();
@@ -4737,20 +4877,12 @@ async function main25() {
4737
4877
  const ref = `${TEMP_REMOTE2}/${UPSTREAM_BRANCH}`;
4738
4878
  const baseRef = `${TEMP_REMOTE2}/${BASE_BRANCH}`;
4739
4879
  const upstreamFiles = runGit(
4740
- [
4741
- "ls-tree",
4742
- "-r",
4743
- "--name-only",
4744
- "-z",
4745
- ref,
4746
- "--",
4747
- `${INFRASTRUCTURE_DIR}/`
4748
- ],
4880
+ ["ls-tree", "-r", "--name-only", "-z", ref, "--", ...pathspecs],
4749
4881
  { silent: true }
4750
4882
  ).split("\0").filter(Boolean);
4751
4883
  if (upstreamFiles.length === 0) {
4752
4884
  console.log(
4753
- ` No files found under '${INFRASTRUCTURE_DIR}/' on ${ref}. Nothing to merge.`
4885
+ ` No files found under ${scopeLabel} on ${ref}. Nothing to merge.`
4754
4886
  );
4755
4887
  return;
4756
4888
  }
@@ -4825,7 +4957,9 @@ async function main25() {
4825
4957
  var import_fs6 = require("fs");
4826
4958
  var import_path25 = require("path");
4827
4959
  var INFRASTRUCTURE_DIR2 = "infrastructure";
4828
- async function main26() {
4960
+ async function main26({
4961
+ keep = []
4962
+ } = {}) {
4829
4963
  const fullPath = (0, import_path25.join)(process.cwd(), INFRASTRUCTURE_DIR2);
4830
4964
  if (!(0, import_fs6.existsSync)(fullPath)) {
4831
4965
  console.log(
@@ -4833,24 +4967,152 @@ async function main26() {
4833
4967
  );
4834
4968
  return;
4835
4969
  }
4836
- console.log(`Deleting '${INFRASTRUCTURE_DIR2}/'...
4837
- `);
4838
4970
  assertIsGitRepo();
4839
- runGit(["rm", "-rf", "--ignore-unmatch", "--", `${INFRASTRUCTURE_DIR2}/`], {
4840
- silent: true
4971
+ if (keep.length === 0) {
4972
+ console.log(`Deleting '${INFRASTRUCTURE_DIR2}/'...
4973
+ `);
4974
+ runGit(["rm", "-rf", "--ignore-unmatch", "--", `${INFRASTRUCTURE_DIR2}/`], {
4975
+ silent: true
4976
+ });
4977
+ console.log(
4978
+ ` Staged deletion of tracked files under '${INFRASTRUCTURE_DIR2}/'.`
4979
+ );
4980
+ if ((0, import_fs6.existsSync)(fullPath)) {
4981
+ (0, import_fs6.rmSync)(fullPath, { recursive: true, force: true });
4982
+ console.log(
4983
+ ` Removed remaining untracked files under '${INFRASTRUCTURE_DIR2}/'.`
4984
+ );
4985
+ }
4986
+ console.log(
4987
+ `
4988
+ Done. Review the staged deletion with: git diff --cached -- '${INFRASTRUCTURE_DIR2}/'`
4989
+ );
4990
+ return;
4991
+ }
4992
+ const keepSet = new Set(keep);
4993
+ console.log(
4994
+ `Deleting contents of '${INFRASTRUCTURE_DIR2}/' except: ${[...keepSet].map((name) => `'${name}'`).join(", ")}
4995
+ `
4996
+ );
4997
+ const topLevelEntries = (0, import_fs6.readdirSync)(fullPath, { withFileTypes: true });
4998
+ let deleted = 0;
4999
+ for (const entry of topLevelEntries) {
5000
+ if (keepSet.has(entry.name)) {
5001
+ console.log(` Keeping '${INFRASTRUCTURE_DIR2}/${entry.name}'.`);
5002
+ continue;
5003
+ }
5004
+ const relPath = `${INFRASTRUCTURE_DIR2}/${entry.name}${entry.isDirectory() ? "/" : ""}`;
5005
+ runGit(["rm", "-rf", "--ignore-unmatch", "--", relPath], { silent: true });
5006
+ const fullEntryPath = (0, import_path25.join)(fullPath, entry.name);
5007
+ if ((0, import_fs6.existsSync)(fullEntryPath)) {
5008
+ (0, import_fs6.rmSync)(fullEntryPath, { recursive: true, force: true });
5009
+ }
5010
+ console.log(` Deleted '${relPath}'.`);
5011
+ deleted++;
5012
+ }
5013
+ const presentNames = new Set(topLevelEntries.map((e) => e.name));
5014
+ const missingKeeps = [...keepSet].filter((name) => !presentNames.has(name));
5015
+ if (missingKeeps.length > 0) {
5016
+ console.warn(
5017
+ ` [warn] Requested to keep ${missingKeeps.map((n) => `'${n}'`).join(", ")} but they don't exist under '${INFRASTRUCTURE_DIR2}/'.`
5018
+ );
5019
+ }
5020
+ console.log(
5021
+ `
5022
+ Done. Deleted ${deleted} top-level entr${deleted === 1 ? "y" : "ies"}. Review the staged deletions with: git diff --cached -- '${INFRASTRUCTURE_DIR2}/'`
5023
+ );
5024
+ }
5025
+
5026
+ // src/migrations/v2.0/fix-accept-requested-registration-function-type.ts
5027
+ var import_ts_morph22 = require("ts-morph");
5028
+ var import_path26 = __toESM(require("path"));
5029
+ var TARGET_FUNCTION_NAME = "acceptRequestedRegistration";
5030
+ var ACTION_PARAMETER_NAME = "action";
5031
+ var OLD_TYPE = "ActionInput";
5032
+ var NEW_TYPE = "Extract<ActionInput, { type?: 'REGISTER' }>";
5033
+ function isAcceptRequestedRegistrationParameter(parameter) {
5034
+ const functionDeclaration = parameter.getFirstAncestorByKind(
5035
+ import_ts_morph22.SyntaxKind.FunctionDeclaration
5036
+ );
5037
+ if (functionDeclaration?.getName() === TARGET_FUNCTION_NAME) {
5038
+ return true;
5039
+ }
5040
+ const arrowFunction = parameter.getFirstAncestorByKind(
5041
+ import_ts_morph22.SyntaxKind.ArrowFunction
5042
+ );
5043
+ const variableDeclaration = arrowFunction?.getFirstAncestorByKind(
5044
+ import_ts_morph22.SyntaxKind.VariableDeclaration
5045
+ );
5046
+ return variableDeclaration?.getName() === TARGET_FUNCTION_NAME;
5047
+ }
5048
+ function processFile14(filePath, project) {
5049
+ const sourceFile = project.getSourceFile(filePath);
5050
+ if (!sourceFile) return 0;
5051
+ let updatedCount = 0;
5052
+ for (const parameter of sourceFile.getDescendantsOfKind(
5053
+ import_ts_morph22.SyntaxKind.Parameter
5054
+ )) {
5055
+ if (parameter.getName() !== ACTION_PARAMETER_NAME) {
5056
+ continue;
5057
+ }
5058
+ if (!isAcceptRequestedRegistrationParameter(parameter)) {
5059
+ continue;
5060
+ }
5061
+ const typeNode = parameter.getTypeNode();
5062
+ if (!typeNode || typeNode.getText() !== OLD_TYPE) {
5063
+ continue;
5064
+ }
5065
+ parameter.setType(NEW_TYPE);
5066
+ updatedCount++;
5067
+ console.log(
5068
+ ` [${import_path26.default.relative(process.cwd(), filePath)}] Updated '${ACTION_PARAMETER_NAME}' in ${TARGET_FUNCTION_NAME} to ${NEW_TYPE}`
5069
+ );
5070
+ }
5071
+ return updatedCount;
5072
+ }
5073
+ async function main27() {
5074
+ const srcDir = import_path26.default.join(process.cwd(), "src");
5075
+ console.log(`Scanning for ${TARGET_FUNCTION_NAME} in: ${srcDir}
5076
+ `);
5077
+ const project = new import_ts_morph22.Project({
5078
+ tsConfigFilePath: import_path26.default.resolve(srcDir, "../tsconfig.json"),
5079
+ skipAddingFilesFromTsConfig: false
4841
5080
  });
4842
- console.log(` Staged deletion of tracked files under '${INFRASTRUCTURE_DIR2}/'.`);
4843
- if ((0, import_fs6.existsSync)(fullPath)) {
4844
- (0, import_fs6.rmSync)(fullPath, { recursive: true, force: true });
4845
- console.log(` Removed remaining untracked files under '${INFRASTRUCTURE_DIR2}/'.`);
5081
+ const sourceFiles = project.getSourceFiles().filter((sf) => {
5082
+ const filePath = sf.getFilePath();
5083
+ return filePath.includes("/src/") && !filePath.includes("/node_modules/");
5084
+ });
5085
+ let totalUpdated = 0;
5086
+ const modifiedFiles = [];
5087
+ for (const sourceFile of sourceFiles) {
5088
+ const filePath = sourceFile.getFilePath();
5089
+ const updated = processFile14(filePath, project);
5090
+ if (updated > 0) {
5091
+ totalUpdated += updated;
5092
+ modifiedFiles.push(filePath);
5093
+ }
5094
+ }
5095
+ if (modifiedFiles.length === 0) {
5096
+ console.log(
5097
+ `No '${ACTION_PARAMETER_NAME}: ${OLD_TYPE}' parameter in ${TARGET_FUNCTION_NAME} found. Nothing to do.`
5098
+ );
5099
+ return;
5100
+ }
5101
+ console.log(`
5102
+ Saving ${modifiedFiles.length} modified file(s)...`);
5103
+ for (const filePath of modifiedFiles) {
5104
+ const sourceFile = project.getSourceFileOrThrow(filePath);
5105
+ await sourceFile.save();
5106
+ console.log(` Saved: ${import_path26.default.relative(process.cwd(), filePath)}`);
4846
5107
  }
4847
5108
  console.log(
4848
5109
  `
4849
- Done. Review the staged deletion with: git diff --cached -- '${INFRASTRUCTURE_DIR2}/'`
5110
+ Done. Updated ${totalUpdated} parameter type annotation(s) in ${TARGET_FUNCTION_NAME}.`
4850
5111
  );
4851
5112
  }
4852
5113
 
4853
5114
  // src/migrations/v2.0/index.ts
5115
+ var NON_SWARM_INFRA_SUBDIRS = ["postgres", "metabase"];
4854
5116
  async function runUpgrade(dockerSwarm) {
4855
5117
  await main18();
4856
5118
  await main();
@@ -4875,10 +5137,12 @@ async function runUpgrade(dockerSwarm) {
4875
5137
  await main22();
4876
5138
  await main23();
4877
5139
  await main24();
5140
+ await main27();
4878
5141
  await main17();
4879
5142
  if (dockerSwarm) {
4880
5143
  await main25();
4881
5144
  } else {
4882
- await main26();
5145
+ await main26({ keep: NON_SWARM_INFRA_SUBDIRS });
5146
+ await main25({ subdirs: NON_SWARM_INFRA_SUBDIRS });
4883
5147
  }
4884
5148
  }