@numa-tech/numa 1.12.8 → 1.12.11

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.
Files changed (75) hide show
  1. package/README.md +140 -16
  2. package/dist/ai-registry/commands.js +33 -33
  3. package/dist/ai-registry/commands.js.map +1 -1
  4. package/dist/app-config.d.ts +0 -1
  5. package/dist/app-config.js +4 -2
  6. package/dist/app-config.js.map +1 -1
  7. package/dist/application-candidates/client.d.ts +167 -0
  8. package/dist/application-candidates/client.js +103 -0
  9. package/dist/application-candidates/client.js.map +1 -0
  10. package/dist/application-candidates/commands.d.ts +13 -0
  11. package/dist/application-candidates/commands.js +225 -0
  12. package/dist/application-candidates/commands.js.map +1 -0
  13. package/dist/application-candidates/schemas.d.ts +171 -0
  14. package/dist/application-candidates/schemas.js +80 -0
  15. package/dist/application-candidates/schemas.js.map +1 -0
  16. package/dist/application-onboarding/client.d.ts +256 -0
  17. package/dist/application-onboarding/client.js +213 -0
  18. package/dist/application-onboarding/client.js.map +1 -0
  19. package/dist/application-onboarding/commands.d.ts +64 -0
  20. package/dist/application-onboarding/commands.js +694 -0
  21. package/dist/application-onboarding/commands.js.map +1 -0
  22. package/dist/application-onboarding/errors.d.ts +11 -0
  23. package/dist/application-onboarding/errors.js +51 -0
  24. package/dist/application-onboarding/errors.js.map +1 -0
  25. package/dist/application-onboarding/schemas.d.ts +1167 -0
  26. package/dist/application-onboarding/schemas.js +459 -0
  27. package/dist/application-onboarding/schemas.js.map +1 -0
  28. package/dist/application-onboarding/scm-credentials.d.ts +8 -0
  29. package/dist/application-onboarding/scm-credentials.js +98 -0
  30. package/dist/application-onboarding/scm-credentials.js.map +1 -0
  31. package/dist/application-onboarding/tui-model.d.ts +131 -0
  32. package/dist/application-onboarding/tui-model.js +554 -0
  33. package/dist/application-onboarding/tui-model.js.map +1 -0
  34. package/dist/application-onboarding/tui.d.ts +14 -0
  35. package/dist/application-onboarding/tui.js +554 -0
  36. package/dist/application-onboarding/tui.js.map +1 -0
  37. package/dist/authorization/access-request-client.d.ts +45 -0
  38. package/dist/authorization/access-request-client.js +93 -0
  39. package/dist/authorization/access-request-client.js.map +1 -0
  40. package/dist/authorization/access-request-commands.d.ts +25 -0
  41. package/dist/authorization/access-request-commands.js +196 -0
  42. package/dist/authorization/access-request-commands.js.map +1 -0
  43. package/dist/authorization/access-request-schemas.d.ts +123 -0
  44. package/dist/authorization/access-request-schemas.js +67 -0
  45. package/dist/authorization/access-request-schemas.js.map +1 -0
  46. package/dist/authorization/client.d.ts +50 -0
  47. package/dist/authorization/client.js +113 -0
  48. package/dist/authorization/client.js.map +1 -0
  49. package/dist/authorization/commands.d.ts +40 -0
  50. package/dist/authorization/commands.js +296 -0
  51. package/dist/authorization/commands.js.map +1 -0
  52. package/dist/authorization/errors.d.ts +13 -0
  53. package/dist/authorization/errors.js +70 -0
  54. package/dist/authorization/errors.js.map +1 -0
  55. package/dist/authorization/schemas.d.ts +187 -0
  56. package/dist/authorization/schemas.js +101 -0
  57. package/dist/authorization/schemas.js.map +1 -0
  58. package/dist/cli.js +44 -8
  59. package/dist/cli.js.map +1 -1
  60. package/dist/command-catalog.js +270 -26
  61. package/dist/command-catalog.js.map +1 -1
  62. package/dist/web-console-page.d.ts +1 -1
  63. package/dist/web-console-page.js +1 -1
  64. package/dist/web-console.js +6 -5
  65. package/dist/web-console.js.map +1 -1
  66. package/examples/ai-registry/README.md +18 -0
  67. package/examples/ai-registry/nacos-instance.username-password.example.json +25 -0
  68. package/package.json +4 -4
  69. package/skills/numa-ai-registry/SKILL.md +5 -5
  70. package/skills/numa-ai-registry/evals/evals.json +1 -1
  71. package/skills/numa-ai-registry/references/command-contract.md +22 -22
  72. package/skills/numa-cli/SKILL.md +44 -0
  73. package/skills/numa-cli/agents/openai.yaml +4 -0
  74. package/skills/numa-cli/evals/evals.json +35 -0
  75. package/skills/numa-cli/references/pipeline-release.md +107 -0
@@ -0,0 +1,131 @@
1
+ import type { JsonValue, OnboardingCapabilities, OnboardingEvent, OnboardingMode, OnboardingPreflight, OnboardingSession } from "./schemas.js";
2
+ export declare const ONBOARDING_STEPS: readonly ["MODE", "BASIC_INFORMATION", "OWNERSHIP", "SOURCE_CONTROL", "TECHNOLOGY", "STANDARDIZATION", "DELIVERY", "REVIEW", "EXECUTION"];
3
+ export type OnboardingStepCode = typeof ONBOARDING_STEPS[number];
4
+ export interface CatalogChoice {
5
+ value: string;
6
+ label: string;
7
+ }
8
+ export interface WizardCatalog {
9
+ connections: Array<Record<string, unknown>>;
10
+ stacks: Array<Record<string, unknown>>;
11
+ templates: Array<Record<string, unknown>>;
12
+ repositories: Array<Record<string, unknown>>;
13
+ teams: Array<Record<string, unknown>>;
14
+ systems: Array<Record<string, unknown>>;
15
+ businessDomains: Array<Record<string, unknown>>;
16
+ environments: Array<Record<string, unknown>>;
17
+ errors: string[];
18
+ repositoryStatus?: string;
19
+ repositoryNextCursor?: string | null;
20
+ }
21
+ export interface WizardField {
22
+ path: string;
23
+ label: string;
24
+ kind: "text" | "number" | "select" | "boolean" | "json" | "tiers" | "choiceOrText";
25
+ required?: boolean;
26
+ choices?: CatalogChoice[];
27
+ placeholder?: string;
28
+ }
29
+ export interface OnboardingTuiState {
30
+ phase: "LOADING" | "SELECT_MODE" | "SESSION" | "ERROR";
31
+ modes: OnboardingMode[];
32
+ modeIndex: number;
33
+ session?: OnboardingSession;
34
+ stepIndex: number;
35
+ fieldIndex: number;
36
+ draft: Record<string, unknown>;
37
+ dirty: boolean;
38
+ editing: boolean;
39
+ editBuffer: string;
40
+ events: OnboardingEvent[];
41
+ afterSequence: number;
42
+ catalog: WizardCatalog;
43
+ preflight?: OnboardingPreflight;
44
+ assessment?: Record<string, unknown>;
45
+ busy: boolean;
46
+ confirmation?: "APPLY" | "CANCEL";
47
+ idempotencyKey?: string;
48
+ notice?: string;
49
+ error?: string;
50
+ }
51
+ export type OnboardingTuiAction = {
52
+ type: "BOOTSTRAP_LOADED";
53
+ capabilities: OnboardingCapabilities;
54
+ catalog: Partial<WizardCatalog>;
55
+ preferredMode?: OnboardingMode;
56
+ } | {
57
+ type: "MODE_MOVE";
58
+ direction: -1 | 1;
59
+ } | {
60
+ type: "SESSION_LOADED";
61
+ session: OnboardingSession;
62
+ preserveStep?: boolean;
63
+ } | {
64
+ type: "SET_STEP";
65
+ index: number;
66
+ } | {
67
+ type: "MOVE_FIELD";
68
+ direction: -1 | 1;
69
+ maximum: number;
70
+ } | {
71
+ type: "START_EDIT";
72
+ value: string;
73
+ } | {
74
+ type: "EDIT_BUFFER";
75
+ value: string;
76
+ } | {
77
+ type: "STOP_EDIT";
78
+ } | {
79
+ type: "SET_DRAFT_VALUE";
80
+ path: string;
81
+ value: unknown;
82
+ } | {
83
+ type: "SET_DRAFT";
84
+ draft: Record<string, unknown>;
85
+ } | {
86
+ type: "EVENTS_LOADED";
87
+ events: OnboardingEvent[];
88
+ } | {
89
+ type: "CATALOG_LOADED";
90
+ catalog: Partial<WizardCatalog>;
91
+ } | {
92
+ type: "PREFLIGHT_LOADED";
93
+ preflight: OnboardingPreflight;
94
+ } | {
95
+ type: "ASSESSMENT_LOADED";
96
+ assessment: Record<string, unknown>;
97
+ } | {
98
+ type: "BUSY";
99
+ value: boolean;
100
+ notice?: string;
101
+ } | {
102
+ type: "CONFIRM";
103
+ value?: "APPLY" | "CANCEL";
104
+ } | {
105
+ type: "IDEMPOTENCY_KEY";
106
+ value: string;
107
+ } | {
108
+ type: "NOTICE";
109
+ value: string;
110
+ } | {
111
+ type: "ERROR";
112
+ value: string;
113
+ };
114
+ export declare function emptyWizardDraft(mode: OnboardingMode): Record<string, unknown>;
115
+ export declare function initialOnboardingTuiState(): OnboardingTuiState;
116
+ export declare function reduceOnboardingTuiState(state: OnboardingTuiState, action: OnboardingTuiAction): OnboardingTuiState;
117
+ export declare function draftFromSession(session: OnboardingSession): Record<string, unknown>;
118
+ export declare function fieldsForState(state: OnboardingTuiState): WizardField[];
119
+ export declare function valueAtPath(root: Record<string, unknown>, path: string): unknown;
120
+ export declare function stringAt(root: Record<string, unknown>, path: string): string;
121
+ export declare function displayFieldValue(field: WizardField, draft: Record<string, unknown>): string;
122
+ export declare function parseFieldInput(field: WizardField, input: string): unknown;
123
+ export declare function assertCredentialFreeDraft(draft: Record<string, unknown>): void;
124
+ export declare function cycleFieldValue(state: OnboardingTuiState, field: WizardField, direction: -1 | 1): Record<string, unknown>;
125
+ export declare function applyRepositorySelection(draft: Record<string, unknown>, repositories: Array<Record<string, unknown>>, externalId: string): Record<string, unknown>;
126
+ export declare function applyTemplateDefaults(draft: Record<string, unknown>, template: Record<string, unknown> | undefined): Record<string, unknown>;
127
+ export declare function validateTemplateParameters(draft: Record<string, unknown>, templates: Array<Record<string, unknown>>): string[];
128
+ export declare function validateWizardBeforeStep(state: OnboardingTuiState, targetStepIndex: number): string[];
129
+ export declare function currentStepCode(state: OnboardingTuiState): OnboardingStepCode;
130
+ export declare function draftAsJsonValue(draft: Record<string, unknown>): JsonValue;
131
+ export declare function safeTerminalWidth(columns: number | undefined): number;
@@ -0,0 +1,554 @@
1
+ import { OnboardingError } from "./errors.js";
2
+ import { CredentialFreeOnboardingValueSchema } from "./schemas.js";
3
+ export const ONBOARDING_STEPS = [
4
+ "MODE",
5
+ "BASIC_INFORMATION",
6
+ "OWNERSHIP",
7
+ "SOURCE_CONTROL",
8
+ "TECHNOLOGY",
9
+ "STANDARDIZATION",
10
+ "DELIVERY",
11
+ "REVIEW",
12
+ "EXECUTION"
13
+ ];
14
+ export function emptyWizardDraft(mode) {
15
+ return {
16
+ schemaVersion: 1,
17
+ mode,
18
+ application: { lifecycle: "EXPERIMENTAL", tier: "TIER_3" },
19
+ repository: { defaultBranch: "main" },
20
+ technology: { repoMode: "STANDALONE", parameters: {} },
21
+ delivery: {
22
+ jenkinsRequired: true,
23
+ tiers: ["DEVELOP"],
24
+ gitopsRequired: false,
25
+ nacosRequired: false
26
+ }
27
+ };
28
+ }
29
+ export function initialOnboardingTuiState() {
30
+ return {
31
+ phase: "LOADING",
32
+ modes: ["NEW", "EXISTING"],
33
+ modeIndex: 0,
34
+ stepIndex: 0,
35
+ fieldIndex: 0,
36
+ draft: emptyWizardDraft("NEW"),
37
+ dirty: false,
38
+ editing: false,
39
+ editBuffer: "",
40
+ events: [],
41
+ afterSequence: 0,
42
+ catalog: {
43
+ connections: [],
44
+ stacks: [],
45
+ templates: [],
46
+ repositories: [],
47
+ teams: [],
48
+ systems: [],
49
+ businessDomains: [],
50
+ environments: [],
51
+ errors: []
52
+ },
53
+ busy: false
54
+ };
55
+ }
56
+ export function reduceOnboardingTuiState(state, action) {
57
+ switch (action.type) {
58
+ case "BOOTSTRAP_LOADED": {
59
+ const supported = action.capabilities.supportedModes ?? action.capabilities.modes;
60
+ const modes = supported?.length ? supported : ["NEW", "EXISTING"];
61
+ const preferredIndex = action.preferredMode == null ? -1 : modes.indexOf(action.preferredMode);
62
+ const selectedIndex = Math.max(0, preferredIndex);
63
+ return {
64
+ ...state,
65
+ phase: "SELECT_MODE",
66
+ modes,
67
+ modeIndex: selectedIndex,
68
+ draft: emptyWizardDraft(modes[selectedIndex] ?? "NEW"),
69
+ catalog: mergeCatalog(state.catalog, action.catalog),
70
+ busy: false,
71
+ error: undefined
72
+ };
73
+ }
74
+ case "MODE_MOVE": {
75
+ const modeIndex = Math.min(state.modes.length - 1, Math.max(0, state.modeIndex + action.direction));
76
+ return { ...state, modeIndex, draft: emptyWizardDraft(state.modes[modeIndex] ?? "NEW") };
77
+ }
78
+ case "SESSION_LOADED": {
79
+ const serverStep = stepIndexForSession(action.session);
80
+ return {
81
+ ...state,
82
+ phase: "SESSION",
83
+ session: action.session,
84
+ stepIndex: action.preserveStep ? state.stepIndex : serverStep,
85
+ fieldIndex: 0,
86
+ draft: draftFromSession(action.session),
87
+ dirty: false,
88
+ editing: false,
89
+ editBuffer: "",
90
+ busy: false,
91
+ confirmation: undefined,
92
+ error: undefined
93
+ };
94
+ }
95
+ case "SET_STEP":
96
+ return {
97
+ ...state,
98
+ stepIndex: clamp(action.index, 0, ONBOARDING_STEPS.length - 1),
99
+ fieldIndex: 0,
100
+ editing: false,
101
+ editBuffer: ""
102
+ };
103
+ case "MOVE_FIELD":
104
+ return { ...state, fieldIndex: clamp(state.fieldIndex + action.direction, 0, Math.max(0, action.maximum - 1)) };
105
+ case "START_EDIT":
106
+ return { ...state, editing: true, editBuffer: action.value, error: undefined };
107
+ case "EDIT_BUFFER":
108
+ return { ...state, editBuffer: action.value.slice(0, 16_384) };
109
+ case "STOP_EDIT":
110
+ return { ...state, editing: false, editBuffer: "" };
111
+ case "SET_DRAFT_VALUE":
112
+ return changedDraft(state, setDraftPath(state.draft, action.path, action.value));
113
+ case "SET_DRAFT":
114
+ return changedDraft(state, action.draft);
115
+ case "EVENTS_LOADED": {
116
+ const bySequence = new Map(state.events.map((event) => [event.sequenceNo, event]));
117
+ for (const event of action.events) {
118
+ if (event.sequenceNo > state.afterSequence)
119
+ bySequence.set(event.sequenceNo, event);
120
+ }
121
+ const events = [...bySequence.values()].sort((left, right) => left.sequenceNo - right.sequenceNo);
122
+ return {
123
+ ...state,
124
+ events,
125
+ afterSequence: events.reduce((maximum, event) => Math.max(maximum, event.sequenceNo), state.afterSequence)
126
+ };
127
+ }
128
+ case "CATALOG_LOADED":
129
+ return { ...state, catalog: mergeCatalog(state.catalog, action.catalog), busy: false };
130
+ case "PREFLIGHT_LOADED":
131
+ return { ...state, preflight: action.preflight, busy: false, error: undefined };
132
+ case "ASSESSMENT_LOADED":
133
+ return { ...state, assessment: action.assessment, busy: false, error: undefined };
134
+ case "BUSY":
135
+ return { ...state, busy: action.value, notice: action.notice ?? state.notice, error: undefined };
136
+ case "CONFIRM":
137
+ return { ...state, confirmation: action.value };
138
+ case "IDEMPOTENCY_KEY":
139
+ return { ...state, idempotencyKey: action.value };
140
+ case "NOTICE":
141
+ return { ...state, notice: action.value, busy: false, error: undefined };
142
+ case "ERROR":
143
+ return { ...state, phase: state.session ? "SESSION" : "ERROR", error: action.value, busy: false };
144
+ }
145
+ }
146
+ function changedDraft(state, draft) {
147
+ return {
148
+ ...state,
149
+ draft,
150
+ dirty: true,
151
+ editing: false,
152
+ editBuffer: "",
153
+ preflight: undefined,
154
+ idempotencyKey: undefined,
155
+ confirmation: undefined,
156
+ error: undefined
157
+ };
158
+ }
159
+ function mergeCatalog(current, update) {
160
+ return {
161
+ connections: update.connections ?? current.connections,
162
+ stacks: update.stacks ?? current.stacks,
163
+ templates: update.templates ?? current.templates,
164
+ repositories: update.repositories ?? current.repositories,
165
+ teams: update.teams ?? current.teams,
166
+ systems: update.systems ?? current.systems,
167
+ businessDomains: update.businessDomains ?? current.businessDomains,
168
+ environments: update.environments ?? current.environments,
169
+ errors: update.errors ?? current.errors,
170
+ repositoryStatus: update.repositoryStatus ?? current.repositoryStatus,
171
+ repositoryNextCursor: update.repositoryNextCursor !== undefined
172
+ ? update.repositoryNextCursor
173
+ : current.repositoryNextCursor
174
+ };
175
+ }
176
+ export function draftFromSession(session) {
177
+ const base = emptyWizardDraft(session.mode);
178
+ if (!session.draft || typeof session.draft !== "object" || Array.isArray(session.draft))
179
+ return base;
180
+ const incoming = cloneObject(session.draft);
181
+ return {
182
+ ...base,
183
+ ...incoming,
184
+ schemaVersion: typeof incoming.schemaVersion === "number" ? incoming.schemaVersion : 1,
185
+ mode: session.mode,
186
+ application: { ...asObject(base.application), ...asObject(incoming.application) },
187
+ repository: { ...asObject(base.repository), ...asObject(incoming.repository) },
188
+ technology: { ...asObject(base.technology), ...asObject(incoming.technology) },
189
+ delivery: { ...asObject(base.delivery), ...asObject(incoming.delivery) }
190
+ };
191
+ }
192
+ export function fieldsForState(state) {
193
+ const step = ONBOARDING_STEPS[state.stepIndex];
194
+ const mode = state.session?.mode ?? state.modes[state.modeIndex] ?? "NEW";
195
+ if (step === "BASIC_INFORMATION")
196
+ return [
197
+ { path: "application.code", label: "Application code", kind: "text", required: true, placeholder: "order-service" },
198
+ { path: "application.name", label: "Application name", kind: "text", required: true },
199
+ { path: "application.description", label: "Description", kind: "text" },
200
+ { path: "application.lifecycle", label: "Lifecycle", kind: "select", choices: choices(["EXPERIMENTAL", "PRODUCTION", "DEPRECATED"]) },
201
+ { path: "application.tier", label: "Application tier", kind: "text", placeholder: "TIER_3" }
202
+ ];
203
+ if (step === "OWNERSHIP")
204
+ return [
205
+ { path: "application.teamId", label: "Team ID", kind: "number", required: true, choices: entityChoices(state.catalog.teams) },
206
+ { path: "application.systemId", label: "System ID", kind: "number", required: true, choices: entityChoices(state.catalog.systems) },
207
+ { path: "application.businessDomainId", label: "Business domain ID", kind: "number", choices: entityChoices(state.catalog.businessDomains) },
208
+ { path: "application.defaultEnvironmentId", label: "Default environment ID", kind: "number", choices: entityChoices(state.catalog.environments) },
209
+ { path: "application.ownerUsername", label: "Owner username", kind: "text" }
210
+ ];
211
+ if (step === "SOURCE_CONTROL") {
212
+ const connectionChoices = state.catalog.connections.map((item) => ({
213
+ value: text(item.code),
214
+ label: [text(item.name), text(item.provider), text(item.health)].filter(Boolean).join(" · ")
215
+ })).filter((item) => item.value);
216
+ const common = [{
217
+ path: "repository.connectionCode",
218
+ label: "SCM connection",
219
+ kind: "select",
220
+ required: true,
221
+ choices: includeCurrent(connectionChoices, stringAt(state.draft, "repository.connectionCode"))
222
+ }];
223
+ const addModule = mode === "NEW" && stringAt(state.draft, "technology.repoMode") === "ADD_MODULE";
224
+ if (mode === "NEW" && !addModule)
225
+ return [...common,
226
+ { path: "repository.namespace", label: "Organization / namespace", kind: "text", required: true },
227
+ { path: "repository.name", label: "Repository name", kind: "text", required: true },
228
+ { path: "repository.defaultBranch", label: "Default branch", kind: "text" }
229
+ ];
230
+ const repositoryChoices = state.catalog.repositories.map((item) => ({
231
+ value: text(item.externalId ?? item.id),
232
+ label: text(item.fullName ?? item.name ?? item.externalId ?? item.id)
233
+ })).filter((item) => item.value);
234
+ return [...common,
235
+ {
236
+ path: "repository.externalId",
237
+ label: "Existing repository external ID",
238
+ kind: "choiceOrText",
239
+ required: true,
240
+ choices: includeCurrent(repositoryChoices, stringAt(state.draft, "repository.externalId"))
241
+ },
242
+ { path: "repository.expectedHead", label: "Expected repository HEAD", kind: "text", required: true, placeholder: "immutable commit SHA" },
243
+ { path: "repository.defaultBranch", label: "Default branch", kind: "text" }
244
+ ];
245
+ }
246
+ if (step === "TECHNOLOGY") {
247
+ const stackChoices = state.catalog.stacks.map((item) => ({ value: text(item.code), label: stackLabel(item) })).filter((item) => item.value);
248
+ const stackCode = stringAt(state.draft, "technology.stackCode");
249
+ const repoMode = stringAt(state.draft, "technology.repoMode");
250
+ const templates = state.catalog.templates.filter((item) => (!stackCode || text(item.technologyStackCode) === stackCode)
251
+ && (!repoMode || !text(item.repoMode) || text(item.repoMode) === repoMode));
252
+ const templateChoices = templates.map((item) => ({ value: text(item.code), label: templateLabel(item) })).filter((item) => item.value);
253
+ return [
254
+ {
255
+ path: "technology.stackCode",
256
+ label: "Standard technology stack",
257
+ kind: "select",
258
+ required: true,
259
+ choices: includeCurrent(stackChoices, stackCode)
260
+ },
261
+ { path: "technology.repoMode", label: "Repository mode", kind: "select", choices: choices(["STANDALONE", "ADD_MODULE"]) },
262
+ ...(mode === "NEW" ? [{
263
+ path: "technology.templateCode",
264
+ label: "Copier template",
265
+ kind: "select",
266
+ required: true,
267
+ choices: includeCurrent(templateChoices, stringAt(state.draft, "technology.templateCode"))
268
+ }] : []),
269
+ { path: "technology.parameters", label: "Template parameters JSON", kind: "json" }
270
+ ];
271
+ }
272
+ if (step === "DELIVERY")
273
+ return [
274
+ { path: "delivery.jenkinsRequired", label: "Jenkins required", kind: "boolean" },
275
+ { path: "delivery.tiers", label: "Jenkins tiers", kind: "tiers" },
276
+ { path: "delivery.gitopsRequired", label: "GitOps required", kind: "boolean" },
277
+ { path: "delivery.nacosRequired", label: "Nacos required", kind: "boolean" }
278
+ ];
279
+ return [];
280
+ }
281
+ export function valueAtPath(root, path) {
282
+ let current = root;
283
+ for (const part of path.split(".")) {
284
+ if (!current || typeof current !== "object" || Array.isArray(current))
285
+ return undefined;
286
+ current = current[part];
287
+ }
288
+ return current;
289
+ }
290
+ export function stringAt(root, path) {
291
+ const value = valueAtPath(root, path);
292
+ return typeof value === "string" ? value : value == null ? "" : String(value);
293
+ }
294
+ export function displayFieldValue(field, draft) {
295
+ const value = valueAtPath(draft, field.path);
296
+ if (field.kind === "boolean")
297
+ return value === true ? "yes" : "no";
298
+ if (field.kind === "json")
299
+ return JSON.stringify(value ?? {}, null, 0);
300
+ if (field.kind === "tiers")
301
+ return Array.isArray(value) ? value.join(",") : "";
302
+ if (value == null || value === "")
303
+ return field.placeholder ? `<${field.placeholder}>` : "—";
304
+ const raw = String(value);
305
+ const choice = field.choices?.find((item) => item.value === raw);
306
+ return choice ? `${choice.value} · ${choice.label}` : raw;
307
+ }
308
+ export function parseFieldInput(field, input) {
309
+ const trimmed = input.trim();
310
+ if (field.kind === "number") {
311
+ if (!trimmed)
312
+ return undefined;
313
+ const parsed = Number(trimmed);
314
+ if (!Number.isSafeInteger(parsed) || parsed <= 0) {
315
+ throw new OnboardingError(`${field.label} must be a positive integer.`, "onboarding_input_invalid");
316
+ }
317
+ return parsed;
318
+ }
319
+ if (field.kind === "json") {
320
+ if (!trimmed)
321
+ return {};
322
+ let parsed;
323
+ try {
324
+ parsed = JSON.parse(trimmed);
325
+ }
326
+ catch (error) {
327
+ throw new OnboardingError(`${field.label} must be valid JSON: ${error instanceof Error ? error.message : String(error)}`, "onboarding_input_invalid");
328
+ }
329
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
330
+ throw new OnboardingError(`${field.label} must be a JSON object.`, "onboarding_input_invalid");
331
+ }
332
+ return parsed;
333
+ }
334
+ if (field.kind === "tiers") {
335
+ const tiers = [...new Set(trimmed.split(",").map((item) => item.trim().toUpperCase()).filter(Boolean))];
336
+ if (tiers.some((tier) => !["DEVELOP", "STAGING", "PRODUCTION"].includes(tier))) {
337
+ throw new OnboardingError("Jenkins tiers must be DEVELOP, STAGING, or PRODUCTION.", "onboarding_input_invalid");
338
+ }
339
+ return tiers;
340
+ }
341
+ if (/[\u0000-\u001f\u007f]/u.test(input)) {
342
+ throw new OnboardingError(`${field.label} contains control characters.`, "onboarding_input_invalid");
343
+ }
344
+ return trimmed || undefined;
345
+ }
346
+ export function assertCredentialFreeDraft(draft) {
347
+ const parsed = CredentialFreeOnboardingValueSchema.safeParse(draft);
348
+ if (!parsed.success) {
349
+ throw new OnboardingError(`Draft contains credential-bearing input: ${parsed.error.issues[0]?.path.join(".") || "unknown field"}.`, "onboarding_secret_not_allowed");
350
+ }
351
+ }
352
+ export function cycleFieldValue(state, field, direction) {
353
+ if (field.kind === "boolean") {
354
+ return setDraftPath(state.draft, field.path, valueAtPath(state.draft, field.path) !== true);
355
+ }
356
+ const options = field.choices ?? [];
357
+ if (!options.length)
358
+ return state.draft;
359
+ const current = stringAt(state.draft, field.path);
360
+ const currentIndex = options.findIndex((option) => option.value === current);
361
+ const nextIndex = currentIndex < 0
362
+ ? direction > 0 ? 0 : options.length - 1
363
+ : (currentIndex + direction + options.length) % options.length;
364
+ const selected = options[nextIndex].value;
365
+ return setDraftPath(state.draft, field.path, field.kind === "number" ? Number(selected) : selected);
366
+ }
367
+ export function applyRepositorySelection(draft, repositories, externalId) {
368
+ let result = setDraftPath(draft, "repository.externalId", externalId || undefined);
369
+ const repository = repositories.find((item) => text(item.externalId ?? item.id) === externalId);
370
+ if (!repository)
371
+ return result;
372
+ result = setDraftPath(result, "repository.expectedHead", text(repository.headRevision) || undefined);
373
+ result = setDraftPath(result, "repository.name", text(repository.name) || undefined);
374
+ result = setDraftPath(result, "repository.namespace", undefined);
375
+ return setDraftPath(result, "repository.defaultBranch", text(repository.defaultBranch) || "main");
376
+ }
377
+ export function applyTemplateDefaults(draft, template) {
378
+ const schema = asObject(template?.parameterSchema);
379
+ const defaults = defaultsFromSchema(schema);
380
+ return setDraftPath(draft, "technology.parameters", defaults);
381
+ }
382
+ export function validateTemplateParameters(draft, templates) {
383
+ const templateCode = stringAt(draft, "technology.templateCode");
384
+ const template = templates.find((item) => text(item.code) === templateCode);
385
+ if (!template)
386
+ return templateCode ? ["Selected template is not in the active template catalog."] : ["Select a template."];
387
+ const schema = asObject(template.parameterSchema);
388
+ const parameters = asObject(valueAtPath(draft, "technology.parameters"));
389
+ return validateSchemaObject(schema, parameters, "technology.parameters");
390
+ }
391
+ export function validateWizardBeforeStep(state, targetStepIndex) {
392
+ if (targetStepIndex < ONBOARDING_STEPS.indexOf("STANDARDIZATION"))
393
+ return [];
394
+ const errors = [];
395
+ const mode = state.session?.mode ?? state.modes[state.modeIndex] ?? "NEW";
396
+ const requireText = (path) => {
397
+ if (!stringAt(state.draft, path).trim())
398
+ errors.push(`${path} is required.`);
399
+ };
400
+ requireText("application.code");
401
+ requireText("application.name");
402
+ if (!positiveIntegerAt(state.draft, "application.teamId"))
403
+ errors.push("application.teamId must be a positive integer.");
404
+ if (!positiveIntegerAt(state.draft, "application.systemId"))
405
+ errors.push("application.systemId must be a positive integer.");
406
+ requireText("repository.connectionCode");
407
+ const addModule = mode === "NEW" && stringAt(state.draft, "technology.repoMode") === "ADD_MODULE";
408
+ if (mode === "NEW" && !addModule) {
409
+ requireText("repository.namespace");
410
+ requireText("repository.name");
411
+ }
412
+ else {
413
+ requireText("repository.externalId");
414
+ requireText("repository.expectedHead");
415
+ }
416
+ requireText("technology.stackCode");
417
+ if (mode === "NEW") {
418
+ requireText("technology.templateCode");
419
+ const selected = state.catalog.templates.find((item) => text(item.code) === stringAt(state.draft, "technology.templateCode"));
420
+ if (selected)
421
+ errors.push(...validateTemplateParameters(state.draft, state.catalog.templates));
422
+ }
423
+ return [...new Set(errors)];
424
+ }
425
+ function validateSchemaObject(schema, value, path) {
426
+ const errors = [];
427
+ const required = Array.isArray(schema.required) ? schema.required.filter((item) => typeof item === "string") : [];
428
+ for (const key of required) {
429
+ if (value[key] == null || value[key] === "")
430
+ errors.push(`${path}.${key} is required.`);
431
+ }
432
+ for (const [key, propertyValue] of Object.entries(asObject(schema.properties))) {
433
+ if (value[key] == null)
434
+ continue;
435
+ const property = asObject(propertyValue);
436
+ const expected = text(property.type);
437
+ const actual = value[key];
438
+ if (expected === "string" && typeof actual !== "string")
439
+ errors.push(`${path}.${key} must be a string.`);
440
+ if ((expected === "integer" && (!Number.isInteger(actual) || typeof actual !== "number"))
441
+ || (expected === "number" && typeof actual !== "number"))
442
+ errors.push(`${path}.${key} must be a ${expected}.`);
443
+ if (expected === "boolean" && typeof actual !== "boolean")
444
+ errors.push(`${path}.${key} must be a boolean.`);
445
+ if (expected === "array" && !Array.isArray(actual))
446
+ errors.push(`${path}.${key} must be an array.`);
447
+ if (expected === "object" && (!actual || typeof actual !== "object" || Array.isArray(actual)))
448
+ errors.push(`${path}.${key} must be an object.`);
449
+ if (Array.isArray(property.enum) && !property.enum.some((item) => JSON.stringify(item) === JSON.stringify(actual))) {
450
+ errors.push(`${path}.${key} must be one of ${property.enum.map(String).join(", ")}.`);
451
+ }
452
+ if (typeof property.pattern === "string" && typeof actual === "string") {
453
+ try {
454
+ if (!new RegExp(property.pattern, "u").test(actual))
455
+ errors.push(`${path}.${key} does not match the template pattern.`);
456
+ }
457
+ catch {
458
+ errors.push(`${path}.${key} has an invalid registry pattern.`);
459
+ }
460
+ }
461
+ }
462
+ return errors;
463
+ }
464
+ function defaultsFromSchema(schema) {
465
+ const result = {};
466
+ for (const [key, value] of Object.entries(asObject(schema.properties))) {
467
+ const property = asObject(value);
468
+ if (property.default !== undefined)
469
+ result[key] = cloneValue(property.default);
470
+ }
471
+ return result;
472
+ }
473
+ export function currentStepCode(state) {
474
+ return ONBOARDING_STEPS[state.stepIndex] ?? "BASIC_INFORMATION";
475
+ }
476
+ function stepIndexForSession(session) {
477
+ const known = ONBOARDING_STEPS.indexOf(session.currentStep);
478
+ if (known >= 0)
479
+ return known;
480
+ return ["RUNNING", "WAITING_EXTERNAL", "VERIFYING", "COMPLETED", "FAILED", "CANCELLED"].includes(session.state)
481
+ ? ONBOARDING_STEPS.length - 1
482
+ : 1;
483
+ }
484
+ function setDraftPath(root, path, value) {
485
+ const result = cloneObject(root);
486
+ const parts = path.split(".");
487
+ let current = result;
488
+ for (const part of parts.slice(0, -1)) {
489
+ current[part] = asObject(current[part]);
490
+ current = current[part];
491
+ }
492
+ const leaf = parts[parts.length - 1];
493
+ if (value === undefined)
494
+ delete current[leaf];
495
+ else
496
+ current[leaf] = cloneValue(value);
497
+ return result;
498
+ }
499
+ function cloneObject(value) {
500
+ return cloneValue(value);
501
+ }
502
+ function cloneValue(value) {
503
+ if (Array.isArray(value))
504
+ return value.map(cloneValue);
505
+ if (value && typeof value === "object") {
506
+ return Object.fromEntries(Object.entries(value).map(([key, item]) => [key, cloneValue(item)]));
507
+ }
508
+ return value;
509
+ }
510
+ function asObject(value) {
511
+ return value && typeof value === "object" && !Array.isArray(value) ? cloneObject(value) : {};
512
+ }
513
+ function text(value) {
514
+ return typeof value === "string" ? value : typeof value === "number" ? String(value) : "";
515
+ }
516
+ function choices(values) {
517
+ return values.map((value) => ({ value, label: value }));
518
+ }
519
+ function includeCurrent(options, current) {
520
+ return current && !options.some((item) => item.value === current)
521
+ ? [{ value: current, label: `${current} (saved value)` }, ...options]
522
+ : options;
523
+ }
524
+ function stackLabel(item) {
525
+ return [text(item.name), text(item.language), text(item.framework), text(item.runtimeVersion)].filter(Boolean).join(" · ");
526
+ }
527
+ function templateLabel(item) {
528
+ return [text(item.name), text(item.sourceRevision), text(item.repoMode)].filter(Boolean).join(" · ");
529
+ }
530
+ function entityChoices(items) {
531
+ return items.flatMap((item) => {
532
+ const value = text(item.id ?? item.value);
533
+ if (!value)
534
+ return [];
535
+ return [{
536
+ value,
537
+ label: [text(item.name ?? item.label), text(item.code), text(item.status)].filter(Boolean).join(" · ")
538
+ }];
539
+ });
540
+ }
541
+ function positiveIntegerAt(root, path) {
542
+ const value = valueAtPath(root, path);
543
+ return typeof value === "number" && Number.isSafeInteger(value) && value > 0;
544
+ }
545
+ function clamp(value, minimum, maximum) {
546
+ return Math.min(maximum, Math.max(minimum, value));
547
+ }
548
+ export function draftAsJsonValue(draft) {
549
+ return draft;
550
+ }
551
+ export function safeTerminalWidth(columns) {
552
+ return columns != null && Number.isFinite(columns) && columns > 0 ? Math.floor(columns) : 100;
553
+ }
554
+ //# sourceMappingURL=tui-model.js.map