@highstate/cli 0.9.18 → 0.9.20

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.
@@ -2,7 +2,7 @@ import { describe, it, expect } from "vitest"
2
2
  import { applySchemaTransformations } from "./schema-transformer"
3
3
 
4
4
  describe("applySchemaTransformations", () => {
5
- it("should transform simple values to entity/schema structure", async () => {
5
+ it("should transform simple values using helper functions", async () => {
6
6
  const input = `
7
7
  const spec = {
8
8
  inputs: {
@@ -21,13 +21,16 @@ const spec = {
21
21
 
22
22
  const result = await applySchemaTransformations(input)
23
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.`")
24
+ expect(result).toContain(
25
+ "$addInputDescription(clusterEntity, `The Kubernetes cluster to deploy on.`)",
26
+ )
27
+ expect(result).toContain("$addArgumentDescription(Type.Number(), `The port number to use.`)")
28
+ expect(result).toContain(
29
+ 'import { $addArgumentDescription, $addInputDescription } from "@highstate/contract"',
30
+ )
28
31
  })
29
32
 
30
- it("should inject description into existing meta field", async () => {
33
+ it("should wrap existing structured objects with helper functions", async () => {
31
34
  const input = `
32
35
  const spec = {
33
36
  args: {
@@ -46,14 +49,13 @@ const spec = {
46
49
 
47
50
  const result = await applySchemaTransformations(input)
48
51
 
52
+ expect(result).toContain("$addArgumentDescription({")
49
53
  expect(result).toContain('displayName: "API Token"')
50
54
  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) =>")
55
+ expect(result).toContain("}, `The API token for authentication.`)")
54
56
  })
55
57
 
56
- it("should add meta field if it doesn't exist in structured object", async () => {
58
+ it("should wrap structured objects with helper functions", async () => {
57
59
  const input = `
58
60
  const spec = {
59
61
  inputs: {
@@ -69,10 +71,10 @@ const spec = {
69
71
 
70
72
  const result = await applySchemaTransformations(input)
71
73
 
74
+ expect(result).toContain("$addInputDescription({")
72
75
  expect(result).toContain("entity: endpointEntity")
73
76
  expect(result).toContain("required: false")
74
- expect(result).toContain("meta: {")
75
- expect(result).toContain("description: `The target endpoint.`")
77
+ expect(result).toContain("}, `The target endpoint.`)")
76
78
  })
77
79
 
78
80
  it("should handle $args marker function", async () => {
@@ -88,8 +90,9 @@ const spec = {
88
90
 
89
91
  const result = await applySchemaTransformations(input)
90
92
 
91
- expect(result).toContain("schema: Type.String()")
92
- expect(result).toContain("description: `The configuration file path.`")
93
+ expect(result).toContain(
94
+ "$addArgumentDescription(Type.String(), `The configuration file path.`)",
95
+ )
93
96
  })
94
97
 
95
98
  it("should handle $inputs marker function", async () => {
@@ -105,8 +108,7 @@ const spec = {
105
108
 
106
109
  const result = await applySchemaTransformations(input)
107
110
 
108
- expect(result).toContain("entity: dataEntity")
109
- expect(result).toContain("description: `The source data.`")
111
+ expect(result).toContain("$addInputDescription(dataEntity, `The source data.`)")
110
112
  })
111
113
 
112
114
  it("should handle $outputs marker function", async () => {
@@ -122,8 +124,7 @@ const spec = {
122
124
 
123
125
  const result = await applySchemaTransformations(input)
124
126
 
125
- expect(result).toContain("entity: resultEntity")
126
- expect(result).toContain("description: `The processed result.`")
127
+ expect(result).toContain("$addInputDescription(resultEntity, `The processed result.`)")
127
128
  })
128
129
 
129
130
  it("should handle $secrets marker function", async () => {
@@ -139,8 +140,7 @@ const spec = {
139
140
 
140
141
  const result = await applySchemaTransformations(input)
141
142
 
142
- expect(result).toContain("schema: Type.String()")
143
- expect(result).toContain("description: `The database password.`")
143
+ expect(result).toContain("$addArgumentDescription(Type.String(), `The database password.`)")
144
144
  })
145
145
 
146
146
  it("should ignore properties without JSDoc comments", async () => {
@@ -158,8 +158,7 @@ const spec = {
158
158
  const result = await applySchemaTransformations(input)
159
159
 
160
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.`")
161
+ expect(result).toContain("$addInputDescription(endpointEntity, `Only this one has a comment.`)") // transformed
163
162
  })
164
163
 
165
164
  it("should ignore properties not in target objects", async () => {
@@ -183,7 +182,7 @@ const spec = {
183
182
  const result = await applySchemaTransformations(input)
184
183
 
185
184
  expect(result).toContain('someProperty: "value"') // unchanged
186
- expect(result).toContain("entity: clusterEntity") // transformed
185
+ expect(result).toContain("$addInputDescription(clusterEntity, `This should be transformed.`)") // transformed
187
186
  })
188
187
 
189
188
  it("should clean JSDoc comments properly", async () => {
@@ -200,11 +199,10 @@ const spec = {
200
199
 
201
200
  const result = await applySchemaTransformations(input)
202
201
 
203
- expect(result).toContain("schema: Type.String()")
204
202
  expect(result).toContain(
205
- "description: `This is a description with \\`backticks\\` and \\${template} literals.",
203
+ "$addArgumentDescription(Type.String(), `This is a description with \\`backticks\\` and \\${template} literals.",
206
204
  )
207
- expect(result).toContain("It also has multiple lines.`")
205
+ expect(result).toContain("It also has multiple lines.`)")
208
206
  })
209
207
 
210
208
  it("should add .meta() to z.object fields with JSDoc comments", async () => {
@@ -445,13 +443,10 @@ var virtualMachine = defineUnit({
445
443
 
446
444
  const result = await applySchemaTransformations(input)
447
445
 
448
- // Should transform args.ipv4 to entity/schema structure (existing transformer behavior)
449
- expect(result).toContain("ipv4: {")
446
+ // Should transform args.ipv4 using $addArgumentDescription helper
447
+ expect(result).toContain("ipv4: $addArgumentDescription({")
450
448
  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
- )
449
+ expect(result).toContain("}, `The IPv4 address configuration for the virtual machine.`)")
455
450
 
456
451
  // Should add .meta() to z.object fields with JSDoc comments inside the discriminated union
457
452
  expect(result).toContain(
@@ -476,14 +471,162 @@ var virtualMachine = defineUnit({
476
471
  expect(result).toContain('type: z.literal("dhcp")') // unchanged
477
472
  expect(result).toContain('type: z.literal("static"),') // unchanged
478
473
 
479
- // Should properly handle inputs.vendorData with existing meta (no change expected since it already has meta)
480
- expect(result).toContain("vendorData: {")
474
+ // Should properly handle inputs.vendorData using $addInputDescription helper
475
+ expect(result).toContain("vendorData: $addInputDescription({")
481
476
  expect(result).toContain("entity: fileEntity,")
482
477
  expect(result).toContain("required: false,")
483
- expect(result).toContain("You can provide a cloud-config from the distribution component.")
478
+ expect(result).toContain("}, `The cloud-init vendor data to use for the virtual machine.")
479
+ expect(result).toContain("You can provide a cloud-config from the distribution component.`)")
484
480
 
485
481
  // Should NOT transform other inputs without JSDoc
486
482
  expect(result).toContain("proxmoxCluster: clusterEntity,") // unchanged
487
483
  expect(result).toContain("image: imageEntity,") // unchanged
488
484
  })
485
+
486
+ it("should add description to defineUnit function with JSDoc", async () => {
487
+ const input = `
488
+ /**
489
+ * Installs the Gateway API CRDs to the cluster.
490
+ */
491
+ export const gatewayApi = defineUnit({
492
+ type: "k8s.gateway-api",
493
+ inputs: {
494
+ k8sCluster: clusterEntity,
495
+ },
496
+ outputs: {
497
+ k8sCluster: clusterEntity,
498
+ },
499
+ meta: {
500
+ title: "Gateway API",
501
+ icon: "devicon:kubernetes",
502
+ secondaryIcon: "mdi:api",
503
+ secondaryIconColor: "#4CAF50",
504
+ category: "Kubernetes",
505
+ },
506
+ source: {
507
+ package: "@highstate/k8s",
508
+ path: "units/gateway-api",
509
+ },
510
+ })`
511
+
512
+ const result = await applySchemaTransformations(input)
513
+
514
+ expect(result).toContain("description: `Installs the Gateway API CRDs to the cluster.`")
515
+ expect(result).toContain('title: "Gateway API"')
516
+ expect(result).toContain('icon: "devicon:kubernetes"')
517
+ })
518
+
519
+ it("should add meta field to defineEntity without existing meta", async () => {
520
+ const input = `
521
+ /**
522
+ * Represents a Kubernetes cluster.
523
+ */
524
+ export const clusterEntity = defineEntity({
525
+ type: "k8s.cluster",
526
+ schema: z.object({
527
+ name: z.string(),
528
+ endpoint: z.string(),
529
+ }),
530
+ })`
531
+
532
+ const result = await applySchemaTransformations(input)
533
+
534
+ expect(result).toContain("meta: {")
535
+ expect(result).toContain("description: `Represents a Kubernetes cluster.`")
536
+ expect(result).toContain("schema: z.object({")
537
+ })
538
+
539
+ it("should add description to defineComponent function", async () => {
540
+ const input = `
541
+ /**
542
+ * A reusable database component.
543
+ */
544
+ export const database = defineComponent({
545
+ type: "database",
546
+ components: {
547
+ server: serverUnit,
548
+ storage: storageUnit,
549
+ },
550
+ meta: {
551
+ category: "Database",
552
+ },
553
+ })`
554
+
555
+ const result = await applySchemaTransformations(input)
556
+
557
+ expect(result).toContain("description: `A reusable database component.`")
558
+ expect(result).toContain('category: "Database"')
559
+ })
560
+
561
+ it("should not transform define functions without JSDoc", async () => {
562
+ const input = `
563
+ export const simpleUnit = defineUnit({
564
+ type: "simple",
565
+ meta: {
566
+ title: "Simple Unit",
567
+ },
568
+ })`
569
+
570
+ const result = await applySchemaTransformations(input)
571
+
572
+ expect(result).not.toContain("description:")
573
+ expect(result).toContain('title: "Simple Unit"')
574
+ })
575
+
576
+ it("should not create nested meta.meta.description structure", async () => {
577
+ const input = `
578
+ /**
579
+ * Traefik Gateway unit definition for Kubernetes.
580
+ */
581
+ var traefikGateway = defineUnit({
582
+ type: "k8s.apps.traefik-gateway.v1",
583
+ meta: {
584
+ title: "Traefik Gateway",
585
+ icon: "simple-icons:traefikproxy",
586
+ category: "Network",
587
+ },
588
+ })`
589
+
590
+ const result = await applySchemaTransformations(input)
591
+
592
+ // Should have description at the correct level
593
+ expect(result).toContain("meta: {")
594
+ expect(result).toContain("description: `Traefik Gateway unit definition for Kubernetes.`")
595
+ expect(result).toContain('title: "Traefik Gateway"')
596
+ expect(result).toContain('icon: "simple-icons:traefikproxy"')
597
+ expect(result).toContain('category: "Network"')
598
+
599
+ // Should NOT have nested meta.meta structure
600
+ expect(result).not.toContain("meta: {\\s*meta: {")
601
+
602
+ // Verify the structure more precisely - should only have one meta object
603
+ const metaMatches = (result.match(/meta\s*:\s*\{/g) || []).length
604
+ expect(metaMatches).toBe(1)
605
+ })
606
+
607
+ it("should replace existing description in defineUnit meta object", async () => {
608
+ const input = `
609
+ /**
610
+ * Updated description for the unit.
611
+ */
612
+ export const testUnit = defineUnit({
613
+ type: "test",
614
+ meta: {
615
+ title: "Test Unit",
616
+ description: \`Old description.\`,
617
+ category: "Test",
618
+ },
619
+ })`
620
+
621
+ const result = await applySchemaTransformations(input)
622
+
623
+ expect(result).toContain("description: `Updated description for the unit.`")
624
+ expect(result).not.toContain("Old description")
625
+ expect(result).toContain('title: "Test Unit"')
626
+ expect(result).toContain('category: "Test"')
627
+
628
+ // Should still only have one meta object
629
+ const metaMatches = (result.match(/meta\s*:\s*\{/g) || []).length
630
+ expect(metaMatches).toBe(1)
631
+ })
489
632
  })