kicadts 0.0.1 → 0.0.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.
Files changed (78) hide show
  1. package/.github/workflows/bun-formatcheck.yml +26 -0
  2. package/.github/workflows/bun-pver-release.yml +70 -0
  3. package/.github/workflows/bun-test.yml +32 -0
  4. package/.github/workflows/bun-typecheck.yml +26 -0
  5. package/.vscode/settings.json +1 -1
  6. package/AGENTS.md +1 -0
  7. package/LICENSE +21 -0
  8. package/README.md +101 -91
  9. package/TODO.md +46 -0
  10. package/bunfig.toml +2 -2
  11. package/lib/sexpr/classes/At.ts +15 -0
  12. package/lib/sexpr/classes/Bus.ts +23 -3
  13. package/lib/sexpr/classes/BusEntry.ts +30 -3
  14. package/lib/sexpr/classes/EmbeddedFonts.ts +1 -3
  15. package/lib/sexpr/classes/Footprint.ts +157 -27
  16. package/lib/sexpr/classes/FootprintAttr.ts +3 -1
  17. package/lib/sexpr/classes/FootprintModel.ts +1 -4
  18. package/lib/sexpr/classes/FootprintNetTiePadGroups.ts +3 -1
  19. package/lib/sexpr/classes/FootprintPad.ts +206 -54
  20. package/lib/sexpr/classes/FpArc.ts +23 -0
  21. package/lib/sexpr/classes/FpCircle.ts +24 -3
  22. package/lib/sexpr/classes/FpLine.ts +31 -3
  23. package/lib/sexpr/classes/FpPoly.ts +24 -4
  24. package/lib/sexpr/classes/FpRect.ts +24 -3
  25. package/lib/sexpr/classes/FpText.ts +43 -9
  26. package/lib/sexpr/classes/FpTextBox.ts +43 -5
  27. package/lib/sexpr/classes/GrLine.ts +20 -1
  28. package/lib/sexpr/classes/GrText.ts +38 -12
  29. package/lib/sexpr/classes/Image.ts +38 -11
  30. package/lib/sexpr/classes/Junction.ts +36 -4
  31. package/lib/sexpr/classes/KicadPcb.ts +49 -1
  32. package/lib/sexpr/classes/KicadSch.ts +119 -25
  33. package/lib/sexpr/classes/Label.ts +45 -5
  34. package/lib/sexpr/classes/NoConnect.ts +20 -3
  35. package/lib/sexpr/classes/PadLayers.ts +13 -1
  36. package/lib/sexpr/classes/PadOptions.ts +4 -5
  37. package/lib/sexpr/classes/PadPrimitiveGrArc.ts +22 -4
  38. package/lib/sexpr/classes/PadPrimitiveGrCircle.ts +23 -4
  39. package/lib/sexpr/classes/PadPrimitives.ts +3 -1
  40. package/lib/sexpr/classes/PadSize.ts +15 -0
  41. package/lib/sexpr/classes/PadTeardrops.ts +3 -1
  42. package/lib/sexpr/classes/PcbGeneral.ts +14 -7
  43. package/lib/sexpr/classes/PcbLayerDefinition.ts +5 -1
  44. package/lib/sexpr/classes/Property.ts +64 -9
  45. package/lib/sexpr/classes/Pts.ts +7 -5
  46. package/lib/sexpr/classes/SchematicText.ts +39 -9
  47. package/lib/sexpr/classes/Segment.ts +21 -0
  48. package/lib/sexpr/classes/SegmentNet.ts +3 -1
  49. package/lib/sexpr/classes/Setup/PcbPlotParams.ts +10 -50
  50. package/lib/sexpr/classes/Setup/Setup.ts +12 -11
  51. package/lib/sexpr/classes/Setup/Stackup.ts +14 -19
  52. package/lib/sexpr/classes/Setup/StackupLayerProperties.ts +3 -1
  53. package/lib/sexpr/classes/Setup/StackupProperties.ts +0 -1
  54. package/lib/sexpr/classes/Setup/base.ts +1 -3
  55. package/lib/sexpr/classes/Setup/setupMultiValueProperties.ts +0 -1
  56. package/lib/sexpr/classes/Sheet.ts +85 -3
  57. package/lib/sexpr/classes/SheetPin.ts +4 -1
  58. package/lib/sexpr/classes/Symbol.ts +176 -51
  59. package/lib/sexpr/classes/TextEffects.ts +25 -8
  60. package/lib/sexpr/classes/TitleBlock.ts +21 -4
  61. package/lib/sexpr/classes/Via.ts +38 -3
  62. package/lib/sexpr/classes/ViaNet.ts +2 -1
  63. package/lib/sexpr/classes/Wire.ts +23 -3
  64. package/lib/sexpr/classes/Xy.ts +1 -3
  65. package/lib/sexpr/classes/Zone.ts +1 -3
  66. package/lib/sexpr/parseToPrimitiveSExpr.ts +6 -1
  67. package/lib/sexpr/utils/strokeFromArgs.ts +5 -6
  68. package/lib/sexpr/utils/toStringValue.ts +2 -1
  69. package/package.json +2 -1
  70. package/scripts/download-references.ts +24 -22
  71. package/tests/fixtures/expectEqualPrimitiveSExpr.ts +6 -7
  72. package/tests/fixtures/png-matcher.ts +109 -0
  73. package/tests/fixtures/preload.ts +1 -0
  74. package/tests/sexpr/classes/FootprintPad.test.ts +8 -1
  75. package/tests/sexpr/classes/Image.test.ts +9 -1
  76. package/tests/sexpr/classes/KicadSch.test.ts +1 -3
  77. package/tests/sexpr/classes/Setup.test.ts +0 -1
  78. package/bun.lock +0 -48
@@ -101,7 +101,12 @@ export function tokenize(input: string): Token[] {
101
101
  }
102
102
 
103
103
  // Number or symbol
104
- if (isSymbolInitial(current) || current === "-" || current === "+" || current === ".") {
104
+ if (
105
+ isSymbolInitial(current) ||
106
+ current === "-" ||
107
+ current === "+" ||
108
+ current === "."
109
+ ) {
105
110
  // read a maximal token until delimiter
106
111
  let start = i
107
112
  while (i < input.length) {
@@ -2,13 +2,12 @@ import { SxClass } from "../base-classes/SxClass"
2
2
  import type { PrimitiveSExpr } from "../parseToPrimitiveSExpr"
3
3
  import { Stroke } from "../classes/Stroke"
4
4
 
5
- export const strokeFromArgs = (
6
- args: PrimitiveSExpr[],
7
- ): Stroke | undefined => {
5
+ export const strokeFromArgs = (args: PrimitiveSExpr[]): Stroke | undefined => {
8
6
  try {
9
- const parsed = SxClass.parsePrimitiveSexpr(
10
- ["stroke", ...args] as PrimitiveSExpr,
11
- )
7
+ const parsed = SxClass.parsePrimitiveSexpr([
8
+ "stroke",
9
+ ...args,
10
+ ] as PrimitiveSExpr)
12
11
  if (parsed instanceof Stroke) {
13
12
  return parsed
14
13
  }
@@ -5,6 +5,7 @@ export const toStringValue = (
5
5
  ): string | undefined => {
6
6
  if (value === undefined) return undefined
7
7
  if (typeof value === "string") return value
8
- if (typeof value === "number" || typeof value === "boolean") return String(value)
8
+ if (typeof value === "number" || typeof value === "boolean")
9
+ return String(value)
9
10
  return undefined
10
11
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "kicadts",
3
3
  "module": "dist/index.js",
4
- "version": "0.0.1",
4
+ "version": "0.0.2",
5
5
  "type": "module",
6
6
  "repository": {
7
7
  "type": "git",
@@ -18,6 +18,7 @@
18
18
  "devDependencies": {
19
19
  "@biomejs/biome": "^2.2.4",
20
20
  "@types/bun": "latest",
21
+ "looks-same": "^10.0.1",
21
22
  "tsup": "^8.5.0"
22
23
  },
23
24
  "peerDependencies": {
@@ -1,11 +1,11 @@
1
- import { mkdir, writeFile } from "node:fs/promises";
2
- import { resolve, dirname } from "node:path";
3
- import { fileURLToPath } from "node:url";
1
+ import { mkdir, writeFile } from "node:fs/promises"
2
+ import { resolve, dirname } from "node:path"
3
+ import { fileURLToPath } from "node:url"
4
4
 
5
5
  type ReferenceSpec = {
6
- readonly url: string;
7
- readonly filename: string;
8
- };
6
+ readonly url: string
7
+ readonly filename: string
8
+ }
9
9
 
10
10
  const references: ReferenceSpec[] = [
11
11
  {
@@ -28,39 +28,41 @@ const references: ReferenceSpec[] = [
28
28
  url: "https://gitlab.com/kicad/services/kicad-dev-docs/-/raw/master/content/file-formats/sexpr-symbol-lib/_index.en.adoc",
29
29
  filename: "SCH_SYM_SEXPR.adoc",
30
30
  },
31
- ];
31
+ ]
32
32
 
33
- const __dirname = dirname(fileURLToPath(import.meta.url));
34
- const repoRoot = resolve(__dirname, "..");
35
- const referencesDir = resolve(repoRoot, "references");
33
+ const __dirname = dirname(fileURLToPath(import.meta.url))
34
+ const repoRoot = resolve(__dirname, "..")
35
+ const referencesDir = resolve(repoRoot, "references")
36
36
 
37
37
  async function fetchReference(reference: ReferenceSpec): Promise<void> {
38
- const response = await fetch(reference.url);
38
+ const response = await fetch(reference.url)
39
39
 
40
40
  if (!response.ok) {
41
- throw new Error(`${reference.url} (${response.status} ${response.statusText})`);
41
+ throw new Error(
42
+ `${reference.url} (${response.status} ${response.statusText})`,
43
+ )
42
44
  }
43
45
 
44
- const content = await response.text();
45
- const targetPath = resolve(referencesDir, reference.filename);
46
+ const content = await response.text()
47
+ const targetPath = resolve(referencesDir, reference.filename)
46
48
 
47
- await writeFile(targetPath, content, "utf8");
48
- console.log(`Saved ${reference.filename}`);
49
+ await writeFile(targetPath, content, "utf8")
50
+ console.log(`Saved ${reference.filename}`)
49
51
  }
50
52
 
51
53
  async function main(): Promise<void> {
52
- await mkdir(referencesDir, { recursive: true });
54
+ await mkdir(referencesDir, { recursive: true })
53
55
 
54
56
  await Promise.all(
55
57
  references.map(async (reference) => {
56
58
  try {
57
- await fetchReference(reference);
59
+ await fetchReference(reference)
58
60
  } catch (error) {
59
- console.error(`Failed to download ${reference.filename}:`, error);
60
- process.exitCode = 1;
61
+ console.error(`Failed to download ${reference.filename}:`, error)
62
+ process.exitCode = 1
61
63
  }
62
64
  }),
63
- );
65
+ )
64
66
  }
65
67
 
66
- await main();
68
+ await main()
@@ -100,13 +100,14 @@ export const expectEqualPrimitiveSExpr = (
100
100
  const normalizedExpected = canonicalizePrimitiveSExpr(expected)
101
101
 
102
102
  const rootPath = options.path ?? "sexpr"
103
- const contextEntries = options.context
104
- ? Object.entries(options.context)
105
- : []
103
+ const contextEntries = options.context ? Object.entries(options.context) : []
106
104
 
107
105
  const formatPath = (segments: readonly string[]): string => segments.join("")
108
106
 
109
- const buildPayload = (path: string, value: unknown): Record<string, unknown> => {
107
+ const buildPayload = (
108
+ path: string,
109
+ value: unknown,
110
+ ): Record<string, unknown> => {
110
111
  if (contextEntries.length === 0) {
111
112
  return { path, value }
112
113
  }
@@ -131,9 +132,7 @@ export const expectEqualPrimitiveSExpr = (
131
132
  )
132
133
  }
133
134
 
134
- const getNodeToken = (
135
- value: CanonicalPrimitiveSExpr,
136
- ): string | undefined => {
135
+ const getNodeToken = (value: CanonicalPrimitiveSExpr): string | undefined => {
137
136
  if (!Array.isArray(value)) {
138
137
  return undefined
139
138
  }
@@ -0,0 +1,109 @@
1
+ import { expect, type MatcherResult } from "bun:test"
2
+ import * as fs from "node:fs"
3
+ import * as path from "node:path"
4
+ import looksSame from "looks-same"
5
+
6
+ /**
7
+ * Matcher for PNG snapshot testing.
8
+ *
9
+ * Usage:
10
+ * expect(pngBuffer).toMatchPngSnapshot(import.meta.path, "optionalName");
11
+ */
12
+ async function toMatchPngSnapshot(
13
+ // biome-ignore lint/suspicious/noExplicitAny: bun doesn't expose
14
+ this: any,
15
+ receivedMaybePromise: Buffer | Uint8Array | Promise<Buffer | Uint8Array>,
16
+ testPathOriginal: string,
17
+ pngName?: string,
18
+ ): Promise<MatcherResult> {
19
+ const received = await receivedMaybePromise
20
+ const testPath = testPathOriginal
21
+ .replace(/\.test\.tsx?$/, "")
22
+ .replace(/\.test\.ts$/, "")
23
+ const snapshotDir = path.join(path.dirname(testPath), "__snapshots__")
24
+ const snapshotName = pngName
25
+ ? `${pngName}.snap.png`
26
+ : `${path.basename(testPath)}.snap.png`
27
+ const filePath = path.join(snapshotDir, snapshotName)
28
+
29
+ if (!fs.existsSync(snapshotDir)) {
30
+ fs.mkdirSync(snapshotDir, { recursive: true })
31
+ }
32
+
33
+ const updateSnapshot =
34
+ process.argv.includes("--update-snapshots") ||
35
+ process.argv.includes("-u") ||
36
+ Boolean(process.env["BUN_UPDATE_SNAPSHOTS"])
37
+ const forceUpdate = Boolean(process.env["FORCE_BUN_UPDATE_SNAPSHOTS"])
38
+
39
+ const fileExists = fs.existsSync(filePath)
40
+
41
+ if (!fileExists) {
42
+ console.log("Writing PNG snapshot to", filePath)
43
+ fs.writeFileSync(filePath, received)
44
+ return {
45
+ message: () => `PNG snapshot created at ${filePath}`,
46
+ pass: true,
47
+ }
48
+ }
49
+
50
+ const existingSnapshot = fs.readFileSync(filePath)
51
+
52
+ const result: any = await looksSame(
53
+ Buffer.from(received),
54
+ Buffer.from(existingSnapshot),
55
+ {
56
+ strict: false,
57
+ tolerance: 2,
58
+ },
59
+ )
60
+
61
+ if (updateSnapshot) {
62
+ if (!forceUpdate && result.equal) {
63
+ return {
64
+ message: () => "PNG snapshot matches",
65
+ pass: true,
66
+ }
67
+ }
68
+ console.log("Updating PNG snapshot at", filePath)
69
+ fs.writeFileSync(filePath, received)
70
+ return {
71
+ message: () => `PNG snapshot updated at ${filePath}`,
72
+ pass: true,
73
+ }
74
+ }
75
+
76
+ if (result.equal) {
77
+ return {
78
+ message: () => "PNG snapshot matches",
79
+ pass: true,
80
+ }
81
+ }
82
+
83
+ const diffPath = filePath.replace(/\.snap\.png$/, ".diff.png")
84
+ await looksSame.createDiff({
85
+ reference: Buffer.from(existingSnapshot),
86
+ current: Buffer.from(received),
87
+ diff: diffPath,
88
+ highlightColor: "#ff00ff",
89
+ })
90
+
91
+ return {
92
+ message: () => `PNG snapshot does not match. Diff saved at ${diffPath}`,
93
+ pass: false,
94
+ }
95
+ }
96
+
97
+ // Register the matcher globally for Bun's expect
98
+ expect.extend({
99
+ toMatchPngSnapshot: toMatchPngSnapshot as any,
100
+ })
101
+
102
+ declare module "bun:test" {
103
+ interface Matchers<T = unknown> {
104
+ toMatchPngSnapshot(
105
+ testPath: string,
106
+ pngName?: string,
107
+ ): Promise<MatcherResult>
108
+ }
109
+ }
@@ -0,0 +1 @@
1
+ import "./png-matcher"
@@ -1,4 +1,11 @@
1
- import { FootprintPad, PadLayers, PadNet, PadSize, PadDrill, SxClass } from "lib/sexpr"
1
+ import {
2
+ FootprintPad,
3
+ PadLayers,
4
+ PadNet,
5
+ PadSize,
6
+ PadDrill,
7
+ SxClass,
8
+ } from "lib/sexpr"
2
9
  import { expect, test } from "bun:test"
3
10
 
4
11
  test("FootprintPad", () => {
@@ -1,4 +1,12 @@
1
- import { At, Image, ImageData, ImageScale, Layer, SxClass, Uuid } from "lib/sexpr"
1
+ import {
2
+ At,
3
+ Image,
4
+ ImageData,
5
+ ImageScale,
6
+ Layer,
7
+ SxClass,
8
+ Uuid,
9
+ } from "lib/sexpr"
2
10
  import { expect, test } from "bun:test"
3
11
 
4
12
  test("Image", () => {
@@ -40,9 +40,7 @@ test("KicadSch parse", () => {
40
40
 
41
41
  expect(schematic.version).toBe(20240101)
42
42
  expect(schematic.generator).toBe("kicad-cli")
43
- expect(schematic.uuid?.value).toBe(
44
- "01234567-89ab-cdef-0123-456789abcdef",
45
- )
43
+ expect(schematic.uuid?.value).toBe("01234567-89ab-cdef-0123-456789abcdef")
46
44
 
47
45
  expect(schematic.paper).toBeInstanceOf(Paper)
48
46
  expect(schematic.titleBlock).toBeInstanceOf(TitleBlock)
@@ -186,4 +186,3 @@ test("Setup", () => {
186
186
  )"
187
187
  `)
188
188
  })
189
-
package/bun.lock DELETED
@@ -1,48 +0,0 @@
1
- {
2
- "lockfileVersion": 1,
3
- "workspaces": {
4
- "": {
5
- "name": "kicadts",
6
- "devDependencies": {
7
- "@biomejs/biome": "^2.2.4",
8
- "@types/bun": "latest",
9
- },
10
- "peerDependencies": {
11
- "typescript": "^5",
12
- },
13
- },
14
- },
15
- "packages": {
16
- "@biomejs/biome": ["@biomejs/biome@2.2.4", "", { "optionalDependencies": { "@biomejs/cli-darwin-arm64": "2.2.4", "@biomejs/cli-darwin-x64": "2.2.4", "@biomejs/cli-linux-arm64": "2.2.4", "@biomejs/cli-linux-arm64-musl": "2.2.4", "@biomejs/cli-linux-x64": "2.2.4", "@biomejs/cli-linux-x64-musl": "2.2.4", "@biomejs/cli-win32-arm64": "2.2.4", "@biomejs/cli-win32-x64": "2.2.4" }, "bin": { "biome": "bin/biome" } }, "sha512-TBHU5bUy/Ok6m8c0y3pZiuO/BZoY/OcGxoLlrfQof5s8ISVwbVBdFINPQZyFfKwil8XibYWb7JMwnT8wT4WVPg=="],
17
-
18
- "@biomejs/cli-darwin-arm64": ["@biomejs/cli-darwin-arm64@2.2.4", "", { "os": "darwin", "cpu": "arm64" }, "sha512-RJe2uiyaloN4hne4d2+qVj3d3gFJFbmrr5PYtkkjei1O9c+BjGXgpUPVbi8Pl8syumhzJjFsSIYkcLt2VlVLMA=="],
19
-
20
- "@biomejs/cli-darwin-x64": ["@biomejs/cli-darwin-x64@2.2.4", "", { "os": "darwin", "cpu": "x64" }, "sha512-cFsdB4ePanVWfTnPVaUX+yr8qV8ifxjBKMkZwN7gKb20qXPxd/PmwqUH8mY5wnM9+U0QwM76CxFyBRJhC9tQwg=="],
21
-
22
- "@biomejs/cli-linux-arm64": ["@biomejs/cli-linux-arm64@2.2.4", "", { "os": "linux", "cpu": "arm64" }, "sha512-M/Iz48p4NAzMXOuH+tsn5BvG/Jb07KOMTdSVwJpicmhN309BeEyRyQX+n1XDF0JVSlu28+hiTQ2L4rZPvu7nMw=="],
23
-
24
- "@biomejs/cli-linux-arm64-musl": ["@biomejs/cli-linux-arm64-musl@2.2.4", "", { "os": "linux", "cpu": "arm64" }, "sha512-7TNPkMQEWfjvJDaZRSkDCPT/2r5ESFPKx+TEev+I2BXDGIjfCZk2+b88FOhnJNHtksbOZv8ZWnxrA5gyTYhSsQ=="],
25
-
26
- "@biomejs/cli-linux-x64": ["@biomejs/cli-linux-x64@2.2.4", "", { "os": "linux", "cpu": "x64" }, "sha512-orr3nnf2Dpb2ssl6aihQtvcKtLySLta4E2UcXdp7+RTa7mfJjBgIsbS0B9GC8gVu0hjOu021aU8b3/I1tn+pVQ=="],
27
-
28
- "@biomejs/cli-linux-x64-musl": ["@biomejs/cli-linux-x64-musl@2.2.4", "", { "os": "linux", "cpu": "x64" }, "sha512-m41nFDS0ksXK2gwXL6W6yZTYPMH0LughqbsxInSKetoH6morVj43szqKx79Iudkp8WRT5SxSh7qVb8KCUiewGg=="],
29
-
30
- "@biomejs/cli-win32-arm64": ["@biomejs/cli-win32-arm64@2.2.4", "", { "os": "win32", "cpu": "arm64" }, "sha512-NXnfTeKHDFUWfxAefa57DiGmu9VyKi0cDqFpdI+1hJWQjGJhJutHPX0b5m+eXvTKOaf+brU+P0JrQAZMb5yYaQ=="],
31
-
32
- "@biomejs/cli-win32-x64": ["@biomejs/cli-win32-x64@2.2.4", "", { "os": "win32", "cpu": "x64" }, "sha512-3Y4V4zVRarVh/B/eSHczR4LYoSVyv3Dfuvm3cWs5w/HScccS0+Wt/lHOcDTRYeHjQmMYVC3rIRWqyN2EI52+zg=="],
33
-
34
- "@types/bun": ["@types/bun@1.2.22", "", { "dependencies": { "bun-types": "1.2.22" } }, "sha512-5A/KrKos2ZcN0c6ljRSOa1fYIyCKhZfIVYeuyb4snnvomnpFqC0tTsEkdqNxbAgExV384OETQ//WAjl3XbYqQA=="],
35
-
36
- "@types/node": ["@types/node@24.5.2", "", { "dependencies": { "undici-types": "~7.12.0" } }, "sha512-FYxk1I7wPv3K2XBaoyH2cTnocQEu8AOZ60hPbsyukMPLv5/5qr7V1i8PLHdl6Zf87I+xZXFvPCXYjiTFq+YSDQ=="],
37
-
38
- "@types/react": ["@types/react@19.1.13", "", { "dependencies": { "csstype": "^3.0.2" } }, "sha512-hHkbU/eoO3EG5/MZkuFSKmYqPbSVk5byPFa3e7y/8TybHiLMACgI8seVYlicwk7H5K/rI2px9xrQp/C+AUDTiQ=="],
39
-
40
- "bun-types": ["bun-types@1.2.22", "", { "dependencies": { "@types/node": "*" }, "peerDependencies": { "@types/react": "^19" } }, "sha512-hwaAu8tct/Zn6Zft4U9BsZcXkYomzpHJX28ofvx7k0Zz2HNz54n1n+tDgxoWFGB4PcFvJXJQloPhaV2eP3Q6EA=="],
41
-
42
- "csstype": ["csstype@3.1.3", "", {}, "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw=="],
43
-
44
- "typescript": ["typescript@5.9.2", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-CWBzXQrc/qOkhidw1OzBTQuYRbfyxDXJMVJ1XNwUHGROVmuaeiEm3OslpZ1RV96d7SKKjZKrSJu3+t/xlw3R9A=="],
45
-
46
- "undici-types": ["undici-types@7.12.0", "", {}, "sha512-goOacqME2GYyOZZfb5Lgtu+1IDmAlAEu5xnD3+xTzS10hT0vzpf0SPjkXwAw9Jm+4n/mQGDP3LO8CPbYROeBfQ=="],
47
- }
48
- }