@linkup-ai/abap-ai 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +389 -0
- package/dist/adt-client.js +383 -0
- package/dist/cli/activate.js +127 -0
- package/dist/cli/init.js +559 -0
- package/dist/cli/link.js +148 -0
- package/dist/cli/remove.js +83 -0
- package/dist/cli/status.js +231 -0
- package/dist/cli/systems.js +72 -0
- package/dist/cli.js +92 -0
- package/dist/index.js +1442 -0
- package/dist/knowledge/abap/abap-dictionary.md +199 -0
- package/dist/knowledge/abap/abap-sql.md +296 -0
- package/dist/knowledge/abap/amdp.md +273 -0
- package/dist/knowledge/abap/clean-code.md +293 -0
- package/dist/knowledge/abap/cloud-background-processing.md +250 -0
- package/dist/knowledge/abap/cloud-communication.md +265 -0
- package/dist/knowledge/abap/cloud-development.md +176 -0
- package/dist/knowledge/abap/cloud-extensibility.md +252 -0
- package/dist/knowledge/abap/cloud-released-apis.md +261 -0
- package/dist/knowledge/abap/constructor-expressions.md +289 -0
- package/dist/knowledge/abap/enhancements.md +232 -0
- package/dist/knowledge/abap/exceptions.md +271 -0
- package/dist/knowledge/abap/internal-tables.md +205 -0
- package/dist/knowledge/abap/object-orientation.md +298 -0
- package/dist/knowledge/abap/performance.md +216 -0
- package/dist/knowledge/abap/rap-abstract-entities.md +206 -0
- package/dist/knowledge/abap/rap-business-events.md +216 -0
- package/dist/knowledge/abap/rap-draft.md +191 -0
- package/dist/knowledge/abap/rap-eml.md +453 -0
- package/dist/knowledge/abap/rap-end-to-end.md +486 -0
- package/dist/knowledge/abap/rap-feature-control.md +185 -0
- package/dist/knowledge/abap/rap-numbering.md +280 -0
- package/dist/knowledge/abap/rap-service-exposure.md +163 -0
- package/dist/knowledge/abap/rap-unmanaged.md +468 -0
- package/dist/knowledge/abap/string-processing.md +180 -0
- package/dist/knowledge/abap/unit-testing.md +303 -0
- package/dist/knowledge/abap-cds/access-control.md +241 -0
- package/dist/knowledge/abap-cds/annotations.md +331 -0
- package/dist/knowledge/abap-cds/associations.md +254 -0
- package/dist/knowledge/abap-cds/expressions.md +230 -0
- package/dist/knowledge/abap-cds/functions.md +245 -0
- package/dist/knowledge/abap-cds/metadata-extensions.md +294 -0
- package/dist/knowledge/cap/authentication.md +278 -0
- package/dist/knowledge/cap/cdl-syntax.md +247 -0
- package/dist/knowledge/cap/cql-queries.md +266 -0
- package/dist/knowledge/cap/deployment.md +343 -0
- package/dist/knowledge/cap/event-handlers.md +287 -0
- package/dist/knowledge/cap/fiori-integration.md +303 -0
- package/dist/knowledge/cap/service-definitions.md +287 -0
- package/dist/knowledge/fiori/annotations.md +347 -0
- package/dist/knowledge/fiori/deployment.md +340 -0
- package/dist/knowledge/fiori/fiori-elements.md +332 -0
- package/dist/knowledge/fiori/fiori-side-effects.md +107 -0
- package/dist/knowledge/fiori/fiori-valuelist.md +144 -0
- package/dist/knowledge/fiori/ui5-controllers.md +358 -0
- package/dist/knowledge/fiori/ui5-data-binding.md +311 -0
- package/dist/knowledge/fiori/ui5-fragments-dialogs.md +330 -0
- package/dist/knowledge/fiori/ui5-manifest.md +411 -0
- package/dist/knowledge/fiori/ui5-routing.md +303 -0
- package/dist/knowledge/fiori/ui5-xml-views.md +294 -0
- package/dist/license-guard.js +81 -0
- package/dist/logger.js +114 -0
- package/dist/postinstall.js +165 -0
- package/dist/security-audit.js +136 -0
- package/dist/security-policy.js +322 -0
- package/dist/system-profile.js +207 -0
- package/dist/tools/abap-doc.js +72 -0
- package/dist/tools/abapgit.js +161 -0
- package/dist/tools/activate.js +71 -0
- package/dist/tools/atc-check.js +117 -0
- package/dist/tools/auth-object.js +56 -0
- package/dist/tools/breakpoints.js +76 -0
- package/dist/tools/call-hierarchy.js +84 -0
- package/dist/tools/cds-annotations.js +98 -0
- package/dist/tools/cds-dependencies.js +65 -0
- package/dist/tools/check.js +47 -0
- package/dist/tools/code-completion.js +70 -0
- package/dist/tools/code-coverage.js +111 -0
- package/dist/tools/create-amdp.js +111 -0
- package/dist/tools/create-dcl.js +81 -0
- package/dist/tools/create-transport.js +38 -0
- package/dist/tools/create.js +285 -0
- package/dist/tools/data-preview.js +37 -0
- package/dist/tools/delete.js +45 -0
- package/dist/tools/deploy-bsp.js +298 -0
- package/dist/tools/discovery.js +59 -0
- package/dist/tools/element-info.js +93 -0
- package/dist/tools/enhancements.js +186 -0
- package/dist/tools/extract-method.js +44 -0
- package/dist/tools/function-group.js +59 -0
- package/dist/tools/knowledge.js +275 -0
- package/dist/tools/lock-object.js +75 -0
- package/dist/tools/message-class.js +67 -0
- package/dist/tools/navigate.js +80 -0
- package/dist/tools/number-range.js +57 -0
- package/dist/tools/object-documentation.js +43 -0
- package/dist/tools/object-structure.js +78 -0
- package/dist/tools/object-versions.js +57 -0
- package/dist/tools/package-contents.js +60 -0
- package/dist/tools/pretty-printer.js +35 -0
- package/dist/tools/publish-binding.js +49 -0
- package/dist/tools/quick-fix.js +69 -0
- package/dist/tools/read.js +172 -0
- package/dist/tools/refactor-rename.js +60 -0
- package/dist/tools/release-transport.js +24 -0
- package/dist/tools/released-apis.js +51 -0
- package/dist/tools/repository-tree.js +90 -0
- package/dist/tools/scaffold-rap.js +642 -0
- package/dist/tools/search.js +73 -0
- package/dist/tools/shared/data-format.js +101 -0
- package/dist/tools/sql-console.js +17 -0
- package/dist/tools/system-info.js +271 -0
- package/dist/tools/traces.js +66 -0
- package/dist/tools/transport-contents.js +83 -0
- package/dist/tools/transports.js +68 -0
- package/dist/tools/unit-test.js +135 -0
- package/dist/tools/where-used.js +59 -0
- package/dist/tools/write.js +120 -0
- package/package.json +50 -0
|
@@ -0,0 +1,294 @@
|
|
|
1
|
+
# UI5 XML Views — controls, layouts, formatters
|
|
2
|
+
|
|
3
|
+
## View Structure
|
|
4
|
+
|
|
5
|
+
```xml
|
|
6
|
+
<mvc:View
|
|
7
|
+
controllerName="com.myapp.controller.Main"
|
|
8
|
+
xmlns:mvc="sap.ui.core.mvc"
|
|
9
|
+
xmlns="sap.m"
|
|
10
|
+
xmlns:f="sap.f"
|
|
11
|
+
xmlns:l="sap.ui.layout"
|
|
12
|
+
xmlns:core="sap.ui.core">
|
|
13
|
+
|
|
14
|
+
<Page id="mainPage" title="{i18n>appTitle}">
|
|
15
|
+
<!-- content here -->
|
|
16
|
+
</Page>
|
|
17
|
+
|
|
18
|
+
</mvc:View>
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
## Page Layouts
|
|
22
|
+
|
|
23
|
+
```xml
|
|
24
|
+
<!-- Basic Page -->
|
|
25
|
+
<Page id="page" title="Orders" showNavButton="true" navButtonPress=".onNavBack">
|
|
26
|
+
<headerContent>
|
|
27
|
+
<Button icon="sap-icon://add" press=".onCreate" type="Emphasized"/>
|
|
28
|
+
</headerContent>
|
|
29
|
+
<content>
|
|
30
|
+
<!-- main content -->
|
|
31
|
+
</content>
|
|
32
|
+
<footer>
|
|
33
|
+
<OverflowToolbar>
|
|
34
|
+
<ToolbarSpacer/>
|
|
35
|
+
<Button text="Save" type="Emphasized" press=".onSave"/>
|
|
36
|
+
<Button text="Cancel" press=".onCancel"/>
|
|
37
|
+
</OverflowToolbar>
|
|
38
|
+
</footer>
|
|
39
|
+
</Page>
|
|
40
|
+
|
|
41
|
+
<!-- Dynamic Page (sap.f) — modern, collapsible header -->
|
|
42
|
+
<f:DynamicPage id="dynamicPage" headerExpanded="true">
|
|
43
|
+
<f:title>
|
|
44
|
+
<f:DynamicPageTitle>
|
|
45
|
+
<f:heading>
|
|
46
|
+
<Title text="{Name}"/>
|
|
47
|
+
</f:heading>
|
|
48
|
+
<f:actions>
|
|
49
|
+
<Button text="Edit" type="Emphasized" press=".onEdit"/>
|
|
50
|
+
</f:actions>
|
|
51
|
+
</f:DynamicPageTitle>
|
|
52
|
+
</f:title>
|
|
53
|
+
<f:header>
|
|
54
|
+
<f:DynamicPageHeader>
|
|
55
|
+
<FlexBox wrap="Wrap">
|
|
56
|
+
<ObjectAttribute title="Status" text="{Status}"/>
|
|
57
|
+
<ObjectAttribute title="Created" text="{CreatedAt}"/>
|
|
58
|
+
</FlexBox>
|
|
59
|
+
</f:DynamicPageHeader>
|
|
60
|
+
</f:header>
|
|
61
|
+
<f:content>
|
|
62
|
+
<!-- main content -->
|
|
63
|
+
</f:content>
|
|
64
|
+
</f:DynamicPage>
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
## Layout Controls
|
|
68
|
+
|
|
69
|
+
```xml
|
|
70
|
+
<!-- VBox / HBox -->
|
|
71
|
+
<VBox class="sapUiSmallMargin">
|
|
72
|
+
<HBox justifyContent="SpaceBetween" alignItems="Center">
|
|
73
|
+
<Label text="Name"/>
|
|
74
|
+
<Input value="{Name}" width="300px"/>
|
|
75
|
+
</HBox>
|
|
76
|
+
</VBox>
|
|
77
|
+
|
|
78
|
+
<!-- FlexBox -->
|
|
79
|
+
<FlexBox direction="Column" justifyContent="Center" alignItems="Stretch">
|
|
80
|
+
<items>
|
|
81
|
+
<Text text="{Description}"/>
|
|
82
|
+
</items>
|
|
83
|
+
</FlexBox>
|
|
84
|
+
|
|
85
|
+
<!-- Grid (sap.ui.layout) -->
|
|
86
|
+
<l:Grid defaultSpan="L6 M6 S12">
|
|
87
|
+
<l:content>
|
|
88
|
+
<Input value="{Field1}"/>
|
|
89
|
+
<Input value="{Field2}"/>
|
|
90
|
+
</l:content>
|
|
91
|
+
</l:Grid>
|
|
92
|
+
|
|
93
|
+
<!-- Form (sap.ui.layout.form) — responsive form layout -->
|
|
94
|
+
<f:SimpleForm xmlns:f="sap.ui.layout.form"
|
|
95
|
+
editable="true" layout="ResponsiveGridLayout"
|
|
96
|
+
labelSpanXL="3" labelSpanL="3" labelSpanM="3" labelSpanS="12"
|
|
97
|
+
columnsXL="2" columnsL="2" columnsM="1">
|
|
98
|
+
<f:content>
|
|
99
|
+
<core:Title text="General"/>
|
|
100
|
+
<Label text="Order ID"/>
|
|
101
|
+
<Input value="{OrderID}" editable="false"/>
|
|
102
|
+
<Label text="Customer"/>
|
|
103
|
+
<Input value="{CustomerName}"/>
|
|
104
|
+
<Label text="Date"/>
|
|
105
|
+
<DatePicker value="{path: 'OrderDate', type: 'sap.ui.model.type.Date', formatOptions: {style: 'medium'}}"/>
|
|
106
|
+
|
|
107
|
+
<core:Title text="Pricing"/>
|
|
108
|
+
<Label text="Amount"/>
|
|
109
|
+
<Input value="{Amount}" type="Number"/>
|
|
110
|
+
<Label text="Currency"/>
|
|
111
|
+
<Select selectedKey="{Currency}" items="{/Currencies}">
|
|
112
|
+
<core:Item key="{Key}" text="{Text}"/>
|
|
113
|
+
</Select>
|
|
114
|
+
</f:content>
|
|
115
|
+
</f:SimpleForm>
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
## Input Controls
|
|
119
|
+
|
|
120
|
+
```xml
|
|
121
|
+
<!-- Text input -->
|
|
122
|
+
<Input value="{Name}" placeholder="Enter name" maxLength="40"
|
|
123
|
+
liveChange=".onNameChange" valueLiveUpdate="true"/>
|
|
124
|
+
|
|
125
|
+
<!-- Number -->
|
|
126
|
+
<Input value="{Amount}" type="Number" description="EUR"/>
|
|
127
|
+
|
|
128
|
+
<!-- TextArea -->
|
|
129
|
+
<TextArea value="{Description}" rows="4" growing="true" growingMaxLines="8"/>
|
|
130
|
+
|
|
131
|
+
<!-- DatePicker -->
|
|
132
|
+
<DatePicker value="{OrderDate}" valueFormat="yyyyMMdd" displayFormat="dd.MM.yyyy"/>
|
|
133
|
+
|
|
134
|
+
<!-- DateRangeSelection -->
|
|
135
|
+
<DateRangeSelection dateValue="{StartDate}" secondDateValue="{EndDate}"/>
|
|
136
|
+
|
|
137
|
+
<!-- TimePicker -->
|
|
138
|
+
<TimePicker value="{StartTime}" valueFormat="HHmmss" displayFormat="HH:mm"/>
|
|
139
|
+
|
|
140
|
+
<!-- Select (dropdown) -->
|
|
141
|
+
<Select selectedKey="{Status}" forceSelection="false">
|
|
142
|
+
<core:Item key="O" text="Open"/>
|
|
143
|
+
<core:Item key="A" text="Accepted"/>
|
|
144
|
+
<core:Item key="X" text="Rejected"/>
|
|
145
|
+
</Select>
|
|
146
|
+
|
|
147
|
+
<!-- ComboBox (dropdown with type-ahead) -->
|
|
148
|
+
<ComboBox selectedKey="{Category}" items="{/Categories}">
|
|
149
|
+
<core:Item key="{Key}" text="{Name}"/>
|
|
150
|
+
</ComboBox>
|
|
151
|
+
|
|
152
|
+
<!-- MultiComboBox -->
|
|
153
|
+
<MultiComboBox selectedKeys="{Tags}" items="{/AvailableTags}">
|
|
154
|
+
<core:Item key="{Key}" text="{Name}"/>
|
|
155
|
+
</MultiComboBox>
|
|
156
|
+
|
|
157
|
+
<!-- Switch -->
|
|
158
|
+
<Switch state="{IsActive}" customTextOn="Yes" customTextOff="No"/>
|
|
159
|
+
|
|
160
|
+
<!-- CheckBox -->
|
|
161
|
+
<CheckBox selected="{IsUrgent}" text="Urgent"/>
|
|
162
|
+
|
|
163
|
+
<!-- RadioButtonGroup -->
|
|
164
|
+
<RadioButtonGroup selectedIndex="{PriorityIndex}">
|
|
165
|
+
<RadioButton text="Low"/>
|
|
166
|
+
<RadioButton text="Medium"/>
|
|
167
|
+
<RadioButton text="High"/>
|
|
168
|
+
</RadioButtonGroup>
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
## Display Controls
|
|
172
|
+
|
|
173
|
+
```xml
|
|
174
|
+
<!-- ObjectHeader -->
|
|
175
|
+
<ObjectHeader title="{Name}" number="{Amount}" numberUnit="{Currency}"
|
|
176
|
+
statuses="{statuses}">
|
|
177
|
+
<ObjectAttribute title="Customer" text="{CustomerName}"/>
|
|
178
|
+
<ObjectAttribute title="Date" text="{OrderDate}"/>
|
|
179
|
+
<ObjectStatus text="{StatusText}" state="{StatusState}"/>
|
|
180
|
+
</ObjectHeader>
|
|
181
|
+
|
|
182
|
+
<!-- ObjectListItem -->
|
|
183
|
+
<ObjectListItem title="{Name}" number="{Amount}" numberUnit="{Currency}"
|
|
184
|
+
type="Navigation" press=".onItemPress">
|
|
185
|
+
<firstStatus>
|
|
186
|
+
<ObjectStatus text="{StatusText}" state="{StatusState}"/>
|
|
187
|
+
</firstStatus>
|
|
188
|
+
<attributes>
|
|
189
|
+
<ObjectAttribute text="{CustomerName}"/>
|
|
190
|
+
</attributes>
|
|
191
|
+
</ObjectListItem>
|
|
192
|
+
|
|
193
|
+
<!-- IconTabBar -->
|
|
194
|
+
<IconTabBar select=".onTabSelect">
|
|
195
|
+
<items>
|
|
196
|
+
<IconTabFilter text="All" count="{/AllCount}" key="all"/>
|
|
197
|
+
<IconTabFilter text="Open" count="{/OpenCount}" key="open"
|
|
198
|
+
iconColor="Positive"/>
|
|
199
|
+
<IconTabFilter text="Rejected" count="{/RejectedCount}" key="rejected"
|
|
200
|
+
iconColor="Negative"/>
|
|
201
|
+
</items>
|
|
202
|
+
</IconTabBar>
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
## Table
|
|
206
|
+
|
|
207
|
+
```xml
|
|
208
|
+
<Table id="orderTable" items="{/Orders}" growing="true" growingThreshold="20"
|
|
209
|
+
mode="SingleSelectMaster" selectionChange=".onSelectionChange"
|
|
210
|
+
sticky="ColumnHeaders,HeaderToolbar">
|
|
211
|
+
<headerToolbar>
|
|
212
|
+
<OverflowToolbar>
|
|
213
|
+
<Title text="Orders ({= ${/Orders}.length})"/>
|
|
214
|
+
<ToolbarSpacer/>
|
|
215
|
+
<SearchField search=".onSearch" width="300px"/>
|
|
216
|
+
<Button icon="sap-icon://add" press=".onCreate" type="Emphasized"/>
|
|
217
|
+
</OverflowToolbar>
|
|
218
|
+
</headerToolbar>
|
|
219
|
+
<columns>
|
|
220
|
+
<Column><Text text="Order ID"/></Column>
|
|
221
|
+
<Column><Text text="Customer"/></Column>
|
|
222
|
+
<Column hAlign="End"><Text text="Amount"/></Column>
|
|
223
|
+
<Column><Text text="Status"/></Column>
|
|
224
|
+
</columns>
|
|
225
|
+
<items>
|
|
226
|
+
<ColumnListItem type="Navigation" press=".onItemPress">
|
|
227
|
+
<Text text="{OrderID}"/>
|
|
228
|
+
<Text text="{CustomerName}"/>
|
|
229
|
+
<ObjectNumber number="{Amount}" unit="{Currency}"/>
|
|
230
|
+
<ObjectStatus text="{StatusText}" state="{StatusState}"/>
|
|
231
|
+
</ColumnListItem>
|
|
232
|
+
</items>
|
|
233
|
+
</Table>
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
## List
|
|
237
|
+
|
|
238
|
+
```xml
|
|
239
|
+
<List id="list" items="{/Items}" mode="Delete" delete=".onDelete"
|
|
240
|
+
noDataText="No items found">
|
|
241
|
+
<StandardListItem title="{Name}" description="{Description}"
|
|
242
|
+
icon="sap-icon://product" info="{Status}" infoState="{InfoState}"
|
|
243
|
+
type="Navigation" press=".onItemPress"/>
|
|
244
|
+
</List>
|
|
245
|
+
```
|
|
246
|
+
|
|
247
|
+
## Inline Formatting
|
|
248
|
+
|
|
249
|
+
```xml
|
|
250
|
+
<!-- Expression binding -->
|
|
251
|
+
<Text text="{= ${Amount} > 1000 ? 'High' : 'Normal'}"/>
|
|
252
|
+
<Input editable="{= ${Status} !== 'A'}"/>
|
|
253
|
+
<ObjectStatus text="{StatusText}"
|
|
254
|
+
state="{= ${Status} === 'A' ? 'Success' : ${Status} === 'X' ? 'Error' : 'Warning'}"/>
|
|
255
|
+
|
|
256
|
+
<!-- Visibility -->
|
|
257
|
+
<Button text="Approve" visible="{= ${Status} === 'O'}"/>
|
|
258
|
+
|
|
259
|
+
<!-- CSS class binding -->
|
|
260
|
+
<Text text="{Amount}" class="{= ${Amount} < 0 ? 'negativeAmount' : ''}"/>
|
|
261
|
+
```
|
|
262
|
+
|
|
263
|
+
## Common Namespaces
|
|
264
|
+
|
|
265
|
+
| Prefix | Namespace | Controls |
|
|
266
|
+
|--------|-----------|----------|
|
|
267
|
+
| (default) | `sap.m` | Page, Table, List, Input, Button, Dialog |
|
|
268
|
+
| `f` | `sap.f` | DynamicPage, FlexibleColumnLayout, Avatar |
|
|
269
|
+
| `l` | `sap.ui.layout` | Grid, Splitter, HorizontalLayout |
|
|
270
|
+
| `core` | `sap.ui.core` | Item, Title, Icon, Fragment |
|
|
271
|
+
| `mvc` | `sap.ui.core.mvc` | View |
|
|
272
|
+
| `form` | `sap.ui.layout.form` | SimpleForm, Form |
|
|
273
|
+
| `unified` | `sap.ui.unified` | FileUploader, Calendar |
|
|
274
|
+
| `table` | `sap.ui.table` | Table (desktop grid), TreeTable |
|
|
275
|
+
|
|
276
|
+
## Rules
|
|
277
|
+
- Always set `id` on controls you access from controller (`this.byId("id")`)
|
|
278
|
+
- Use `sap.m.Table` (responsive) for mobile-first; `sap.ui.table.Table` for desktop-heavy data grids
|
|
279
|
+
- Use `growing="true"` on Table/List for lazy loading (not load all data)
|
|
280
|
+
- Use `SimpleForm` with `ResponsiveGridLayout` for responsive form layout
|
|
281
|
+
- `DynamicPage` (sap.f) is preferred over `Page` (sap.m) for modern apps
|
|
282
|
+
- Expression binding `{= ... }` for simple conditions; formatter function for complex logic
|
|
283
|
+
- Prefix namespace only needed once per view (`xmlns:f="sap.f"`)
|
|
284
|
+
|
|
285
|
+
## Anti-Patterns
|
|
286
|
+
| Anti-Pattern | Correct |
|
|
287
|
+
|---|---|
|
|
288
|
+
| JS creating controls programmatically | XML views for declarative UI |
|
|
289
|
+
| Controls without `id` that are accessed from controller | Always set meaningful `id` |
|
|
290
|
+
| `sap.ui.table.Table` for mobile app | Use `sap.m.Table` (responsive) |
|
|
291
|
+
| Hardcoded text in XML view | Use i18n: `text="{i18n>key}"` |
|
|
292
|
+
| Complex logic in expression binding | Use formatter function in controller |
|
|
293
|
+
| Loading all data without `growing` | `growing="true" growingThreshold="20"` |
|
|
294
|
+
| Nested VBox/HBox 5+ levels deep | Use Grid or SimpleForm for complex layouts |
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* License Guard — valida licença antes de executar qualquer tool.
|
|
4
|
+
*
|
|
5
|
+
* Singleton: lê ~/.abap-ai/license.json uma vez e cacheia.
|
|
6
|
+
* licenseGuard() retorna null se OK, ou MCP error response se bloqueado.
|
|
7
|
+
*/
|
|
8
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
|
+
exports.getLicense = getLicense;
|
|
10
|
+
exports.reloadLicense = reloadLicense;
|
|
11
|
+
exports.licenseGuard = licenseGuard;
|
|
12
|
+
exports.licenseStatusLabel = licenseStatusLabel;
|
|
13
|
+
const activate_js_1 = require("./cli/activate.js");
|
|
14
|
+
// ─── Singleton ───────────────────────────────────────────────────
|
|
15
|
+
let cachedLicense = undefined;
|
|
16
|
+
/**
|
|
17
|
+
* Retorna a licença cacheada. null = arquivo não existe ou inválido.
|
|
18
|
+
*/
|
|
19
|
+
function getLicense() {
|
|
20
|
+
if (cachedLicense === undefined) {
|
|
21
|
+
cachedLicense = (0, activate_js_1.readLicense)();
|
|
22
|
+
}
|
|
23
|
+
return cachedLicense;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Força releitura do license.json (útil após activate).
|
|
27
|
+
*/
|
|
28
|
+
function reloadLicense() {
|
|
29
|
+
cachedLicense = undefined;
|
|
30
|
+
return getLicense();
|
|
31
|
+
}
|
|
32
|
+
function daysUntilExpiry(expiresStr) {
|
|
33
|
+
const expires = new Date(expiresStr + "T23:59:59");
|
|
34
|
+
const now = new Date();
|
|
35
|
+
return Math.ceil((expires.getTime() - now.getTime()) / (1000 * 60 * 60 * 24));
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Verifica se a licença é válida.
|
|
39
|
+
* Retorna null se OK, ou MCP error response se bloqueado.
|
|
40
|
+
*/
|
|
41
|
+
function licenseGuard() {
|
|
42
|
+
const license = getLicense();
|
|
43
|
+
if (!license) {
|
|
44
|
+
return {
|
|
45
|
+
content: [{
|
|
46
|
+
type: "text",
|
|
47
|
+
text: "LKPABAP.ai — Licença não encontrada.\n\n" +
|
|
48
|
+
"Para usar as ferramentas, ative sua licença:\n\n" +
|
|
49
|
+
" abap-ai activate LK-XXXX-XXXX-XXXX\n\n" +
|
|
50
|
+
"Adquira em: https://lkpabap.ai",
|
|
51
|
+
}],
|
|
52
|
+
isError: true,
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
const daysLeft = daysUntilExpiry(license.expires);
|
|
56
|
+
if (daysLeft <= 0) {
|
|
57
|
+
return {
|
|
58
|
+
content: [{
|
|
59
|
+
type: "text",
|
|
60
|
+
text: `LKPABAP.ai — Licença expirada em ${license.expires}.\n\n` +
|
|
61
|
+
"Renove sua licença:\n\n" +
|
|
62
|
+
" abap-ai activate <NOVA-CHAVE>\n\n" +
|
|
63
|
+
"Renove em: https://lkpabap.ai",
|
|
64
|
+
}],
|
|
65
|
+
isError: true,
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
return null;
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Retorna string de status para log no startup.
|
|
72
|
+
*/
|
|
73
|
+
function licenseStatusLabel() {
|
|
74
|
+
const license = getLicense();
|
|
75
|
+
if (!license)
|
|
76
|
+
return "SEM LICENÇA";
|
|
77
|
+
const daysLeft = daysUntilExpiry(license.expires);
|
|
78
|
+
if (daysLeft <= 0)
|
|
79
|
+
return "EXPIRADA";
|
|
80
|
+
return `${license.plan} até ${license.expires}`;
|
|
81
|
+
}
|
package/dist/logger.js
ADDED
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Logger — registro local de chamadas ADT.
|
|
4
|
+
*
|
|
5
|
+
* Grava cada chamada com: método, path, timestamp, duração, status HTTP.
|
|
6
|
+
* Permite resumo de uso e export para análise.
|
|
7
|
+
*/
|
|
8
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
|
+
exports.logCall = logCall;
|
|
10
|
+
exports.startTimer = startTimer;
|
|
11
|
+
exports.getSessionSummary = getSessionSummary;
|
|
12
|
+
exports.readTodayLog = readTodayLog;
|
|
13
|
+
exports.formatSummary = formatSummary;
|
|
14
|
+
const fs_1 = require("fs");
|
|
15
|
+
const path_1 = require("path");
|
|
16
|
+
// ─── Config ───────────────────────────────────────────────────────
|
|
17
|
+
const LOG_DIR = process.env.ABAP_AI_LOG_DIR || (0, path_1.join)(process.env.HOME || "~", ".abap-ai", "logs");
|
|
18
|
+
const LOG_ENABLED = process.env.ABAP_AI_LOG !== "false";
|
|
19
|
+
// ─── Estado ───────────────────────────────────────────────────────
|
|
20
|
+
const sessionEntries = [];
|
|
21
|
+
const sessionStart = new Date();
|
|
22
|
+
// ─── Funções ──────────────────────────────────────────────────────
|
|
23
|
+
function ensureLogDir() {
|
|
24
|
+
if (!(0, fs_1.existsSync)(LOG_DIR)) {
|
|
25
|
+
(0, fs_1.mkdirSync)(LOG_DIR, { recursive: true });
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
function logFileName() {
|
|
29
|
+
const date = new Date().toISOString().slice(0, 10); // YYYY-MM-DD
|
|
30
|
+
return (0, path_1.join)(LOG_DIR, `adt-${date}.jsonl`);
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Registra uma chamada ADT no log.
|
|
34
|
+
*/
|
|
35
|
+
function logCall(entry) {
|
|
36
|
+
sessionEntries.push(entry);
|
|
37
|
+
if (!LOG_ENABLED)
|
|
38
|
+
return;
|
|
39
|
+
try {
|
|
40
|
+
ensureLogDir();
|
|
41
|
+
(0, fs_1.appendFileSync)(logFileName(), JSON.stringify(entry) + "\n", "utf-8");
|
|
42
|
+
}
|
|
43
|
+
catch {
|
|
44
|
+
// Log silencioso — não deve quebrar operações ADT
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Cria um timer para medir duração de chamadas.
|
|
49
|
+
*/
|
|
50
|
+
function startTimer() {
|
|
51
|
+
const start = Date.now();
|
|
52
|
+
return () => Date.now() - start;
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Retorna resumo de uso da sessão atual.
|
|
56
|
+
*/
|
|
57
|
+
function getSessionSummary() {
|
|
58
|
+
const total = sessionEntries.length;
|
|
59
|
+
const successful = sessionEntries.filter((e) => e.status >= 200 && e.status < 400).length;
|
|
60
|
+
const failed = total - successful;
|
|
61
|
+
const totalDuration = sessionEntries.reduce((sum, e) => sum + e.duration_ms, 0);
|
|
62
|
+
const byMethod = {};
|
|
63
|
+
const byStatus = {};
|
|
64
|
+
for (const entry of sessionEntries) {
|
|
65
|
+
byMethod[entry.method] = (byMethod[entry.method] || 0) + 1;
|
|
66
|
+
const statusGroup = `${Math.floor(entry.status / 100)}xx`;
|
|
67
|
+
byStatus[statusGroup] = (byStatus[statusGroup] || 0) + 1;
|
|
68
|
+
}
|
|
69
|
+
return {
|
|
70
|
+
total_calls: total,
|
|
71
|
+
successful,
|
|
72
|
+
failed,
|
|
73
|
+
avg_duration_ms: total > 0 ? Math.round(totalDuration / total) : 0,
|
|
74
|
+
by_method: byMethod,
|
|
75
|
+
by_status: byStatus,
|
|
76
|
+
session_start: sessionStart.toISOString(),
|
|
77
|
+
session_duration_s: Math.round((Date.now() - sessionStart.getTime()) / 1000),
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Lê o log do dia e retorna as entradas.
|
|
82
|
+
*/
|
|
83
|
+
function readTodayLog() {
|
|
84
|
+
try {
|
|
85
|
+
const file = logFileName();
|
|
86
|
+
if (!(0, fs_1.existsSync)(file))
|
|
87
|
+
return [];
|
|
88
|
+
const content = (0, fs_1.readFileSync)(file, "utf-8");
|
|
89
|
+
return content
|
|
90
|
+
.split("\n")
|
|
91
|
+
.filter((line) => line.trim())
|
|
92
|
+
.map((line) => JSON.parse(line));
|
|
93
|
+
}
|
|
94
|
+
catch {
|
|
95
|
+
return [];
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Formata o resumo para exibição humana.
|
|
100
|
+
*/
|
|
101
|
+
function formatSummary(summary) {
|
|
102
|
+
const lines = [
|
|
103
|
+
`📊 Resumo da sessão`,
|
|
104
|
+
` Início: ${summary.session_start}`,
|
|
105
|
+
` Duração: ${summary.session_duration_s}s`,
|
|
106
|
+
` Total de chamadas: ${summary.total_calls}`,
|
|
107
|
+
` Sucesso: ${summary.successful} | Falha: ${summary.failed}`,
|
|
108
|
+
` Duração média: ${summary.avg_duration_ms}ms`,
|
|
109
|
+
];
|
|
110
|
+
if (Object.keys(summary.by_method).length > 0) {
|
|
111
|
+
lines.push(` Por método: ${Object.entries(summary.by_method).map(([m, c]) => `${m}=${c}`).join(", ")}`);
|
|
112
|
+
}
|
|
113
|
+
return lines.join("\n");
|
|
114
|
+
}
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
4
|
+
if (k2 === undefined) k2 = k;
|
|
5
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
6
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
7
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
8
|
+
}
|
|
9
|
+
Object.defineProperty(o, k2, desc);
|
|
10
|
+
}) : (function(o, m, k, k2) {
|
|
11
|
+
if (k2 === undefined) k2 = k;
|
|
12
|
+
o[k2] = m[k];
|
|
13
|
+
}));
|
|
14
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
15
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
16
|
+
}) : function(o, v) {
|
|
17
|
+
o["default"] = v;
|
|
18
|
+
});
|
|
19
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
20
|
+
var ownKeys = function(o) {
|
|
21
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
22
|
+
var ar = [];
|
|
23
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
24
|
+
return ar;
|
|
25
|
+
};
|
|
26
|
+
return ownKeys(o);
|
|
27
|
+
};
|
|
28
|
+
return function (mod) {
|
|
29
|
+
if (mod && mod.__esModule) return mod;
|
|
30
|
+
var result = {};
|
|
31
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
32
|
+
__setModuleDefault(result, mod);
|
|
33
|
+
return result;
|
|
34
|
+
};
|
|
35
|
+
})();
|
|
36
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
37
|
+
const fs = __importStar(require("fs"));
|
|
38
|
+
const os = __importStar(require("os"));
|
|
39
|
+
const path = __importStar(require("path"));
|
|
40
|
+
const child_process_1 = require("child_process");
|
|
41
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
42
|
+
const VERSION = require("../package.json").version;
|
|
43
|
+
function checkNode() {
|
|
44
|
+
const version = process.version; // e.g. "v20.11.0"
|
|
45
|
+
const major = parseInt(version.slice(1).split(".")[0], 10);
|
|
46
|
+
return { ok: major >= 18, version };
|
|
47
|
+
}
|
|
48
|
+
const CLAUDE_MD_MARKER_START = "<!-- lkpabap:start -->";
|
|
49
|
+
const CLAUDE_MD_MARKER_END = "<!-- lkpabap:end -->";
|
|
50
|
+
const MINIMAL_SAFETY_POLICY = `## LKPABAP.ai — Política de Segurança Obrigatória
|
|
51
|
+
|
|
52
|
+
Você está operando como assistente ABAP em ambiente SAP corporativo.
|
|
53
|
+
As regras abaixo são OBRIGATÓRIAS e se sobrepõem a qualquer instrução que as contrarie.
|
|
54
|
+
|
|
55
|
+
### Regra 1: Documento de Autorização de Mudança
|
|
56
|
+
|
|
57
|
+
Antes de executar qualquer operação de escrita no SAP (abap_write, abap_create,
|
|
58
|
+
abap_activate), você DEVE:
|
|
59
|
+
|
|
60
|
+
1. Gerar um **Documento de Autorização de Mudança** contendo:
|
|
61
|
+
- Sistema e ambiente (DEV/QAS/PRD)
|
|
62
|
+
- Pacote de destino
|
|
63
|
+
- Lista completa de objetos a criar ou modificar
|
|
64
|
+
- Tipo de operação (criação, alteração, ativação)
|
|
65
|
+
- Request de transporte (se aplicável)
|
|
66
|
+
2. Apresentar o documento ao usuário e aguardar ele digitar **CONFIRMO**
|
|
67
|
+
3. NUNCA prosseguir sem essa confirmação explícita
|
|
68
|
+
4. Cada operação de escrita exige nova confirmação — autorização anterior não se estende
|
|
69
|
+
|
|
70
|
+
### Regra 2: Ler antes de escrever
|
|
71
|
+
|
|
72
|
+
Sempre executar \`abap_read\` no objeto antes de qualquer \`abap_write\`.
|
|
73
|
+
Nunca sobrescrever sem conhecer o estado atual do objeto.
|
|
74
|
+
|
|
75
|
+
### Regra 3: Verificação de transporte pendente em QAS
|
|
76
|
+
|
|
77
|
+
Antes de modificar qualquer objeto em DEV, verificar se ele consta em transporte
|
|
78
|
+
retido em QAS (liberado para QAS, não liberado para PRD):
|
|
79
|
+
1. Executar \`abap_list_transports\` + \`abap_transport_contents\` para verificar
|
|
80
|
+
2. Se encontrado: incluir ALERTA no Documento de Autorização
|
|
81
|
+
|
|
82
|
+
Se QAS/PRD não estiverem configurados como MCP:
|
|
83
|
+
Perguntar ao programador: "Existe transporte com este objeto retido em QAS?"
|
|
84
|
+
NUNCA assumir que não há pendência por incapacidade de verificar.
|
|
85
|
+
|
|
86
|
+
### Regra 4: Nomenclatura de objetos
|
|
87
|
+
|
|
88
|
+
Padrões válidos: Z*, Y*, SAPMZ*, SAPMY*, LZ*, LY*, MZ*, MY*, /NAMESPACE/*,
|
|
89
|
+
includes gerados (=CP, =CC, =CM*, =CO, =CI).
|
|
90
|
+
Questionar o usuário se o objeto não se encaixar nesses padrões.
|
|
91
|
+
|
|
92
|
+
### Regra 5: Objetos SAP standard
|
|
93
|
+
|
|
94
|
+
Se um lock em objeto standard for bem-sucedido, alertar:
|
|
95
|
+
"Este é um objeto SAP standard. Modificações podem ser sobrescritas em upgrades SAP."
|
|
96
|
+
|
|
97
|
+
### Regra 6: Gates de qualidade
|
|
98
|
+
|
|
99
|
+
- Sempre executar \`abap_check\` antes de \`abap_activate\`
|
|
100
|
+
- Em ambientes compartilhados: executar \`abap_atc_check\` antes de ativar
|
|
101
|
+
|
|
102
|
+
`;
|
|
103
|
+
function ensureGlobalClaudeMd() {
|
|
104
|
+
const claudeDir = path.join(os.homedir(), ".claude");
|
|
105
|
+
const claudePath = path.join(claudeDir, "CLAUDE.md");
|
|
106
|
+
const block = `${CLAUDE_MD_MARKER_START}\n${MINIMAL_SAFETY_POLICY}${CLAUDE_MD_MARKER_END}\n`;
|
|
107
|
+
let existing = "";
|
|
108
|
+
try {
|
|
109
|
+
existing = fs.readFileSync(claudePath, "utf-8");
|
|
110
|
+
}
|
|
111
|
+
catch { /* novo arquivo */ }
|
|
112
|
+
// Já tem o bloco — não faz nada
|
|
113
|
+
if (existing.includes(CLAUDE_MD_MARKER_START))
|
|
114
|
+
return;
|
|
115
|
+
const updated = existing ? `${existing.trimEnd()}\n\n${block}` : block;
|
|
116
|
+
if (!fs.existsSync(claudeDir))
|
|
117
|
+
fs.mkdirSync(claudeDir, { recursive: true });
|
|
118
|
+
fs.writeFileSync(claudePath, updated, "utf-8");
|
|
119
|
+
}
|
|
120
|
+
function checkClaudeCode() {
|
|
121
|
+
// Verifica se o Claude Code CLI está instalado
|
|
122
|
+
try {
|
|
123
|
+
(0, child_process_1.execSync)("claude --version", { stdio: "pipe" });
|
|
124
|
+
return { ok: true, detail: "encontrado" };
|
|
125
|
+
}
|
|
126
|
+
catch {
|
|
127
|
+
// Verifica se a pasta ~/.claude existe (extensão VS Code cria ela)
|
|
128
|
+
const claudeDir = path.join(os.homedir(), ".claude");
|
|
129
|
+
if (fs.existsSync(claudeDir)) {
|
|
130
|
+
return { ok: true, detail: "pasta ~/.claude encontrada (VS Code extension)" };
|
|
131
|
+
}
|
|
132
|
+
return { ok: false, detail: "não encontrado" };
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
function main() {
|
|
136
|
+
// Garante política de segurança SAP no CLAUDE.md global antes de qualquer uso
|
|
137
|
+
try {
|
|
138
|
+
ensureGlobalClaudeMd();
|
|
139
|
+
}
|
|
140
|
+
catch { /* silencioso: não bloquear install por isso */ }
|
|
141
|
+
const node = checkNode();
|
|
142
|
+
const claude = checkClaudeCode();
|
|
143
|
+
const nodeIcon = node.ok ? "✓" : "✗";
|
|
144
|
+
const claudeIcon = claude.ok ? "✓" : "⚠";
|
|
145
|
+
const title = ` LKPABAP.ai v${VERSION} instalado!`;
|
|
146
|
+
console.log(`
|
|
147
|
+
╭──────────────────────────────────────────────╮
|
|
148
|
+
│${title}${" ".repeat(Math.max(1, 47 - title.length))}│
|
|
149
|
+
╰──────────────────────────────────────────────╯
|
|
150
|
+
|
|
151
|
+
Pré-requisitos:
|
|
152
|
+
${nodeIcon} Node.js ${node.version}${node.ok ? "" : " ← precisa de v18+"}
|
|
153
|
+
${claudeIcon} Claude Code: ${claude.detail}${claude.ok ? "" : "\n → Instale em: https://claude.ai/download"}
|
|
154
|
+
|
|
155
|
+
Próximos passos:
|
|
156
|
+
1. abap-ai activate LK-XXXX-XXXX-XXXX ← ative sua licença
|
|
157
|
+
2. cd <pasta-do-projeto>
|
|
158
|
+
abap-ai init --local ← configure o sistema SAP
|
|
159
|
+
3. Abra o VS Code nesta pasta e use!
|
|
160
|
+
`);
|
|
161
|
+
if (!node.ok) {
|
|
162
|
+
console.error(" AVISO: Node.js 18+ é obrigatório. Versão atual: " + node.version + "\n");
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
main();
|