@cspeach/cli 0.9.0 → 1.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 +1 -1
- package/dist/agent/intent-system-prompt.js +1 -1
- package/dist/agent/loop.js +228 -26
- package/dist/agent/providers/license-gate.js +44 -0
- package/dist/agent/skill-checkpoint.js +1 -1
- package/dist/agent/tool-dispatch.js +15 -0
- package/dist/approvals/canonical.js +91 -0
- package/dist/approvals/jwt.js +39 -2
- package/dist/approvals/op-labels.js +124 -0
- package/dist/approvals/render.js +42 -36
- package/dist/auth/org-anthropic-key.js +25 -0
- package/dist/classifier/client.js +18 -3
- package/dist/cli.js +15 -0
- package/dist/commands/compact.js +28 -2
- package/dist/commands/config-set.js +284 -0
- package/dist/commands/config-show.js +20 -0
- package/dist/commands/export-audit.js +43 -0
- package/dist/commands/help.js +5 -0
- package/dist/commands/login.js +31 -14
- package/dist/commands/plan-audit-evidence.js +266 -0
- package/dist/commands/plan-audit.js +692 -0
- package/dist/commands/plan-chain.js +671 -0
- package/dist/commands/plan-continue.js +179 -0
- package/dist/commands/plan-gate.js +154 -0
- package/dist/commands/plan-model-tier.js +83 -0
- package/dist/commands/plan-resume.js +728 -46
- package/dist/config/loader.js +223 -5
- package/dist/config/model-defaults.js +14 -0
- package/dist/cost/pricing.js +27 -1
- package/dist/doctor/checks/_http-probe.js +1 -0
- package/dist/doctor/checks/cert.js +14 -3
- package/dist/doctor/checks/sap.js +30 -8
- package/dist/doctor/checks/system-roles.js +41 -0
- package/dist/doctor/checks/zcspeach.js +19 -4
- package/dist/doctor/run.js +2 -0
- package/dist/models/resolve.js +61 -0
- package/dist/models/server-config.js +155 -0
- package/dist/one-shot.js +76 -6
- package/dist/projects/answer-blockers.js +137 -0
- package/dist/projects/extract-cca.js +111 -17
- package/dist/projects/extract-modernize.js +4 -2
- package/dist/projects/extract-plan.js +184 -37
- package/dist/projects/extract-spec-gap.js +34 -7
- package/dist/projects/extract-test-coverage.js +4 -2
- package/dist/projects/extract-upgrade.js +116 -23
- package/dist/projects/handover-md.js +195 -0
- package/dist/projects/index.js +5 -2
- package/dist/projects/merge-cca.js +292 -0
- package/dist/projects/merge-upgrade.js +173 -0
- package/dist/projects/migration.js +103 -1
- package/dist/projects/output-paths.js +27 -0
- package/dist/projects/plan-run.js +285 -27
- package/dist/projects/plan-schema.js +136 -3
- package/dist/projects/promote-command.js +25 -2
- package/dist/projects/promote.js +128 -0
- package/dist/projects/run-lease.js +157 -0
- package/dist/projects/save-command.js +259 -21
- package/dist/projects/status.js +3 -1
- package/dist/projects/validate.js +1 -1
- package/dist/projects/workspace.js +164 -20
- package/dist/renderer/notices.js +64 -0
- package/dist/renderer/progress-chatter.js +8 -0
- package/dist/renderer/status-footer.js +22 -12
- package/dist/renderer/thinking-heartbeat.js +64 -8
- package/dist/renderer/todo-block.js +51 -0
- package/dist/renderer/tool-widget.js +55 -4
- package/dist/renderer/tty.js +43 -4
- package/dist/renderer/verify-chain.js +77 -0
- package/dist/repl/at-picker.js +60 -7
- package/dist/repl/bracketed-paste.js +28 -19
- package/dist/repl/builtin-commands.js +42 -0
- package/dist/repl/current-transport.js +10 -0
- package/dist/repl/early-line-buffer.js +68 -0
- package/dist/repl/history.js +86 -0
- package/dist/repl/ink-stdin-guard.js +64 -0
- package/dist/repl/inquirer-guard.js +70 -5
- package/dist/repl/mode-ceiling.js +16 -0
- package/dist/repl/mode-cycle.js +104 -0
- package/dist/repl/numbered-menu.js +131 -0
- package/dist/repl/post-turn-status.js +26 -6
- package/dist/repl/rule8-detector.js +17 -2
- package/dist/repl/safety-confirm.js +111 -2
- package/dist/repl/safety-mode-state.js +19 -3
- package/dist/repl/slash-completer.js +5 -0
- package/dist/repl/slash-picker.js +10 -15
- package/dist/repl.js +1232 -95
- package/dist/rewind/candidates.js +194 -0
- package/dist/rewind/cli.js +137 -0
- package/dist/rewind/format.js +27 -0
- package/dist/rewind/restore.js +245 -0
- package/dist/router/classifier.js +150 -6
- package/dist/sap/capability-matrix.js +20 -0
- package/dist/sap/capability-matrix.json +11236 -0
- package/dist/sap/capability.js +146 -0
- package/dist/sap/connection-manager.js +19 -1
- package/dist/sap/onboarding.js +42 -4
- package/dist/session/audit-export.js +459 -0
- package/dist/session/context-report.js +163 -0
- package/dist/session/pending.js +27 -0
- package/dist/session/recap.js +160 -0
- package/dist/skill-catalog.js +51 -40
- package/dist/skills/bundled-skills.js +272 -1
- package/dist/skills/promotion-dispatch.js +23 -0
- package/dist/tools/_command-shared.js +36 -12
- package/dist/tools/_filesystem-shared.js +139 -4
- package/dist/tools/_flag.js +25 -0
- package/dist/tools/approval.js +177 -26
- package/dist/tools/ask-question.js +400 -7
- package/dist/tools/capability/tool.js +74 -0
- package/dist/tools/dispatch-skill.js +22 -1
- package/dist/tools/extend-model/anchored-insert.js +1414 -0
- package/dist/tools/extend-model/tool.js +340 -0
- package/dist/tools/filesystem/extract-document.js +57 -0
- package/dist/tools/filesystem/file-edit.js +12 -2
- package/dist/tools/filesystem/file-read.js +2 -2
- package/dist/tools/filesystem/file-write.js +11 -2
- package/dist/tools/filesystem/glob.js +11 -0
- package/dist/tools/filesystem/grep.js +10 -0
- package/dist/tools/filesystem/read-document.js +107 -0
- package/dist/tools/fiori/apply.js +50 -0
- package/dist/tools/fiori/bin.js +3 -0
- package/dist/tools/fiori/catalog/index.js +27 -0
- package/dist/tools/fiori/catalog/value-help.js +230 -0
- package/dist/tools/fiori/catalog/viz-chart.js +177 -0
- package/dist/tools/fiori/cli.js +71 -0
- package/dist/tools/fiori/deploy-config.js +73 -0
- package/dist/tools/fiori/fe-extend.js +76 -0
- package/dist/tools/fiori/fe-scaffold.js +71 -0
- package/dist/tools/fiori/floorplan-map.js +19 -0
- package/dist/tools/fiori/i18n.js +39 -0
- package/dist/tools/fiori/manifest.js +70 -0
- package/dist/tools/fiori/render.js +77 -0
- package/dist/tools/fiori/samples/data/index.json +13602 -0
- package/dist/tools/fiori/samples/data/sources.generated.js +808 -0
- package/dist/tools/fiori/samples/loader.js +248 -0
- package/dist/tools/fiori/samples/search.js +63 -0
- package/dist/tools/fiori/samples/types.js +2 -0
- package/dist/tools/fiori/scaffold.js +39 -0
- package/dist/tools/fiori/smoke/assertions.js +74 -0
- package/dist/tools/fiori/smoke/browser.js +52 -0
- package/dist/tools/fiori/smoke/driver.js +89 -0
- package/dist/tools/fiori/smoke/freestyle-spec.js +317 -0
- package/dist/tools/fiori/smoke/run-smoke.js +149 -0
- package/dist/tools/fiori/tools.js +681 -0
- package/dist/tools/fiori/types.js +1 -0
- package/dist/tools/local-build.js +86 -0
- package/dist/tools/local-files.js +31 -0
- package/dist/tools/project/_merge-shared.js +68 -0
- package/dist/tools/project/cca_merge.js +164 -0
- package/dist/tools/project/playbook_get.js +1 -1
- package/dist/tools/project/upgrade_merge_progress.js +206 -0
- package/dist/tools/sap-read.js +132 -20
- package/dist/tools/sap-write.js +550 -21
- package/dist/tools/shell/shell_exec.js +41 -6
- package/dist/tools/snapshot.js +63 -14
- package/dist/tools/subagent/agent_run.js +27 -3
- package/dist/tools/subagent/background_run.js +17 -1
- package/dist/tools/todo.js +144 -0
- package/dist/tools/transport-resolution.js +86 -0
- package/dist/tools/transport.js +224 -5
- package/dist/tools/write-mode.js +4 -0
- package/dist/ui/app.js +378 -21
- package/dist/ui/approval-modal.js +49 -16
- package/dist/ui/ask-question-emitter.js +14 -0
- package/dist/ui/body.js +13 -0
- package/dist/ui/context-grid.js +108 -0
- package/dist/ui/footer.js +120 -27
- package/dist/ui/header.js +7 -0
- package/dist/ui/line-resolution.js +35 -8
- package/dist/ui/rewind-emitter.js +10 -0
- package/dist/ui/rewind-panel.js +81 -0
- package/dist/ui/sap-state-store.js +1 -0
- package/dist/ui/session-timeline.js +1 -0
- package/dist/ui/status-line.js +43 -0
- package/dist/ui/text-input.js +214 -0
- package/dist/ui/todo-emitter.js +25 -0
- package/dist/ui/todo-panel.js +64 -0
- package/dist/ui/turn-status-emitter.js +50 -4
- package/dist/ui/turn-status.js +18 -3
- package/dist/ui/widgets/ask-form.js +242 -0
- package/dist/ui/widgets/ask-question-modal.js +21 -8
- package/package.json +22 -3
- package/bench/README.md +0 -78
- package/bench/prompts/abap-document-cds.md +0 -44
- package/bench/prompts/abap-explain-bdef-handler.md +0 -57
- package/bench/prompts/abap-test-method.md +0 -42
- package/bench/results/abap-document-cds/claude-haiku-4-5.md +0 -189
- package/bench/results/abap-document-cds/claude-opus-4-7.md +0 -120
- package/bench/results/abap-document-cds/claude-sonnet-4-6.md +0 -151
- package/bench/results/abap-explain-bdef-handler/claude-haiku-4-5.md +0 -112
- package/bench/results/abap-explain-bdef-handler/claude-opus-4-7.md +0 -101
- package/bench/results/abap-explain-bdef-handler/claude-sonnet-4-6.md +0 -101
- package/bench/results/abap-test-method/claude-haiku-4-5.md +0 -186
- package/bench/results/abap-test-method/claude-opus-4-7.md +0 -193
- package/bench/results/abap-test-method/claude-sonnet-4-6.md +0 -234
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
// cspeach-cli/src/tools/fiori/apply.ts
|
|
2
|
+
import { readFileSync, writeFileSync, mkdirSync, existsSync } from 'node:fs';
|
|
3
|
+
import { dirname } from 'node:path';
|
|
4
|
+
import { getEntry } from './catalog/index.js';
|
|
5
|
+
import { renderEntry } from './render.js';
|
|
6
|
+
import { mergeManifest } from './manifest.js';
|
|
7
|
+
import { appendI18n } from './i18n.js';
|
|
8
|
+
import { resolveSafePath } from '../_filesystem-shared.js';
|
|
9
|
+
/**
|
|
10
|
+
* Render a catalog entry and apply it into an existing scaffolded app on disk.
|
|
11
|
+
*
|
|
12
|
+
* SECURITY — containment is enforced HERE, on every write.
|
|
13
|
+
* --------------------------------------------------------
|
|
14
|
+
* Each output path is derived from a catalog EJS template rendered with
|
|
15
|
+
* CUSTOMER params (e.g. `webapp/ext/fragment/<%- ns %>Chart.fragment.xml`), so a
|
|
16
|
+
* hostile param could otherwise carry `..` and escape `appDir`. Every path this
|
|
17
|
+
* function writes — rendered files, manifest.json, and the i18n file (whose
|
|
18
|
+
* relative location is itself read from on-disk manifest data) — is resolved
|
|
19
|
+
* through `resolveSafePath(opts.appDir, rel)`, which throws PathOutsideRootError
|
|
20
|
+
* if the resolved absolute path is not contained under appDir. This is the
|
|
21
|
+
* authoritative defense; the catalog-side `ns` pattern (render.ts) is the
|
|
22
|
+
* second, earlier layer.
|
|
23
|
+
*/
|
|
24
|
+
export function applyEntry(opts) {
|
|
25
|
+
const rendered = renderEntry(getEntry(opts.entryName), opts.params);
|
|
26
|
+
// 1. write files — re-validate EVERY rendered (param-derived) path against appDir.
|
|
27
|
+
for (const f of rendered.files) {
|
|
28
|
+
const abs = resolveSafePath(opts.appDir, f.path);
|
|
29
|
+
mkdirSync(dirname(abs), { recursive: true });
|
|
30
|
+
writeFileSync(abs, f.content, 'utf8');
|
|
31
|
+
}
|
|
32
|
+
// 2. merge manifest — ALWAYS re-read from disk (I2). Fixed relative path, but
|
|
33
|
+
// routed through resolveSafePath for uniform containment.
|
|
34
|
+
const manifestPath = resolveSafePath(opts.appDir, 'webapp/manifest.json');
|
|
35
|
+
const manifest = JSON.parse(readFileSync(manifestPath, 'utf8'));
|
|
36
|
+
let merged = mergeManifest(manifest, rendered.manifestPatch);
|
|
37
|
+
for (const lib of rendered.libraries) {
|
|
38
|
+
merged = mergeManifest(merged, { 'sap.ui5': { dependencies: { libs: { [lib]: {} } } } });
|
|
39
|
+
}
|
|
40
|
+
writeFileSync(manifestPath, JSON.stringify(merged, null, 2), 'utf8');
|
|
41
|
+
// 3. append i18n — resolve path from sap.app.i18n (M1). i18nRel is on-disk
|
|
42
|
+
// data, not a render param, but it still drives a write target, so contain it.
|
|
43
|
+
const i18nRel = merged['sap.app']?.i18n ?? 'i18n/i18n.properties';
|
|
44
|
+
const i18nPath = resolveSafePath(opts.appDir, `webapp/${i18nRel}`);
|
|
45
|
+
const existing = existsSync(i18nPath) ? readFileSync(i18nPath, 'utf8') : '';
|
|
46
|
+
if (Object.keys(rendered.i18nKeys).length) {
|
|
47
|
+
mkdirSync(dirname(i18nPath), { recursive: true });
|
|
48
|
+
writeFileSync(i18nPath, appendI18n(existing, rendered.i18nKeys), 'utf8');
|
|
49
|
+
}
|
|
50
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
// cspeach-cli/src/tools/fiori/catalog/index.ts
|
|
2
|
+
//
|
|
3
|
+
// Catalog index for the Fiori engine.
|
|
4
|
+
// v1 scope: only the two "fiddly" SAPUI5 patterns — viz-chart and value-help.
|
|
5
|
+
// confirm-dialog (4.1), list-table-page (4.2), object-detail-page (4.3),
|
|
6
|
+
// message-toast (4.4) are SKILL.md recipes, not catalog entries.
|
|
7
|
+
import { vizChart } from './viz-chart.js';
|
|
8
|
+
import { valueHelp } from './value-help.js';
|
|
9
|
+
export const CATALOG = [vizChart, valueHelp];
|
|
10
|
+
export function getEntry(name) {
|
|
11
|
+
const e = CATALOG.find((x) => x.name === name);
|
|
12
|
+
if (!e)
|
|
13
|
+
throw new Error(`Unknown catalog entry "${name}". Available: ${CATALOG.map((c) => c.name).join(', ')}`);
|
|
14
|
+
return e;
|
|
15
|
+
}
|
|
16
|
+
export function listCatalog() {
|
|
17
|
+
// params included so the agent can construct a correct `apply --params` call
|
|
18
|
+
// straight from `fiori list` — no trial-and-error against required-param errors.
|
|
19
|
+
return CATALOG.map(({ name, title, description, categories, libraries, params }) => ({
|
|
20
|
+
name,
|
|
21
|
+
title,
|
|
22
|
+
description,
|
|
23
|
+
categories,
|
|
24
|
+
libraries,
|
|
25
|
+
params,
|
|
26
|
+
}));
|
|
27
|
+
}
|
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
// cspeach-cli/src/tools/fiori/catalog/value-help.ts
|
|
2
|
+
//
|
|
3
|
+
// SAPUI5 ValueHelpDialog (F4 help) entry.
|
|
4
|
+
// Authored against the public sap.ui.comp.valuehelpdialog.ValueHelpDialog API.
|
|
5
|
+
// Source inspiration: SAPUI5 SDK ValueHelpDialog demokit (ui5.sap.com/#/api/sap.ui.comp.valuehelpdialog.ValueHelpDialog).
|
|
6
|
+
// This is an original authoring against the public API — not a copy of any Apache-licensed sample.
|
|
7
|
+
// NOTE: sap.ui.comp is a SAPUI5-only library (not available in OpenUI5).
|
|
8
|
+
export const valueHelp = {
|
|
9
|
+
name: 'value-help',
|
|
10
|
+
title: 'ValueHelpDialog (F4 value help)',
|
|
11
|
+
description: 'A sap.ui.comp.valuehelpdialog.ValueHelpDialog F4 help backed by a second OData entity. ' +
|
|
12
|
+
'Opens a search-and-select dialog, lets the user pick a key+description row, and writes the ' +
|
|
13
|
+
'selected key back to a target field. Single-select by default. Use for any code/key field ' +
|
|
14
|
+
'that needs a search dialog (customer, material, cost centre, etc.).',
|
|
15
|
+
engine: 'freestyle',
|
|
16
|
+
ui5MinVersion: '1.54.0',
|
|
17
|
+
libraries: ['sap.ui.comp', 'sap.m', 'sap.ui.core'],
|
|
18
|
+
categories: ['dialog', 'value-help', 'f4', 'search', 'selection'],
|
|
19
|
+
files: [
|
|
20
|
+
{
|
|
21
|
+
// Fragment: declares the ValueHelpDialog as a dependent; used by the controller mixin.
|
|
22
|
+
// The dialog is created programmatically in the mixin (not in XML) because
|
|
23
|
+
// ValueHelpDialog requires JS-side column and token configuration.
|
|
24
|
+
// This fragment is a thin host container — a Panel that shows the currently
|
|
25
|
+
// selected value token inline (optional), giving the agent a drop-in XML widget.
|
|
26
|
+
path: 'webapp/ext/fragment/<%- ns %>ValueHelp.fragment.xml',
|
|
27
|
+
content: `<core:FragmentDefinition
|
|
28
|
+
xmlns="sap.m"
|
|
29
|
+
xmlns:core="sap.ui.core">
|
|
30
|
+
<!--
|
|
31
|
+
<%- ns %>ValueHelp — F4 value-help trigger widget.
|
|
32
|
+
Drop this fragment into any form field row.
|
|
33
|
+
Bind the Input value to your form model field (e.g. {formModel>/<%- targetField %>}).
|
|
34
|
+
The press handler opens the ValueHelpDialog via the controller mixin.
|
|
35
|
+
-->
|
|
36
|
+
<HBox alignItems="Center">
|
|
37
|
+
<Input
|
|
38
|
+
id="<%- ns %>VHInput"
|
|
39
|
+
value="{formModel>/<%- targetField %>}"
|
|
40
|
+
placeholder="{i18n><%- ns %>.vhPlaceholder}"
|
|
41
|
+
showValueHelp="true"
|
|
42
|
+
valueHelpRequest=".open<%- ns %>ValueHelp"
|
|
43
|
+
width="100%" />
|
|
44
|
+
</HBox>
|
|
45
|
+
</core:FragmentDefinition>`,
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
// Controller mixin: creates and wires the ValueHelpDialog
|
|
49
|
+
path: 'webapp/controller/ext/<%- ns %>ValueHelp.js',
|
|
50
|
+
content: `sap.ui.define([
|
|
51
|
+
"sap/ui/comp/valuehelpdialog/ValueHelpDialog",
|
|
52
|
+
"sap/m/Token",
|
|
53
|
+
"sap/ui/model/Filter",
|
|
54
|
+
"sap/ui/model/FilterOperator"
|
|
55
|
+
], function (ValueHelpDialog, Token, Filter, FilterOperator) {
|
|
56
|
+
"use strict";
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Controller mixin for <%- ns %>ValueHelp.
|
|
60
|
+
*
|
|
61
|
+
* Provides F4 value help for the "<%- targetField %>" field.
|
|
62
|
+
* Help entity: <%- helpEntityPath %> Key: <%- keyField %> Description: <%- descriptionField %>
|
|
63
|
+
*
|
|
64
|
+
* Usage:
|
|
65
|
+
* Object.assign(this, <%- ns %>ValueHelpMixin);
|
|
66
|
+
* // In your XML form bind the Input valueHelpRequest=".open<%- ns %>ValueHelp"
|
|
67
|
+
*/
|
|
68
|
+
var <%- ns %>ValueHelpMixin = {
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Opens the ValueHelpDialog. Wired to valueHelpRequest on the Input control.
|
|
72
|
+
* @param {sap.ui.base.Event} oEvent
|
|
73
|
+
*/
|
|
74
|
+
open<%- ns %>ValueHelp: function (oEvent) {
|
|
75
|
+
var oView = this.getView();
|
|
76
|
+
var oModel = oView.getModel(); // default OData model
|
|
77
|
+
|
|
78
|
+
if (!this._o<%- ns %>VHDialog) {
|
|
79
|
+
this._o<%- ns %>VHDialog = new ValueHelpDialog({
|
|
80
|
+
title: oView.getModel("i18n").getResourceBundle().getText("<%- ns %>.vhTitle"),
|
|
81
|
+
supportMultiselect: false,
|
|
82
|
+
supportRanges: false,
|
|
83
|
+
key: "<%- keyField %>",
|
|
84
|
+
descriptionKey: "<%- descriptionField %>",
|
|
85
|
+
ok: this._on<%- ns %>VHConfirm.bind(this),
|
|
86
|
+
cancel: this._on<%- ns %>VHCancel.bind(this),
|
|
87
|
+
afterClose: function () {
|
|
88
|
+
this._o<%- ns %>VHDialog.destroy();
|
|
89
|
+
this._o<%- ns %>VHDialog = null;
|
|
90
|
+
}.bind(this)
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
// Device-agnostic column setup via the "columns" named model.
|
|
94
|
+
// ValueHelpDialog.getTable() returns sap.ui.table.Table on desktop/tablet
|
|
95
|
+
// and sap.m.Table on phone. Neither shares the same column/items API,
|
|
96
|
+
// so we use the canonical "columns" model approach that works for both.
|
|
97
|
+
var oTable = this._o<%- ns %>VHDialog.getTable();
|
|
98
|
+
oTable.setModel(oModel);
|
|
99
|
+
|
|
100
|
+
// 1. Describe columns via a named JSON model — VHD reads this model
|
|
101
|
+
// to render grid columns on desktop (sap.ui.table.Table).
|
|
102
|
+
var oColModel = new sap.ui.model.json.JSONModel({
|
|
103
|
+
cols: [
|
|
104
|
+
{ label: "<%- keyField %>", template: "<%- keyField %>" },
|
|
105
|
+
{ label: "<%- descriptionField %>", template: "<%- descriptionField %>" }
|
|
106
|
+
]
|
|
107
|
+
});
|
|
108
|
+
oTable.setModel(oColModel, "columns");
|
|
109
|
+
|
|
110
|
+
// 2. Bind rows/items device-aware: sap.ui.table.Table uses bindRows,
|
|
111
|
+
// sap.m.Table (phone) uses bindItems.
|
|
112
|
+
// Normalise the entity path: strip a leading slash then re-prepend one
|
|
113
|
+
// so we never produce double-slash regardless of what helpEntityPath contains.
|
|
114
|
+
var sEntityPath = "/<%- helpEntityPath %>".replace(/^\\/+/, "/");
|
|
115
|
+
if (oTable.bindRows) {
|
|
116
|
+
// sap.ui.table.Table — desktop / tablet
|
|
117
|
+
oTable.bindRows(sEntityPath);
|
|
118
|
+
} else {
|
|
119
|
+
// sap.m.Table — phone
|
|
120
|
+
oTable.bindItems({
|
|
121
|
+
path: sEntityPath,
|
|
122
|
+
template: new sap.m.ColumnListItem({
|
|
123
|
+
cells: [
|
|
124
|
+
new sap.m.Text({ text: "{<%- keyField %>}" }),
|
|
125
|
+
new sap.m.Text({ text: "{<%- descriptionField %>}" })
|
|
126
|
+
]
|
|
127
|
+
})
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
this._o<%- ns %>VHDialog.update();
|
|
131
|
+
|
|
132
|
+
// Pre-select current value if set
|
|
133
|
+
var sCurrentKey = oView.getModel("formModel") &&
|
|
134
|
+
oView.getModel("formModel").getProperty("/<%- targetField %>");
|
|
135
|
+
if (sCurrentKey) {
|
|
136
|
+
this._o<%- ns %>VHDialog.setTokens([new Token({ key: sCurrentKey, text: sCurrentKey })]);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
this._o<%- ns %>VHDialog.open();
|
|
141
|
+
},
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* OK handler — transfers the selected token key to the target field.
|
|
145
|
+
* @param {sap.ui.base.Event} oEvent
|
|
146
|
+
*/
|
|
147
|
+
_on<%- ns %>VHConfirm: function (oEvent) {
|
|
148
|
+
var aTokens = oEvent.getParameter("tokens");
|
|
149
|
+
var oFormModel = this.getView().getModel("formModel");
|
|
150
|
+
if (aTokens && aTokens.length > 0 && oFormModel) {
|
|
151
|
+
oFormModel.setProperty("/<%- targetField %>", aTokens[0].getKey());
|
|
152
|
+
}
|
|
153
|
+
if (this._o<%- ns %>VHDialog) {
|
|
154
|
+
this._o<%- ns %>VHDialog.close();
|
|
155
|
+
}
|
|
156
|
+
},
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* Cancel handler — closes without changes.
|
|
160
|
+
*/
|
|
161
|
+
_on<%- ns %>VHCancel: function () {
|
|
162
|
+
if (this._o<%- ns %>VHDialog) {
|
|
163
|
+
this._o<%- ns %>VHDialog.close();
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
};
|
|
167
|
+
|
|
168
|
+
return <%- ns %>ValueHelpMixin;
|
|
169
|
+
});`,
|
|
170
|
+
},
|
|
171
|
+
],
|
|
172
|
+
manifestPatch: {
|
|
173
|
+
'sap.ui5': {
|
|
174
|
+
dependencies: {
|
|
175
|
+
libs: {
|
|
176
|
+
'sap.ui.comp': {},
|
|
177
|
+
},
|
|
178
|
+
},
|
|
179
|
+
},
|
|
180
|
+
},
|
|
181
|
+
i18nKeys: {
|
|
182
|
+
'<%- ns %>.vhTitle': '<%- ns %> Search',
|
|
183
|
+
'<%- ns %>.vhPlaceholder': 'Select <%- ns %>…',
|
|
184
|
+
},
|
|
185
|
+
params: {
|
|
186
|
+
appId: {
|
|
187
|
+
type: 'string',
|
|
188
|
+
required: true,
|
|
189
|
+
from: 'context.app.id',
|
|
190
|
+
description: 'Application namespace ID, e.g. zsales.app',
|
|
191
|
+
},
|
|
192
|
+
ns: {
|
|
193
|
+
type: 'string',
|
|
194
|
+
required: true,
|
|
195
|
+
// SECURITY: ns lands directly in a file path
|
|
196
|
+
// (webapp/ext/fragment/<ns>ValueHelp.fragment.xml). Constrain to a simple
|
|
197
|
+
// UI5-identifier fragment — letters/digits only, leading letter — so no
|
|
198
|
+
// dot, slash, or `..` can ever reach join(appDir, path).
|
|
199
|
+
pattern: '[A-Za-z][A-Za-z0-9]*',
|
|
200
|
+
description: 'PascalCase prefix for IDs and method names, e.g. Customer. Simple identifier only (letters/digits, leading letter).',
|
|
201
|
+
},
|
|
202
|
+
helpEntityPath: {
|
|
203
|
+
type: 'string',
|
|
204
|
+
required: true,
|
|
205
|
+
description: 'Absolute OData binding path for the help entity set, e.g. /Customers',
|
|
206
|
+
},
|
|
207
|
+
keyField: {
|
|
208
|
+
type: 'string',
|
|
209
|
+
required: true,
|
|
210
|
+
description: 'Property name of the key column in the help entity, e.g. CustomerID',
|
|
211
|
+
},
|
|
212
|
+
descriptionField: {
|
|
213
|
+
type: 'string',
|
|
214
|
+
required: true,
|
|
215
|
+
description: 'Property name of the description column in the help entity, e.g. CustomerName',
|
|
216
|
+
},
|
|
217
|
+
targetField: {
|
|
218
|
+
type: 'string',
|
|
219
|
+
required: true,
|
|
220
|
+
description: 'Property path on formModel to write the selected key into, e.g. CustomerID',
|
|
221
|
+
},
|
|
222
|
+
},
|
|
223
|
+
source: {
|
|
224
|
+
url: 'https://ui5.sap.com/#/api/sap.ui.comp.valuehelpdialog.ValueHelpDialog',
|
|
225
|
+
license: 'SAPUI5 — authored against the public ValueHelpDialog API; not copied from Apache-licensed source',
|
|
226
|
+
adaptedFrom: 'Original authoring against sap.ui.comp.valuehelpdialog.ValueHelpDialog public API. ' +
|
|
227
|
+
'sap.ui.comp is a SAPUI5-only library (not OpenUI5); targets SAPUI5 runtime. ' +
|
|
228
|
+
'Pattern adapted from standard F4 help implementation approach documented in SAP ABAP Fiori guidelines.',
|
|
229
|
+
},
|
|
230
|
+
};
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
// cspeach-cli/src/tools/fiori/catalog/viz-chart.ts
|
|
2
|
+
//
|
|
3
|
+
// SAPUI5 VizFrame column/bar chart entry.
|
|
4
|
+
// Authored against the public sap.viz.ui5.controls.VizFrame API.
|
|
5
|
+
// Source inspiration: SAPUI5 SDK VizFrame demokit samples (ui5.sap.com/#/entity/sap.viz.ui5.controls.VizFrame).
|
|
6
|
+
// This is an original authoring against the public API — not a copy of any Apache-licensed sample.
|
|
7
|
+
export const vizChart = {
|
|
8
|
+
name: 'viz-chart',
|
|
9
|
+
title: 'VizFrame chart (column / bar / line)',
|
|
10
|
+
description: 'A sap.viz.ui5.controls.VizFrame chart bound to an OData list via FlattenedDataset. ' +
|
|
11
|
+
'Supports column, bar, and line chart types. Configures one dimension (category axis) ' +
|
|
12
|
+
'and one or more measures (value axis). Use when you need an analytic chart inline in a page or dialog.',
|
|
13
|
+
engine: 'freestyle',
|
|
14
|
+
ui5MinVersion: '1.54.0',
|
|
15
|
+
libraries: ['sap.viz', 'sap.m', 'sap.ui.core'],
|
|
16
|
+
categories: ['chart', 'analytics', 'visualization'],
|
|
17
|
+
files: [
|
|
18
|
+
{
|
|
19
|
+
// Fragment: VizFrame with inline FlattenedDataset
|
|
20
|
+
path: 'webapp/ext/fragment/<%- ns %>Chart.fragment.xml',
|
|
21
|
+
content: `<core:FragmentDefinition
|
|
22
|
+
xmlns="sap.m"
|
|
23
|
+
xmlns:core="sap.ui.core"
|
|
24
|
+
xmlns:viz="sap.viz.ui5.controls"
|
|
25
|
+
xmlns:viz.data="sap.viz.ui5.data"
|
|
26
|
+
xmlns:viz.feeds="sap.viz.ui5.controls.common.feeds">
|
|
27
|
+
<viz:VizFrame
|
|
28
|
+
id="<%- ns %>VizFrame"
|
|
29
|
+
vizType="<%- locals.vizType || 'column' %>"
|
|
30
|
+
width="100%"
|
|
31
|
+
height="380px"
|
|
32
|
+
uiConfig="{applicationSet:'fiori2'}"
|
|
33
|
+
vizProperties="{ title: { visible: false } }">
|
|
34
|
+
<viz:dataset>
|
|
35
|
+
<viz.data:FlattenedDataset data="{<%- listPath %>}">
|
|
36
|
+
<viz.data:dimensions>
|
|
37
|
+
<viz.data:DimensionDefinition
|
|
38
|
+
name="<%- dimensionField %>"
|
|
39
|
+
value="{<%- dimensionField %>}" />
|
|
40
|
+
</viz.data:dimensions>
|
|
41
|
+
<viz.data:measures>
|
|
42
|
+
<viz.data:MeasureDefinition
|
|
43
|
+
name="<%- measureFields[0] %>"
|
|
44
|
+
value="{<%- measureFields[0] %>}" />
|
|
45
|
+
</viz.data:measures>
|
|
46
|
+
</viz.data:FlattenedDataset>
|
|
47
|
+
</viz:dataset>
|
|
48
|
+
<viz:feeds>
|
|
49
|
+
<viz.feeds:FeedItem
|
|
50
|
+
uid="categoryAxis"
|
|
51
|
+
type="Dimension"
|
|
52
|
+
values="<%- dimensionField %>" />
|
|
53
|
+
<viz.feeds:FeedItem
|
|
54
|
+
uid="valueAxis"
|
|
55
|
+
type="Measure"
|
|
56
|
+
values="<%- measureFields[0] %>" />
|
|
57
|
+
</viz:feeds>
|
|
58
|
+
</viz:VizFrame>
|
|
59
|
+
</core:FragmentDefinition>`,
|
|
60
|
+
},
|
|
61
|
+
{
|
|
62
|
+
// Controller mixin: lazy-loads the fragment and wires additional measures
|
|
63
|
+
path: 'webapp/controller/ext/<%- ns %>Chart.js',
|
|
64
|
+
content: `sap.ui.define([
|
|
65
|
+
"sap/ui/core/Fragment"
|
|
66
|
+
], function (Fragment) {
|
|
67
|
+
"use strict";
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Controller mixin for <%- ns %>Chart.
|
|
71
|
+
* Renders a VizFrame column/bar/line chart bound to <%- listPath %>.
|
|
72
|
+
* Dimension: <%- dimensionField %>.
|
|
73
|
+
*
|
|
74
|
+
* Usage: extend your controller with Object.assign(this, <%- ns %>ChartMixin).
|
|
75
|
+
*/
|
|
76
|
+
var <%- ns %>ChartMixin = {
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Open / show the chart fragment as a dependent of the current view.
|
|
80
|
+
* The fragment ID will be <view-id>--<%- ns %>VizFrame.
|
|
81
|
+
*/
|
|
82
|
+
open<%- ns %>Chart: function () {
|
|
83
|
+
var oView = this.getView();
|
|
84
|
+
if (!this._p<%- ns %>Chart) {
|
|
85
|
+
this._p<%- ns %>Chart = Fragment.load({
|
|
86
|
+
id: oView.getId(),
|
|
87
|
+
name: "<%- appId %>.ext.fragment.<%- ns %>Chart",
|
|
88
|
+
controller: this
|
|
89
|
+
}).then(function (oFrag) {
|
|
90
|
+
oView.addDependent(oFrag);
|
|
91
|
+
// Wire additional measures if provided
|
|
92
|
+
var oVizFrame = oView.byId("<%- ns %>VizFrame");
|
|
93
|
+
if (oVizFrame) {
|
|
94
|
+
var oDataset = oVizFrame.getDataset();
|
|
95
|
+
var aExtraMeasures = <%- JSON.stringify(measureFields) %>.slice(1);
|
|
96
|
+
aExtraMeasures.forEach(function (sMeasure) {
|
|
97
|
+
oDataset.addMeasure(
|
|
98
|
+
new sap.viz.ui5.data.MeasureDefinition({ name: sMeasure, value: "{" + sMeasure + "}" })
|
|
99
|
+
);
|
|
100
|
+
oVizFrame.addFeed(
|
|
101
|
+
new sap.viz.ui5.controls.common.feeds.FeedItem({
|
|
102
|
+
uid: "valueAxis", type: "Measure", values: sMeasure
|
|
103
|
+
})
|
|
104
|
+
);
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
return oFrag;
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
this._p<%- ns %>Chart.then(function (oFrag) {
|
|
111
|
+
if (typeof oFrag.open === "function") { oFrag.open(); }
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
return <%- ns %>ChartMixin;
|
|
117
|
+
});`,
|
|
118
|
+
},
|
|
119
|
+
],
|
|
120
|
+
manifestPatch: {
|
|
121
|
+
'sap.ui5': {
|
|
122
|
+
dependencies: {
|
|
123
|
+
libs: {
|
|
124
|
+
'sap.viz': {},
|
|
125
|
+
},
|
|
126
|
+
},
|
|
127
|
+
},
|
|
128
|
+
},
|
|
129
|
+
i18nKeys: {
|
|
130
|
+
'<%- ns %>.chartTitle': '<%- ns %> Chart',
|
|
131
|
+
},
|
|
132
|
+
params: {
|
|
133
|
+
appId: {
|
|
134
|
+
type: 'string',
|
|
135
|
+
required: true,
|
|
136
|
+
from: 'context.app.id',
|
|
137
|
+
description: 'Application namespace ID, e.g. zsales.app',
|
|
138
|
+
},
|
|
139
|
+
ns: {
|
|
140
|
+
type: 'string',
|
|
141
|
+
required: true,
|
|
142
|
+
// SECURITY: ns lands directly in a file path
|
|
143
|
+
// (webapp/ext/fragment/<ns>Chart.fragment.xml). Constrain to a simple
|
|
144
|
+
// UI5-identifier fragment — letters/digits only, leading letter — so no
|
|
145
|
+
// dot, slash, or `..` can ever reach join(appDir, path).
|
|
146
|
+
pattern: '[A-Za-z][A-Za-z0-9]*',
|
|
147
|
+
description: 'PascalCase prefix for all generated IDs and method names, e.g. Revenue. Simple identifier only (letters/digits, leading letter).',
|
|
148
|
+
},
|
|
149
|
+
vizType: {
|
|
150
|
+
type: 'string',
|
|
151
|
+
required: false,
|
|
152
|
+
description: 'VizFrame vizType: column (default), bar, line, pie',
|
|
153
|
+
},
|
|
154
|
+
listPath: {
|
|
155
|
+
type: 'string',
|
|
156
|
+
required: true,
|
|
157
|
+
description: 'OData binding path for the list, e.g. /SalesOrderItems',
|
|
158
|
+
},
|
|
159
|
+
dimensionField: {
|
|
160
|
+
type: 'string',
|
|
161
|
+
required: true,
|
|
162
|
+
description: 'Property name used as the chart dimension (category axis), e.g. Material',
|
|
163
|
+
},
|
|
164
|
+
measureFields: {
|
|
165
|
+
type: 'array',
|
|
166
|
+
required: true,
|
|
167
|
+
description: 'Array of property names for value-axis measures, e.g. ["NetAmount","TaxAmount"]',
|
|
168
|
+
},
|
|
169
|
+
},
|
|
170
|
+
source: {
|
|
171
|
+
url: 'https://ui5.sap.com/#/entity/sap.viz.ui5.controls.VizFrame',
|
|
172
|
+
license: 'SAPUI5 — authored against the public VizFrame API; not copied from Apache-licensed source',
|
|
173
|
+
adaptedFrom: 'Original authoring against sap.viz.ui5.controls.VizFrame + sap.viz.ui5.data.FlattenedDataset public API ' +
|
|
174
|
+
'(SAPUI5 SDK demokit samples used as API reference only). ' +
|
|
175
|
+
'sap.viz is a SAPUI5-only library (not OpenUI5); targets SAPUI5 runtime.',
|
|
176
|
+
},
|
|
177
|
+
};
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
// cspeach-cli/src/tools/fiori/cli.ts
|
|
2
|
+
import { listCatalog } from './catalog/index.js';
|
|
3
|
+
import { scaffoldFreestyle } from './scaffold.js';
|
|
4
|
+
import { applyEntry } from './apply.js';
|
|
5
|
+
import { writeDeployConfig } from './deploy-config.js';
|
|
6
|
+
/** Parse `--key value` / `--key=value` / `--json '<json>'` into a record. */
|
|
7
|
+
function parseFlags(argv) {
|
|
8
|
+
const out = {};
|
|
9
|
+
for (let i = 0; i < argv.length; i++) {
|
|
10
|
+
const a = argv[i];
|
|
11
|
+
if (!a.startsWith('--'))
|
|
12
|
+
continue;
|
|
13
|
+
const eq = a.indexOf('=');
|
|
14
|
+
if (eq !== -1)
|
|
15
|
+
out[a.slice(2, eq)] = a.slice(eq + 1);
|
|
16
|
+
else {
|
|
17
|
+
out[a.slice(2)] = argv[i + 1] ?? '';
|
|
18
|
+
i++;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
return out;
|
|
22
|
+
}
|
|
23
|
+
/** Engine command surface invoked by the agent via shell_exec. Returns exit code. */
|
|
24
|
+
export async function runFioriCli(argv, log = console.log) {
|
|
25
|
+
const [cmd, ...rest] = argv;
|
|
26
|
+
try {
|
|
27
|
+
switch (cmd) {
|
|
28
|
+
case 'list':
|
|
29
|
+
log(JSON.stringify(listCatalog(), null, 2));
|
|
30
|
+
return 0;
|
|
31
|
+
case 'scaffold': {
|
|
32
|
+
const f = parseFlags(rest);
|
|
33
|
+
await scaffoldFreestyle({
|
|
34
|
+
basePath: f.basePath, appId: f.appId, appTitle: f.appTitle ?? f.appId,
|
|
35
|
+
template: f.template ?? 'basic',
|
|
36
|
+
service: f.serviceUrl ? { url: f.serviceUrl, path: f.servicePath, version: f.serviceVersion ?? '4.0' } : undefined,
|
|
37
|
+
typescript: f.typescript === 'true',
|
|
38
|
+
ui5Version: f.ui5Version,
|
|
39
|
+
});
|
|
40
|
+
log(`scaffolded ${f.appId} at ${f.basePath}`);
|
|
41
|
+
return 0;
|
|
42
|
+
}
|
|
43
|
+
case 'apply': {
|
|
44
|
+
const f = parseFlags(rest);
|
|
45
|
+
applyEntry({ appDir: f.appDir, entryName: f.entry, params: JSON.parse(f.params ?? '{}') });
|
|
46
|
+
log(`applied ${f.entry} into ${f.appDir}`);
|
|
47
|
+
return 0;
|
|
48
|
+
}
|
|
49
|
+
case 'deploy-config': {
|
|
50
|
+
const f = parseFlags(rest);
|
|
51
|
+
writeDeployConfig(f.appDir, {
|
|
52
|
+
url: f.url, client: f.client, appName: f.name,
|
|
53
|
+
description: f.description,
|
|
54
|
+
package: f.package, transport: f.transport ?? '',
|
|
55
|
+
ignoreCertErrors: f.ignoreCertErrors === 'true',
|
|
56
|
+
});
|
|
57
|
+
log(`deploy config written: ${f.appDir}/ui5-deploy.yaml (app ${f.name} -> package ${f.package}` +
|
|
58
|
+
`${f.transport ? `, transport ${f.transport}` : ''}). ` +
|
|
59
|
+
`Run \`npm run deploy-test\` to validate without deploying.`);
|
|
60
|
+
return 0;
|
|
61
|
+
}
|
|
62
|
+
default:
|
|
63
|
+
log(`Unknown fiori subcommand "${cmd}". Use: list | scaffold | apply | deploy-config`);
|
|
64
|
+
return 1;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
catch (e) {
|
|
68
|
+
log(`ERROR: ${e.message}`);
|
|
69
|
+
return 1;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
// cspeach-cli/src/tools/fiori/deploy-config.ts
|
|
2
|
+
//
|
|
3
|
+
// Writes the ABAP-repository deploy configuration for a scaffolded app:
|
|
4
|
+
// ui5-deploy.yaml (deploy-to-abap custom task, shape grounded against the
|
|
5
|
+
// bundled @sap/ux-ui5-tooling `fiori deploy --help` output, 2026-06-10) and
|
|
6
|
+
// the package.json `deploy` / `deploy-test` scripts.
|
|
7
|
+
//
|
|
8
|
+
// Credentials are NEVER written here — `fiori deploy --username/--password`
|
|
9
|
+
// take the NAMES of environment variables at deploy time.
|
|
10
|
+
import { readFileSync, writeFileSync } from 'node:fs';
|
|
11
|
+
import { join } from 'node:path';
|
|
12
|
+
export function writeDeployConfig(appDir, opts) {
|
|
13
|
+
const appName = opts.appName;
|
|
14
|
+
if (appName.length > 15) {
|
|
15
|
+
throw new Error(`BSP app name "${appName}" exceeds 15 characters (ABAP repository limit)`);
|
|
16
|
+
}
|
|
17
|
+
if (appName !== appName.toUpperCase()) {
|
|
18
|
+
throw new Error(`BSP app name "${appName}" must be uppercase`);
|
|
19
|
+
}
|
|
20
|
+
const pkg = opts.package.toUpperCase();
|
|
21
|
+
if (!/^(\$TMP|[ZY][A-Z0-9_]*|\/[A-Z0-9_]+\/[A-Z0-9_]+)$/.test(pkg)) {
|
|
22
|
+
throw new Error(`Package "${opts.package}" is not in the customer namespace (Z-/Y-prefixed, $TMP, or /namespace/pkg)`);
|
|
23
|
+
}
|
|
24
|
+
const isTmp = pkg === '$TMP';
|
|
25
|
+
if (!isTmp && !/^[A-Z0-9]{3}K[0-9]{6,}$/.test(opts.transport)) {
|
|
26
|
+
throw new Error(`A valid workbench transport (e.g. S4HK903359) is required unless package is $TMP — got "${opts.transport}"`);
|
|
27
|
+
}
|
|
28
|
+
const pkgJsonPath = join(appDir, 'package.json');
|
|
29
|
+
const pkgJson = JSON.parse(readFileSync(pkgJsonPath, 'utf8'));
|
|
30
|
+
const projectName = pkgJson.name ?? appName.toLowerCase();
|
|
31
|
+
// TLS posture (2026-06-14): only disable cert verification when the caller
|
|
32
|
+
// explicitly opted into insecure (wired from the per-alias
|
|
33
|
+
// insecure_skip_tls_verify flag). When a CA is configured / verification is
|
|
34
|
+
// on, leave cert errors enabled — the OS/Node trust + CA handle it. The
|
|
35
|
+
// insecure line carries a loud dev-only comment.
|
|
36
|
+
const insecure = opts.ignoreCertErrors === true;
|
|
37
|
+
const certLine = insecure
|
|
38
|
+
? 'ignoreCertErrors: true # dev only — configure a CA for production'
|
|
39
|
+
: 'ignoreCertErrors: false';
|
|
40
|
+
const yaml = `# yaml-language-server: $schema=https://sap.github.io/ui5-tooling/schema/ui5.yaml.json
|
|
41
|
+
# Deploy configuration — written by the CSPeach fiori engine.
|
|
42
|
+
# Run \`npm run deploy-test\` first: the backend validates WITHOUT deploying.
|
|
43
|
+
specVersion: "4.0"
|
|
44
|
+
metadata:
|
|
45
|
+
name: ${projectName}
|
|
46
|
+
type: application
|
|
47
|
+
builder:
|
|
48
|
+
resources:
|
|
49
|
+
excludes:
|
|
50
|
+
- /test/**
|
|
51
|
+
- /localService/**
|
|
52
|
+
customTasks:
|
|
53
|
+
- name: deploy-to-abap
|
|
54
|
+
afterTask: generateCachebusterInfo
|
|
55
|
+
configuration:
|
|
56
|
+
${certLine}
|
|
57
|
+
target:
|
|
58
|
+
url: ${opts.url}
|
|
59
|
+
client: "${opts.client}"
|
|
60
|
+
app:
|
|
61
|
+
name: ${appName}
|
|
62
|
+
description: ${opts.description ?? projectName}
|
|
63
|
+
package: ${pkg}
|
|
64
|
+
transport: ${isTmp ? '""' : opts.transport}
|
|
65
|
+
exclude:
|
|
66
|
+
- /test/
|
|
67
|
+
`;
|
|
68
|
+
writeFileSync(join(appDir, 'ui5-deploy.yaml'), yaml, 'utf8');
|
|
69
|
+
pkgJson.scripts = pkgJson.scripts ?? {};
|
|
70
|
+
pkgJson.scripts['deploy'] = 'npm run build && fiori deploy --config ui5-deploy.yaml';
|
|
71
|
+
pkgJson.scripts['deploy-test'] = 'npm run build && fiori deploy --config ui5-deploy.yaml --testMode --yes';
|
|
72
|
+
writeFileSync(pkgJsonPath, JSON.stringify(pkgJson, null, 2) + '\n', 'utf8');
|
|
73
|
+
}
|