@open-rap/compiler 0.0.0-stage → 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/LICENSE +21 -0
- package/README.md +21 -2
- package/dist/agent/server.d.ts +35 -0
- package/dist/agent/server.js +408 -0
- package/dist/agent/server.js.map +1 -0
- package/dist/authoring/abapgit.d.ts +38 -0
- package/dist/authoring/abapgit.js +242 -0
- package/dist/authoring/abapgit.js.map +1 -0
- package/dist/authoring/catalog.d.ts +48 -0
- package/dist/authoring/catalog.js +117 -0
- package/dist/authoring/catalog.js.map +1 -0
- package/dist/authoring/floorplan.d.ts +28 -0
- package/dist/authoring/floorplan.js +31 -0
- package/dist/authoring/floorplan.js.map +1 -0
- package/dist/authoring/generate.d.ts +57 -0
- package/dist/authoring/generate.js +630 -0
- package/dist/authoring/generate.js.map +1 -0
- package/dist/authoring/mcp.d.ts +15 -0
- package/dist/authoring/mcp.js +52 -0
- package/dist/authoring/mcp.js.map +1 -0
- package/dist/authoring/new.d.ts +35 -0
- package/dist/authoring/new.js +389 -0
- package/dist/authoring/new.js.map +1 -0
- package/dist/bdef/ast.d.ts +249 -0
- package/dist/bdef/ast.js +3 -0
- package/dist/bdef/ast.js.map +1 -0
- package/dist/bdef/lexer.d.ts +105 -0
- package/dist/bdef/lexer.js +148 -0
- package/dist/bdef/lexer.js.map +1 -0
- package/dist/bdef/parser.d.ts +22 -0
- package/dist/bdef/parser.js +1012 -0
- package/dist/bdef/parser.js.map +1 -0
- package/dist/build.d.ts +26 -0
- package/dist/build.js +62 -0
- package/dist/build.js.map +1 -0
- package/dist/cds/adapter.d.ts +91 -0
- package/dist/cds/adapter.js +381 -0
- package/dist/cds/adapter.js.map +1 -0
- package/dist/cds/expression-ast.d.ts +65 -0
- package/dist/cds/expression-ast.js +330 -0
- package/dist/cds/expression-ast.js.map +1 -0
- package/dist/cli.d.ts +9 -0
- package/dist/cli.js +485 -0
- package/dist/cli.js.map +1 -0
- package/dist/dcl/parser.d.ts +59 -0
- package/dist/dcl/parser.js +338 -0
- package/dist/dcl/parser.js.map +1 -0
- package/dist/ddic/catalog.d.ts +17 -0
- package/dist/ddic/catalog.js +211 -0
- package/dist/ddic/catalog.js.map +1 -0
- package/dist/ddic/cds-types.d.ts +47 -0
- package/dist/ddic/cds-types.js +131 -0
- package/dist/ddic/cds-types.js.map +1 -0
- package/dist/ddic/table-source.d.ts +70 -0
- package/dist/ddic/table-source.js +500 -0
- package/dist/ddic/table-source.js.map +1 -0
- package/dist/ddic/xml.d.ts +65 -0
- package/dist/ddic/xml.js +201 -0
- package/dist/ddic/xml.js.map +1 -0
- package/dist/dev.d.ts +128 -0
- package/dist/dev.js +434 -0
- package/dist/dev.js.map +1 -0
- package/dist/diagnostics.d.ts +33 -0
- package/dist/diagnostics.js +53 -0
- package/dist/diagnostics.js.map +1 -0
- package/dist/gen/abap/derived-types.d.ts +26 -0
- package/dist/gen/abap/derived-types.js +323 -0
- package/dist/gen/abap/derived-types.js.map +1 -0
- package/dist/gen/abap/eml.d.ts +118 -0
- package/dist/gen/abap/eml.js +362 -0
- package/dist/gen/abap/eml.js.map +1 -0
- package/dist/gen/abap/enums.d.ts +13 -0
- package/dist/gen/abap/enums.js +74 -0
- package/dist/gen/abap/enums.js.map +1 -0
- package/dist/gen/abap/framework-http.d.ts +14 -0
- package/dist/gen/abap/framework-http.js +1049 -0
- package/dist/gen/abap/framework-http.js.map +1 -0
- package/dist/gen/abap/framework-query.d.ts +14 -0
- package/dist/gen/abap/framework-query.js +225 -0
- package/dist/gen/abap/framework-query.js.map +1 -0
- package/dist/gen/abap/framework-utils.d.ts +29 -0
- package/dist/gen/abap/framework-utils.js +3707 -0
- package/dist/gen/abap/framework-utils.js.map +1 -0
- package/dist/gen/abap/framework.d.ts +12 -0
- package/dist/gen/abap/framework.js +480 -0
- package/dist/gen/abap/framework.js.map +1 -0
- package/dist/gen/abap/generate.d.ts +40 -0
- package/dist/gen/abap/generate.js +238 -0
- package/dist/gen/abap/generate.js.map +1 -0
- package/dist/gen/abap/rewrite.d.ts +82 -0
- package/dist/gen/abap/rewrite.js +957 -0
- package/dist/gen/abap/rewrite.js.map +1 -0
- package/dist/gen/abap/shadow.d.ts +25 -0
- package/dist/gen/abap/shadow.js +175 -0
- package/dist/gen/abap/shadow.js.map +1 -0
- package/dist/gen/abap/tokens.d.ts +33 -0
- package/dist/gen/abap/tokens.js +87 -0
- package/dist/gen/abap/tokens.js.map +1 -0
- package/dist/gen/app/index.d.ts +25 -0
- package/dist/gen/app/index.js +322 -0
- package/dist/gen/app/index.js.map +1 -0
- package/dist/gen/csdl.d.ts +11 -0
- package/dist/gen/csdl.js +562 -0
- package/dist/gen/csdl.js.map +1 -0
- package/dist/gen/deploy.d.ts +12 -0
- package/dist/gen/deploy.js +53 -0
- package/dist/gen/deploy.js.map +1 -0
- package/dist/gen/docs/index.d.ts +35 -0
- package/dist/gen/docs/index.js +267 -0
- package/dist/gen/docs/index.js.map +1 -0
- package/dist/gen/docs/markdown.d.ts +23 -0
- package/dist/gen/docs/markdown.js +187 -0
- package/dist/gen/docs/markdown.js.map +1 -0
- package/dist/gen/docs/site.d.ts +17 -0
- package/dist/gen/docs/site.js +106 -0
- package/dist/gen/docs/site.js.map +1 -0
- package/dist/gen/keycloak.d.ts +21 -0
- package/dist/gen/keycloak.js +90 -0
- package/dist/gen/keycloak.js.map +1 -0
- package/dist/gen/launchpad/index.d.ts +48 -0
- package/dist/gen/launchpad/index.js +302 -0
- package/dist/gen/launchpad/index.js.map +1 -0
- package/dist/gen/launchpad/ui.d.ts +14 -0
- package/dist/gen/launchpad/ui.js +350 -0
- package/dist/gen/launchpad/ui.js.map +1 -0
- package/dist/gen/mcp.d.ts +23 -0
- package/dist/gen/mcp.js +244 -0
- package/dist/gen/mcp.js.map +1 -0
- package/dist/gen/sql-expressions.d.ts +12 -0
- package/dist/gen/sql-expressions.js +136 -0
- package/dist/gen/sql-expressions.js.map +1 -0
- package/dist/gen/sql.d.ts +38 -0
- package/dist/gen/sql.js +612 -0
- package/dist/gen/sql.js.map +1 -0
- package/dist/gen/ui/index.d.ts +7 -0
- package/dist/gen/ui/index.js +27 -0
- package/dist/gen/ui/index.js.map +1 -0
- package/dist/gen/ui/layout.d.ts +230 -0
- package/dist/gen/ui/layout.js +511 -0
- package/dist/gen/ui/layout.js.map +1 -0
- package/dist/gen/ui/static.d.ts +55 -0
- package/dist/gen/ui/static.js +2901 -0
- package/dist/gen/ui/static.js.map +1 -0
- package/dist/gen/ui/views.d.ts +10 -0
- package/dist/gen/ui/views.js +511 -0
- package/dist/gen/ui/views.js.map +1 -0
- package/dist/gen/vendor.d.ts +23 -0
- package/dist/gen/vendor.js +68 -0
- package/dist/gen/vendor.js.map +1 -0
- package/dist/gen/xml.d.ts +9 -0
- package/dist/gen/xml.js +42 -0
- package/dist/gen/xml.js.map +1 -0
- package/dist/index.d.ts +20 -0
- package/dist/index.js +20 -0
- package/dist/index.js.map +1 -0
- package/dist/input/project.d.ts +50 -0
- package/dist/input/project.js +128 -0
- package/dist/input/project.js.map +1 -0
- package/dist/ir/access-controls.d.ts +11 -0
- package/dist/ir/access-controls.js +148 -0
- package/dist/ir/access-controls.js.map +1 -0
- package/dist/ir/authorizations.d.ts +25 -0
- package/dist/ir/authorizations.js +178 -0
- package/dist/ir/authorizations.js.map +1 -0
- package/dist/ir/behaviors.d.ts +16 -0
- package/dist/ir/behaviors.js +548 -0
- package/dist/ir/behaviors.js.map +1 -0
- package/dist/ir/builder.d.ts +20 -0
- package/dist/ir/builder.js +505 -0
- package/dist/ir/builder.js.map +1 -0
- package/dist/ir/config.d.ts +25 -0
- package/dist/ir/config.js +130 -0
- package/dist/ir/config.js.map +1 -0
- package/dist/ir/dictionary.d.ts +54 -0
- package/dist/ir/dictionary.js +328 -0
- package/dist/ir/dictionary.js.map +1 -0
- package/dist/ir/entities.d.ts +58 -0
- package/dist/ir/entities.js +676 -0
- package/dist/ir/entities.js.map +1 -0
- package/dist/ir/expressions.d.ts +33 -0
- package/dist/ir/expressions.js +327 -0
- package/dist/ir/expressions.js.map +1 -0
- package/dist/ir/handlers.d.ts +63 -0
- package/dist/ir/handlers.js +398 -0
- package/dist/ir/handlers.js.map +1 -0
- package/dist/ir/number-ranges.d.ts +24 -0
- package/dist/ir/number-ranges.js +74 -0
- package/dist/ir/number-ranges.js.map +1 -0
- package/dist/ir/seed.d.ts +23 -0
- package/dist/ir/seed.js +78 -0
- package/dist/ir/seed.js.map +1 -0
- package/dist/ir/service.d.ts +21 -0
- package/dist/ir/service.js +96 -0
- package/dist/ir/service.js.map +1 -0
- package/dist/launchpad/config.d.ts +47 -0
- package/dist/launchpad/config.js +321 -0
- package/dist/launchpad/config.js.map +1 -0
- package/dist/launchpad/workspace.d.ts +48 -0
- package/dist/launchpad/workspace.js +166 -0
- package/dist/launchpad/workspace.js.map +1 -0
- package/dist/srvd/parser.d.ts +36 -0
- package/dist/srvd/parser.js +193 -0
- package/dist/srvd/parser.js.map +1 -0
- package/dist/standard/data.d.ts +18 -0
- package/dist/standard/data.js +534 -0
- package/dist/standard/data.js.map +1 -0
- package/dist/standard/index.d.ts +36 -0
- package/dist/standard/index.js +507 -0
- package/dist/standard/index.js.map +1 -0
- package/dist/version.d.ts +2 -0
- package/dist/version.js +4 -0
- package/dist/version.js.map +1 -0
- package/docs/AGENTS.md +79 -0
- package/docs/ANNOTATIONS.md +76 -0
- package/docs/DECISIONS.md +1526 -0
- package/docs/DEPLOYMENT.md +113 -0
- package/docs/DIAGNOSTICS.md +116 -0
- package/docs/FEATURES.md +287 -0
- package/docs/GUIDE.md +560 -0
- package/docs/KNOWN_GAPS.md +216 -0
- package/docs/MCP.md +84 -0
- package/docs/PLAN.md +690 -0
- package/docs/README.md +148 -0
- package/docs/REMOTE_APIS.md +121 -0
- package/docs/ROADMAP.md +35 -0
- package/docs/UTILITIES.md +95 -0
- package/package.json +49 -3
|
@@ -0,0 +1,630 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `rapt generate <src> <kind> <name>` (decision D46): one new object with the file names abapGit
|
|
3
|
+
* expects, like the creation wizards of the ABAP Development Tools. A skeleton when there is
|
|
4
|
+
* nothing to start from; derived from an existing object otherwise (`--from`): a view entity from
|
|
5
|
+
* its table, a projection from its view, a metadata extension, an access control or a behavior
|
|
6
|
+
* definition from an entity, a behavior pool from its behavior definition. The abapGit XML is
|
|
7
|
+
* written too (`rapt abapgit`). An existing file is never overwritten.
|
|
8
|
+
*/
|
|
9
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
10
|
+
import path from "node:path";
|
|
11
|
+
import { getAnnotation, isUuid } from "@open-rap/model";
|
|
12
|
+
import { parseBuiltInType } from "../ddic/catalog.js";
|
|
13
|
+
import { parseBdef } from "../bdef/parser.js";
|
|
14
|
+
import { abapGitXml, xmlElement } from "../ddic/table-source.js";
|
|
15
|
+
import { Diagnostics } from "../diagnostics.js";
|
|
16
|
+
import { filesOfType, loadProject } from "../input/project.js";
|
|
17
|
+
import { Dictionary } from "../ir/dictionary.js";
|
|
18
|
+
import { EntityResolver } from "../ir/entities.js";
|
|
19
|
+
import { generateAbapGitFiles } from "./abapgit.js";
|
|
20
|
+
export const OBJECT_KINDS = {
|
|
21
|
+
table: "database table (.tabl.astabl)",
|
|
22
|
+
view: "CDS view entity, from a table with --from (.ddls.asddls)",
|
|
23
|
+
projection: "CDS projection view, from a view with --from (.ddls.asddls)",
|
|
24
|
+
abstract: "CDS abstract entity, e.g. an action parameter (.ddls.asddls)",
|
|
25
|
+
custom: "CDS custom entity and its query class (.ddls.asddls, .clas.abap)",
|
|
26
|
+
tableentity: "CDS table entity: a table and its entity, the persistence of a BO (.ddls.asddls)",
|
|
27
|
+
type: "CDS simple type (.drty.acds)",
|
|
28
|
+
dataelement: "data element, of a built-in type (--type abap.char(10)) or a domain (--from) (.dtel.xml)",
|
|
29
|
+
domain: "domain of a built-in type (--type abap.char(1)) (.doma.xml)",
|
|
30
|
+
enum: "CDS enumerated type (.drty.acds)",
|
|
31
|
+
metadata: "metadata extension (UI annotations) of an entity (.ddlx.asddlxs)",
|
|
32
|
+
access: "access control (DCL) of an entity (.dcls.asdcls)",
|
|
33
|
+
behavior: "behavior definition of a root view or projection (.bdef.asbdef)",
|
|
34
|
+
pool: "behavior pool of a behavior definition, with its handlers (.clas.abap, .clas.locals_imp.abap)",
|
|
35
|
+
class: "ABAP class (.clas.abap)",
|
|
36
|
+
interface: "ABAP interface (.intf.abap)",
|
|
37
|
+
service: "service definition, exposing --expose A,B (.srvd.srvdsrv)",
|
|
38
|
+
binding: "OData V4 UI service binding of a service definition --for (.srvb.xml)",
|
|
39
|
+
};
|
|
40
|
+
const NAME = /^[A-Za-z][A-Za-z0-9_]*$/;
|
|
41
|
+
function sourceFolder(root) {
|
|
42
|
+
const dotAbapGit = path.join(root, ".abapgit.xml");
|
|
43
|
+
const folder = existsSync(dotAbapGit) ? (/<STARTING_FOLDER>([^<]*)<\/STARTING_FOLDER>/.exec(readFileSync(dotAbapGit, "utf8"))?.[1] ?? "/src/") : "/src/";
|
|
44
|
+
return path.join(root, folder.replace(/^\/+/, ""));
|
|
45
|
+
}
|
|
46
|
+
/** `travel_uuid` → `TravelUUID`, `agency_id` → `AgencyID`, `begin_date` → `BeginDate`. */
|
|
47
|
+
export function elementName(column) {
|
|
48
|
+
return column
|
|
49
|
+
.toLowerCase()
|
|
50
|
+
.split("_")
|
|
51
|
+
.filter((p) => p !== "")
|
|
52
|
+
.map((p) => (p === "uuid" || p === "id" ? p.toUpperCase() : p.charAt(0).toUpperCase() + p.slice(1)))
|
|
53
|
+
.join("");
|
|
54
|
+
}
|
|
55
|
+
/** `ZR_Travel` → `Travel`, `ZRT_C_Booking` → `Booking`: the alias of an entity in a BDEF. */
|
|
56
|
+
export function aliasOf(entity) {
|
|
57
|
+
const parts = entity.split("_");
|
|
58
|
+
const last = parts[parts.length - 1] ?? entity;
|
|
59
|
+
return last.charAt(0).toUpperCase() + last.slice(1);
|
|
60
|
+
}
|
|
61
|
+
/** The behavior pool of a BDEF, as SAP names it: `ZR_TRAVEL` → `ZBP_R_TRAVEL`. */
|
|
62
|
+
export function poolName(bdef) {
|
|
63
|
+
const upper = bdef.toUpperCase();
|
|
64
|
+
const rest = upper.replace(/^[ZY]_?/, "");
|
|
65
|
+
return `${upper.charAt(0) === "Y" ? "Y" : "Z"}BP_${rest}`.slice(0, 30);
|
|
66
|
+
}
|
|
67
|
+
const ADMIN = {
|
|
68
|
+
ABP_CREATION_USER: "@Semantics.user.createdBy: true",
|
|
69
|
+
ABP_CREATION_TSTMPL: "@Semantics.systemDateTime.createdAt: true",
|
|
70
|
+
ABP_LASTCHANGE_USER: "@Semantics.user.lastChangedBy: true",
|
|
71
|
+
ABP_LASTCHANGE_TSTMPL: "@Semantics.systemDateTime.lastChangedAt: true",
|
|
72
|
+
ABP_LOCINST_LASTCHANGE_TSTMPL: "@Semantics.systemDateTime.localInstanceLastChangedAt: true",
|
|
73
|
+
};
|
|
74
|
+
const label = (text) => text.replace(/([a-z0-9])([A-Z])/g, "$1 $2").replace(/'/g, "''");
|
|
75
|
+
function context(root) {
|
|
76
|
+
const project = loadProject(root);
|
|
77
|
+
const diagnostics = new Diagnostics();
|
|
78
|
+
const dictionary = new Dictionary(project, diagnostics);
|
|
79
|
+
return { project, dictionary, entities: new EntityResolver(project, dictionary, diagnostics) };
|
|
80
|
+
}
|
|
81
|
+
function entity(ctx, name, what) {
|
|
82
|
+
if (name === undefined) {
|
|
83
|
+
return `${what}: give the entity with --from`;
|
|
84
|
+
}
|
|
85
|
+
return ctx.entities.resolve(name) ?? `${name}: no CDS entity of that name in the sources`;
|
|
86
|
+
}
|
|
87
|
+
// ------------------------------------------------------------------------------------- templates
|
|
88
|
+
function tableSource(name, description) {
|
|
89
|
+
return `@EndUserText.label : '${description}'
|
|
90
|
+
@AbapCatalog.enhancement.category : #NOT_EXTENSIBLE
|
|
91
|
+
@AbapCatalog.tableCategory : #TRANSPARENT
|
|
92
|
+
@AbapCatalog.deliveryClass : #A
|
|
93
|
+
@AbapCatalog.dataMaintenance : #RESTRICTED
|
|
94
|
+
define table ${name} {
|
|
95
|
+
key client : abap.clnt not null;
|
|
96
|
+
key uuid : sysuuid_x16 not null;
|
|
97
|
+
description : abap.char(256);
|
|
98
|
+
created_by : abp_creation_user;
|
|
99
|
+
created_at : abp_creation_tstmpl;
|
|
100
|
+
last_changed_by : abp_lastchange_user;
|
|
101
|
+
last_changed_at : abp_lastchange_tstmpl;
|
|
102
|
+
local_last_changed_at : abp_locinst_lastchange_tstmpl;
|
|
103
|
+
}
|
|
104
|
+
`;
|
|
105
|
+
}
|
|
106
|
+
function viewSource(name, table, root, description) {
|
|
107
|
+
const columns = table.columns.filter((c) => !c.client);
|
|
108
|
+
const width = Math.max(...columns.map((c) => c.name.length)) + 1;
|
|
109
|
+
const elements = columns.map((c) => {
|
|
110
|
+
const admin = ADMIN[c.type.dataElement ?? ""];
|
|
111
|
+
const lines = [` @EndUserText.label: '${label(elementName(c.name))}'`, ...(admin === undefined ? [] : [` ${admin}`])];
|
|
112
|
+
return `${lines.join("\n")}\n${c.key ? " key " : " "}${c.name.padEnd(width)}as ${elementName(c.name)}`;
|
|
113
|
+
});
|
|
114
|
+
return `@AccessControl.authorizationCheck: #NOT_REQUIRED
|
|
115
|
+
@EndUserText.label: '${description}'
|
|
116
|
+
define ${root ? "root " : ""}view entity ${name}
|
|
117
|
+
as select from ${table.name}
|
|
118
|
+
{
|
|
119
|
+
${elements.join(",\n")}
|
|
120
|
+
}
|
|
121
|
+
`;
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* The name of the projection of another entity, by the same renaming as `name` ← `base`:
|
|
125
|
+
* ZC_Travel ← ZR_Travel gives ZC_Booking ← ZR_Booking.
|
|
126
|
+
*/
|
|
127
|
+
function renamed(name, base, target) {
|
|
128
|
+
let common = 0;
|
|
129
|
+
while (common < Math.min(name.length, base.length) && name[name.length - 1 - common]?.toUpperCase() === base[base.length - 1 - common]?.toUpperCase()) {
|
|
130
|
+
common++;
|
|
131
|
+
}
|
|
132
|
+
const from = base.slice(0, base.length - common);
|
|
133
|
+
const to = name.slice(0, name.length - common);
|
|
134
|
+
return from !== "" && target.toUpperCase().startsWith(from.toUpperCase()) ? `${to}${target.slice(from.length)}` : target;
|
|
135
|
+
}
|
|
136
|
+
function projectionSource(name, base, description) {
|
|
137
|
+
const associations = base.associations.map((a) => {
|
|
138
|
+
const target = renamed(name, base.name, a.target);
|
|
139
|
+
switch (a.kind) {
|
|
140
|
+
case "composition":
|
|
141
|
+
return ` ${a.name} : redirected to composition child ${target}`;
|
|
142
|
+
case "toParent":
|
|
143
|
+
return ` ${a.name} : redirected to parent ${target}`;
|
|
144
|
+
default:
|
|
145
|
+
return ` ${a.name}`;
|
|
146
|
+
}
|
|
147
|
+
});
|
|
148
|
+
const elements = [...base.fields.map((f) => `${f.key ? " key " : " "}${f.name}`), ...associations];
|
|
149
|
+
return `@AccessControl.authorizationCheck: #NOT_REQUIRED
|
|
150
|
+
@EndUserText.label: '${description}'
|
|
151
|
+
@Metadata.allowExtensions: true
|
|
152
|
+
@Search.searchable: true
|
|
153
|
+
define ${base.root ? "root " : ""}view entity ${name}
|
|
154
|
+
provider contract transactional_query
|
|
155
|
+
as projection on ${base.name}
|
|
156
|
+
{
|
|
157
|
+
${elements.join(",\n")}
|
|
158
|
+
}
|
|
159
|
+
`;
|
|
160
|
+
}
|
|
161
|
+
function abstractSource(name, description) {
|
|
162
|
+
return `@EndUserText.label: '${description}'
|
|
163
|
+
define abstract entity ${name}
|
|
164
|
+
{
|
|
165
|
+
Value : abap.char(40);
|
|
166
|
+
}
|
|
167
|
+
`;
|
|
168
|
+
}
|
|
169
|
+
function tableEntitySource(name, root, description) {
|
|
170
|
+
return `@ClientHandling.type: #CLIENT_DEPENDENT
|
|
171
|
+
@AbapCatalog.deliveryClass: #APPLICATION_DATA
|
|
172
|
+
@AccessControl.authorizationCheck: #NOT_REQUIRED
|
|
173
|
+
@EndUserText.label: '${description}'
|
|
174
|
+
define ${root ? "root " : ""}table entity ${name}
|
|
175
|
+
{
|
|
176
|
+
key ID : abap.char(10);
|
|
177
|
+
Description : abap.char(40);
|
|
178
|
+
}
|
|
179
|
+
`;
|
|
180
|
+
}
|
|
181
|
+
function simpleTypeSource(name, description) {
|
|
182
|
+
return `@EndUserText.label: '${description}'
|
|
183
|
+
define type ${name} : abap.char(40);
|
|
184
|
+
`;
|
|
185
|
+
}
|
|
186
|
+
function enumTypeSource(name, description) {
|
|
187
|
+
return `@EndUserText.label: '${description}'
|
|
188
|
+
define type ${name} : abap.char(1) enum
|
|
189
|
+
{
|
|
190
|
+
@EndUserText.label: 'Initial'
|
|
191
|
+
INITIAL_VALUE = initial;
|
|
192
|
+
@EndUserText.label: 'Value A'
|
|
193
|
+
VALUE_A = 'A';
|
|
194
|
+
}
|
|
195
|
+
`;
|
|
196
|
+
}
|
|
197
|
+
function customSource(name, query, description) {
|
|
198
|
+
return `@EndUserText.label: '${description}'
|
|
199
|
+
@ObjectModel.query.implementedBy: 'ABAP:${query.toUpperCase()}'
|
|
200
|
+
define custom entity ${name}
|
|
201
|
+
{
|
|
202
|
+
key ID : abap.char(10);
|
|
203
|
+
Description : abap.char(40);
|
|
204
|
+
}
|
|
205
|
+
`;
|
|
206
|
+
}
|
|
207
|
+
function queryClass(name, entityName) {
|
|
208
|
+
return `"! <p class="shorttext synchronized">Query of ${entityName}</p>
|
|
209
|
+
CLASS ${name.toLowerCase()} DEFINITION PUBLIC FINAL CREATE PUBLIC.
|
|
210
|
+
PUBLIC SECTION.
|
|
211
|
+
INTERFACES if_rap_query_provider.
|
|
212
|
+
ENDCLASS.
|
|
213
|
+
|
|
214
|
+
|
|
215
|
+
|
|
216
|
+
CLASS ${name.toLowerCase()} IMPLEMENTATION.
|
|
217
|
+
|
|
218
|
+
METHOD if_rap_query_provider~select.
|
|
219
|
+
DATA rows TYPE STANDARD TABLE OF ${entityName.toLowerCase()} WITH EMPTY KEY.
|
|
220
|
+
" read the data (an API, a table…), then apply the filter, the sort order and the paging
|
|
221
|
+
IF io_request->is_total_numb_of_rec_requested( ).
|
|
222
|
+
io_response->set_total_number_of_records( lines( rows ) ).
|
|
223
|
+
ENDIF.
|
|
224
|
+
IF io_request->is_data_requested( ).
|
|
225
|
+
io_response->set_data( rows ).
|
|
226
|
+
ENDIF.
|
|
227
|
+
ENDMETHOD.
|
|
228
|
+
|
|
229
|
+
ENDCLASS.
|
|
230
|
+
`;
|
|
231
|
+
}
|
|
232
|
+
function metadataSource(target) {
|
|
233
|
+
const visible = target.fields.filter((f) => !(isUuid(f.type) && f.key) && getAnnotation(f.annotations, "Semantics.systemDateTime.localInstanceLastChangedAt") !== true);
|
|
234
|
+
const title = visible.find((f) => !f.key || target.fields.length === 1) ?? target.fields[0];
|
|
235
|
+
const typeName = aliasOf(target.name);
|
|
236
|
+
const blocks = target.fields.map((f, i) => {
|
|
237
|
+
if (!visible.includes(f)) {
|
|
238
|
+
return `${i === 0 ? "" : "\n"} @UI.hidden: true\n ${f.name};`;
|
|
239
|
+
}
|
|
240
|
+
const position = (visible.indexOf(f) + 1) * 10;
|
|
241
|
+
const selection = visible.indexOf(f) < 3 ? `,\n selectionField: [ { position: ${position} } ]` : "";
|
|
242
|
+
return `${i === 0 ? "" : "\n"} @UI: { lineItem: [ { position: ${position}, importance: #${visible.indexOf(f) < 3 ? "HIGH" : "MEDIUM"} } ],
|
|
243
|
+
identification: [ { position: ${position} } ]${selection} }
|
|
244
|
+
${f.name};`;
|
|
245
|
+
});
|
|
246
|
+
const facet = ` @UI.facet: [ { id: '${typeName}', purpose: #STANDARD, type: #IDENTIFICATION_REFERENCE, label: '${typeName}', position: 10 } ]\n`;
|
|
247
|
+
return `@Metadata.layer: #CORE
|
|
248
|
+
@UI.headerInfo: {
|
|
249
|
+
typeName: '${typeName}',
|
|
250
|
+
typeNamePlural: '${typeName.endsWith("s") ? typeName : `${typeName}s`}'${title === undefined ? "" : `,\n title: { type: #STANDARD, value: '${title.name}' }`}
|
|
251
|
+
}
|
|
252
|
+
annotate entity ${target.name} with
|
|
253
|
+
{
|
|
254
|
+
${facet}${blocks.join("\n")}
|
|
255
|
+
}
|
|
256
|
+
`;
|
|
257
|
+
}
|
|
258
|
+
function accessSource(target, description) {
|
|
259
|
+
return `@EndUserText.label: '${description}'
|
|
260
|
+
@MappingRole: true
|
|
261
|
+
define role ${target.name} {
|
|
262
|
+
grant select on ${target.name};
|
|
263
|
+
}
|
|
264
|
+
`;
|
|
265
|
+
}
|
|
266
|
+
/** A managed BDEF on a root view entity and its compositions, or a projection BDEF. */
|
|
267
|
+
function behaviorSource(ctx, rootEntity) {
|
|
268
|
+
const tree = [];
|
|
269
|
+
const walk = (current, parent) => {
|
|
270
|
+
tree.push(parent === undefined ? { entity: current } : { entity: current, parent });
|
|
271
|
+
for (const composition of current.associations.filter((a) => a.kind === "composition")) {
|
|
272
|
+
const child = ctx.entities.resolve(composition.target);
|
|
273
|
+
const toParent = child?.associations.find((a) => a.kind === "toParent");
|
|
274
|
+
if (child !== undefined && toParent !== undefined) {
|
|
275
|
+
walk(child, { entity: current, toParent: toParent.name, foreignKey: toParent.on.map((o) => o.source) });
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
};
|
|
279
|
+
walk(rootEntity);
|
|
280
|
+
const compositions = (e) => e.associations.filter((a) => a.kind === "composition").map((a) => ` association ${a.name} { create; }`);
|
|
281
|
+
if (rootEntity.kind === "projection") {
|
|
282
|
+
return `projection;
|
|
283
|
+
strict ( 2 );
|
|
284
|
+
${tree
|
|
285
|
+
.map(({ entity: e, parent }) => {
|
|
286
|
+
const etag = e.fields.some((f) => isAdmin(f, "localInstanceLastChangedAt")) ? "\nuse etag" : "";
|
|
287
|
+
const ops = parent === undefined ? [" use create;", " use update;", " use delete;"] : [" use update;", " use delete;"];
|
|
288
|
+
const associations = [...e.associations.filter((a) => a.kind === "composition").map((a) => ` use association ${a.name} { create; }`), ...(parent === undefined ? [] : [` use association ${parent.toParent};`])];
|
|
289
|
+
return `
|
|
290
|
+
define behavior for ${e.name} alias ${aliasOf(e.name)}${etag}
|
|
291
|
+
{
|
|
292
|
+
${[...ops, ...(associations.length === 0 ? [] : ["", ...associations])].join("\n")}
|
|
293
|
+
}`;
|
|
294
|
+
})
|
|
295
|
+
.join("\n")}
|
|
296
|
+
`;
|
|
297
|
+
}
|
|
298
|
+
const root = tree[0]?.entity ?? rootEntity;
|
|
299
|
+
return `managed implementation in class ${poolName(root.name).toLowerCase()} unique;
|
|
300
|
+
strict ( 2 );
|
|
301
|
+
${tree
|
|
302
|
+
.map(({ entity: e, parent }) => {
|
|
303
|
+
const table = e.source ?? "";
|
|
304
|
+
const etagField = e.fields.find((f) => isAdmin(f, "localInstanceLastChangedAt"));
|
|
305
|
+
const uuidKeys = e.fields.filter((f) => f.key && isUuid(f.type));
|
|
306
|
+
const readonly = e.fields.filter((f) => f.semantics !== undefined).map((f) => f.name);
|
|
307
|
+
const parentKeys = parent === undefined ? [] : e.fields.filter((f) => !f.key && parent.foreignKey.some((k) => k.toUpperCase() === f.name.toUpperCase())).map((f) => f.name);
|
|
308
|
+
const header = [
|
|
309
|
+
`define behavior for ${e.name} alias ${aliasOf(e.name)}`,
|
|
310
|
+
`persistent table ${table}`,
|
|
311
|
+
parent === undefined ? "lock master" : `lock dependent by ${parent.toParent}`,
|
|
312
|
+
parent === undefined ? "authorization master ( global )" : `authorization dependent by ${parent.toParent}`,
|
|
313
|
+
...(etagField === undefined ? [] : [`etag master ${etagField.name}`]),
|
|
314
|
+
];
|
|
315
|
+
const body = [
|
|
316
|
+
...(parent === undefined ? [" create;", " update;", " delete;"] : [" update;", " delete;"]),
|
|
317
|
+
"",
|
|
318
|
+
...(uuidKeys.length === 1 ? [` field ( numbering : managed, readonly ) ${uuidKeys[0]?.name ?? ""};`] : []),
|
|
319
|
+
...(readonly.length + parentKeys.length > 0 ? [` field ( readonly ) ${[...parentKeys, ...readonly].join(", ")};`] : []),
|
|
320
|
+
...compositions(e),
|
|
321
|
+
...(parent === undefined ? [] : [` association ${parent.toParent};`]),
|
|
322
|
+
"",
|
|
323
|
+
` mapping for ${table} corresponding`,
|
|
324
|
+
" {",
|
|
325
|
+
...e.fields.filter((f) => f.column !== undefined).map((f) => ` ${f.name} = ${f.column ?? ""};`),
|
|
326
|
+
" }",
|
|
327
|
+
];
|
|
328
|
+
return `\n${header.join("\n")}\n{\n${body.join("\n").replace(/\n\n\n/g, "\n\n")}\n}`;
|
|
329
|
+
})
|
|
330
|
+
.join("\n")}
|
|
331
|
+
`;
|
|
332
|
+
}
|
|
333
|
+
function isAdmin(field, semantics) {
|
|
334
|
+
return field.semantics === semantics;
|
|
335
|
+
}
|
|
336
|
+
function classSource(name, description) {
|
|
337
|
+
return `"! <p class="shorttext synchronized">${description}</p>
|
|
338
|
+
CLASS ${name.toLowerCase()} DEFINITION PUBLIC FINAL CREATE PUBLIC.
|
|
339
|
+
PUBLIC SECTION.
|
|
340
|
+
PROTECTED SECTION.
|
|
341
|
+
PRIVATE SECTION.
|
|
342
|
+
ENDCLASS.
|
|
343
|
+
|
|
344
|
+
|
|
345
|
+
|
|
346
|
+
CLASS ${name.toLowerCase()} IMPLEMENTATION.
|
|
347
|
+
ENDCLASS.
|
|
348
|
+
`;
|
|
349
|
+
}
|
|
350
|
+
function interfaceSource(name, description) {
|
|
351
|
+
return `"! <p class="shorttext synchronized">${description}</p>
|
|
352
|
+
INTERFACE ${name.toLowerCase()} PUBLIC.
|
|
353
|
+
ENDINTERFACE.
|
|
354
|
+
`;
|
|
355
|
+
}
|
|
356
|
+
/** The behavior pool of a managed BDEF: one handler per entity, global authorization. */
|
|
357
|
+
function poolSources(name, bdefName, bdefSource) {
|
|
358
|
+
const parsed = parseBdef(bdefSource);
|
|
359
|
+
if (parsed.ast === undefined) {
|
|
360
|
+
return `${bdefName}: the behavior definition does not parse (${parsed.errors[0]?.message ?? ""})`;
|
|
361
|
+
}
|
|
362
|
+
const root = parsed.ast.behaviors.find((b) => b.root) ?? parsed.ast.behaviors[0];
|
|
363
|
+
if (root === undefined) {
|
|
364
|
+
return `${bdefName}: no "define behavior for" in the behavior definition`;
|
|
365
|
+
}
|
|
366
|
+
const handlers = parsed.ast.behaviors.map((b) => {
|
|
367
|
+
const alias = b.alias?.text ?? b.entity.text;
|
|
368
|
+
const local = `lhc_${alias}`.slice(0, 30);
|
|
369
|
+
const authorization = b.options.some((o) => o.kind === "authorizationMaster");
|
|
370
|
+
const definition = authorization
|
|
371
|
+
? `
|
|
372
|
+
PRIVATE SECTION.
|
|
373
|
+
METHODS get_global_authorizations FOR GLOBAL AUTHORIZATION
|
|
374
|
+
IMPORTING REQUEST requested_authorizations FOR ${alias} RESULT result.`
|
|
375
|
+
: "";
|
|
376
|
+
const implementation = authorization
|
|
377
|
+
? `
|
|
378
|
+
METHOD get_global_authorizations.
|
|
379
|
+
" every activity is allowed: check the authorizations here (AUTHORITY-CHECK OBJECT …)
|
|
380
|
+
IF requested_authorizations-%create = if_abap_behv=>mk-on.
|
|
381
|
+
result-%create = if_abap_behv=>auth-allowed.
|
|
382
|
+
ENDIF.
|
|
383
|
+
IF requested_authorizations-%update = if_abap_behv=>mk-on.
|
|
384
|
+
result-%update = if_abap_behv=>auth-allowed.
|
|
385
|
+
ENDIF.
|
|
386
|
+
IF requested_authorizations-%delete = if_abap_behv=>mk-on.
|
|
387
|
+
result-%delete = if_abap_behv=>auth-allowed.
|
|
388
|
+
ENDIF.
|
|
389
|
+
ENDMETHOD.
|
|
390
|
+
`
|
|
391
|
+
: "";
|
|
392
|
+
return `CLASS ${local} DEFINITION INHERITING FROM cl_abap_behavior_handler.${definition}
|
|
393
|
+
ENDCLASS.
|
|
394
|
+
|
|
395
|
+
CLASS ${local} IMPLEMENTATION.${implementation}
|
|
396
|
+
ENDCLASS.`;
|
|
397
|
+
});
|
|
398
|
+
return {
|
|
399
|
+
global: `"! <p class="shorttext synchronized">Behavior pool of ${root.entity.text.toUpperCase()}</p>
|
|
400
|
+
CLASS ${name.toLowerCase()} DEFINITION PUBLIC ABSTRACT FINAL FOR BEHAVIOR OF ${root.entity.text.toLowerCase()}.
|
|
401
|
+
ENDCLASS.
|
|
402
|
+
|
|
403
|
+
|
|
404
|
+
|
|
405
|
+
CLASS ${name.toLowerCase()} IMPLEMENTATION.
|
|
406
|
+
ENDCLASS.
|
|
407
|
+
`,
|
|
408
|
+
locals: `${handlers.join("\n\n")}\n`,
|
|
409
|
+
};
|
|
410
|
+
}
|
|
411
|
+
function serviceSource(name, exposed, description) {
|
|
412
|
+
return `@EndUserText.label: '${description}'
|
|
413
|
+
define service ${name} {
|
|
414
|
+
${exposed.map((e) => ` expose ${e} as ${aliasOf(e)};`).join("\n")}
|
|
415
|
+
}
|
|
416
|
+
`;
|
|
417
|
+
}
|
|
418
|
+
/** The DDIC type of a built-in type: DATATYPE, LENG, DECIMALS. */
|
|
419
|
+
function ddicOf(type) {
|
|
420
|
+
const datatype = type.kind === "bool" ? "CHAR" : type.kind === "timestamp" || type.kind === "timestampl" ? "DEC" : type.kind.toUpperCase();
|
|
421
|
+
const length = type.kind === "bool" ? 1 : type.kind === "timestamp" ? 15 : type.kind === "timestampl" ? 21 : type.length;
|
|
422
|
+
const decimals = type.kind === "timestampl" ? 7 : type.decimals;
|
|
423
|
+
return [
|
|
424
|
+
["DATATYPE", datatype],
|
|
425
|
+
["LENG", length === undefined ? undefined : String(length).padStart(6, "0")],
|
|
426
|
+
["DECIMALS", decimals === undefined ? undefined : String(decimals).padStart(6, "0")],
|
|
427
|
+
];
|
|
428
|
+
}
|
|
429
|
+
/** A data element (DTEL), on a domain or a built-in type, with its field labels. */
|
|
430
|
+
function dataElementXml(name, description, base) {
|
|
431
|
+
const text = description.slice(0, 60);
|
|
432
|
+
return abapGitXml("DTEL", xmlElement("DD04V", [
|
|
433
|
+
["ROLLNAME", name.toUpperCase()],
|
|
434
|
+
["DDLANGUAGE", "E"],
|
|
435
|
+
...(base.domain === undefined ? [] : [["DOMNAME", base.domain.toUpperCase()]]),
|
|
436
|
+
["HEADLEN", "55"],
|
|
437
|
+
["SCRLEN1", "10"],
|
|
438
|
+
["SCRLEN2", "20"],
|
|
439
|
+
["SCRLEN3", "40"],
|
|
440
|
+
["DDTEXT", text],
|
|
441
|
+
["REPTEXT", text],
|
|
442
|
+
["SCRTEXT_S", text.slice(0, 10)],
|
|
443
|
+
["SCRTEXT_M", text.slice(0, 20)],
|
|
444
|
+
["SCRTEXT_L", text.slice(0, 40)],
|
|
445
|
+
["DTELMASTER", "E"],
|
|
446
|
+
...(base.type === undefined ? [] : [["REFKIND", "D"], ...ddicOf(base.type)]),
|
|
447
|
+
], 3));
|
|
448
|
+
}
|
|
449
|
+
/** A domain (DOMA) of a built-in type. */
|
|
450
|
+
function domainXml(name, description, type) {
|
|
451
|
+
return abapGitXml("DOMA", xmlElement("DD01V", [["DOMNAME", name.toUpperCase()], ["DDLANGUAGE", "E"], ...ddicOf(type), ["OUTPUTLEN", type.length === undefined ? undefined : String(type.length).padStart(6, "0")], ["DDTEXT", description.slice(0, 60)], ["DOMMASTER", "E"]], 3));
|
|
452
|
+
}
|
|
453
|
+
function bindingSource(name, service, description, webApi = false) {
|
|
454
|
+
const services = ` <SERVICES>
|
|
455
|
+
<item>
|
|
456
|
+
<NAME>${name.toUpperCase()}</NAME>
|
|
457
|
+
<VERSIONS>
|
|
458
|
+
<item>
|
|
459
|
+
<SERVICE_VERSION>0001</SERVICE_VERSION>
|
|
460
|
+
<SERVICE_DEFINITION>${service.toUpperCase()}</SERVICE_DEFINITION>
|
|
461
|
+
<RELEASE_STATE>NOT_RELEASED</RELEASE_STATE>
|
|
462
|
+
</item>
|
|
463
|
+
</VERSIONS>
|
|
464
|
+
</item>
|
|
465
|
+
</SERVICES>`;
|
|
466
|
+
const header = xmlElement("SRVB", [
|
|
467
|
+
["NAME", name.toUpperCase()],
|
|
468
|
+
["DESCRIPTION", description.slice(0, 60)],
|
|
469
|
+
["LANGUAGE", "EN"],
|
|
470
|
+
["BINDING_TYPE", "ODATA"],
|
|
471
|
+
["BINDING_TYPE_VERSION", "V4"],
|
|
472
|
+
["BINDING_TYPE_CATEGORY", webApi ? "1" : "0"],
|
|
473
|
+
], 3);
|
|
474
|
+
return abapGitXml("SRVB", header.replace(/\n {3}<\/SRVB>$/, `\n${services}\n </SRVB>`));
|
|
475
|
+
}
|
|
476
|
+
// ------------------------------------------------------------------------------------- command
|
|
477
|
+
/** Writes a new object into the project `dir`. */
|
|
478
|
+
export function generateObject(dir, kind, name, options = {}) {
|
|
479
|
+
const root = path.resolve(dir);
|
|
480
|
+
if (!(kind in OBJECT_KINDS)) {
|
|
481
|
+
return { written: [], error: `${kind}: unknown kind, one of ${Object.keys(OBJECT_KINDS).join(", ")}` };
|
|
482
|
+
}
|
|
483
|
+
if (!NAME.test(name)) {
|
|
484
|
+
return { written: [], error: `${name}: an object name is made of letters, digits and _, starting with a letter` };
|
|
485
|
+
}
|
|
486
|
+
const limit = kind === "table" ? 16 : kind === "tableentity" ? 25 : 30;
|
|
487
|
+
if (name.length > limit) {
|
|
488
|
+
return { written: [], error: `${name}: at most ${limit} characters` };
|
|
489
|
+
}
|
|
490
|
+
const src = sourceFolder(root);
|
|
491
|
+
const description = options.description ?? label(aliasOf(name));
|
|
492
|
+
const lower = name.toLowerCase();
|
|
493
|
+
const files = [];
|
|
494
|
+
const ctx = () => context(root);
|
|
495
|
+
switch (kind) {
|
|
496
|
+
case "table":
|
|
497
|
+
files.push([`${lower}.tabl.astabl`, tableSource(lower, description)]);
|
|
498
|
+
break;
|
|
499
|
+
case "view": {
|
|
500
|
+
if (options.from === undefined) {
|
|
501
|
+
return { written: [], error: "rapt generate view needs the table: --from <table>" };
|
|
502
|
+
}
|
|
503
|
+
const table = ctx().dictionary.table(options.from.toLowerCase(), new Diagnostics());
|
|
504
|
+
if (table === undefined) {
|
|
505
|
+
return { written: [], error: `${options.from}: no table of that name in the sources` };
|
|
506
|
+
}
|
|
507
|
+
files.push([`${lower}.ddls.asddls`, viewSource(name, table, options.root === true, description)]);
|
|
508
|
+
break;
|
|
509
|
+
}
|
|
510
|
+
case "projection": {
|
|
511
|
+
const base = entity(ctx(), options.from, "rapt generate projection");
|
|
512
|
+
if (typeof base === "string")
|
|
513
|
+
return { written: [], error: base };
|
|
514
|
+
files.push([`${lower}.ddls.asddls`, projectionSource(name, base, description)]);
|
|
515
|
+
break;
|
|
516
|
+
}
|
|
517
|
+
case "abstract":
|
|
518
|
+
files.push([`${lower}.ddls.asddls`, abstractSource(name, description)]);
|
|
519
|
+
break;
|
|
520
|
+
case "tableentity":
|
|
521
|
+
files.push([`${lower}.ddls.asddls`, tableEntitySource(name, options.root === true, description)]);
|
|
522
|
+
break;
|
|
523
|
+
case "type":
|
|
524
|
+
files.push([`${lower}.drty.acds`, simpleTypeSource(name, description)]);
|
|
525
|
+
break;
|
|
526
|
+
case "enum":
|
|
527
|
+
files.push([`${lower}.drty.acds`, enumTypeSource(name, description)]);
|
|
528
|
+
break;
|
|
529
|
+
case "custom": {
|
|
530
|
+
const query = options.for ?? `zcl_${lower.replace(/^[zy]_?/, "")}_query`.slice(0, 30);
|
|
531
|
+
files.push([`${lower}.ddls.asddls`, customSource(name, query, description)], [`${query.toLowerCase()}.clas.abap`, queryClass(query, name)]);
|
|
532
|
+
break;
|
|
533
|
+
}
|
|
534
|
+
case "metadata": {
|
|
535
|
+
const target = entity(ctx(), options.from ?? name, "rapt generate metadata");
|
|
536
|
+
if (typeof target === "string")
|
|
537
|
+
return { written: [], error: target };
|
|
538
|
+
files.push([`${lower}.ddlx.asddlxs`, metadataSource(target)]);
|
|
539
|
+
break;
|
|
540
|
+
}
|
|
541
|
+
case "access": {
|
|
542
|
+
const target = entity(ctx(), options.from ?? name, "rapt generate access");
|
|
543
|
+
if (typeof target === "string")
|
|
544
|
+
return { written: [], error: target };
|
|
545
|
+
files.push([`${lower}.dcls.asdcls`, accessSource(target, description)]);
|
|
546
|
+
break;
|
|
547
|
+
}
|
|
548
|
+
case "behavior": {
|
|
549
|
+
const c = ctx();
|
|
550
|
+
const target = entity(c, options.from ?? name, "rapt generate behavior");
|
|
551
|
+
if (typeof target === "string")
|
|
552
|
+
return { written: [], error: target };
|
|
553
|
+
if (!target.root) {
|
|
554
|
+
return { written: [], error: `${target.name}: a behavior definition is defined for a root entity` };
|
|
555
|
+
}
|
|
556
|
+
if (target.kind !== "view" && target.kind !== "projection") {
|
|
557
|
+
return { written: [], error: `${target.name}: a behavior definition needs a view entity or a projection` };
|
|
558
|
+
}
|
|
559
|
+
files.push([`${lower}.bdef.asbdef`, behaviorSource(c, target)]);
|
|
560
|
+
break;
|
|
561
|
+
}
|
|
562
|
+
case "pool": {
|
|
563
|
+
const project = loadProject(root);
|
|
564
|
+
const bdefs = filesOfType(project, "BDEF").filter((f) => f.extension === "asbdef");
|
|
565
|
+
const bdef = options.for === undefined
|
|
566
|
+
? bdefs.find((f) => new RegExp(`implementation\\s+in\\s+class\\s+${name}\\b`, "i").test(f.content))
|
|
567
|
+
: bdefs.find((f) => f.name === options.for?.toUpperCase());
|
|
568
|
+
if (bdef === undefined) {
|
|
569
|
+
return { written: [], error: options.for === undefined ? `no behavior definition names the class ${name}: give it with --for <BDEF>` : `${options.for}: no behavior definition of that name` };
|
|
570
|
+
}
|
|
571
|
+
const sources = poolSources(name, bdef.name, bdef.content);
|
|
572
|
+
if (typeof sources === "string")
|
|
573
|
+
return { written: [], error: sources };
|
|
574
|
+
files.push([`${lower}.clas.abap`, sources.global], [`${lower}.clas.locals_imp.abap`, sources.locals]);
|
|
575
|
+
break;
|
|
576
|
+
}
|
|
577
|
+
case "class":
|
|
578
|
+
files.push([`${lower}.clas.abap`, classSource(name, description)]);
|
|
579
|
+
break;
|
|
580
|
+
case "interface":
|
|
581
|
+
files.push([`${lower}.intf.abap`, interfaceSource(name, description)]);
|
|
582
|
+
break;
|
|
583
|
+
case "service": {
|
|
584
|
+
const exposed = options.expose ?? (options.from === undefined ? [] : [options.from]);
|
|
585
|
+
if (exposed.length === 0) {
|
|
586
|
+
return { written: [], error: "rapt generate service needs the exposed entities: --expose A,B" };
|
|
587
|
+
}
|
|
588
|
+
files.push([`${lower}.srvd.srvdsrv`, serviceSource(name, exposed, description)]);
|
|
589
|
+
break;
|
|
590
|
+
}
|
|
591
|
+
case "dataelement":
|
|
592
|
+
case "domain": {
|
|
593
|
+
const builtIn = parseBuiltInType(options.type ?? (kind === "domain" || options.from === undefined ? "abap.char(10)" : ""));
|
|
594
|
+
if (kind === "dataelement" && options.from !== undefined) {
|
|
595
|
+
files.push([`${lower}.dtel.xml`, dataElementXml(name, description, { domain: options.from })]);
|
|
596
|
+
break;
|
|
597
|
+
}
|
|
598
|
+
if (builtIn === undefined) {
|
|
599
|
+
return { written: [], error: `${options.type ?? ""}: a built-in type is expected, e.g. --type abap.char(10)` };
|
|
600
|
+
}
|
|
601
|
+
files.push(kind === "domain" ? [`${lower}.doma.xml`, domainXml(name, description, builtIn)] : [`${lower}.dtel.xml`, dataElementXml(name, description, { type: builtIn })]);
|
|
602
|
+
break;
|
|
603
|
+
}
|
|
604
|
+
case "binding": {
|
|
605
|
+
const service = options.for ?? options.from;
|
|
606
|
+
if (service === undefined) {
|
|
607
|
+
return { written: [], error: "rapt generate binding needs the service definition: --for <SRVD>" };
|
|
608
|
+
}
|
|
609
|
+
// --binding-type webapi: OData V4 - Web API (D60)
|
|
610
|
+
const webApi = options.bindingType?.toLowerCase() === "webapi";
|
|
611
|
+
files.push([`${lower}.srvb.xml`, bindingSource(name, service, `${description} (OData V4, ${webApi ? "Web API" : "UI"})`, webApi)]);
|
|
612
|
+
break;
|
|
613
|
+
}
|
|
614
|
+
}
|
|
615
|
+
const absolute = files.map(([file, content]) => [path.join(src, file), content]);
|
|
616
|
+
const existing = absolute.filter(([file]) => existsSync(file)).map(([file]) => path.relative(root, file));
|
|
617
|
+
if (existing.length > 0) {
|
|
618
|
+
return { written: [], error: `already present: ${existing.join(", ")}` };
|
|
619
|
+
}
|
|
620
|
+
const written = [];
|
|
621
|
+
for (const [file, content] of absolute) {
|
|
622
|
+
mkdirSync(path.dirname(file), { recursive: true });
|
|
623
|
+
writeFileSync(file, content);
|
|
624
|
+
written.push(path.relative(root, file).split(path.sep).join("/"));
|
|
625
|
+
}
|
|
626
|
+
const abapGit = generateAbapGitFiles(root);
|
|
627
|
+
written.push(...abapGit.written.filter((f) => !written.includes(f)));
|
|
628
|
+
return { written };
|
|
629
|
+
}
|
|
630
|
+
//# sourceMappingURL=generate.js.map
|