@cspeach/cli 1.1.18 → 1.1.19
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 +3 -4
- package/dist/agent/loop.js +143 -25
- package/dist/agent/providers/local-provider.js +4 -1
- package/dist/agent/repair-partial.js +76 -19
- package/dist/agent/tool-dispatch.js +58 -21
- package/dist/agent/turn-error-ux.js +77 -26
- package/dist/agent/turn-stream.js +25 -3
- package/dist/approvals/adt-type.js +27 -0
- package/dist/approvals/advisory-prompt.js +48 -0
- package/dist/approvals/advisory-render.js +25 -11
- package/dist/approvals/approval-prompt.js +74 -24
- package/dist/approvals/jwt.js +2 -1
- package/dist/approvals/render.js +64 -36
- package/dist/approvals/risk-floor.js +53 -2
- package/dist/auth/api-key.js +30 -16
- package/dist/auth/keychain.js +0 -0
- package/dist/auth/me.js +25 -7
- package/dist/cli.js +25 -6
- package/dist/commands/compact.js +12 -9
- package/dist/commands/config-set.js +6 -2
- package/dist/commands/config-show.js +7 -5
- package/dist/commands/cost.js +41 -34
- package/dist/commands/help.js +88 -50
- package/dist/commands/login.js +28 -19
- package/dist/commands/logout.js +7 -3
- package/dist/commands/plan-chain.js +71 -77
- package/dist/commands/plan-continue.js +1 -0
- package/dist/commands/plan-resume.js +20 -12
- package/dist/commands/whoami.js +23 -9
- package/dist/config/loader.js +15 -3
- package/dist/cost/pricing.js +6 -5
- package/dist/doctor/checks/forge-rules.js +13 -3
- package/dist/doctor/checks/keychain.js +6 -4
- package/dist/doctor/run.js +101 -20
- package/dist/index.js +5 -2
- package/dist/lib/graceful-exit.js +56 -0
- package/dist/lib/piped-prompt.js +65 -0
- package/dist/lock-contention.js +2 -2
- package/dist/one-shot.js +135 -40
- package/dist/projects/answer-blockers.js +6 -1
- package/dist/projects/handover-md.js +15 -14
- package/dist/projects/plan-run.js +97 -54
- package/dist/projects/promote-command.js +11 -2
- package/dist/projects/save-command.js +3 -2
- package/dist/projects/status.js +2 -1
- package/dist/projects/yes-no.js +12 -0
- package/dist/renderer/abap-inline.js +18 -14
- package/dist/renderer/answer-trim.js +69 -0
- package/dist/renderer/banners.js +8 -8
- package/dist/renderer/brand-settled.js +19 -0
- package/dist/renderer/change-card.js +156 -0
- package/dist/renderer/checklist-format.js +130 -0
- package/dist/renderer/color-mode.js +45 -0
- package/dist/renderer/error-detail.js +104 -0
- package/dist/renderer/fence-state.js +46 -0
- package/dist/renderer/footer-line.js +96 -0
- package/dist/renderer/glyphs.js +25 -0
- package/dist/renderer/goodbye.js +38 -0
- package/dist/renderer/legacy-palette.js +41 -0
- package/dist/renderer/look.js +30 -0
- package/dist/renderer/markdown.js +299 -92
- package/dist/renderer/notice-log.js +72 -0
- package/dist/renderer/notice-shape.js +48 -0
- package/dist/renderer/notices.js +40 -8
- package/dist/renderer/panel-rows.js +16 -0
- package/dist/renderer/pipeline.js +66 -0
- package/dist/renderer/progress-chatter.js +23 -17
- package/dist/renderer/rendering-mode.js +34 -0
- package/dist/renderer/routing-line.js +14 -0
- package/dist/renderer/sanitize.js +12 -0
- package/dist/renderer/severity.js +2 -7
- package/dist/renderer/startup-lines.js +152 -0
- package/dist/renderer/status-footer.js +52 -34
- package/dist/renderer/steering-echo.js +41 -0
- package/dist/renderer/syntax.js +31 -12
- package/dist/renderer/tables.js +5 -1
- package/dist/renderer/theme.js +44 -0
- package/dist/renderer/thinking-heartbeat.js +33 -2
- package/dist/renderer/todo-block.js +10 -49
- package/dist/renderer/tool-labels.js +238 -0
- package/dist/renderer/tool-widget.js +245 -51
- package/dist/renderer/trace.js +42 -0
- package/dist/renderer/transcript-flow.js +132 -0
- package/dist/renderer/tty.js +34 -1
- package/dist/renderer/ui-width.js +42 -0
- package/dist/renderer/verify-chain.js +4 -2
- package/dist/renderer/widget-fallback.js +58 -65
- package/dist/repl/bracketed-paste.js +7 -1
- package/dist/repl/builtin-commands.js +19 -8
- package/dist/repl/credential-handover-gate.js +28 -0
- package/dist/repl/early-line-buffer.js +5 -2
- package/dist/repl/file-picker.js +54 -12
- package/dist/repl/ink-stdin-guard.js +66 -0
- package/dist/repl/inquirer-guard.js +59 -16
- package/dist/repl/inquirer-theme.js +27 -33
- package/dist/repl/paste-marker.js +19 -0
- package/dist/repl/plan-turn-end.js +20 -0
- package/dist/repl/post-turn-status.js +36 -11
- package/dist/repl/reroute-turn.js +21 -0
- package/dist/repl/reset-tty-stdin.js +44 -0
- package/dist/repl/restore-guard.js +22 -0
- package/dist/repl/resume-standalone.js +63 -0
- package/dist/repl/rule8-detector.js +11 -3
- package/dist/repl/safety-confirm.js +161 -93
- package/dist/repl/session-spend-line.js +8 -4
- package/dist/repl/themed-prompts.js +19 -0
- package/dist/repl/ui-look-command.js +75 -0
- package/dist/repl/update-method-preview-hook.js +48 -5
- package/dist/repl.js +795 -332
- package/dist/rewind/cli.js +3 -2
- package/dist/rewind/format.js +14 -9
- package/dist/rewind/restore.js +11 -0
- package/dist/router/classifier.js +47 -4
- package/dist/router/intent-extractor.js +24 -8
- package/dist/router/piped-routing.js +23 -0
- package/dist/router/resume-routing.js +16 -0
- package/dist/router/routing-failure.js +57 -0
- package/dist/sap/first-run-choice.js +1 -1
- package/dist/sap/onboarding.js +3 -2
- package/dist/sap/standalone-onboarding.js +1 -1
- package/dist/sap/system-info.js +4 -2
- package/dist/sap/unreachable.js +28 -0
- package/dist/sap-errors/clean-error-text.js +182 -0
- package/dist/session/interrupt-reason.js +39 -0
- package/dist/session/recap.js +36 -24
- package/dist/session/resume.js +104 -36
- package/dist/session/store.js +69 -27
- package/dist/session/user-prompt.js +21 -0
- package/dist/skill-catalog.js +7 -0
- package/dist/skills/bundled-skills.js +76 -69
- package/dist/standards/standards-init.js +1 -1
- package/dist/test-helpers/answer-any.js +46 -0
- package/dist/tools/approval.js +27 -11
- package/dist/tools/ask-question.js +47 -5
- package/dist/tools/filesystem/file-write.js +19 -2
- package/dist/tools/fiori/fe-extend.js +16 -1
- package/dist/tools/fiori/fe-scaffold.js +105 -3
- package/dist/tools/fiori/html-escapes.js +28 -0
- package/dist/tools/fiori/metadata/audit.js +605 -0
- package/dist/tools/fiori/metadata/references.js +104 -0
- package/dist/tools/fiori/metadata/types.js +8 -0
- package/dist/tools/fiori/preview/app-guard.js +215 -0
- package/dist/tools/fiori/preview/env.js +193 -0
- package/dist/tools/fiori/preview/readiness.js +127 -0
- package/dist/tools/fiori/preview/registry.js +412 -0
- package/dist/tools/fiori/preview/start.js +469 -0
- package/dist/tools/fiori/samples/loader.js +20 -5
- package/dist/tools/fiori/scaffold.js +18 -6
- package/dist/tools/fiori/smoke/app-driver-page.js +372 -0
- package/dist/tools/fiori/smoke/app-driver.js +100 -0
- package/dist/tools/fiori/smoke/assertions.js +161 -24
- package/dist/tools/fiori/smoke/audit-columns.js +21 -0
- package/dist/tools/fiori/smoke/batch.js +273 -0
- package/dist/tools/fiori/smoke/draft-safety.js +241 -0
- package/dist/tools/fiori/smoke/draft-smoke.js +473 -0
- package/dist/tools/fiori/smoke/driver.js +171 -4
- package/dist/tools/fiori/smoke/evidence.js +35 -0
- package/dist/tools/fiori/smoke/labels-codes.js +207 -0
- package/dist/tools/fiori/smoke/nav-error.js +26 -0
- package/dist/tools/fiori/smoke/run-smoke.js +155 -12
- package/dist/tools/fiori/tools.js +651 -7
- package/dist/tools/fiori/ui5-version.js +16 -0
- package/dist/tools/index.js +8 -0
- package/dist/tools/local-build.js +6 -0
- package/dist/tools/sap-read.js +16 -1
- package/dist/tools/sap-write.js +122 -10
- package/dist/tools/shell/shell_exec.js +30 -12
- package/dist/tools/syntax-state.js +67 -0
- package/dist/tools/todo.js +23 -18
- package/dist/tools/transport-fit.js +462 -0
- package/dist/tools/transport-resolution.js +3 -1
- package/dist/tools/transport.js +33 -7
- package/dist/tools/verify.js +184 -8
- package/dist/ui/app.js +284 -146
- package/dist/ui/approval-modal.js +41 -15
- package/dist/ui/ask-answer-text.js +17 -0
- package/dist/ui/body.js +25 -6
- package/dist/ui/card-slot.js +98 -0
- package/dist/ui/checklist.js +9 -0
- package/dist/ui/coaching-picker-classic.js +8 -5
- package/dist/ui/command-palette.js +6 -4
- package/dist/ui/confirm-request.js +18 -0
- package/dist/ui/context-grid.js +2 -1
- package/dist/ui/file-palette.js +6 -4
- package/dist/ui/files-card-emitter.js +19 -0
- package/dist/ui/footer-line.js +16 -0
- package/dist/ui/footer.js +105 -104
- package/dist/ui/input-wrap.js +122 -0
- package/dist/ui/interrupt.js +103 -0
- package/dist/ui/key-burst.js +117 -0
- package/dist/ui/line-resolution.js +4 -3
- package/dist/ui/live-area.js +71 -0
- package/dist/ui/login-banner.js +54 -43
- package/dist/ui/rewind-panel.js +76 -24
- package/dist/ui/role-props.js +47 -0
- package/dist/ui/sap-state-store.js +1 -0
- package/dist/ui/session-timeline.js +49 -3
- package/dist/ui/skill-picker.js +5 -47
- package/dist/ui/text-input.js +128 -24
- package/dist/ui/todo-emitter.js +19 -0
- package/dist/ui/turn-status-emitter.js +28 -2
- package/dist/ui/turn-status.js +16 -19
- package/dist/ui/use-card-request.js +44 -0
- package/dist/ui/widgets/ask-card.js +521 -0
- package/dist/ui/widgets/bar-chart.js +11 -5
- package/dist/ui/widgets/coaching-picker.js +50 -55
- package/dist/ui/widgets/confirm-card.js +193 -0
- package/dist/ui/widgets/dep-graph.js +3 -2
- package/dist/ui/widgets/diff-viewer.js +6 -5
- package/dist/ui/widgets/files-card.js +97 -0
- package/dist/ui/widgets/question-card.js +2 -1
- package/dist/ui/widgets/reclaim-raw-stdin.js +24 -0
- package/dist/ui/widgets/shortcuts-sheet.js +25 -0
- package/dist/ui/widgets/stack-frames.js +2 -1
- package/dist/upgrade-check.js +39 -8
- package/package.json +11 -2
- package/test-harness/fe-draft/app.ts +188 -0
- package/test-harness/fe-draft/fe-draft.e2e.test.ts +461 -0
- package/test-harness/fe-draft/manifest-enhancer.unit.test.ts +51 -0
- package/test-harness/fe-draft/routes.ts +224 -0
- package/test-harness/fe-draft/routes.unit.test.ts +45 -0
- package/test-harness/fe-draft/server.ts +225 -0
- package/test-harness/fe-draft/variants.ts +196 -0
- package/vitest.smoke-e2e.config.ts +20 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cspeach/cli",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.19",
|
|
4
4
|
"description": "CSPeach — AI-assisted ABAP development CLI. Direct SAP access, 34 skills, safety gates.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"abap",
|
|
@@ -37,6 +37,7 @@
|
|
|
37
37
|
"typecheck": "tsc --noEmit",
|
|
38
38
|
"test": "vitest run",
|
|
39
39
|
"test:watch": "vitest",
|
|
40
|
+
"test:smoke-e2e": "vitest run --config vitest.smoke-e2e.config.ts",
|
|
40
41
|
"adt:smoke": "tsx scripts/adt-smoke.ts",
|
|
41
42
|
"smoke:all": "pnpm zcspeach:smoke && pnpm adt:smoke",
|
|
42
43
|
"zcspeach:smoke": "tsx scripts/zcspeach-smoke.ts",
|
|
@@ -52,16 +53,19 @@
|
|
|
52
53
|
"dependencies": {
|
|
53
54
|
"@anthropic-ai/sdk": "^0.90.0",
|
|
54
55
|
"@cspeach/register-core": "0.1.0",
|
|
55
|
-
"@cspeach/sap-client": "0.1.
|
|
56
|
+
"@cspeach/sap-client": "0.1.15",
|
|
56
57
|
"@iarna/toml": "^2.2.5",
|
|
57
58
|
"@inkjs/ui": "^2.0.0",
|
|
58
59
|
"@inquirer/prompts": "^7.0.0",
|
|
59
60
|
"@napi-rs/keyring": "2.1.0",
|
|
60
61
|
"@noble/ed25519": "2.1.0",
|
|
61
62
|
"@noble/hashes": "1.4.0",
|
|
63
|
+
"@sap-ux/annotation-converter": "0.10.21",
|
|
64
|
+
"@sap-ux/edmx-parser": "0.10.0",
|
|
62
65
|
"@sap-ux/fe-fpm-writer": "0.43.7",
|
|
63
66
|
"@sap-ux/fiori-elements-writer": "^3.0.39",
|
|
64
67
|
"@sap-ux/fiori-freestyle-writer": "3.0.12",
|
|
68
|
+
"@sap-ux/vocabularies-types": "0.15.0",
|
|
65
69
|
"adm-zip": "0.5.17",
|
|
66
70
|
"chalk": "^5.3.0",
|
|
67
71
|
"cli-highlight": "^2.1.11",
|
|
@@ -83,11 +87,13 @@
|
|
|
83
87
|
"openai": "^6.49.0",
|
|
84
88
|
"puppeteer-core": "24.10.2",
|
|
85
89
|
"react": "^18.3.0",
|
|
90
|
+
"string-width": "^7.2.0",
|
|
86
91
|
"turndown": "^7.2.0",
|
|
87
92
|
"unpdf": "1.7.0",
|
|
88
93
|
"zod": "^3.23.0"
|
|
89
94
|
},
|
|
90
95
|
"devDependencies": {
|
|
96
|
+
"@sap-ux/ui5-middleware-fe-mockserver": "2.4.17",
|
|
91
97
|
"@types/adm-zip": "0.5.8",
|
|
92
98
|
"@types/cross-spawn": "^6.0.6",
|
|
93
99
|
"@types/diff": "^7.0.2",
|
|
@@ -95,10 +101,13 @@
|
|
|
95
101
|
"@types/node": "^20.17.0",
|
|
96
102
|
"@types/react": "^18.3.0",
|
|
97
103
|
"@types/turndown": "^5.0.5",
|
|
104
|
+
"@ui5/builder": "4.3.2",
|
|
105
|
+
"@ui5/fs": "4.0.6",
|
|
98
106
|
"docx": "^9.0.2",
|
|
99
107
|
"husky": "^9.1.7",
|
|
100
108
|
"ink-testing-library": "^4.0.0",
|
|
101
109
|
"pdf-lib": "^1.17.1",
|
|
110
|
+
"strip-ansi": "6.0.1",
|
|
102
111
|
"tsx": "^4.19.0",
|
|
103
112
|
"typescript": "^5.7.0",
|
|
104
113
|
"vitest": "^2.1.0"
|
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
// cspeach-cli/test-harness/fe-draft/app.ts
|
|
2
|
+
//
|
|
3
|
+
// L1-2b — the apps the offline harness serves, built with the PRODUCT's own
|
|
4
|
+
// tools (fiori_scaffold_fe, fiori_fe_extend handlers) so a scaffold regression
|
|
5
|
+
// shows up here, not in a sitting.
|
|
6
|
+
//
|
|
7
|
+
// good — an LROP app over the captured draft service, exactly as
|
|
8
|
+
// fiori_scaffold_fe writes it, plus one harmless custom table
|
|
9
|
+
// action ("Ping") so the good app has something to press.
|
|
10
|
+
// broken — the same app whose custom table action ("Boom") is wired to a
|
|
11
|
+
// handler that THROWS. A smoke that runs the press exercise must
|
|
12
|
+
// fail `exercises` on it.
|
|
13
|
+
// brokenPress — the same app with the custom action exactly as
|
|
14
|
+
// fiori_fe_extend writes it when asked for `eventHandler: true`:
|
|
15
|
+
// `"press": ""`. Pressing it makes UI5 try to load "/.js".
|
|
16
|
+
import { readFile, writeFile, mkdir } from 'node:fs/promises';
|
|
17
|
+
import * as path from 'node:path';
|
|
18
|
+
import { fileURLToPath } from 'node:url';
|
|
19
|
+
import { fioriScaffoldFeHandler, feExtendHandler } from '../../src/tools/fiori/tools.js';
|
|
20
|
+
import type { ToolContext } from '../../src/tools/index.js';
|
|
21
|
+
import { openUrlFromStartScript } from './routes.js';
|
|
22
|
+
|
|
23
|
+
const HERE = path.dirname(fileURLToPath(import.meta.url));
|
|
24
|
+
|
|
25
|
+
/** The captured draft service (ZUI_LAS_INSPECTIONLOG_O4) — read in place by the mockserver. */
|
|
26
|
+
export const DRAFT_METADATA = path.resolve(
|
|
27
|
+
HERE,
|
|
28
|
+
'../../src/tools/fiori/__tests__/fixtures/metadata/v4-draft-main.xml',
|
|
29
|
+
);
|
|
30
|
+
export const DRAFT_SERVICE_PATH =
|
|
31
|
+
'/sap/opu/odata4/sap/zui_las_inspectionlog_o4/srvd/sap/zui_las_inspectionlog_o4/0001/';
|
|
32
|
+
export const DRAFT_MAIN_ENTITY = 'InspectionLog';
|
|
33
|
+
/** Fully-qualified draft actions, as `SAP__common.DraftRoot` names them in the capture. */
|
|
34
|
+
export const DRAFT_NS = 'com.sap.gateway.srvd.zui_las_inspectionlog_o4.v0001';
|
|
35
|
+
|
|
36
|
+
/** The action button id suffix FE V4 renders for a manifest custom action. */
|
|
37
|
+
export const customActionSelector = (name: string): string => `[id$="::CustomAction::${name}"]`;
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Fixed active rows for the draft root. Invented values, US sample names; user
|
|
41
|
+
* fields are MJOHNSON (the fixture convention). Status / Severity are the raw
|
|
42
|
+
* one-letter codes the captured service really carries with no text — the shape
|
|
43
|
+
* L1-3's no-raw-codes check has to catch.
|
|
44
|
+
*/
|
|
45
|
+
const row = (n: number, equipment: string, inspector: string, severity: string, status: string) => ({
|
|
46
|
+
InspectionUUID: `00000000-0000-4000-8000-00000000000${n}`,
|
|
47
|
+
EquipmentName: equipment,
|
|
48
|
+
InspectionDate: '2026-09-0' + n,
|
|
49
|
+
Inspector: inspector,
|
|
50
|
+
Severity: severity,
|
|
51
|
+
SeverityCriticality: severity === 'H' ? 1 : severity === 'M' ? 2 : 3,
|
|
52
|
+
Status: status,
|
|
53
|
+
Note: '',
|
|
54
|
+
CreatedBy: 'MJOHNSON',
|
|
55
|
+
LocalLastChangedBy: 'MJOHNSON',
|
|
56
|
+
IsActiveEntity: true,
|
|
57
|
+
HasActiveEntity: false,
|
|
58
|
+
HasDraftEntity: false,
|
|
59
|
+
});
|
|
60
|
+
export const INSPECTION_ROWS = [
|
|
61
|
+
row(1, 'Pump P-100', 'Mike Johnson', 'H', 'O'),
|
|
62
|
+
row(2, 'Valve V-200', 'Sarah Davis', 'M', 'C'),
|
|
63
|
+
row(3, 'Motor M-300', 'David Carter', 'L', 'O'),
|
|
64
|
+
];
|
|
65
|
+
export const HARNESS_MOCK_ROWS = { [DRAFT_MAIN_ENTITY]: INSPECTION_ROWS };
|
|
66
|
+
|
|
67
|
+
export const BROKEN_MARKER = 'CSP-HARNESS-BROKEN: the Boom handler threw';
|
|
68
|
+
|
|
69
|
+
function ctxFor(root: string): ToolContext {
|
|
70
|
+
return {
|
|
71
|
+
adt: {} as ToolContext['adt'],
|
|
72
|
+
sapAlias: 'HARNESS',
|
|
73
|
+
session: {} as ToolContext['session'],
|
|
74
|
+
cwd: root,
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
async function must(r: { content: string; is_error?: boolean }, what: string): Promise<void> {
|
|
79
|
+
if (r.is_error) throw new Error(`${what} failed: ${r.content}`);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export interface HarnessApp {
|
|
83
|
+
/** Absolute app dir (holds webapp/). */
|
|
84
|
+
dir: string;
|
|
85
|
+
appId: string;
|
|
86
|
+
/** The page the scaffold's `npm start` opens (FLP sandbox + intent), app-relative. */
|
|
87
|
+
startEntry: string;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/** The FLP sandbox entry `npm start` opens for this app. */
|
|
91
|
+
export const flpEntry = (app: HarnessApp): string => app.startEntry;
|
|
92
|
+
|
|
93
|
+
/** Scaffold the LROP app through the fiori_scaffold_fe handler. `ui5Version` undefined = the product default. */
|
|
94
|
+
async function scaffold(root: string, rel: string, appId: string, ui5Version?: string): Promise<HarnessApp> {
|
|
95
|
+
await must(
|
|
96
|
+
await fioriScaffoldFeHandler(
|
|
97
|
+
{
|
|
98
|
+
basePath: rel,
|
|
99
|
+
appId,
|
|
100
|
+
appTitle: 'Inspection Log (harness)',
|
|
101
|
+
template: 'lrop',
|
|
102
|
+
serviceUrl: 'http://localhost',
|
|
103
|
+
servicePath: DRAFT_SERVICE_PATH,
|
|
104
|
+
mainEntity: DRAFT_MAIN_ENTITY,
|
|
105
|
+
...(ui5Version ? { ui5Version } : {}),
|
|
106
|
+
},
|
|
107
|
+
ctxFor(root),
|
|
108
|
+
),
|
|
109
|
+
'fiori_scaffold_fe',
|
|
110
|
+
);
|
|
111
|
+
const dir = path.join(root, rel);
|
|
112
|
+
const pkg = JSON.parse(await readFile(path.join(dir, 'package.json'), 'utf8'));
|
|
113
|
+
return { dir, appId, startEntry: openUrlFromStartScript(pkg.scripts.start) };
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/** Add a custom table action through the fiori_fe_extend handler. */
|
|
117
|
+
async function addTableAction(root: string, rel: string, name: string, settings: Record<string, unknown>): Promise<void> {
|
|
118
|
+
await must(
|
|
119
|
+
await feExtendHandler(
|
|
120
|
+
{
|
|
121
|
+
basePath: rel,
|
|
122
|
+
op: 'custom-action',
|
|
123
|
+
params: {
|
|
124
|
+
name,
|
|
125
|
+
target: { page: `${DRAFT_MAIN_ENTITY}List`, control: '@com.sap.vocabularies.UI.v1.LineItem' },
|
|
126
|
+
settings: { text: name, ...settings },
|
|
127
|
+
},
|
|
128
|
+
},
|
|
129
|
+
ctxFor(root),
|
|
130
|
+
),
|
|
131
|
+
'fiori_fe_extend custom-action',
|
|
132
|
+
);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Point a custom action's `press` at `ext/<Name>.js#onPress` and write that
|
|
137
|
+
* module with a known body (the good app's harmless handler, the broken app's
|
|
138
|
+
* throwing one), replacing the stub fiori_fe_extend generated.
|
|
139
|
+
*/
|
|
140
|
+
async function wireHandler(app: HarnessApp, name: string, body: string): Promise<void> {
|
|
141
|
+
const manifestPath = path.join(app.dir, 'webapp', 'manifest.json');
|
|
142
|
+
const manifest = JSON.parse(await readFile(manifestPath, 'utf8'));
|
|
143
|
+
const action =
|
|
144
|
+
manifest['sap.ui5'].routing.targets[`${DRAFT_MAIN_ENTITY}List`].options.settings.controlConfiguration[
|
|
145
|
+
'@com.sap.vocabularies.UI.v1.LineItem'
|
|
146
|
+
].actions[name];
|
|
147
|
+
action.press = `${app.appId}.ext.${name}.onPress`;
|
|
148
|
+
await writeFile(manifestPath, JSON.stringify(manifest, null, 2));
|
|
149
|
+
await mkdir(path.join(app.dir, 'webapp', 'ext'), { recursive: true });
|
|
150
|
+
await writeFile(
|
|
151
|
+
path.join(app.dir, 'webapp', 'ext', `${name}.js`),
|
|
152
|
+
`sap.ui.define([], function () {\n 'use strict';\n return { onPress: function () { ${body} } };\n});\n`,
|
|
153
|
+
);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/** The good app: scaffold as the product writes it, plus a harmless "Ping" action. */
|
|
157
|
+
export async function buildGoodApp(root: string, ui5Version?: string): Promise<HarnessApp> {
|
|
158
|
+
const app = await scaffold(root, 'good', 'z.harness.good', ui5Version);
|
|
159
|
+
await addTableAction(root, 'good', 'Ping', {});
|
|
160
|
+
await wireHandler(app, 'Ping', 'return;');
|
|
161
|
+
return app;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/** The broken app: the "Boom" action's handler throws. */
|
|
165
|
+
export async function buildBrokenApp(root: string, ui5Version?: string): Promise<HarnessApp> {
|
|
166
|
+
const app = await scaffold(root, 'broken', 'z.harness.broken', ui5Version);
|
|
167
|
+
await addTableAction(root, 'broken', 'Boom', {});
|
|
168
|
+
await wireHandler(app, 'Boom', `throw new Error(${JSON.stringify(BROKEN_MARKER)});`);
|
|
169
|
+
return app;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* The broken-press app: a custom action whose press names a handler module that
|
|
174
|
+
* does not exist (a renamed or deleted ext file). fiori_fe_extend no longer
|
|
175
|
+
* writes an empty press — it always generates a handler — so the realistic
|
|
176
|
+
* broken case is a dangling reference.
|
|
177
|
+
*/
|
|
178
|
+
export async function buildBrokenPressApp(root: string, ui5Version?: string): Promise<HarnessApp> {
|
|
179
|
+
const app = await scaffold(root, 'broken-press', 'z.harness.brokenpress', ui5Version);
|
|
180
|
+
await addTableAction(root, 'broken-press', 'Boom', {});
|
|
181
|
+
const manifestPath = path.join(app.dir, 'webapp', 'manifest.json');
|
|
182
|
+
const manifest = JSON.parse(await readFile(manifestPath, 'utf8'));
|
|
183
|
+
manifest['sap.ui5'].routing.targets[`${DRAFT_MAIN_ENTITY}List`].options.settings.controlConfiguration[
|
|
184
|
+
'@com.sap.vocabularies.UI.v1.LineItem'
|
|
185
|
+
].actions.Boom.press = `${app.appId}.ext.missing.Missing.onPress`;
|
|
186
|
+
await writeFile(manifestPath, JSON.stringify(manifest, null, 2));
|
|
187
|
+
return app;
|
|
188
|
+
}
|