@cspeach/cli 1.1.0 → 1.1.2

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.
@@ -62,6 +62,31 @@ export async function scaffoldFioriElements(opts) {
62
62
  ui5: { minUI5Version: ui5Version, version: ui5Version },
63
63
  };
64
64
  await generate(opts.basePath, config, fs);
65
+ if (!opts.localAnnotations) {
66
+ // @sap-ux writers force-inject a 'localAnnotationsName: annotation' dataSource for
67
+ // EDMX services even when none was requested, but only write the XML file when
68
+ // metadata is passed — leaving a manifest reference that 404s at boot (F-L2-7).
69
+ const manifestPath = join(opts.basePath, 'webapp', 'manifest.json');
70
+ const manifest = fs.readJSON(manifestPath);
71
+ const ds = manifest?.['sap.app']?.dataSources ?? {};
72
+ const removed = Object.keys(ds).filter((k) => ds[k]?.type === 'ODataAnnotation');
73
+ if (removed.length > 0) {
74
+ for (const k of removed)
75
+ delete ds[k];
76
+ for (const d of Object.values(ds)) {
77
+ if (Array.isArray(d?.settings?.annotations)) {
78
+ d.settings.annotations = d.settings.annotations.filter((name) => !removed.includes(name));
79
+ }
80
+ }
81
+ fs.writeJSON(manifestPath, manifest);
82
+ // also drop the orphan empty EDMX if the writer emitted one (metadata path)
83
+ for (const k of removed) {
84
+ const orphan = join(opts.basePath, 'webapp', 'annotations', `${k}.xml`);
85
+ if (fs.exists(orphan))
86
+ fs.delete(orphan);
87
+ }
88
+ }
89
+ }
65
90
  // Overwrite the writer's empty EDMX skeleton with the caller's real annotations
66
91
  // EDMX, in the mem-fs editor, BEFORE commit so the flushed file is the caller's.
67
92
  if (opts.localAnnotations) {
@@ -3,15 +3,21 @@
3
3
  // under the gateway/odata4 path. An incidental non-OData 404 (favicon, a
4
4
  // analytics beacon) must NOT false-fail an otherwise healthy render.
5
5
  const ODATA_URL = /\$metadata|\$batch|\/sap\/opu\/odata|\/odata4?\//i;
6
+ // Unbuilt dev-serve artifacts: `fiori run` 404s Component-preload.js and
7
+ // resources.json on every app that hasn't been through `ui5 build`.
8
+ // Dev-mode noise, not app defects (F-L2-12) — must not fail boot.
9
+ const UNBUILT_DEV_404 = /(Component-preload\.js|resources\.json)/i;
10
+ const LOAD_FAILURE = /(404|failed to load)/i;
6
11
  /** Boot check: the app loaded without throwing anything to the console. */
7
12
  export function checkBoot(consoleErrors) {
8
- const ok = consoleErrors.length === 0;
13
+ const real = consoleErrors.filter((e) => !(UNBUILT_DEV_404.test(e) && LOAD_FAILURE.test(e)));
14
+ const ok = real.length === 0;
9
15
  return {
10
16
  name: 'boot',
11
17
  ok,
12
18
  detail: ok
13
19
  ? 'app booted with no console errors'
14
- : `${consoleErrors.length} console error(s) during boot: ${consoleErrors.slice(0, 3).join(' | ')}`,
20
+ : `${real.length} console error(s) during boot: ${real.slice(0, 3).join(' | ')}`,
15
21
  };
16
22
  }
17
23
  /** OData check: no 4xx/5xx on any $metadata / $batch / entity-set request. */
@@ -74,6 +74,12 @@ export async function openApp(executablePath, url, timeoutMs) {
74
74
  }, name);
75
75
  await page.waitForNetworkIdle({ idleTime: 200, timeout: timeoutMs }).catch(() => { });
76
76
  },
77
+ // F-L2-10A: evidence capture. The caller owns the artifact dir (it is
78
+ // created before this is called) and owns failure handling — a rejection
79
+ // here is caught there and the entry omitted, never failing the smoke.
80
+ screenshot: async (target) => {
81
+ await page.screenshot({ path: target });
82
+ },
77
83
  close: async () => {
78
84
  await browser.close();
79
85
  },
@@ -18,6 +18,9 @@
18
18
  //
19
19
  // The browser + driver are injectable (RunSmokeDeps) so the unit suite drives
20
20
  // every path with fakes and NEVER launches a real browser.
21
+ import { mkdirSync } from 'node:fs';
22
+ import * as os from 'node:os';
23
+ import * as path from 'node:path';
21
24
  import { resolveBrowserExecutable } from './browser.js';
22
25
  import { openApp as realOpenApp } from './driver.js';
23
26
  import { checkBoot, checkOData, checkColumns, checkRows, checkExercise, } from './assertions.js';
@@ -44,6 +47,30 @@ export const SMOKE_SELECTORS = {
44
47
  // us to an auth endpoint instead of serving the app.
45
48
  const LOGON_URL = /sap-system-login|\/saml2\/|\/logon|\/login/i;
46
49
  const DEFAULT_TIMEOUT_MS = 30_000;
50
+ /**
51
+ * A filesystem-safe app name for the artifact dir, derived from the preview URL:
52
+ * the last real path segment (`…/mycourses/index.html` → `mycourses`), falling
53
+ * back to host+port (`http://localhost:8080/index.html` → `localhost-8080`).
54
+ */
55
+ function appNameFromUrl(appUrl) {
56
+ let raw;
57
+ try {
58
+ const u = new URL(appUrl);
59
+ const segs = u.pathname.split('/').filter((s) => s.length > 0 && !s.includes('.'));
60
+ raw = segs.length > 0 ? segs[segs.length - 1] : u.host;
61
+ }
62
+ catch {
63
+ raw = appUrl;
64
+ }
65
+ const slug = raw.replace(/[^A-Za-z0-9._-]+/g, '-').replace(/^-+|-+$/g, '');
66
+ return slug.length > 0 ? slug : 'app';
67
+ }
68
+ /** Local-time `yyyyMMdd-HHmmss` stamp — one dir per smoke run, sorts naturally. */
69
+ function runStamp(now = new Date()) {
70
+ const p2 = (n) => String(n).padStart(2, '0');
71
+ return (`${now.getFullYear()}${p2(now.getMonth() + 1)}${p2(now.getDate())}` +
72
+ `-${p2(now.getHours())}${p2(now.getMinutes())}${p2(now.getSeconds())}`);
73
+ }
47
74
  /** A not-run result: manual verification, a single explanatory skipped check. */
48
75
  function skipped(detail) {
49
76
  return { verification: 'manual', passed: false, checks: [{ name: 'skipped', ok: false, detail }] };
@@ -111,6 +138,31 @@ export async function runSmoke(spec, deps) {
111
138
  catch {
112
139
  return skipped(`preview server not reachable at ${spec.appUrl}; run npm run start first`);
113
140
  }
141
+ // F-L2-10A evidence capture. The dir is created LAZILY on the first successful
142
+ // capture, so a skip path (or a driver with no screenshot capability) writes
143
+ // nothing at all. Every capture is try/catch-wrapped: a screenshot failure
144
+ // omits the entry and NEVER fails the smoke.
145
+ const screenshots = {};
146
+ const canShoot = typeof page.screenshot === 'function';
147
+ let shotDir;
148
+ const capture = async (key, file) => {
149
+ if (!page.screenshot)
150
+ return;
151
+ try {
152
+ shotDir ??=
153
+ deps.artifactDir ??
154
+ path.join(os.homedir(), '.cspeach', 'smoke', `${appNameFromUrl(spec.appUrl)}-${runStamp()}`);
155
+ mkdirSync(shotDir, { recursive: true });
156
+ const target = path.join(shotDir, file);
157
+ await page.screenshot(target);
158
+ screenshots[key] = target;
159
+ }
160
+ catch {
161
+ // Evidence is best-effort — a capture problem must never fail the smoke.
162
+ }
163
+ };
164
+ // Hoisted so the finally block can decide whether a failure shot is owed.
165
+ const checks = [];
114
166
  try {
115
167
  // Skip path 4 — SAP logon page (deployed BSP / unauthenticated preview).
116
168
  // Detect via a logon-redirect URL OR the presence of a logon-form field, and
@@ -120,9 +172,10 @@ export async function runSmoke(spec, deps) {
120
172
  if (LOGON_URL.test(url) || logonHits.length > 0) {
121
173
  return skipped(`preview served a SAP logon page (not authenticated) at ${url}; run npm run start first`);
122
174
  }
175
+ // The app is open past the logon check — this is the boot evidence shot.
176
+ await capture('boot', 'boot.png');
123
177
  // Real render — assert content. boot + odata always; columns only when the
124
178
  // spec asked for them; rows only for a rows-bearing (table) control.
125
- const checks = [];
126
179
  checks.push(checkBoot(page.consoleErrors()));
127
180
  checks.push(checkOData(page.failedRequests()));
128
181
  if (spec.expectedColumns && spec.expectedColumns.length > 0) {
@@ -141,9 +194,23 @@ export async function runSmoke(spec, deps) {
141
194
  const results = await runExercises(page, spec.exercises);
142
195
  checks.push(checkExercise(results));
143
196
  }
144
- return { verification: 'smoke', passed: checks.every((c) => c.ok), checks };
197
+ // Post-exercise evidence: the app's final state after every declared
198
+ // interaction ran (and simply the settled app when the spec declares none).
199
+ await capture('postExercise', 'post-exercise.png');
200
+ // `screenshots` is the SAME object the finally block may still add the
201
+ // failure entry to, so a failing run carries it even though the value is
202
+ // built here. It rides along only when the driver can shoot at all.
203
+ return {
204
+ verification: 'smoke',
205
+ passed: checks.every((c) => c.ok),
206
+ checks,
207
+ ...(canShoot ? { screenshots } : {}),
208
+ };
145
209
  }
146
210
  finally {
211
+ // A failing check earns a failure shot — captured BEFORE the page is closed.
212
+ if (checks.some((c) => !c.ok))
213
+ await capture('failure', 'failure.png');
147
214
  await page.close();
148
215
  }
149
216
  }
@@ -595,8 +595,10 @@ registerTool({
595
595
  'derived one. Derivation notes surface as a `warnings` array in the result. SKIPPABLE BUT NEVER ' +
596
596
  'SILENTLY GREEN: when the render_smoke config is off, no browser is found, the preview is unreachable, ' +
597
597
  'or the page is a SAP logon form, it returns verification:"manual" + passed:false with a "skipped" ' +
598
- 'check explaining why — it can never report a passing smoke without a real render. Flag-gated (enabled ' +
599
- 'with local_build), read-only.',
598
+ 'check explaining why — it can never report a passing smoke without a real render. On a real render it ' +
599
+ 'also saves boot / post-exercise / failure screenshots under ~/.cspeach/smoke/ and returns their absolute ' +
600
+ 'paths in a `screenshots` object (best-effort evidence — a capture failure never fails the smoke). ' +
601
+ 'Flag-gated (enabled with local_build), read-only.',
600
602
  isMutating: false,
601
603
  category: 'fiori',
602
604
  flagGated: true,
@@ -32,6 +32,10 @@ import { auditLog } from '@cspeach/sap-client';
32
32
  import { ERR } from '../errors/codes.js';
33
33
  import { interceptUpdateMethod, CancelledByUserError, } from './update-method-intercept.js';
34
34
  import { resolveWriteTransport } from './transport-resolution.js';
35
+ // F-L2-1: statusline-only wire — records the transport a write ACTUALLY
36
+ // stamped so the `tr` segment stops reading `none` for plan-supplied
37
+ // transports. Never touches the session transport pointer.
38
+ import { noteWriteTransport } from '../repl/current-transport.js';
35
39
  import { presentSafetyConfirmation } from '../repl/safety-confirm.js';
36
40
  import { isSafetyConfirmEnabled } from '../repl/safety-mode-state.js';
37
41
  // ── Transcript visibility helpers (D16-display + tour slide-7, 2026-06-12) ──
@@ -208,6 +212,7 @@ registerTool({
208
212
  };
209
213
  }
210
214
  await auditLog('setSource', args.type, args.name, 'success', Date.now() - startedAt);
215
+ noteWriteTransport(resolved.transport); // F-L2-1 statusline wire
211
216
  // ── Gate 4: Verify — syntax check (Rule 10) ──────────────────────────────
212
217
  const syntax = await verifySyntax(ctx.adt, args.name, args.type);
213
218
  await finalizeToolCall(ctx.session, toolUseId, JSON.stringify({ written: true, syntax }), false, Date.now() - startedAt);
@@ -400,6 +405,7 @@ registerTool({
400
405
  };
401
406
  }
402
407
  await auditLog('updateMethod', 'CLAS', args.class_name, 'success', Date.now() - startedAt, `method=${args.method_name}`);
408
+ noteWriteTransport(resolved.transport); // F-L2-1 statusline wire
403
409
  // ── Gate 4: Verify — syntax check (Rule 10) ──────────────────────────────
404
410
  const syntax = await verifySyntax(ctx.adt, args.class_name, 'CLAS');
405
411
  await finalizeToolCall(ctx.session, toolUseId, JSON.stringify({ updated: true, syntax }), false, Date.now() - startedAt);
@@ -530,6 +536,7 @@ registerTool({
530
536
  };
531
537
  }
532
538
  await auditLog('deleteObject', args.type, args.name, 'success', Date.now() - startedAt);
539
+ noteWriteTransport(resolved.transport); // F-L2-1 statusline wire
533
540
  // ── Post-check: object should no longer exist ────────────────────────────
534
541
  const stillExists = await ctx.adt.objectStructure(args.type, args.name)
535
542
  .then(() => true)
@@ -630,6 +637,9 @@ registerTool({
630
637
  is_error: true,
631
638
  };
632
639
  }
640
+ // F-L2-1 statusline wire. This path never reaches resolveWriteTransport —
641
+ // the 3-tier effectiveTransport above is what SAP stamped.
642
+ noteWriteTransport(effectiveTransport);
633
643
  await finalizeToolCall(ctx.session, toolUseId, JSON.stringify(result), false, Date.now() - startedAt);
634
644
  return {
635
645
  content: JSON.stringify({
@@ -752,6 +762,7 @@ registerTool({
752
762
  };
753
763
  }
754
764
  await auditLog('setClassInclude', 'CLAS', args.className, 'success', Date.now() - startedAt, `include=${args.includeType}`);
765
+ noteWriteTransport(resolved.transport); // F-L2-1 statusline wire
755
766
  await finalizeToolCall(ctx.session, toolUseId, JSON.stringify({ written: true, variant: result.variantUsed }), false, Date.now() - startedAt);
756
767
  // Tour slide-7 chain. Honest states: includes have no standalone syntax
757
768
  // gate (checked at activation), so ③ renders as deferred, never faked.
@@ -879,6 +890,11 @@ registerTool({
879
890
  is_error: true,
880
891
  };
881
892
  }
893
+ // F-L2-1 statusline wire. Recorded on CREATED, not on active: the object is
894
+ // already in effectiveTransport by then, so a create-succeeded /
895
+ // activation-failed run must still show which request holds it. DDIC creates
896
+ // bypass resolveWriteTransport — effectiveTransport is what SAP stamped.
897
+ noteWriteTransport(effectiveTransport);
882
898
  if (!result.active) {
883
899
  // Rule 10: created but did not activate — surface as an error.
884
900
  await finalizeToolCall(ctx.session, toolUseId, JSON.stringify(result), true, Date.now() - startedAt);
@@ -1002,6 +1018,8 @@ registerTool({
1002
1018
  is_error: true,
1003
1019
  };
1004
1020
  }
1021
+ // F-L2-1 statusline wire — see the createDomain note above (created, not active).
1022
+ noteWriteTransport(effectiveTransport);
1005
1023
  if (!result.active) {
1006
1024
  // Rule 10: created but did not activate — surface as an error.
1007
1025
  await finalizeToolCall(ctx.session, toolUseId, JSON.stringify(result), true, Date.now() - startedAt);
@@ -97,7 +97,11 @@ export async function todoSetHandler(args, ctx) {
97
97
  ctx.chunkEmitter?.emit('chunk', formatTodoChecklistBlock(todos));
98
98
  const result = {
99
99
  count: todos.length,
100
- in_progress: inProgress[0]?.text ?? null,
100
+ // LAST-wins, matching the app strip and Ctrl+T panel (both `.at(-1)`).
101
+ // With a batched transition (several items flipped in one call) first-wins
102
+ // would tell the model step 2 is running while the user's ▶ points at
103
+ // step 5 — the echo and the UI must name the same item.
104
+ in_progress: inProgress.at(-1)?.text ?? null,
101
105
  };
102
106
  if (inProgress.length > 1) {
103
107
  result.warning =
@@ -108,10 +112,14 @@ export async function todoSetHandler(args, ctx) {
108
112
  }
109
113
  registerTool({
110
114
  name: 'todo_set',
111
- // Model-behavior lever — this exact framing is binding (task-4 brief).
115
+ // Model-behavior lever — this exact framing is binding (task-4 brief;
116
+ // the no-batching nudge is binding per the F-L2-3 task-5 brief).
112
117
  description: 'Maintain your task list for multi-step work. Call with the FULL updated list whenever you ' +
113
118
  'complete a step, start a new one, or discover new work. Exactly one item should be ' +
114
- 'in_progress at a time. The user sees this list — keep items short and outcome-shaped.',
119
+ 'in_progress at a time. The user sees this list — keep items short and outcome-shaped. ' +
120
+ 'Update at EVERY step transition: when you finish a step, immediately call todo_set ' +
121
+ 'marking it completed and the next step in_progress. Never batch several finished steps ' +
122
+ 'into one later call — the user watches the ▶ indicator live.',
115
123
  isMutating: false,
116
124
  category: 'session',
117
125
  input_schema: {
package/dist/ui/app.js CHANGED
@@ -510,7 +510,7 @@ export function App({ chunkEmitter, onSubmit, onSlashTrigger, onHistoryTrigger,
510
510
  askQuestionPendingRef.current = null;
511
511
  setAskQuestionPending(null);
512
512
  }
513
- } }, askQuestionSeq))), _jsx(TurnStatus, { inProgressTodo: todos.find((t) => t.status === 'in_progress')?.text ?? null }), _jsx(StatusLine, {}), _jsx(Footer, { onSubmit: beginSubmit, onSlashTrigger: handleSlashTrigger, onHistoryTrigger: handleHistoryTrigger, prefill: prefill, onValueChange: setFooterValue, onBufferChange: setFooterBufferCount,
513
+ } }, askQuestionSeq))), _jsx(TurnStatus, { inProgressTodo: todos.filter((t) => t.status === 'in_progress').at(-1)?.text ?? null }), _jsx(StatusLine, {}), _jsx(Footer, { onSubmit: beginSubmit, onSlashTrigger: handleSlashTrigger, onHistoryTrigger: handleHistoryTrigger, prefill: prefill, onValueChange: setFooterValue, onBufferChange: setFooterBufferCount,
514
514
  // Task 7 — multiline announcements (draft discarded) land as
515
515
  // dim scrollback lines, house style for Footer-adjacent notices.
516
516
  onNotice: (t) => chunkEmitter.emit('chunk', `\x1b[2m${t}\x1b[22m\n`),
@@ -14,7 +14,7 @@ import React, { createContext, useContext } from 'react';
14
14
  const DEFAULT_STATE = {
15
15
  system: '', user: '', transport: null, locksHeld: [],
16
16
  tokenUsageToday: 0, dollarSpendToday: 0, dollarCap: 100, recentTurns: [],
17
- writeMode: null,
17
+ writeMode: null, lastWriteTransport: null,
18
18
  };
19
19
  /**
20
20
  * Rate limiter: calls `fn` at most once every `minIntervalMs`. Trailing-only:
@@ -37,7 +37,7 @@ export function StatusLine() {
37
37
  // the field lengths.
38
38
  return (_jsxs(Text, { wrap: "truncate-end", children: [s.system
39
39
  ? _jsx(Text, { color: "cyan", children: s.system })
40
- : _jsx(Text, { dimColor: true, children: "no system" }), _jsx(Text, { dimColor: true, children: " \u2502 " }), _jsx(Text, { color: "white", children: s.user || '—' }), _jsx(Text, { dimColor: true, children: " \u2502 tr " }), s.transport
41
- ? _jsx(Text, { color: "yellow", children: s.transport })
40
+ : _jsx(Text, { dimColor: true, children: "no system" }), _jsx(Text, { dimColor: true, children: " \u2502 " }), _jsx(Text, { color: "white", children: s.user || '—' }), _jsx(Text, { dimColor: true, children: " \u2502 tr " }), (s.transport ?? s.lastWriteTransport)
41
+ ? _jsx(Text, { color: "yellow", children: s.transport ?? s.lastWriteTransport })
42
42
  : _jsx(Text, { dimColor: true, children: "none" }), s.writeMode && (_jsxs(_Fragment, { children: [_jsx(Text, { dimColor: true, children: " \u2502 " }), _jsx(Text, { color: MODE_COLORS[s.writeMode], children: s.writeMode })] })), _jsx(Text, { dimColor: true, children: " \u2502 " }), _jsx(Text, { color: "green", children: spendDisplay })] }));
43
43
  }
@@ -16,8 +16,10 @@ import { Box, Text, useInput } from 'ink';
16
16
  *
17
17
  * Lists longer than TODO_VISIBLE_ROWS get a scrolling window
18
18
  * (CommandPalette pattern): ↑/↓ move the window, hidden counts show as
19
- * "↑ N more" / "↓ N more". The initial window anchors the in_progress
19
+ * "↑ N more" / "↓ N more". The initial window anchors the live in_progress
20
20
  * item ~3 rows from the top so the current step is visible on open.
21
+ * "Live" = the LAST in_progress item in list order (F-L2-3) — see
22
+ * activeTodoIndex below.
21
23
  */
22
24
  const PEACH = '#FFCBA4';
23
25
  export const TODO_VISIBLE_ROWS = 12;
@@ -28,15 +30,31 @@ export function formatTodoCounts(todos) {
28
30
  const open = todos.filter((t) => t.status === 'pending').length;
29
31
  return `Tasks — ${done} done · ${active} active · ${open} open`;
30
32
  }
33
+ /**
34
+ * Index of the LIVE step — the LAST in_progress item in list order, or -1.
35
+ *
36
+ * F-L2-3 (sitting #1): todo_set tolerates several in_progress items (a
37
+ * warning, not an error — todo.ts), and models routinely batch transitions,
38
+ * leaving an earlier item still marked in_progress. List order IS plan
39
+ * order, so the later item is the one actually being worked; picking the
40
+ * FIRST match pinned the ▶ on a step already long finished.
41
+ */
42
+ export function activeTodoIndex(todos) {
43
+ for (let i = todos.length - 1; i >= 0; i--) {
44
+ if (todos[i].status === 'in_progress')
45
+ return i;
46
+ }
47
+ return -1;
48
+ }
31
49
  export function TodoPanel({ todos, onClose }) {
32
50
  const maxOffset = Math.max(0, todos.length - TODO_VISIBLE_ROWS);
33
- // Anchor the initial window so the in_progress item sits ~3 rows from
51
+ const activeIdx = activeTodoIndex(todos);
52
+ // Anchor the initial window so the live in_progress item sits ~3 rows from
34
53
  // the top (CommandPalette's cursor rule, with the active item as cursor).
35
54
  const [offset, setOffset] = useState(() => {
36
- const idx = todos.findIndex((t) => t.status === 'in_progress');
37
- if (idx <= 3)
55
+ if (activeIdx <= 3)
38
56
  return 0;
39
- return Math.min(idx - 3, maxOffset);
57
+ return Math.min(activeIdx - 3, maxOffset);
40
58
  });
41
59
  useInput((input, key) => {
42
60
  if (key.escape || input === 'q' || (key.ctrl && (input === 't' || input === 'T'))) {
@@ -56,7 +74,10 @@ export function TodoPanel({ todos, onClose }) {
56
74
  if (t.status === 'completed') {
57
75
  return _jsxs(Text, { dimColor: true, wrap: "truncate-end", children: ["\u25A3 ", t.text] }, key);
58
76
  }
59
- if (t.status === 'in_progress') {
77
+ // Only the LIVE step gets the ▶; a stale earlier in_progress item
78
+ // (batched transition) renders as an ordinary open row, so the
79
+ // panel never shows two arrows or points at finished work.
80
+ if (t.status === 'in_progress' && clamped + i === activeIdx) {
60
81
  return _jsxs(Text, { color: PEACH, bold: true, wrap: "truncate-end", children: ["\u25B6 ", t.text] }, key);
61
82
  }
62
83
  return _jsxs(Text, { wrap: "truncate-end", children: ["\u25A1 ", t.text] }, key);
@@ -0,0 +1,26 @@
1
+ {
2
+ "name": "zlas.inspectionlog",
3
+ "version": "0.0.1",
4
+ "description": "Description of zlas.inspectionlog.",
5
+ "keywords": [
6
+ "ui5",
7
+ "openui5",
8
+ "sapui5"
9
+ ],
10
+ "main": "webapp/index.html",
11
+ "dependencies": {},
12
+ "devDependencies": {
13
+ "@ui5/cli": "^4.0.33",
14
+ "@sap/ux-ui5-tooling": "1"
15
+ },
16
+ "scripts": {
17
+ "start": "fiori run --open \"/test/flpSandbox.html?sap-ui-xx-viewCache=false#zlasinspectionlog-tile\"",
18
+ "start-local": "fiori run --config ./ui5-local.yaml --open \"/test/flpSandbox.html?sap-ui-xx-viewCache=false#zlasinspectionlog-tile\"",
19
+ "build": "ui5 build --config=ui5.yaml --clean-dest --dest dist",
20
+ "deploy": "fiori verify",
21
+ "deploy-config": "fiori add deploy-config",
22
+ "start-noflp": "fiori run --open \"/index.html?sap-ui-xx-viewCache=false\"",
23
+ "start-variants-management": "fiori run --open \"/preview.html?sap-ui-xx-viewCache=false&fiori-tools-rta-mode=true&sap-ui-rta-skip-flex-validation=true#zlasinspectionlog-tile\""
24
+ },
25
+ "sapux": true
26
+ }
@@ -0,0 +1,45 @@
1
+ # yaml-language-server: $schema=https://sap.github.io/ui5-tooling/schema/ui5.yaml.json
2
+
3
+ specVersion: "4.0"
4
+ metadata:
5
+ name: zlas.inspectionlog
6
+ type: application
7
+ framework:
8
+ name: SAPUI5
9
+ version: 1.120.0
10
+ libraries:
11
+ - name: sap.m
12
+ - name: sap.ui.core
13
+ - name: sap.fe.templates
14
+ - name: sap.ushell
15
+ - name: themelib_sap_fiori_3
16
+ server:
17
+ customMiddleware:
18
+ - name: fiori-tools-appreload
19
+ afterMiddleware: compression
20
+ configuration:
21
+ port: 35729
22
+ path: webapp
23
+ delay: 300
24
+ - name: fiori-tools-preview
25
+ afterMiddleware: fiori-tools-appreload
26
+ configuration:
27
+ flp:
28
+ theme: sap_fiori_3
29
+ path: test/flpSandbox.html
30
+ intent:
31
+ object: zlasinspectionlog
32
+ action: tile
33
+ - name: fiori-tools-proxy
34
+ afterMiddleware: compression
35
+ configuration:
36
+ ignoreCertErrors: false # If set to true, certificate errors will be ignored. E.g. self-signed certificates will be accepted
37
+ backend:
38
+ - path: /sap
39
+ url: https://172.16.24.98:44300
40
+ client: '100'
41
+ builder:
42
+ resources:
43
+ excludes:
44
+ - /test/**
45
+ - /localService/**
@@ -0,0 +1,41 @@
1
+ # yaml-language-server: $schema=https://sap.github.io/ui5-tooling/schema/ui5.yaml.json
2
+
3
+ specVersion: "4.0"
4
+ metadata:
5
+ name: zlas.inspectionlog
6
+ type: application
7
+ server:
8
+ customMiddleware:
9
+ - name: fiori-tools-proxy
10
+ afterMiddleware: compression
11
+ configuration:
12
+ ignoreCertErrors: true # Dev-only: accept the backend's self-signed certificate
13
+ ui5:
14
+ path:
15
+ - /resources
16
+ - /test-resources
17
+ url: https://ui5.sap.com
18
+ backend:
19
+ - path: /sap
20
+ url: https://172.16.24.98:44300
21
+ client: '100'
22
+ - name: fiori-tools-appreload
23
+ afterMiddleware: compression
24
+ configuration:
25
+ port: 35729
26
+ path: webapp
27
+ delay: 300
28
+ - name: fiori-tools-preview
29
+ afterMiddleware: fiori-tools-appreload
30
+ configuration:
31
+ flp:
32
+ theme: sap_fiori_3
33
+ path: test/flpSandbox.html
34
+ intent:
35
+ object: zlasinspectionlog
36
+ action: tile
37
+ builder:
38
+ resources:
39
+ excludes:
40
+ - /test/**
41
+ - /localService/**
@@ -0,0 +1,12 @@
1
+ sap.ui.define(
2
+ ["sap/fe/core/AppComponent"],
3
+ function (Component) {
4
+ "use strict";
5
+
6
+ return Component.extend("zlas.inspectionlog.Component", {
7
+ metadata: {
8
+ manifest: "json"
9
+ }
10
+ });
11
+ }
12
+ );
@@ -0,0 +1,9 @@
1
+ # This is the resource bundle for zlas.inspectionlog
2
+
3
+ #Texts for manifest.json
4
+
5
+ #XTIT: Application name
6
+ appTitle=Equipment Inspection Logs
7
+
8
+ #YDES: Application description
9
+ appDescription=Description of zlas.inspectionlog.
@@ -0,0 +1,35 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <meta http-equiv="X-UA-Compatible" content="IE=edge">
7
+ <title>Equipment Inspection Logs</title>
8
+ <style>
9
+ html, body, body > div, #container, #container-uiarea {
10
+ height: 100%;
11
+ }
12
+ </style>
13
+ <script
14
+ id="sap-ui-bootstrap"
15
+ src="resources/sap-ui-core.js"
16
+ data-sap-ui-theme="sap_fiori_3"
17
+ data-sap-ui-resource-roots='{
18
+ "zlas.inspectionlog": "./"
19
+ }'
20
+ data-sap-ui-on-init="module:sap/ui/core/ComponentSupport"
21
+ data-sap-ui-compat-version="edge"
22
+ data-sap-ui-async="true"
23
+ data-sap-ui-frame-options="trusted"
24
+ ></script>
25
+ </head>
26
+ <body class="sapUiBody sapUiSizeCompact" id="content">
27
+ <div
28
+ data-sap-ui-component
29
+ data-name="zlas.inspectionlog"
30
+ data-id="container"
31
+ data-settings='{"id" : "zlas.inspectionlog"}'
32
+ data-handle-validation="true"
33
+ ></div>
34
+ </body>
35
+ </html>