@highstate/cli 0.9.15 → 0.9.18
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/dist/chunk-CMECLVT7.js +11 -0
- package/dist/chunk-CMECLVT7.js.map +1 -0
- package/dist/highstate.manifest.json +1 -1
- package/dist/library-loader-ZABUULFB.js +83 -0
- package/dist/library-loader-ZABUULFB.js.map +1 -0
- package/dist/main.js +407 -1145
- package/dist/main.js.map +1 -1
- package/package.json +24 -6
- package/src/commands/backend/identity.ts +24 -0
- package/src/commands/build.ts +42 -5
- package/src/main.ts +2 -0
- package/src/shared/index.ts +1 -0
- package/src/shared/library-loader.ts +129 -0
- package/src/shared/schema-transformer.spec.ts +489 -0
- package/src/shared/schema-transformer.ts +325 -13
- package/src/shared/schemas.ts +41 -0
- package/src/shared/source-hash-calculator.ts +129 -26
- package/src/shared/utils.ts +6 -0
|
@@ -0,0 +1,489 @@
|
|
|
1
|
+
import { describe, it, expect } from "vitest"
|
|
2
|
+
import { applySchemaTransformations } from "./schema-transformer"
|
|
3
|
+
|
|
4
|
+
describe("applySchemaTransformations", () => {
|
|
5
|
+
it("should transform simple values to entity/schema structure", async () => {
|
|
6
|
+
const input = `
|
|
7
|
+
const spec = {
|
|
8
|
+
inputs: {
|
|
9
|
+
/**
|
|
10
|
+
* The Kubernetes cluster to deploy on.
|
|
11
|
+
*/
|
|
12
|
+
cluster: clusterEntity,
|
|
13
|
+
},
|
|
14
|
+
args: {
|
|
15
|
+
/**
|
|
16
|
+
* The port number to use.
|
|
17
|
+
*/
|
|
18
|
+
port: Type.Number(),
|
|
19
|
+
},
|
|
20
|
+
}`
|
|
21
|
+
|
|
22
|
+
const result = await applySchemaTransformations(input)
|
|
23
|
+
|
|
24
|
+
expect(result).toContain("entity: clusterEntity")
|
|
25
|
+
expect(result).toContain("schema: Type.Number()")
|
|
26
|
+
expect(result).toContain("description: `The Kubernetes cluster to deploy on.`")
|
|
27
|
+
expect(result).toContain("description: `The port number to use.`")
|
|
28
|
+
})
|
|
29
|
+
|
|
30
|
+
it("should inject description into existing meta field", async () => {
|
|
31
|
+
const input = `
|
|
32
|
+
const spec = {
|
|
33
|
+
args: {
|
|
34
|
+
/**
|
|
35
|
+
* The API token for authentication.
|
|
36
|
+
*/
|
|
37
|
+
token: {
|
|
38
|
+
schema: Type.String(),
|
|
39
|
+
meta: {
|
|
40
|
+
displayName: "API Token",
|
|
41
|
+
sensitive: true,
|
|
42
|
+
},
|
|
43
|
+
},
|
|
44
|
+
},
|
|
45
|
+
}`
|
|
46
|
+
|
|
47
|
+
const result = await applySchemaTransformations(input)
|
|
48
|
+
|
|
49
|
+
expect(result).toContain('displayName: "API Token"')
|
|
50
|
+
expect(result).toContain("sensitive: true")
|
|
51
|
+
expect(result).toContain("description: `The API token for authentication.`")
|
|
52
|
+
expect(result).not.toContain("...obj")
|
|
53
|
+
expect(result).not.toContain("((obj) =>")
|
|
54
|
+
})
|
|
55
|
+
|
|
56
|
+
it("should add meta field if it doesn't exist in structured object", async () => {
|
|
57
|
+
const input = `
|
|
58
|
+
const spec = {
|
|
59
|
+
inputs: {
|
|
60
|
+
/**
|
|
61
|
+
* The target endpoint.
|
|
62
|
+
*/
|
|
63
|
+
endpoint: {
|
|
64
|
+
entity: endpointEntity,
|
|
65
|
+
required: false,
|
|
66
|
+
},
|
|
67
|
+
},
|
|
68
|
+
}`
|
|
69
|
+
|
|
70
|
+
const result = await applySchemaTransformations(input)
|
|
71
|
+
|
|
72
|
+
expect(result).toContain("entity: endpointEntity")
|
|
73
|
+
expect(result).toContain("required: false")
|
|
74
|
+
expect(result).toContain("meta: {")
|
|
75
|
+
expect(result).toContain("description: `The target endpoint.`")
|
|
76
|
+
})
|
|
77
|
+
|
|
78
|
+
it("should handle $args marker function", async () => {
|
|
79
|
+
const input = `
|
|
80
|
+
const spec = {
|
|
81
|
+
args: $args({
|
|
82
|
+
/**
|
|
83
|
+
* The configuration file path.
|
|
84
|
+
*/
|
|
85
|
+
configPath: Type.String(),
|
|
86
|
+
}),
|
|
87
|
+
}`
|
|
88
|
+
|
|
89
|
+
const result = await applySchemaTransformations(input)
|
|
90
|
+
|
|
91
|
+
expect(result).toContain("schema: Type.String()")
|
|
92
|
+
expect(result).toContain("description: `The configuration file path.`")
|
|
93
|
+
})
|
|
94
|
+
|
|
95
|
+
it("should handle $inputs marker function", async () => {
|
|
96
|
+
const input = `
|
|
97
|
+
const spec = {
|
|
98
|
+
inputs: $inputs({
|
|
99
|
+
/**
|
|
100
|
+
* The source data.
|
|
101
|
+
*/
|
|
102
|
+
source: dataEntity,
|
|
103
|
+
}),
|
|
104
|
+
}`
|
|
105
|
+
|
|
106
|
+
const result = await applySchemaTransformations(input)
|
|
107
|
+
|
|
108
|
+
expect(result).toContain("entity: dataEntity")
|
|
109
|
+
expect(result).toContain("description: `The source data.`")
|
|
110
|
+
})
|
|
111
|
+
|
|
112
|
+
it("should handle $outputs marker function", async () => {
|
|
113
|
+
const input = `
|
|
114
|
+
const spec = {
|
|
115
|
+
outputs: $outputs({
|
|
116
|
+
/**
|
|
117
|
+
* The processed result.
|
|
118
|
+
*/
|
|
119
|
+
result: resultEntity,
|
|
120
|
+
}),
|
|
121
|
+
}`
|
|
122
|
+
|
|
123
|
+
const result = await applySchemaTransformations(input)
|
|
124
|
+
|
|
125
|
+
expect(result).toContain("entity: resultEntity")
|
|
126
|
+
expect(result).toContain("description: `The processed result.`")
|
|
127
|
+
})
|
|
128
|
+
|
|
129
|
+
it("should handle $secrets marker function", async () => {
|
|
130
|
+
const input = `
|
|
131
|
+
const spec = {
|
|
132
|
+
secrets: $secrets({
|
|
133
|
+
/**
|
|
134
|
+
* The database password.
|
|
135
|
+
*/
|
|
136
|
+
dbPassword: Type.String(),
|
|
137
|
+
}),
|
|
138
|
+
}`
|
|
139
|
+
|
|
140
|
+
const result = await applySchemaTransformations(input)
|
|
141
|
+
|
|
142
|
+
expect(result).toContain("schema: Type.String()")
|
|
143
|
+
expect(result).toContain("description: `The database password.`")
|
|
144
|
+
})
|
|
145
|
+
|
|
146
|
+
it("should ignore properties without JSDoc comments", async () => {
|
|
147
|
+
const input = `
|
|
148
|
+
const spec = {
|
|
149
|
+
inputs: {
|
|
150
|
+
cluster: clusterEntity,
|
|
151
|
+
/**
|
|
152
|
+
* Only this one has a comment.
|
|
153
|
+
*/
|
|
154
|
+
endpoint: endpointEntity,
|
|
155
|
+
},
|
|
156
|
+
}`
|
|
157
|
+
|
|
158
|
+
const result = await applySchemaTransformations(input)
|
|
159
|
+
|
|
160
|
+
expect(result).toContain("cluster: clusterEntity") // unchanged
|
|
161
|
+
expect(result).toContain("entity: endpointEntity") // transformed
|
|
162
|
+
expect(result).toContain("description: `Only this one has a comment.`")
|
|
163
|
+
})
|
|
164
|
+
|
|
165
|
+
it("should ignore properties not in target objects", async () => {
|
|
166
|
+
const input = `
|
|
167
|
+
const config = {
|
|
168
|
+
/**
|
|
169
|
+
* This should not be transformed.
|
|
170
|
+
*/
|
|
171
|
+
someProperty: "value",
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
const spec = {
|
|
175
|
+
inputs: {
|
|
176
|
+
/**
|
|
177
|
+
* This should be transformed.
|
|
178
|
+
*/
|
|
179
|
+
cluster: clusterEntity,
|
|
180
|
+
},
|
|
181
|
+
}`
|
|
182
|
+
|
|
183
|
+
const result = await applySchemaTransformations(input)
|
|
184
|
+
|
|
185
|
+
expect(result).toContain('someProperty: "value"') // unchanged
|
|
186
|
+
expect(result).toContain("entity: clusterEntity") // transformed
|
|
187
|
+
})
|
|
188
|
+
|
|
189
|
+
it("should clean JSDoc comments properly", async () => {
|
|
190
|
+
const input = `
|
|
191
|
+
const spec = {
|
|
192
|
+
args: {
|
|
193
|
+
/**
|
|
194
|
+
* This is a description with \`backticks\` and \${template} literals.
|
|
195
|
+
* It also has multiple lines.
|
|
196
|
+
*/
|
|
197
|
+
value: Type.String(),
|
|
198
|
+
},
|
|
199
|
+
}`
|
|
200
|
+
|
|
201
|
+
const result = await applySchemaTransformations(input)
|
|
202
|
+
|
|
203
|
+
expect(result).toContain("schema: Type.String()")
|
|
204
|
+
expect(result).toContain(
|
|
205
|
+
"description: `This is a description with \\`backticks\\` and \\${template} literals.",
|
|
206
|
+
)
|
|
207
|
+
expect(result).toContain("It also has multiple lines.`")
|
|
208
|
+
})
|
|
209
|
+
|
|
210
|
+
it("should add .meta() to z.object fields with JSDoc comments", async () => {
|
|
211
|
+
const input = `
|
|
212
|
+
const userSchema = z.object({
|
|
213
|
+
/**
|
|
214
|
+
* The user's unique identifier.
|
|
215
|
+
*/
|
|
216
|
+
id: z.string(),
|
|
217
|
+
/**
|
|
218
|
+
* The user's email address.
|
|
219
|
+
*/
|
|
220
|
+
email: z.string().email(),
|
|
221
|
+
name: z.string(), // no comment, should not be transformed
|
|
222
|
+
})`
|
|
223
|
+
|
|
224
|
+
const result = await applySchemaTransformations(input)
|
|
225
|
+
|
|
226
|
+
// Should add import at the top
|
|
227
|
+
expect(result).toContain(
|
|
228
|
+
'import { camelCaseToHumanReadable as __camelCaseToHumanReadable } from "@highstate/contract"',
|
|
229
|
+
)
|
|
230
|
+
|
|
231
|
+
// Should add .meta() with both title and description
|
|
232
|
+
expect(result).toContain(
|
|
233
|
+
'id: z.string().meta({ title: __camelCaseToHumanReadable("id"), description: `The user\'s unique identifier.` })',
|
|
234
|
+
)
|
|
235
|
+
expect(result).toContain(
|
|
236
|
+
'email: z.string().email().meta({ title: __camelCaseToHumanReadable("email"), description: `The user\'s email address.` })',
|
|
237
|
+
)
|
|
238
|
+
expect(result).toContain("name: z.string(), // no comment") // unchanged
|
|
239
|
+
})
|
|
240
|
+
|
|
241
|
+
it("should not add .meta() if already present in z.object fields", async () => {
|
|
242
|
+
const input = `
|
|
243
|
+
const userSchema = z.object({
|
|
244
|
+
/**
|
|
245
|
+
* The user's identifier.
|
|
246
|
+
*/
|
|
247
|
+
id: z.string().meta({ displayName: "ID" }),
|
|
248
|
+
})`
|
|
249
|
+
|
|
250
|
+
const result = await applySchemaTransformations(input)
|
|
251
|
+
|
|
252
|
+
// Should not modify the field that already has .meta()
|
|
253
|
+
expect(result).toContain('id: z.string().meta({ displayName: "ID" })')
|
|
254
|
+
expect(result).not.toContain("description:")
|
|
255
|
+
})
|
|
256
|
+
|
|
257
|
+
it("should handle nested z.object patterns", async () => {
|
|
258
|
+
const input = `
|
|
259
|
+
const schema = z.object({
|
|
260
|
+
user: z.object({
|
|
261
|
+
/**
|
|
262
|
+
* The user's name.
|
|
263
|
+
*/
|
|
264
|
+
name: z.string(),
|
|
265
|
+
profile: z.object({
|
|
266
|
+
/**
|
|
267
|
+
* The user's age.
|
|
268
|
+
*/
|
|
269
|
+
age: z.number(),
|
|
270
|
+
}),
|
|
271
|
+
}),
|
|
272
|
+
})`
|
|
273
|
+
|
|
274
|
+
const result = await applySchemaTransformations(input)
|
|
275
|
+
|
|
276
|
+
expect(result).toContain(
|
|
277
|
+
'name: z.string().meta({ title: __camelCaseToHumanReadable("name"), description: `The user\'s name.` })',
|
|
278
|
+
)
|
|
279
|
+
expect(result).toContain(
|
|
280
|
+
'age: z.number().meta({ title: __camelCaseToHumanReadable("age"), description: `The user\'s age.` })',
|
|
281
|
+
)
|
|
282
|
+
})
|
|
283
|
+
|
|
284
|
+
it("should handle multiple JSDoc-commented fields in the same z.object without conflicts", async () => {
|
|
285
|
+
const input = `
|
|
286
|
+
const networkSchema = z.discriminatedUnion("type", [
|
|
287
|
+
z.object({
|
|
288
|
+
type: z.literal("dhcp"),
|
|
289
|
+
}),
|
|
290
|
+
z.object({
|
|
291
|
+
type: z.literal("static"),
|
|
292
|
+
|
|
293
|
+
/**
|
|
294
|
+
* The IPv4 address to assign to the virtual machine.
|
|
295
|
+
*/
|
|
296
|
+
address: z.string().optional(),
|
|
297
|
+
|
|
298
|
+
/**
|
|
299
|
+
* The CIDR prefix for the IPv4 address.
|
|
300
|
+
*
|
|
301
|
+
* By default, this is set to 24.
|
|
302
|
+
*/
|
|
303
|
+
prefix: z.number().default(24),
|
|
304
|
+
|
|
305
|
+
/**
|
|
306
|
+
* The IPv4 gateway for the virtual machine.
|
|
307
|
+
*
|
|
308
|
+
* If not specified, will be set to the first address in the subnet.
|
|
309
|
+
*/
|
|
310
|
+
gateway: z.string().optional(),
|
|
311
|
+
}),
|
|
312
|
+
]).default({ type: "dhcp" })`
|
|
313
|
+
|
|
314
|
+
const result = await applySchemaTransformations(input)
|
|
315
|
+
|
|
316
|
+
expect(result).toContain(
|
|
317
|
+
'address: z.string().optional().meta({ title: __camelCaseToHumanReadable("address"), description: `The IPv4 address to assign to the virtual machine.` })',
|
|
318
|
+
)
|
|
319
|
+
expect(result).toContain(
|
|
320
|
+
'prefix: z.number().default(24).meta({ title: __camelCaseToHumanReadable("prefix"), description: `The CIDR prefix for the IPv4 address.',
|
|
321
|
+
)
|
|
322
|
+
expect(result).toContain("By default, this is set to 24.` })")
|
|
323
|
+
expect(result).toContain(
|
|
324
|
+
'gateway: z.string().optional().meta({ title: __camelCaseToHumanReadable("gateway"), description: `The IPv4 gateway for the virtual machine.',
|
|
325
|
+
)
|
|
326
|
+
expect(result).toContain(
|
|
327
|
+
"If not specified, will be set to the first address in the subnet.` })",
|
|
328
|
+
)
|
|
329
|
+
expect(result).toContain('type: z.literal("static"),') // unchanged, no comment
|
|
330
|
+
})
|
|
331
|
+
|
|
332
|
+
it("should handle z.object fields inside z.discriminatedUnion", async () => {
|
|
333
|
+
const input = `
|
|
334
|
+
const schema = z.discriminatedUnion("type", [
|
|
335
|
+
z.object({
|
|
336
|
+
type: z.literal("dhcp"),
|
|
337
|
+
}),
|
|
338
|
+
z.object({
|
|
339
|
+
type: z.literal("static"),
|
|
340
|
+
/**
|
|
341
|
+
* The IPv4 address to assign to the virtual machine.
|
|
342
|
+
*/
|
|
343
|
+
address: z.string().optional(),
|
|
344
|
+
/**
|
|
345
|
+
* The CIDR prefix for the IPv4 address.
|
|
346
|
+
*/
|
|
347
|
+
prefix: z.number().default(24),
|
|
348
|
+
}),
|
|
349
|
+
]).default({ type: "dhcp" })`
|
|
350
|
+
|
|
351
|
+
const result = await applySchemaTransformations(input)
|
|
352
|
+
|
|
353
|
+
expect(result).toContain(
|
|
354
|
+
'address: z.string().optional().meta({ title: __camelCaseToHumanReadable("address"), description: `The IPv4 address to assign to the virtual machine.` })',
|
|
355
|
+
)
|
|
356
|
+
expect(result).toContain(
|
|
357
|
+
'prefix: z.number().default(24).meta({ title: __camelCaseToHumanReadable("prefix"), description: `The CIDR prefix for the IPv4 address.` })',
|
|
358
|
+
)
|
|
359
|
+
expect(result).toContain('type: z.literal("dhcp"),') // unchanged, no comment
|
|
360
|
+
expect(result).toContain('type: z.literal("static"),') // unchanged, no comment
|
|
361
|
+
})
|
|
362
|
+
|
|
363
|
+
it("should handle the full defineUnit case with discriminated union and mixed transformations", async () => {
|
|
364
|
+
const input = `
|
|
365
|
+
var virtualMachine = defineUnit({
|
|
366
|
+
type: "proxmox.virtual-machine",
|
|
367
|
+
args: {
|
|
368
|
+
nodeName: z.string().optional(),
|
|
369
|
+
cpuType: z.string().default("host"),
|
|
370
|
+
cores: z.number().default(1),
|
|
371
|
+
sockets: z.number().default(1),
|
|
372
|
+
memory: z.number().default(512),
|
|
373
|
+
/**
|
|
374
|
+
* The IPv4 address configuration for the virtual machine.
|
|
375
|
+
*/
|
|
376
|
+
ipv4: {
|
|
377
|
+
schema: z.discriminatedUnion("type", [
|
|
378
|
+
z.object({
|
|
379
|
+
type: z.literal("dhcp")
|
|
380
|
+
}),
|
|
381
|
+
z.object({
|
|
382
|
+
type: z.literal("static"),
|
|
383
|
+
/**
|
|
384
|
+
* The IPv4 address to assign to the virtual machine.
|
|
385
|
+
*/
|
|
386
|
+
address: z.string().optional(),
|
|
387
|
+
/**
|
|
388
|
+
* The CIDR prefix for the IPv4 address.
|
|
389
|
+
*
|
|
390
|
+
* By default, this is set to 24.
|
|
391
|
+
*/
|
|
392
|
+
prefix: z.number().default(24),
|
|
393
|
+
/**
|
|
394
|
+
* The IPv4 gateway for the virtual machine.
|
|
395
|
+
*
|
|
396
|
+
* If not specified, will be set to the first address in the subnet.
|
|
397
|
+
*/
|
|
398
|
+
gateway: z.string().optional()
|
|
399
|
+
})
|
|
400
|
+
]).default({ type: "dhcp" })
|
|
401
|
+
},
|
|
402
|
+
dns: z.string().array().optional(),
|
|
403
|
+
datastoreId: z.string().optional(),
|
|
404
|
+
diskSize: z.number().default(8),
|
|
405
|
+
bridge: z.string().default("vmbr0"),
|
|
406
|
+
sshPort: z.number().default(22),
|
|
407
|
+
sshUser: z.string().default("root"),
|
|
408
|
+
waitForAgent: z.boolean().default(true),
|
|
409
|
+
vendorData: z.string().optional()
|
|
410
|
+
},
|
|
411
|
+
secrets: {
|
|
412
|
+
sshPassword: z.string().optional()
|
|
413
|
+
},
|
|
414
|
+
inputs: {
|
|
415
|
+
proxmoxCluster: clusterEntity,
|
|
416
|
+
image: imageEntity,
|
|
417
|
+
sshKeyPair: {
|
|
418
|
+
entity: keyPairEntity,
|
|
419
|
+
required: false
|
|
420
|
+
},
|
|
421
|
+
/**
|
|
422
|
+
* The cloud-init vendor data to use for the virtual machine.
|
|
423
|
+
*
|
|
424
|
+
* You can provide a cloud-config from the distribution component.
|
|
425
|
+
*/
|
|
426
|
+
vendorData: {
|
|
427
|
+
entity: fileEntity,
|
|
428
|
+
required: false,
|
|
429
|
+
}
|
|
430
|
+
},
|
|
431
|
+
outputs: serverOutputs,
|
|
432
|
+
meta: {
|
|
433
|
+
title: "Proxmox Virtual Machine",
|
|
434
|
+
description: "The virtual machine on a Proxmox cluster.",
|
|
435
|
+
category: "Proxmox",
|
|
436
|
+
icon: "simple-icons:proxmox",
|
|
437
|
+
iconColor: "#e56901",
|
|
438
|
+
secondaryIcon: "codicon:vm"
|
|
439
|
+
},
|
|
440
|
+
source: {
|
|
441
|
+
package: "@highstate/proxmox",
|
|
442
|
+
path: "virtual-machine"
|
|
443
|
+
}
|
|
444
|
+
});`
|
|
445
|
+
|
|
446
|
+
const result = await applySchemaTransformations(input)
|
|
447
|
+
|
|
448
|
+
// Should transform args.ipv4 to entity/schema structure (existing transformer behavior)
|
|
449
|
+
expect(result).toContain("ipv4: {")
|
|
450
|
+
expect(result).toContain("schema: z.discriminatedUnion")
|
|
451
|
+
expect(result).toContain("meta: {")
|
|
452
|
+
expect(result).toContain(
|
|
453
|
+
"description: `The IPv4 address configuration for the virtual machine.`",
|
|
454
|
+
)
|
|
455
|
+
|
|
456
|
+
// Should add .meta() to z.object fields with JSDoc comments inside the discriminated union
|
|
457
|
+
expect(result).toContain(
|
|
458
|
+
'address: z.string().optional().meta({ title: __camelCaseToHumanReadable("address"), description: `The IPv4 address to assign to the virtual machine.` })',
|
|
459
|
+
)
|
|
460
|
+
expect(result).toContain(
|
|
461
|
+
'prefix: z.number().default(24).meta({ title: __camelCaseToHumanReadable("prefix"), description: `The CIDR prefix for the IPv4 address.',
|
|
462
|
+
)
|
|
463
|
+
expect(result).toContain("By default, this is set to 24.` })")
|
|
464
|
+
expect(result).toContain(
|
|
465
|
+
'gateway: z.string().optional().meta({ title: __camelCaseToHumanReadable("gateway"), description: `The IPv4 gateway for the virtual machine.',
|
|
466
|
+
)
|
|
467
|
+
expect(result).toContain(
|
|
468
|
+
"If not specified, will be set to the first address in the subnet.` })",
|
|
469
|
+
)
|
|
470
|
+
|
|
471
|
+
// Should NOT transform fields without JSDoc comments
|
|
472
|
+
expect(result).toContain("nodeName: z.string().optional(),") // unchanged
|
|
473
|
+
expect(result).toContain('cpuType: z.string().default("host"),') // unchanged
|
|
474
|
+
expect(result).toContain("cores: z.number().default(1),") // unchanged
|
|
475
|
+
expect(result).toContain("dns: z.string().array().optional(),") // unchanged
|
|
476
|
+
expect(result).toContain('type: z.literal("dhcp")') // unchanged
|
|
477
|
+
expect(result).toContain('type: z.literal("static"),') // unchanged
|
|
478
|
+
|
|
479
|
+
// Should properly handle inputs.vendorData with existing meta (no change expected since it already has meta)
|
|
480
|
+
expect(result).toContain("vendorData: {")
|
|
481
|
+
expect(result).toContain("entity: fileEntity,")
|
|
482
|
+
expect(result).toContain("required: false,")
|
|
483
|
+
expect(result).toContain("You can provide a cloud-config from the distribution component.")
|
|
484
|
+
|
|
485
|
+
// Should NOT transform other inputs without JSDoc
|
|
486
|
+
expect(result).toContain("proxmoxCluster: clusterEntity,") // unchanged
|
|
487
|
+
expect(result).toContain("image: imageEntity,") // unchanged
|
|
488
|
+
})
|
|
489
|
+
})
|