@izara_project/izara-core-generate-service-code 1.0.72 → 1.0.74
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/package.json +3 -2
- package/src/emit/coreFunction/emitCoreFunctionArtifacts.js +3 -0
- package/src/emit/coreFunction/templates/InitialSetupConfig.ejs +100 -28
- package/src/emit/coreFunction/templates/InitialSetupRunner.ejs +4 -2
- package/src/emit/coreFunction/templates/InitialSetupRunner_uploadSchema.ejs +2 -0
- package/src/emit/coreFunction/templates/UploadSchema_Main.ejs +2 -0
- package/src/emit/custom/customFlows.js +42 -0
- package/src/emit/custom/emitCustomFlowApplicationArtifacts.js +44 -14
- package/src/emit/custom/templates/triggerCache/CheckTriggerCache_HdrSqs.ejs +15 -10
- package/src/emit/function/functionYamlArtifact.js +8 -3
- package/src/emit/relationship/templates/ChangeRelationshipComplete_Main.ejs +8 -13
- package/src/emit/relationship/templates/ChangeRelationship_Main.ejs +5 -14
- package/src/emit/relationship/templates/CreateRelationshipComplete_Main.ejs +8 -13
- package/src/emit/relationship/templates/CreateRelationship_Main.ejs +7 -16
- package/src/emit/relationship/templates/DeleteRelationshipComplete_Main.ejs +8 -4
- package/src/emit/relationship/templates/DeleteRelationship_Main.ejs +6 -24
- package/src/emit/relationship/templates/GetRelationshipComplete_Main.ejs +8 -4
- package/src/emit/relationship/templates/MoveRelationshipComplete_Main.ejs +8 -15
- package/src/emit/relationship/templates/MoveRelationship_Main.ejs +6 -15
- package/src/emit/relationship/templates/UpdateRelationshipComplete_Main.ejs +9 -31
- package/src/emit/relationship/templates/UpdateRelationship_Main.ejs +6 -15
- package/src/emit/resource/emitResourceArtifacts.js +2 -1
- package/src/emit/resource/templates/DynamoDBTable_Yaml.ejs +3 -0
- package/src/emit/shared/templates/FlowHandler_HdrApi.ejs +6 -6
- package/src/emit/shared/templates/FlowHandler_HdrDsq.ejs +18 -13
- package/src/emit/shared/templates/FlowHandler_HdrInv.ejs +6 -6
- package/src/emit/shared/templates/FlowHandler_HdrSqs.ejs +18 -13
- package/src/emit/system/templates/WebSocketComplete_Yaml.ejs +2 -0
- package/test/crudApplicationArtifacts.test.js +22 -0
- package/test/customFlowApplicationArtifacts.test.js +206 -0
- package/test/functionYamlArtifact.test.js +5 -2
- package/test/publicApi.test.js +29 -0
- package/test/relationshipApplicationArtifacts.test.js +42 -8
- package/test/resourceArtifacts.test.js +34 -16
|
@@ -602,3 +602,209 @@ async function createTriggerCacheProject(triggerFlowTypes) {
|
|
|
602
602
|
);
|
|
603
603
|
return rootPath;
|
|
604
604
|
}
|
|
605
|
+
|
|
606
|
+
test('supports step-level required overrides in flowSteps properties and messageAttributes', async () => {
|
|
607
|
+
const rootPath = await mkdtemp(
|
|
608
|
+
join(tmpdir(), 'generate-service-code-v2-step-required-')
|
|
609
|
+
);
|
|
610
|
+
roots.add(rootPath);
|
|
611
|
+
const schemaRoot = join(rootPath, 'app/src/schemas/FlowSchemas');
|
|
612
|
+
await mkdir(schemaRoot, { recursive: true });
|
|
613
|
+
|
|
614
|
+
await writeFile(
|
|
615
|
+
join(schemaRoot, 'Reservations.js'),
|
|
616
|
+
`export default {
|
|
617
|
+
flowTag: 'Reservations',
|
|
618
|
+
event: ['ownTopic'],
|
|
619
|
+
stepProperties: {
|
|
620
|
+
reservationItems: { propertyName: 'reservationItems', type: 'array' }
|
|
621
|
+
},
|
|
622
|
+
stepMessageAttributes: {
|
|
623
|
+
userId: { propertyName: 'x-correlation-base-user-id', type: 'string' }
|
|
624
|
+
},
|
|
625
|
+
flowSteps: [
|
|
626
|
+
{
|
|
627
|
+
stepName: 'Start',
|
|
628
|
+
properties: [{ key: 'reservationItems', required: true }],
|
|
629
|
+
messageAttributes: [{ key: 'userId', required: true }]
|
|
630
|
+
},
|
|
631
|
+
{
|
|
632
|
+
stepName: 'Complete',
|
|
633
|
+
event: ['extTopic'],
|
|
634
|
+
properties: ['reservationItems'],
|
|
635
|
+
messageAttributes: ['userId']
|
|
636
|
+
}
|
|
637
|
+
]
|
|
638
|
+
};\n`
|
|
639
|
+
);
|
|
640
|
+
|
|
641
|
+
const result = await generateCustomFlowApplicationArtifacts(rootPath);
|
|
642
|
+
assert.ok(result);
|
|
643
|
+
|
|
644
|
+
const startHdrContent = await readFile(
|
|
645
|
+
join(
|
|
646
|
+
rootPath,
|
|
647
|
+
'app/src/generatedCode/FlowSchemas/Reservations/source/Reservations_HdrSqs.js'
|
|
648
|
+
),
|
|
649
|
+
'utf8'
|
|
650
|
+
);
|
|
651
|
+
assert.match(
|
|
652
|
+
startHdrContent,
|
|
653
|
+
/perRecordsValidatorSchema = {[\s\S]*required: \['reservationItems'\]/
|
|
654
|
+
);
|
|
655
|
+
assert.match(
|
|
656
|
+
startHdrContent,
|
|
657
|
+
/messageAttributeValidatorSchema = {[\s\S]*required: \['x-correlation-base-user-id'\]/
|
|
658
|
+
);
|
|
659
|
+
|
|
660
|
+
const completeHdrContent = await readFile(
|
|
661
|
+
join(
|
|
662
|
+
rootPath,
|
|
663
|
+
'app/src/generatedCode/FlowSchemas/Reservations/source/ReservationsComplete_HdrSqs.js'
|
|
664
|
+
),
|
|
665
|
+
'utf8'
|
|
666
|
+
);
|
|
667
|
+
assert.match(
|
|
668
|
+
completeHdrContent,
|
|
669
|
+
/perRecordsValidatorSchema = {[\s\S]*required: \[\]/
|
|
670
|
+
);
|
|
671
|
+
assert.match(
|
|
672
|
+
completeHdrContent,
|
|
673
|
+
/messageAttributeValidatorSchema = {[\s\S]*required: \[\]/
|
|
674
|
+
);
|
|
675
|
+
});
|
|
676
|
+
|
|
677
|
+
test(
|
|
678
|
+
'picks properties from inProcess flowStep and renders default keys ' +
|
|
679
|
+
'in handler validator schema',
|
|
680
|
+
async () => {
|
|
681
|
+
const rootPath = await mkdtemp(
|
|
682
|
+
join(tmpdir(), 'generate-service-code-v2-inprocess-')
|
|
683
|
+
);
|
|
684
|
+
roots.add(rootPath);
|
|
685
|
+
const schemaRoot = join(rootPath, 'app/src/schemas/FlowSchemas');
|
|
686
|
+
await mkdir(schemaRoot, { recursive: true });
|
|
687
|
+
|
|
688
|
+
await writeFile(
|
|
689
|
+
join(schemaRoot, 'InProcessFlow.js'),
|
|
690
|
+
`export default {
|
|
691
|
+
flowTag: 'InProcessFlow',
|
|
692
|
+
event: ['extTopic', 'lambdaSyncApi'],
|
|
693
|
+
stepProperties: {
|
|
694
|
+
status: { propertyName: 'status', type: 'string', default: 'ACTIVE' },
|
|
695
|
+
retryCount: { propertyName: 'retryCount', type: 'number', default: 0 }
|
|
696
|
+
},
|
|
697
|
+
flowSteps: [
|
|
698
|
+
{
|
|
699
|
+
stepName: 'inProcess',
|
|
700
|
+
properties: ['status', { key: 'retryCount', default: 3 }]
|
|
701
|
+
}
|
|
702
|
+
]
|
|
703
|
+
};\n`
|
|
704
|
+
);
|
|
705
|
+
|
|
706
|
+
const result = await generateCustomFlowApplicationArtifacts(rootPath);
|
|
707
|
+
assert.ok(result);
|
|
708
|
+
|
|
709
|
+
const sqsHdrContent = await readFile(
|
|
710
|
+
join(
|
|
711
|
+
rootPath,
|
|
712
|
+
'app/src/generatedCode/FlowSchemas/InProcessFlow/source/' +
|
|
713
|
+
'InProcessFlow_HdrSqs.js'
|
|
714
|
+
),
|
|
715
|
+
'utf8'
|
|
716
|
+
);
|
|
717
|
+
assert.match(
|
|
718
|
+
sqsHdrContent,
|
|
719
|
+
/status: {[\s\S]*type: 'string',[\s\S]*default: 'ACTIVE'/
|
|
720
|
+
);
|
|
721
|
+
assert.match(
|
|
722
|
+
sqsHdrContent,
|
|
723
|
+
/retryCount: {[\s\S]*type: 'number',[\s\S]*default: 3/
|
|
724
|
+
);
|
|
725
|
+
|
|
726
|
+
const apiHdrContent = await readFile(
|
|
727
|
+
join(
|
|
728
|
+
rootPath,
|
|
729
|
+
'app/src/generatedCode/FlowSchemas/InProcessFlow/source/' +
|
|
730
|
+
'InProcessFlow_HdrApi.js'
|
|
731
|
+
),
|
|
732
|
+
'utf8'
|
|
733
|
+
);
|
|
734
|
+
assert.match(
|
|
735
|
+
apiHdrContent,
|
|
736
|
+
/status: { type: 'string', default: 'ACTIVE' }/
|
|
737
|
+
);
|
|
738
|
+
assert.match(apiHdrContent, /retryCount: { type: 'number', default: 3 }/);
|
|
739
|
+
}
|
|
740
|
+
);
|
|
741
|
+
|
|
742
|
+
test('renders nextSteps IAM permissions in InitialSetupConfig', async () => {
|
|
743
|
+
const { generate } = await import('../src/generate.js');
|
|
744
|
+
const rootPath = await mkdtemp(
|
|
745
|
+
join(tmpdir(), 'generate-service-code-v2-nextsteps-')
|
|
746
|
+
);
|
|
747
|
+
roots.add(rootPath);
|
|
748
|
+
const schemaRoot = join(rootPath, 'app/src/schemas/FlowSchemas');
|
|
749
|
+
await mkdir(schemaRoot, { recursive: true });
|
|
750
|
+
|
|
751
|
+
await writeFile(
|
|
752
|
+
join(schemaRoot, 'Reservations.js'),
|
|
753
|
+
`export default {
|
|
754
|
+
flowTag: 'Reservations',
|
|
755
|
+
event: ['ownTopic'],
|
|
756
|
+
flowSteps: [
|
|
757
|
+
{
|
|
758
|
+
stepName: 'Start',
|
|
759
|
+
nextSteps: [
|
|
760
|
+
{
|
|
761
|
+
targetType: 'extTopic',
|
|
762
|
+
serviceTag: 'GraphHandler',
|
|
763
|
+
topic: 'GetDeepPathSearch_In'
|
|
764
|
+
}
|
|
765
|
+
]
|
|
766
|
+
},
|
|
767
|
+
{
|
|
768
|
+
stepName: 'GetDeepPathSearch',
|
|
769
|
+
event: ['extTopic'],
|
|
770
|
+
nextSteps: [
|
|
771
|
+
{
|
|
772
|
+
targetType: 'ownTopic',
|
|
773
|
+
topic: 'Create_In'
|
|
774
|
+
}
|
|
775
|
+
]
|
|
776
|
+
},
|
|
777
|
+
{
|
|
778
|
+
stepName: 'Complete',
|
|
779
|
+
event: ['extTopic']
|
|
780
|
+
}
|
|
781
|
+
]
|
|
782
|
+
};\n`
|
|
783
|
+
);
|
|
784
|
+
|
|
785
|
+
const result = await generate(rootPath, {
|
|
786
|
+
serviceTag: 'ReservationsService'
|
|
787
|
+
});
|
|
788
|
+
assert.ok(result);
|
|
789
|
+
|
|
790
|
+
const initialSetupConfigContent = await readFile(
|
|
791
|
+
join(
|
|
792
|
+
rootPath,
|
|
793
|
+
'app/src/generatedCode/CoreFunctions/InitialSetupConfig.js'
|
|
794
|
+
),
|
|
795
|
+
'utf8'
|
|
796
|
+
);
|
|
797
|
+
assert.match(initialSetupConfigContent, /roleName: 'ReservationsRole'/);
|
|
798
|
+
assert.match(
|
|
799
|
+
initialSetupConfigContent,
|
|
800
|
+
/ReservationsRolePublishNextStepsTopics/
|
|
801
|
+
);
|
|
802
|
+
assert.match(
|
|
803
|
+
initialSetupConfigContent,
|
|
804
|
+
/snsTopicNameByFlowSchema\(_izContext, 'GetDeepPathSearch_In', 'GraphHandler'\)/
|
|
805
|
+
);
|
|
806
|
+
assert.match(
|
|
807
|
+
initialSetupConfigContent,
|
|
808
|
+
/snsTopicNameByFlowSchema\(\s*_izContext,\s*'Create_In'\)/
|
|
809
|
+
);
|
|
810
|
+
});
|
|
@@ -80,12 +80,15 @@ test('filters all SQS functions by their calling flow', () => {
|
|
|
80
80
|
|
|
81
81
|
assert.match(
|
|
82
82
|
completion,
|
|
83
|
-
|
|
83
|
+
/#<#CreateCompleteHdrSqsFilterPatterns#>[\s\S]*filterPatterns:[\s\S]*\$\{self:custom\.iz_resourcePrefix\}CreateCompleteHdrSqs/
|
|
84
84
|
);
|
|
85
|
+
assert.match(completion, /#<#\/CreateCompleteHdrSqsFilterPatterns#>/);
|
|
85
86
|
assert.match(completion, /exists: false/);
|
|
86
87
|
assert.match(
|
|
87
88
|
ordinary,
|
|
88
|
-
|
|
89
|
+
/#<#CreateHdrSqsFilterPatterns#>[\s\S]*filterPatterns:[\s\S]*\$\{self:custom\.iz_resourcePrefix\}CreateHdrSqs/
|
|
89
90
|
);
|
|
91
|
+
assert.match(ordinary, /#<#\/CreateHdrSqsFilterPatterns#>/);
|
|
90
92
|
assert.match(ordinary, /exists: false/);
|
|
91
93
|
});
|
|
94
|
+
|
package/test/publicApi.test.js
CHANGED
|
@@ -347,3 +347,32 @@ async function createTriggerCacheProject() {
|
|
|
347
347
|
);
|
|
348
348
|
return rootPath;
|
|
349
349
|
}
|
|
350
|
+
|
|
351
|
+
test(
|
|
352
|
+
'emits ignoreExternalServices hooks in UploadSchema_Main.js and ' +
|
|
353
|
+
'commands/uploadSchema.js',
|
|
354
|
+
async () => {
|
|
355
|
+
const rootPath = await createCrudProject();
|
|
356
|
+
await generate(rootPath);
|
|
357
|
+
|
|
358
|
+
const uploadSchemaMain = await readFile(
|
|
359
|
+
join(
|
|
360
|
+
rootPath,
|
|
361
|
+
'app/src/generatedCode/CoreFunctions/UploadSchema_Main.js'
|
|
362
|
+
),
|
|
363
|
+
'utf8'
|
|
364
|
+
);
|
|
365
|
+
assert.match(uploadSchemaMain, /\/\/\(<ignoreExternalServices>\)/);
|
|
366
|
+
assert.match(uploadSchemaMain, /\/\/\(<\/ignoreExternalServices>\)/);
|
|
367
|
+
|
|
368
|
+
const commandUploadSchema = await readFile(
|
|
369
|
+
join(
|
|
370
|
+
rootPath,
|
|
371
|
+
'app/initial_setup/generatedCode/source/commands/uploadSchema.js'
|
|
372
|
+
),
|
|
373
|
+
'utf8'
|
|
374
|
+
);
|
|
375
|
+
assert.match(commandUploadSchema, /\/\/\(<ignoreExternalServices>\)/);
|
|
376
|
+
assert.match(commandUploadSchema, /\/\/\(<\/ignoreExternalServices>\)/);
|
|
377
|
+
}
|
|
378
|
+
);
|
|
@@ -74,20 +74,54 @@ test('emits relationship action and complete handlers with data-first function Y
|
|
|
74
74
|
readFile(
|
|
75
75
|
join(
|
|
76
76
|
rootPath,
|
|
77
|
-
'app/src/generatedCode/FlowRelationships/CreateRelationship/
|
|
77
|
+
'app/src/generatedCode/FlowRelationships/CreateRelationship/' +
|
|
78
|
+
'source/ProcessCreateRelationship_Main.js'
|
|
78
79
|
)
|
|
79
80
|
)
|
|
80
81
|
);
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
'
|
|
86
|
-
),
|
|
87
|
-
'utf8'
|
|
82
|
+
const createMainContent = await readFile(
|
|
83
|
+
join(
|
|
84
|
+
rootPath,
|
|
85
|
+
'app/src/generatedCode/FlowRelationships/CreateRelationship/' +
|
|
86
|
+
'source/CreateRelationship_Main.js'
|
|
88
87
|
),
|
|
88
|
+
'utf8'
|
|
89
|
+
);
|
|
90
|
+
assert.match(
|
|
91
|
+
createMainContent,
|
|
89
92
|
/export default async function createRelationship/
|
|
90
93
|
);
|
|
94
|
+
assert.match(
|
|
95
|
+
createMainContent,
|
|
96
|
+
/awaitingStepIds\.push\(\{[\s\S]*awaitingStepId:/
|
|
97
|
+
);
|
|
98
|
+
assert.match(
|
|
99
|
+
createMainContent,
|
|
100
|
+
/createAwaitingMultipleSteps\(\s*_izContext,\s*asyncFlowSharedLib\.createPendingStepId/
|
|
101
|
+
);
|
|
102
|
+
assert.doesNotMatch(createMainContent, /const linkTypeId = createLinkTypeId/);
|
|
103
|
+
|
|
104
|
+
const createCompleteContent = await readFile(
|
|
105
|
+
join(
|
|
106
|
+
rootPath,
|
|
107
|
+
'app/src/generatedCode/FlowRelationships/CreateRelationship/' +
|
|
108
|
+
'source/CreateRelationshipComplete_Main.js'
|
|
109
|
+
),
|
|
110
|
+
'utf8'
|
|
111
|
+
);
|
|
112
|
+
assert.match(
|
|
113
|
+
createCompleteContent,
|
|
114
|
+
/export default async function createRelationshipComplete\(\s*_izContext,\s*requestParams,/
|
|
115
|
+
);
|
|
116
|
+
assert.match(
|
|
117
|
+
createCompleteContent,
|
|
118
|
+
/const {\s*returnValue,\s*status,\s*errorsFound,\s*graphServiceTag\s*} = requestParams;/
|
|
119
|
+
);
|
|
120
|
+
assert.doesNotMatch(
|
|
121
|
+
createCompleteContent,
|
|
122
|
+
/const linkTypeId = createLinkTypeId/
|
|
123
|
+
);
|
|
124
|
+
|
|
91
125
|
assert.match(yaml, /CreateRelationshipCompleteHdrSqs:/);
|
|
92
126
|
});
|
|
93
127
|
|
|
@@ -27,10 +27,7 @@ test('emits queues, topics, tables, and IAM roles from schema data', async () =>
|
|
|
27
27
|
),
|
|
28
28
|
'utf8'
|
|
29
29
|
);
|
|
30
|
-
assert.match(
|
|
31
|
-
resources,
|
|
32
|
-
/SubscriptionOutCreateToUploadReservedLimitHdrSqs:/
|
|
33
|
-
);
|
|
30
|
+
assert.match(resources, /SubscriptionOutCreateToUploadReservedLimitHdrSqs:/);
|
|
34
31
|
assert.match(resources, /TopicArn: !Ref OutCreate/);
|
|
35
32
|
assert.match(
|
|
36
33
|
resources,
|
|
@@ -45,16 +42,16 @@ test('emits queues, topics, tables, and IAM roles from schema data', async () =>
|
|
|
45
42
|
]) {
|
|
46
43
|
assert.match(resources, new RegExp(`^ ${queueName}:`, 'm'));
|
|
47
44
|
}
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
'resource/sls_yaml/generatedCode/source/generated-dynamoDB-table.yml'
|
|
53
|
-
),
|
|
54
|
-
'utf8'
|
|
45
|
+
const dynamoYaml = await readFile(
|
|
46
|
+
join(
|
|
47
|
+
rootPath,
|
|
48
|
+
'resource/sls_yaml/generatedCode/source/generated-dynamoDB-table.yml'
|
|
55
49
|
),
|
|
56
|
-
|
|
50
|
+
'utf8'
|
|
57
51
|
);
|
|
52
|
+
assert.match(dynamoYaml, /OrdersTable:/);
|
|
53
|
+
assert.match(dynamoYaml, /#<#OrdersTableSetting#>/);
|
|
54
|
+
assert.match(dynamoYaml, /#<#\/OrdersTableSetting#>/);
|
|
58
55
|
const iam = await readFile(
|
|
59
56
|
join(
|
|
60
57
|
rootPath,
|
|
@@ -84,10 +81,7 @@ test('emits queues, topics, tables, and IAM roles from schema data', async () =>
|
|
|
84
81
|
/RolePermissions|ProcessCreateTargetRoleHdrSqs/
|
|
85
82
|
);
|
|
86
83
|
// Per-object RBAC flow queue (e.g. OrderRbacFlowHdrSqs from Order with rbac add-on)
|
|
87
|
-
assert.match(
|
|
88
|
-
roleBlock(iam, 'FlowRbacRole'),
|
|
89
|
-
/OrderRbacFlowHdrSqs/
|
|
90
|
-
);
|
|
84
|
+
assert.match(roleBlock(iam, 'FlowRbacRole'), /OrderRbacFlowHdrSqs/);
|
|
91
85
|
assert.match(resources, /^ CreateRelationshipHdrSqs:/m);
|
|
92
86
|
assert.equal(
|
|
93
87
|
(resources.match(/^ CreateRelationshipHdrSqs:/gm) ?? []).length,
|
|
@@ -219,6 +213,30 @@ test('scopes stored-cache permissions to the flow object table', async () => {
|
|
|
219
213
|
assert.doesNotMatch(role, /Orders|WebSocketTask/);
|
|
220
214
|
});
|
|
221
215
|
|
|
216
|
+
test('emits DynamoDB tables and hooks defined in flowSchemas storageResources', async () => {
|
|
217
|
+
const rootPath = await createProject();
|
|
218
|
+
await writeFile(
|
|
219
|
+
join(rootPath, 'app/src/schemas/FlowSchemas/FlowWithTable.js'),
|
|
220
|
+
"export default { flowTag: 'tableFlow', event: ['ownTopic'], " +
|
|
221
|
+
"storageResources: { main: { storageType: 'dynamoDB', " +
|
|
222
|
+
"tableName: 'FlowCustomTable' } }, identifiers: " +
|
|
223
|
+
"[{ type: 'partitionKey', fieldName: 'flowKey' }] };\n"
|
|
224
|
+
);
|
|
225
|
+
|
|
226
|
+
await generateResourceArtifacts(rootPath);
|
|
227
|
+
|
|
228
|
+
const dynamoYaml = await readFile(
|
|
229
|
+
join(
|
|
230
|
+
rootPath,
|
|
231
|
+
'resource/sls_yaml/generatedCode/source/generated-dynamoDB-table.yml'
|
|
232
|
+
),
|
|
233
|
+
'utf8'
|
|
234
|
+
);
|
|
235
|
+
assert.match(dynamoYaml, /FlowCustomTableTable:/);
|
|
236
|
+
assert.match(dynamoYaml, /#<#FlowCustomTableTableSetting#>/);
|
|
237
|
+
assert.match(dynamoYaml, /#<#\/FlowCustomTableTableSetting#>/);
|
|
238
|
+
});
|
|
239
|
+
|
|
222
240
|
function roleBlock(iam, roleName) {
|
|
223
241
|
const match = iam.match(
|
|
224
242
|
new RegExp(
|