@frontera-sdk/cli 1.52.28 → 1.52.30
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/package.json +5 -4
- package/src/commands/local/data.ts +60 -0
- package/src/commands/local/dev.ts +361 -18
- package/src/commands/local/index-commands.ts +11 -3
- package/src/commands/local/init.ts +88 -7
- package/src/commands/local/sync-request.ts +750 -0
- package/src/commands/local/sync.ts +941 -0
- package/src/commands/skill/bundle-commands.ts +9 -6
- package/src/errors.ts +4 -1
- package/src/exit.ts +3 -0
- package/src/flag-help.ts +48 -0
- package/src/local/local-actions.ts +306 -0
- package/src/local/local-adapt.ts +216 -0
- package/src/local/local-agent-files.ts +9 -2
- package/src/local/local-agent-grants.ts +125 -0
- package/src/local/local-agent-name.ts +71 -0
- package/src/local/local-applied.ts +334 -3
- package/src/local/local-apps.ts +156 -0
- package/src/local/local-blueprint-outline.ts +465 -0
- package/src/local/local-brand.ts +123 -0
- package/src/local/local-build-summary.ts +106 -0
- package/src/local/local-chat-apps.ts +170 -0
- package/src/local/local-dev-references.ts +1132 -0
- package/src/local/local-dev-sync.ts +171 -7
- package/src/local/local-docs.ts +53 -5
- package/src/local/local-env.ts +2 -0
- package/src/local/local-function-draft.ts +33 -0
- package/src/local/local-function-shared.ts +65 -0
- package/src/local/local-function-state.ts +34 -0
- package/src/local/local-knowledge-documents.ts +302 -0
- package/src/local/local-lessons.ts +278 -0
- package/src/local/local-library.ts +124 -0
- package/src/local/local-media-sets.ts +31 -0
- package/src/local/local-object-sets.ts +181 -0
- package/src/local/local-objects.ts +159 -0
- package/src/local/local-operating.ts +9 -5
- package/src/local/local-org-settings.ts +118 -0
- package/src/local/local-portability.ts +346 -0
- package/src/local/local-project.ts +6 -2
- package/src/local/local-quality-checks.ts +171 -0
- package/src/local/local-scaffold.ts +14 -11
- package/src/local/local-schemas.ts +33 -2
- package/src/local/local-secrets.ts +306 -0
- package/src/local/local-stand-ins.ts +442 -0
- package/src/local/local-studio-server.ts +23 -2
- package/src/local/local-sync-base.ts +237 -0
- package/src/local/local-sync-client.ts +93 -0
- package/src/local/local-sync-link.ts +122 -0
- package/src/local/local-sync-merge.ts +395 -0
- package/src/local/local-sync-names.ts +169 -0
- package/src/local/local-sync-plan.ts +717 -0
- package/src/local/local-sync-push.ts +986 -0
- package/src/local/local-sync-refs.ts +221 -0
- package/src/local/local-sync-remote.ts +1482 -0
- package/src/local/local-sync-snapshot.ts +526 -0
- package/src/local/local-synthetic-data.ts +374 -0
- package/src/local/local-test-runner.ts +172 -14
- package/src/local/local-themes.ts +201 -0
- package/src/local/local-workflow-state.ts +131 -0
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* References between a project's files, by name instead of runtime id.
|
|
3
|
+
*
|
|
4
|
+
* An operating config names the Function a step calls by its id, a workflow
|
|
5
|
+
* names the Agent or Plugin a node runs by its id, and ids differ on every
|
|
6
|
+
* runtime. A project file writes `function/refund-eligibility` instead (the
|
|
7
|
+
* kind, a slash, the resource's name), and an instruction link writes
|
|
8
|
+
* `[[function:refund-eligibility]]`. Pulling from a workspace rewrites ids to
|
|
9
|
+
* names (`toNames`); applying to a runtime rewrites names to that runtime's ids
|
|
10
|
+
* (`toIds`). The rewrite walks the whole document, so a new field that holds
|
|
11
|
+
* a reference needs no change here.
|
|
12
|
+
*
|
|
13
|
+
* A value that is not a reference (a procedure's own id, a variable key) is
|
|
14
|
+
* left as it is: only a whole string of the form `kind/name`, or an id the
|
|
15
|
+
* directory knows, is rewritten.
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* `object` is an object definition an Agent's object tools work on
|
|
20
|
+
* (`object/claim_ticket`), by slug; its fields travel in the Agent's file.
|
|
21
|
+
*/
|
|
22
|
+
export const REF_KINDS = ['function', 'workflow', 'agent', 'knowledge', 'plugin', 'objectType', 'action', 'model', 'skill', 'objectSet', 'object'] as const
|
|
23
|
+
export type RefKind = (typeof REF_KINDS)[number]
|
|
24
|
+
|
|
25
|
+
/** Names as resources are named: slugs, API names, install names. */
|
|
26
|
+
const NAME = '[A-Za-z0-9][A-Za-z0-9._-]*'
|
|
27
|
+
const REF = new RegExp(`^(${REF_KINDS.join('|')})/(${NAME})$`)
|
|
28
|
+
/** `[[function:<id or name>]]` in guidance text, the platform's link form. */
|
|
29
|
+
const LINK = new RegExp(`\\[\\[(${REF_KINDS.join('|')}):([^\\]\\s]+)\\]\\]`, 'g')
|
|
30
|
+
/** `function:<id or name>` as a whole value: a procedure's tool list names its tools this way. */
|
|
31
|
+
const PREFIXED = new RegExp(`^(${REF_KINDS.join('|')}):([^\\s:]+)$`)
|
|
32
|
+
const UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i
|
|
33
|
+
|
|
34
|
+
export function isUuid(value: string): boolean {
|
|
35
|
+
return UUID.test(value)
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export function parseRef(value: string): { kind: RefKind; name: string } | null {
|
|
39
|
+
const m = REF.exec(value)
|
|
40
|
+
return m ? { kind: m[1] as RefKind, name: m[2]! } : null
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export function formatRef(kind: RefKind, name: string): string {
|
|
44
|
+
return `${kind}/${name}`
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* The pulled item a reference needs: an object type, a governed Action and a
|
|
49
|
+
* saved object set are Blueprint items (`blueprint/object-types/Customer`,
|
|
50
|
+
* `blueprint/actions/resetLine`, `blueprint/object-sets/open-cases`),
|
|
51
|
+
* everything else its own ref.
|
|
52
|
+
*/
|
|
53
|
+
export function itemRef(kind: RefKind, name: string): string {
|
|
54
|
+
if (kind === 'objectType') return `blueprint/object-types/${name}`
|
|
55
|
+
if (kind === 'action') return `blueprint/actions/${name}`
|
|
56
|
+
if (kind === 'objectSet') return `blueprint/object-sets/${name}`
|
|
57
|
+
return formatRef(kind, name)
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* One runtime's resources: for each kind, name → id. Built from the runtime's
|
|
62
|
+
* own lists (`local-sync-directory.ts`), for the platform workspace when
|
|
63
|
+
* pulling and for the local runtime when applying.
|
|
64
|
+
*/
|
|
65
|
+
export type Directory = Partial<Record<RefKind, Map<string, string>>>
|
|
66
|
+
|
|
67
|
+
/** The same directory the other way round: id → reference. */
|
|
68
|
+
export function invert(directory: Directory): Map<string, { kind: RefKind; name: string }> {
|
|
69
|
+
const out = new Map<string, { kind: RefKind; name: string }>()
|
|
70
|
+
for (const kind of REF_KINDS) {
|
|
71
|
+
for (const [name, id] of directory[kind] ?? []) {
|
|
72
|
+
if (!out.has(id)) out.set(id, { kind, name })
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
return out
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export interface Unresolved {
|
|
79
|
+
/** Where in the document: `procedures.0.steps.2.functionId`. */
|
|
80
|
+
path: string
|
|
81
|
+
/** The value that could not be rewritten: an id the directory does not know, or a name it does not have. */
|
|
82
|
+
value: string
|
|
83
|
+
kind?: RefKind
|
|
84
|
+
/** It is a `[[kind:name]]` link inside guidance text, not a whole value. */
|
|
85
|
+
link?: boolean
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/** Keys whose value looks like `kind/name` but is a format, not a resource (`apiVersion: workflow/v1`). */
|
|
89
|
+
const NOT_REFERENCES = new Set(['apiVersion', '$schema'])
|
|
90
|
+
|
|
91
|
+
function walk(value: unknown, path: string[], visit: (s: string, path: string[]) => string): unknown {
|
|
92
|
+
if (typeof value === 'string') return NOT_REFERENCES.has(path[path.length - 1] ?? '') ? value : visit(value, path)
|
|
93
|
+
if (Array.isArray(value)) return value.map((v, i) => walk(v, [...path, String(i)], visit))
|
|
94
|
+
if (value && typeof value === 'object') {
|
|
95
|
+
return Object.fromEntries(Object.entries(value as Record<string, unknown>).map(([k, v]) => [k, walk(v, [...path, k], visit)]))
|
|
96
|
+
}
|
|
97
|
+
return value
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/** Keys whose value is another resource's id, where an unknown id is worth reporting. */
|
|
101
|
+
const REFERENCE_KEY = /(^|[a-z])(Id|Ids|id|ids)$|^resourceId$|^ref$/
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Ids → `kind/name`, for a document read from a runtime. An id the directory
|
|
105
|
+
* does not know stays as it is and is reported when it sits where a reference
|
|
106
|
+
* would (`functionId`, `resourceId`, a list of `ids`): the pull plan shows it,
|
|
107
|
+
* and nothing writes a name that points nowhere.
|
|
108
|
+
*/
|
|
109
|
+
export function toNames<T>(doc: T, directory: Directory): { doc: T; unresolved: Unresolved[] } {
|
|
110
|
+
const byId = invert(directory)
|
|
111
|
+
const unresolved: Unresolved[] = []
|
|
112
|
+
const out = walk(doc, [], (s, path) => {
|
|
113
|
+
const known = byId.get(s)
|
|
114
|
+
if (known) return formatRef(known.kind, known.name)
|
|
115
|
+
const prefixed = PREFIXED.exec(s)
|
|
116
|
+
if (prefixed && isUuid(prefixed[2]!)) {
|
|
117
|
+
const named = byId.get(prefixed[2]!)
|
|
118
|
+
if (named) return `${prefixed[1]}:${named.name}`
|
|
119
|
+
unresolved.push({ path: path.join('.'), value: prefixed[2]!, kind: prefixed[1] as RefKind })
|
|
120
|
+
return s
|
|
121
|
+
}
|
|
122
|
+
if (isUuid(s)) {
|
|
123
|
+
const key = path.findLast((p) => !/^\d+$/.test(p)) ?? ''
|
|
124
|
+
if (REFERENCE_KEY.test(key)) unresolved.push({ path: path.join('.'), value: s })
|
|
125
|
+
return s
|
|
126
|
+
}
|
|
127
|
+
if (!s.includes('[[')) return s
|
|
128
|
+
return s.replace(LINK, (whole, kind: RefKind, target: string) => {
|
|
129
|
+
const named = byId.get(target)
|
|
130
|
+
if (named) return `[[${kind}:${named.name}]]`
|
|
131
|
+
if (isUuid(target)) unresolved.push({ path: path.join('.'), value: target, kind })
|
|
132
|
+
return whole
|
|
133
|
+
})
|
|
134
|
+
}) as T
|
|
135
|
+
return { doc: out, unresolved }
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* `kind/name` → ids, for a document about to be applied to a runtime. A name
|
|
140
|
+
* the runtime does not have is reported with where it is, and the document
|
|
141
|
+
* is not applied: a missing Function is a missing file, not a blank field.
|
|
142
|
+
* Ids already in the document pass through, so files written before names
|
|
143
|
+
* existed still apply.
|
|
144
|
+
*/
|
|
145
|
+
export function toIds<T>(doc: T, directory: Directory): { doc: T; unresolved: Unresolved[] } {
|
|
146
|
+
const unresolved: Unresolved[] = []
|
|
147
|
+
const out = walk(doc, [], (s, path) => {
|
|
148
|
+
const ref = parseRef(s)
|
|
149
|
+
if (ref) {
|
|
150
|
+
const id = directory[ref.kind]?.get(ref.name)
|
|
151
|
+
if (id) return id
|
|
152
|
+
unresolved.push({ path: path.join('.'), value: s, kind: ref.kind })
|
|
153
|
+
return s
|
|
154
|
+
}
|
|
155
|
+
const prefixed = PREFIXED.exec(s)
|
|
156
|
+
if (prefixed && !isUuid(prefixed[2]!) && directory[prefixed[1] as RefKind] !== undefined) {
|
|
157
|
+
const id = directory[prefixed[1] as RefKind]?.get(prefixed[2]!)
|
|
158
|
+
if (id) return `${prefixed[1]}:${id}`
|
|
159
|
+
unresolved.push({ path: path.join('.'), value: `${prefixed[1]}/${prefixed[2]}`, kind: prefixed[1] as RefKind })
|
|
160
|
+
return s
|
|
161
|
+
}
|
|
162
|
+
if (!s.includes('[[')) return s
|
|
163
|
+
return s.replace(LINK, (whole, kind: RefKind, target: string) => {
|
|
164
|
+
if (isUuid(target)) return whole
|
|
165
|
+
const id = directory[kind]?.get(target)
|
|
166
|
+
if (id) return `[[${kind}:${id}]]`
|
|
167
|
+
unresolved.push({ path: path.join('.'), value: `${kind}/${target}`, kind, link: true })
|
|
168
|
+
return whole
|
|
169
|
+
})
|
|
170
|
+
}) as T
|
|
171
|
+
return { doc: out, unresolved }
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/** Every `kind/name` a document refers to, for listing what a pulled item needs. */
|
|
175
|
+
export function referencesIn(doc: unknown): Array<{ kind: RefKind; name: string }> {
|
|
176
|
+
const seen = new Map<string, { kind: RefKind; name: string }>()
|
|
177
|
+
walk(doc, [], (s) => {
|
|
178
|
+
const ref = parseRef(s)
|
|
179
|
+
if (ref) seen.set(formatRef(ref.kind, ref.name), ref)
|
|
180
|
+
const prefixed = PREFIXED.exec(s)
|
|
181
|
+
if (prefixed && !isUuid(prefixed[2]!)) seen.set(formatRef(prefixed[1] as RefKind, prefixed[2]!), { kind: prefixed[1] as RefKind, name: prefixed[2]! })
|
|
182
|
+
for (const m of s.matchAll(LINK)) {
|
|
183
|
+
if (!isUuid(m[2]!)) seen.set(formatRef(m[1] as RefKind, m[2]!), { kind: m[1] as RefKind, name: m[2]! })
|
|
184
|
+
}
|
|
185
|
+
return s
|
|
186
|
+
})
|
|
187
|
+
return [...seen.values()]
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/** Which kinds a document refers to by name, so a resolver lists only those. */
|
|
191
|
+
export function kindsIn(doc: unknown): Set<RefKind> {
|
|
192
|
+
return new Set(referencesIn(doc).map((r) => r.kind))
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* The workflow nodes that run a governed Action still named by its platform id
|
|
197
|
+
* (`kind: action`, `resourceId: <uuid>`), branches included: a file pulled
|
|
198
|
+
* before Actions were named, or by a key that could not name them.
|
|
199
|
+
*/
|
|
200
|
+
export function governedActionIds(doc: unknown): Array<{ path: string; id: string }> {
|
|
201
|
+
const out: Array<{ path: string; id: string }> = []
|
|
202
|
+
const visit = (v: unknown, path: string[]) => {
|
|
203
|
+
if (Array.isArray(v)) return v.forEach((x, i) => visit(x, [...path, String(i)]))
|
|
204
|
+
if (!v || typeof v !== 'object') return
|
|
205
|
+
const o = v as Record<string, unknown>
|
|
206
|
+
if (o.kind === 'action' && typeof o.resourceId === 'string' && isUuid(o.resourceId)) out.push({ path: [...path, 'resourceId'].join('.'), id: o.resourceId })
|
|
207
|
+
for (const [k, x] of Object.entries(o)) visit(x, [...path, k])
|
|
208
|
+
}
|
|
209
|
+
visit(doc, [])
|
|
210
|
+
return out
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
/** One line per unresolved reference, for an error or a plan. */
|
|
214
|
+
export function describeUnresolved(items: Unresolved[], file?: string): string[] {
|
|
215
|
+
return items.map((u) => {
|
|
216
|
+
const where = `${file ? `${file}: ` : ''}${u.path || '(document)'}`
|
|
217
|
+
if (u.kind === 'action' && !isUuid(u.value)) return `${where}: the governed Action ${parseRef(u.value)?.name ?? u.value} is not on this runtime`
|
|
218
|
+
if (u.kind === 'object' && !isUuid(u.value)) return `${where}: the object definition ${parseRef(u.value)?.name ?? u.value} is not on this runtime`
|
|
219
|
+
return u.kind && !isUuid(u.value) ? `${where}: ${u.value} is not on this runtime` : `${where}: ${u.value} could not be named (deleted, outside the workspace, or not readable with this key)`
|
|
220
|
+
})
|
|
221
|
+
}
|