@platforma-open/milaboratories.mixcr-clonotyping-2.workflow 2.0.0

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.
@@ -0,0 +1,576 @@
1
+ maps := import("@platforma-sdk/workflow-tengo:maps")
2
+ ll := import("@platforma-sdk/workflow-tengo:ll")
3
+ text := import("text")
4
+ json := import("json")
5
+
6
+ a := func(order, defaultVisibility, spec) {
7
+ return maps.merge(spec, {
8
+ "pl7.app/table/orderPriority": string(order),
9
+ "pl7.app/table/visibility": defaultVisibility ? "default" : "optional"
10
+ })
11
+ }
12
+
13
+ toCombinedDomainValue := func(spec) {
14
+ result := [spec.name]
15
+
16
+ for domain in maps.getKeys(spec.domain) {
17
+ result = append(result, [domain, spec.domain[domain]])
18
+ }
19
+ return result
20
+ }
21
+
22
+
23
+
24
+
25
+
26
+
27
+
28
+
29
+
30
+
31
+
32
+
33
+
34
+ assemblingFeatureInfo := func(assemblingFeature) {
35
+ productiveFeature := undefined
36
+ coreVFeature := undefined
37
+ coreJFeature := undefined
38
+ if assemblingFeature == "CDR3" || is_undefined(assemblingFeature) {
39
+ productiveFeature = "CDR3"
40
+ } else if assemblingFeature == "VDJRegion" {
41
+ productiveFeature = "VDJRegion(0,-1)"
42
+ coreVFeature = "{FR1Begin:FR3End}"
43
+ coreJFeature = "FR4"
44
+ } else {
45
+ splittedFeature := text.split(assemblingFeature, "_TO_")
46
+ if len(splittedFeature) == 2 {
47
+ if splittedFeature[1] == "FR4" {
48
+ productiveFeature = "{"+splittedFeature[0] + "Begin:FR4End(-1)}"
49
+ coreVFeature = "{"+splittedFeature[0]+"Begin:FR3End}"
50
+ coreJFeature = "FR4"
51
+ } else {
52
+ productiveFeature = assemblingFeature
53
+ coreVFeature = "{"+splittedFeature[0]+"Begin:FR3End}"
54
+ }
55
+ }
56
+ }
57
+ return {
58
+ productiveFeature: productiveFeature,
59
+ coreGeneFeatures: {
60
+ V: coreVFeature,
61
+ J: coreJFeature
62
+ }
63
+ }
64
+ }
65
+
66
+ exportSpecOpsFromPreset := func(presetSpecForBack) {
67
+ assemblingFeature := undefined
68
+ if !is_undefined(presetSpecForBack.assemblingFeature) {
69
+ ll.assert(len(presetSpecForBack.assemblingFeature) == 1, "Don't support disjoint assembling features")
70
+ assemblingFeature = presetSpecForBack.assemblingFeature[0]
71
+ }
72
+
73
+ return {
74
+ assemblingFeature: assemblingFeature,
75
+ splitByC: presetSpecForBack.splitByC,
76
+ hasUmi: !is_undefined(presetSpecForBack.umiTags) && len(presetSpecForBack.umiTags) > 0,
77
+ cellTags: presetSpecForBack.cellTags
78
+ }
79
+ }
80
+
81
+ calculateExportSpecs := func(presetSpecForBack, blockId) {
82
+ ops := exportSpecOpsFromPreset(presetSpecForBack)
83
+
84
+ assemblingFeature := ops.assemblingFeature
85
+ cellTags := ops.cellTags
86
+ hasUmi := ops.hasUmi
87
+ splitByC := ops.splitByC
88
+
89
+ assemblingFeatureInfo := assemblingFeatureInfo(assemblingFeature)
90
+ productiveFeature := assemblingFeatureInfo.productiveFeature
91
+ coreGeneFeatures := assemblingFeatureInfo.coreGeneFeatures
92
+
93
+ clonotypeKeyColumns := undefined
94
+ if !is_undefined(assemblingFeature) && (is_undefined(cellTags) || len(cellTags) == 0) {
95
+ clonotypeKeyColumns = ["nSeq" + assemblingFeature, "bestVGene", "bestJGene"]
96
+ if splitByC {
97
+ clonotypeKeyColumns += ["bestCGene"]
98
+ }
99
+ }
100
+
101
+ columnsSpecPerSample := []
102
+ columnsSpecPerClonotype := []
103
+
104
+
105
+ exportArgs := []
106
+
107
+
108
+
109
+ columnsSpecPerSample += [ {
110
+ column: "readCount",
111
+ id: "read-count",
112
+ allowNA: false,
113
+ spec: {
114
+ name: "pl7.app/vdj/readCount",
115
+ valueType: "Long",
116
+ annotations: a(90000, !hasUmi, {
117
+ "pl7.app/min": "1",
118
+ "pl7.app/isAbundance": "true",
119
+ "pl7.app/abundance/unit": "reads",
120
+ "pl7.app/abundance/normalized": "false",
121
+ "pl7.app/label": "Number Of Reads"
122
+ })
123
+ }
124
+ }, {
125
+ column: "readFraction",
126
+ id: "read-fraction",
127
+ allowNA: false,
128
+ spec: {
129
+ name: "pl7.app/vdj/readFraction",
130
+ valueType: "Double",
131
+ annotations: a(89000, !hasUmi, {
132
+ "pl7.app/min": "0",
133
+ "pl7.app/max": "1",
134
+ "pl7.app/isAbundance": "true",
135
+ "pl7.app/abundance/unit": "reads",
136
+ "pl7.app/abundance/normalized": "true",
137
+ "pl7.app/label": "Fraction of reads"
138
+ })
139
+ }
140
+ } ]
141
+ exportArgs += [
142
+ [ "-readCount" ],
143
+ [ "-readFraction" ]
144
+ ]
145
+ mainAbundanceColumn := "readFraction"
146
+
147
+ if hasUmi {
148
+ columnsSpecPerSample += [ {
149
+ column: "uniqueMoleculeCount",
150
+ id: "umi-count",
151
+ allowNA: false,
152
+ spec: {
153
+ name: "pl7.app/vdj/uniqueMoleculeCount",
154
+ valueType: "Long",
155
+ annotations: a(88000, true, {
156
+ "pl7.app/min": "1",
157
+ "pl7.app/isAbundance": "true",
158
+ "pl7.app/abundance/unit": "molecules",
159
+ "pl7.app/abundance/normalized": "false",
160
+ "pl7.app/label": "Number of UMI"
161
+ })
162
+ }
163
+ }, {
164
+ column: "uniqueMoleculeFraction",
165
+ id: "umi-fraction",
166
+ allowNA: false,
167
+ spec: {
168
+ name: "pl7.app/vdj/uniqueMoleculeFraction",
169
+ valueType: "Double",
170
+ annotations: a(87500, true, {
171
+ "pl7.app/min": "0",
172
+ "pl7.app/max": "1",
173
+ "pl7.app/isAbundance": "true",
174
+ "pl7.app/abundance/unit": "molecules",
175
+ "pl7.app/abundance/normalized": "true",
176
+ "pl7.app/label": "Fraction of UMI"
177
+ })
178
+ }
179
+ } ]
180
+ exportArgs += [
181
+ [ "-uniqueTagCount", "Molecule" ],
182
+ [ "-uniqueTagFraction", "Molecule" ]
183
+ ]
184
+ mainAbundanceColumn = "uniqueMoleculeFraction"
185
+ }
186
+
187
+
188
+
189
+ orderP := 80000
190
+ geneHitColumnVariants := [ {
191
+ name: "pl7.app/vdj/geneHitWithAllele",
192
+ columnNameSuffix: "Hit",
193
+ idSuffix: "-hit-with-allele",
194
+ labelSuffix: " hit with allele",
195
+ argSuffix: "Hit",
196
+ visible: false
197
+ }, {
198
+ name: "pl7.app/vdj/geneHit",
199
+ columnNameSuffix: "Gene",
200
+ idSuffix: "-gene",
201
+ labelSuffix: " gene",
202
+ argSuffix: "Gene",
203
+ visible: true
204
+ } ]
205
+ for vdjcU in ["V", "D", "J", "C"] {
206
+ vdjcL := text.to_lower(vdjcU)
207
+ for variant in geneHitColumnVariants {
208
+ columnsSpecPerClonotype += [ {
209
+ column: "best" + vdjcU + variant.columnNameSuffix,
210
+ id: "best-" + vdjcL + variant.idSuffix,
211
+ naRegex: "",
212
+ allowNA: vdjcU == "C" || vdjcU == "D",
213
+ spec: {
214
+ name: variant.name,
215
+ valueType: "String",
216
+ domain: {
217
+ "pl7.app/vdj/reference": vdjcU + "Gene"
218
+ },
219
+ annotations: a(orderP, variant.visible, {
220
+ "pl7.app/label": "Best " + vdjcU + variant.labelSuffix,
221
+ "pl7.app/isDiscreteFilter": "true"
222
+ })
223
+ }
224
+ } ]
225
+ exportArgs += [ [ "-" + vdjcL + variant.argSuffix ] ]
226
+ orderP -= 100
227
+ }
228
+ }
229
+
230
+
231
+
232
+ features := undefined
233
+ if is_undefined(assemblingFeature) {
234
+ features = ["CDR1", "FR1", "FR2", "CDR2", "FR3", "CDR3", "FR4"]
235
+ } else if assemblingFeature != "CDR3" {
236
+ features = [assemblingFeature, "CDR1", "FR1", "FR2", "CDR2", "FR3", "CDR3", "FR4"]
237
+ } else {
238
+ features = ["CDR3"]
239
+ }
240
+
241
+ for isImputed in ( is_undefined(assemblingFeature) ? [false, true] : [false] ) {
242
+ imputedU := isImputed ? "Imputed" : ""
243
+ imputedL := text.to_lower(imputedU)
244
+ for featureU in features {
245
+ featureL := text.to_lower(featureU)
246
+ for isAminoAcid in [false, true] {
247
+ alphabet := isAminoAcid ? "aminoacid" : "nucleotide"
248
+ alphabetShort := isAminoAcid ? "aa" : "nt"
249
+ alphabetShortMixcr := isAminoAcid ? "aa" : "n"
250
+ visibility := (featureU == "CDR3") || (featureU == assemblingFeature)
251
+ columnsSpecPerClonotype += [ {
252
+ column: alphabetShortMixcr + "Seq" + imputedU + featureU,
253
+ id: alphabetShortMixcr + "-seq-" + featureL + (isImputed ? "-imputed" : ""),
254
+ naRegex: "region_not_covered",
255
+ spec: {
256
+ name: "pl7.app/vdj/sequence",
257
+ valueType: "String",
258
+ domain: {
259
+ "pl7.app/vdj/feature": featureU,
260
+ "pl7.app/alphabet": alphabet
261
+ },
262
+ annotations: a(orderP, visibility, {
263
+ "pl7.app/vdj/imputed": string(isImputed),
264
+ "pl7.app/label": featureU + " " + alphabetShort
265
+ })
266
+ }
267
+ } ]
268
+ exportArgs += [ [ "-" + alphabetShortMixcr + "Feature" + imputedU, featureU ] ]
269
+ if !isImputed && !isAminoAcid {
270
+ columnsSpecPerSample += [ {
271
+ column: "minQual" + featureU,
272
+ id: "min-qual-" + featureL,
273
+ naRegex: "region_not_covered",
274
+ spec: {
275
+ name: "pl7.app/vdj/sequenceQuality",
276
+ valueType: "Int",
277
+ domain: {
278
+ "pl7.app/vdj/quality": "minQuality",
279
+ "pl7.app/vdj/feature": featureU
280
+ },
281
+ annotations: a(orderP - 10, false, {
282
+ "pl7.app/min": "0",
283
+ "pl7.app/max": "60",
284
+ "pl7.app/label": "Min quality " + featureU
285
+ })
286
+ }
287
+ } ]
288
+ exportArgs += [ [ "-minFeatureQuality", featureU ] ]
289
+ }
290
+ orderP -= 100
291
+ }
292
+ }
293
+ }
294
+
295
+
296
+
297
+ orderP = 10000
298
+
299
+ mutationColumnVariants := [ {
300
+ name: "Mutations",
301
+ valueType: "String",
302
+ labelPart: " mutations in ",
303
+ idPart: "-mutations-"
304
+ }, {
305
+ name: "MutationsCount",
306
+ valueType: "Int",
307
+ labelPart: " mutations count in ",
308
+ idPart: "-mutations-count-"
309
+ }, {
310
+ name: "MutationsRate",
311
+ valueType: "Double",
312
+ labelPart: " mutations rate in ",
313
+ idPart: "-mutations-rate-"
314
+ } ]
315
+
316
+ for isAminoAcid in [false, true] {
317
+ alphabetShort := isAminoAcid ? "AA" : "Nt"
318
+ alphabetShortMixcr := isAminoAcid ? "aa" : "n"
319
+
320
+
321
+ for geneU in ["V", "J"] {
322
+ geneL := text.to_lower(geneU)
323
+
324
+ coreFeature := coreGeneFeatures[geneU]
325
+ if is_undefined(coreFeature) {
326
+ continue
327
+ }
328
+
329
+ for variant in mutationColumnVariants {
330
+ columnsSpecPerClonotype += [ {
331
+ column: alphabetShortMixcr + variant.name + coreFeature,
332
+ id: alphabetShortMixcr + variant.idPart + geneL,
333
+ allowNA: true,
334
+ naRegex: "region_not_covered",
335
+ spec: {
336
+ valueType: variant.valueType,
337
+ name: "pl7.app/vdj/sequence/" + alphabetShortMixcr + variant.name,
338
+ annotations: a(orderP, false, {
339
+ "pl7.app/label": alphabetShort + variant.labelPart + geneU + " gene"
340
+ })
341
+ }
342
+ } ]
343
+ exportArgs += [ [ "-" + alphabetShortMixcr + variant.name, coreFeature ] ]
344
+ orderP -= 100
345
+ }
346
+ }
347
+ }
348
+
349
+
350
+
351
+ flagColumnVariants := [ {
352
+ columnPrefix: "isProductive",
353
+ arg: "-isProductive",
354
+ specName: "pl7.app/vdj/sequence/productive",
355
+ label: "Productive",
356
+ id: "is-productive",
357
+ visibility: true
358
+ }, {
359
+ columnPrefix: "isOOF",
360
+ arg: "-isOOF",
361
+ specName: "pl7.app/vdj/sequence/containsOOF",
362
+ label: "Contains OOF",
363
+ id: "is-oof",
364
+ visibility: false
365
+ }, {
366
+ columnPrefix: "hasStopsIn",
367
+ arg: "-hasStops",
368
+ specName: "pl7.app/vdj/sequence/containsStopCodons",
369
+ label: "Contains stop codons",
370
+ id: "has-stops",
371
+ visibility: false
372
+ } ]
373
+ for variant in flagColumnVariants {
374
+ columnsSpecPerClonotype += [ {
375
+ column: variant.columnPrefix + productiveFeature,
376
+ id: variant.id,
377
+ allowNA: false,
378
+ spec: {
379
+ valueType: "String",
380
+ name: variant.specName,
381
+ annotations: a(orderP, variant.visibility, {
382
+ "pl7.app/label": variant.label,
383
+ "pl7.app/isDiscreteFilter": "true",
384
+ "pl7.app/discreteValues": "['true','false']" } )
385
+ }
386
+ } ]
387
+ exportArgs += [ [ variant.arg, productiveFeature ] ]
388
+ orderP -= 100
389
+ }
390
+
391
+
392
+
393
+ geneRegions := ["VRegion", "DRegion", "JRegion"]
394
+
395
+ for region in geneRegions {
396
+ columnsSpecPerClonotype += [ {
397
+ column: "nSeq" + region + "OfGermline",
398
+ naRegex: "",
399
+ allowNA: true,
400
+ id: "n-seq-" + text.to_lower(region) + "-germline",
401
+ spec: {
402
+ name: "pl7.app/vdj/germlineSequence",
403
+ valueType: "String",
404
+ domain: {
405
+ "pl7.app/vdj/feature": region,
406
+ "pl7.app/alphabet": "nucleotide"
407
+ },
408
+ annotations: a(orderP, false, {
409
+ "pl7.app/label": region[0:1] + " germline"
410
+ })
411
+ }
412
+ } ]
413
+ exportArgs += [ [ "-nFeature", region, "germline" ] ]
414
+ orderP -= 100
415
+ }
416
+
417
+
418
+
419
+ columnsSpecPerClonotype += [ {
420
+ column: "isotypePrimary",
421
+ id: "isotype",
422
+ naRegex: "",
423
+ spec: {
424
+ valueType: "String",
425
+ name: "pl7.app/vdj/isotype",
426
+ annotations: a(orderP, true, {
427
+ "pl7.app/label": "IG isotype",
428
+ "pl7.app/isDiscreteFilter": "true"
429
+ })
430
+ }
431
+ }, {
432
+ column: "topChains",
433
+ id: "top-chains",
434
+ naRegex: "",
435
+ allowNA: false,
436
+ spec: {
437
+ valueType: "String",
438
+ name: "pl7.app/vdj/chain",
439
+ annotations: a(orderP, true, {
440
+ "pl7.app/label": "Chain",
441
+ "pl7.app/isDiscreteFilter": "true",
442
+ "pl7.app/discreteValues": "['TRA','TRB','TRG','TRD','IGH','IGK','IGL']"
443
+ })
444
+ }
445
+ } ]
446
+ exportArgs += [
447
+ [ "-isotype", "primary" ],
448
+ [ "-topChains" ]
449
+ ]
450
+
451
+
452
+
453
+ columnsSpec := columnsSpecPerSample + columnsSpecPerClonotype
454
+
455
+
456
+ columnsByName := {}
457
+ for columnSpec in columnsSpec {
458
+ columnsByName[columnSpec.column] = columnSpec
459
+ }
460
+
461
+
462
+
463
+ axesByClonotypeKey := undefined
464
+
465
+ if !is_undefined(clonotypeKeyColumns) {
466
+ ll.assert(is_undefined(cellTags) || len(cellTags) == 0, "cellTags and clonotypeKeyAxes cannot both be defined")
467
+
468
+
469
+ keyStrincture := []
470
+ for keyColumn in clonotypeKeyColumns {
471
+ columnSpec := columnsByName[keyColumn]
472
+ if is_undefined(columnSpec) {
473
+ ll.panic("column " + keyColumn + " does not exist in export")
474
+ }
475
+ keyStrincture += [ toCombinedDomainValue(columnSpec.spec) ]
476
+ }
477
+
478
+ axesByClonotypeKey = [ {
479
+ column: "clonotypeKey",
480
+ naRegex: "",
481
+ spec: {
482
+ name: "pl7.app/vdj/clonotypeKey",
483
+ type: "String",
484
+ domain: {
485
+ "pl7.app/vdj/clonotypeKey/structure": string(json.encode(keyStrincture))
486
+ },
487
+ annotations: {
488
+ "pl7.app/label": "Clonotype key",
489
+ "pl7.app/table/visibility": "optional",
490
+ "pl7.app/table/orderPriority": "110000"
491
+ }
492
+ }
493
+ } ]
494
+ }
495
+
496
+ axesByClonotypeId := [ {
497
+ column: "cloneId",
498
+ spec: {
499
+ name: "pl7.app/vdj/cloneId",
500
+ type: "Long",
501
+ domain: {
502
+ "pl7.app/blockId": blockId
503
+ },
504
+ annotations: {
505
+ "pl7.app/min": "0",
506
+ "pl7.app/label": "Clone id",
507
+ "pl7.app/table/visibility": "optional",
508
+ "pl7.app/table/orderPriority": "90000"
509
+ }
510
+ }
511
+ } ]
512
+ exportArgs += [ [ "-cloneId" ] ]
513
+
514
+ orderP = 100000
515
+ if !is_undefined(cellTags) && len(cellTags) > 0 {
516
+ for tag in cellTags {
517
+ label := undefined
518
+ if tag == "CELL" {
519
+ label = "Cell tag"
520
+ } else {
521
+ label = text.to_title(tag[:4]) + " " + text.to_lower(tag[4:])
522
+ }
523
+ axesByClonotypeId += [ {
524
+ column: "tagValue" + tag,
525
+ naRegex: "",
526
+ spec: {
527
+ name: "pl7.app/vdj/cellTag",
528
+ type: "String",
529
+ domain: {
530
+ "pl7.app/vdj/cellTagId": tag,
531
+ "pl7.app/blockId": blockId
532
+ },
533
+ annotations: a(orderP, true, {
534
+ "pl7.app/label": label
535
+ })
536
+ }
537
+ } ]
538
+ orderP -= 1
539
+ }
540
+ exportArgs += [ [ "-tags", "Cell" ] ]
541
+
542
+ columnsSpec += [ {
543
+ column: "cellGroup",
544
+ id: "cell-group",
545
+ naRegex: "undefined|contamination",
546
+ allowNA: true,
547
+ spec: {
548
+ name: "pl7.app/vdj/cellGroup",
549
+ valueType: "Long",
550
+ annotations: a(24000, true, {
551
+ "pl7.app/min": "0",
552
+ "pl7.app/label": "Cell group number"
553
+ } )
554
+ }
555
+ } ]
556
+ exportArgs += [ [ "-cellGroup" ] ]
557
+ }
558
+
559
+ return {
560
+ clonotypeKeyColumns: clonotypeKeyColumns,
561
+
562
+ axesByClonotypeId: axesByClonotypeId,
563
+ axesByClonotypeKey: axesByClonotypeKey,
564
+
565
+ columnsSpecPerSample: columnsSpecPerSample,
566
+ columnsSpecPerClonotype: columnsSpecPerClonotype,
567
+
568
+ columnsSpec: columnsSpec,
569
+
570
+ mainAbundanceColumn: mainAbundanceColumn,
571
+
572
+ exportArgs: exportArgs
573
+ }
574
+ }
575
+
576
+ export calculateExportSpecs
Binary file
Binary file
Binary file
package/format.el ADDED
@@ -0,0 +1,43 @@
1
+ ;; This program formats all files inside src directory. Usage: emacs --script ./format.el
2
+
3
+ (defun install-go-mode ()
4
+ "Installs go-mode"
5
+ (require 'package)
6
+ (add-to-list 'package-archives
7
+ '("melpa-stable" . "https://stable.melpa.org/packages/"))
8
+ (package-initialize)
9
+ (unless package-archive-contents
10
+ (package-refresh-contents))
11
+
12
+ (package-install 'go-mode t)
13
+ (require 'go-mode))
14
+
15
+ ;; spaces -> tabs only at the beginning of lines
16
+ (setq tabify-regexp "^\t* [ \t]+")
17
+
18
+ (defun format-file (file)
19
+ "Formats a file according to slightly changed Go rules"
20
+ (message "Format %s" file)
21
+ (save-excursion
22
+ (find-file file)
23
+ (delete-trailing-whitespace) ;; deletes whitespaces
24
+ (go-mode) ;; sets golang rules for indentation
25
+ (tabify (point-min) (point-max)) ;; spaces -> tabs in the whole file
26
+ (indent-region (point-min) (point-max)) ;; indentation in the whole file
27
+ (save-buffer))) ;; save file
28
+
29
+ (install-go-mode)
30
+
31
+ ;; change syntax of a standard go-mode a bit
32
+ (advice-add
33
+ 'go--in-composite-literal-p
34
+ :filter-return
35
+ (lambda (&rest r) t))
36
+
37
+ ;; find all files in src
38
+ (setq files (directory-files-recursively "src" "\\.tengo\\'"))
39
+
40
+ ;; call format on every file.
41
+ (dolist (file files)
42
+ (format-file file))
43
+
package/package.json ADDED
@@ -0,0 +1,21 @@
1
+ {
2
+ "name": "@platforma-open/milaboratories.mixcr-clonotyping-2.workflow",
3
+ "version": "2.0.0",
4
+ "description": "Tengo-based template",
5
+ "devDependencies": {
6
+ "@platforma-sdk/tengo-builder": "^1.19.2",
7
+ "@platforma-sdk/workflow-tengo": "^2.15.4",
8
+ "@milaboratories/software-pframes-conv": "2.1.16",
9
+ "@platforma-open/milaboratories.software-small-binaries": "^1.15.11",
10
+ "@platforma-open/milaboratories.software-mixcr": "4.7.0-147-develop",
11
+ "@platforma-open/milaboratories.software-ptransform": "^1.3.1",
12
+ "@platforma-sdk/test": "^1.22.84",
13
+ "vitest": "^2.1.8",
14
+ "typescript": "~5.5.4"
15
+ },
16
+ "scripts": {
17
+ "build": "rm -rf dist && pl-tengo check && pl-tengo build",
18
+ "format": "/usr/bin/env emacs --script ./format.el",
19
+ "test": "vitest"
20
+ }
21
+ }
@@ -0,0 +1,4 @@
1
+ import fs from 'node:fs/promises'
2
+
3
+ const jsonContent = await fs.readFile(process.argv[2])
4
+ fs.writeFile(process.argv[3], "export " + jsonContent)