@platforma-sdk/workflow-tengo 1.6.4 → 1.7.1

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.
@@ -34,6 +34,7 @@ importSoftware := func(swName) {
34
34
  sw := plapi.getSoftwareInfo(swName)
35
35
  return ll.toStrict({
36
36
  _type: "softwareDescriptor",
37
+ _id: sw.id,
37
38
 
38
39
  resource: smart.resource(sw.id),
39
40
  name: sw.name,
@@ -43,6 +44,21 @@ importSoftware := func(swName) {
43
44
  })
44
45
  }
45
46
 
47
+ /**
48
+ * Checks if given item is a software info
49
+ *
50
+ * @param toCheck: any - item to check
51
+ *
52
+ * @return isSoftware: boolean - true for software info, loaded with importSoftware()
53
+ */
54
+ isSoftwareInfo := func(toCheck) {
55
+ if !ll.isMap(toCheck) {
56
+ return false
57
+ }
58
+
59
+ return (ll.fromStrict(toCheck)._type) == "softwareDescriptor"
60
+ }
61
+
46
62
  /**
47
63
  * Returns asset description suitable for 'exec' operations.
48
64
  * Asset is the way to put static files into working directory before execution of the command.
@@ -53,10 +69,11 @@ importSoftware := func(swName) {
53
69
  * - "@company/package:some-oftware"
54
70
  * - ":my-local-software"
55
71
  */
56
- importAsset := func(swName) {
57
- asset := plapi.getSoftwareInfo(swName)
72
+ importAsset := func(assetName) {
73
+ asset := plapi.getSoftwareInfo(assetName)
58
74
  return ll.toStrict({
59
75
  _type: "assetDescriptor",
76
+ _id: asset.id,
60
77
 
61
78
  resource: smart.resource(asset.id),
62
79
  name: asset.name,
@@ -67,22 +84,24 @@ importAsset := func(swName) {
67
84
  }
68
85
 
69
86
  /**
70
- * Checks if given item is a software info
87
+ * Checks if given item is an asset info
71
88
  *
72
89
  * @param toCheck: any - item to check
73
90
  *
74
- * @return isSoftware: boolean - true for software info, loaded with importSoftware()
91
+ * @return isAsset: boolean - true for asset info, loaded with importAsset()
75
92
  */
76
- isSoftwareInfo := func(toCheck) {
93
+ isAssetInfo := func(toCheck) {
77
94
  if !ll.isMap(toCheck) {
78
95
  return false
79
96
  }
80
97
 
81
- return (ll.fromStrict(toCheck)._type) == "softwareDescriptor"
98
+ return (ll.fromStrict(toCheck)._type) == "assetDescriptor"
82
99
  }
83
100
 
84
101
  export ll.toStrict({
85
102
  importTemplate: importTemplate,
86
103
  importSoftware: importSoftware,
87
- isSoftwareInfo: isSoftwareInfo
104
+ isSoftwareInfo: isSoftwareInfo,
105
+ importAsset: importAsset,
106
+ isAssetInfo: isAssetInfo
88
107
  })
@@ -65,6 +65,11 @@ builder := func() {
65
65
  // workdir processors
66
66
  wdProcessors := {}
67
67
 
68
+ // assets to be extracted into workdir before execution
69
+ assetInfos := {}
70
+ assetDescriptors := {}
71
+ assetRules := {}
72
+
68
73
  stdout := "stdout.txt"
69
74
  stderr := "stderr.txt"
70
75
 
@@ -94,7 +99,7 @@ builder := func() {
94
99
  software: func(sw) {
95
100
  assertEntrypointNotSet()
96
101
  ll.assert(assets.isSoftwareInfo(sw),
97
- "exec.builder.software: <software> parameter is not a software info. Provide the value obtained from ll.importSoftware() here"
102
+ "exec.builder.software: <software> parameter is not a software info. Provide the value obtained from assets.importSoftware() here"
98
103
  )
99
104
 
100
105
  soft = sw
@@ -247,7 +252,7 @@ builder := func() {
247
252
  cache = days * times.hour * 24
248
253
  return self
249
254
  },
250
-
255
+
251
256
  /**
252
257
  * Sets the number of error lines to be captured.
253
258
  *
@@ -262,6 +267,39 @@ builder := func() {
262
267
  return self
263
268
  },
264
269
 
270
+ /**
271
+ * Adds files from given asset into working directory.
272
+ *
273
+ * @param asset: assetInfo - asset info loaded from assets.importAsset()
274
+ * @param destinationDir: string - path inside working directory for files to be extracted from archive.
275
+ * @param pathsInArchive: string[] - paths inside the archive to be extracted into destinationDir.
276
+ * Empty array means 'unpack full archive'.
277
+ */
278
+ addAsset: func(asset, destinationDir, pathsInArchive) {
279
+ ll.assert(assets.isAssetInfo(asset),
280
+ "exec.builder.addAsset: <asset> parameter is not an asset info. Provide the value obtained from assets.importAsset() here")
281
+
282
+ validation.assertJsonSchema(destinationDir, "string", "exec.builder.addAsset: <destinationDir> is not a string. It must be a path inside working directory")
283
+ validation.assertJsonSchema(pathsInArchive, ["string"], "exec.builder.addAsset: <pathsInArchive> must contain list of strings with paths inside archive")
284
+
285
+ assetIdKey := ll.idToString(asset._id)
286
+ if is_undefined(assetRules[assetIdKey]) {
287
+ assetInfos[assetIdKey] = asset.resource
288
+ assetDescriptors[assetIdKey] = ll.fromStrict(asset.descriptor)
289
+ assetRules[assetIdKey] = {}
290
+ }
291
+
292
+ if len(pathsInArchive) == 0 || is_undefined(assetRules[assetIdKey][destinationDir]) {
293
+ assetRules[assetIdKey][destinationDir] = []
294
+ }
295
+
296
+ if (len(pathsInArchive) > 0) {
297
+ assetRules[assetIdKey][destinationDir] = append(assetRules[assetIdKey][destinationDir], pathsInArchive...)
298
+ }
299
+
300
+ return self
301
+ },
302
+
265
303
  /**
266
304
  * Adds a file to the working directory
267
305
  *
@@ -474,7 +512,10 @@ builder := func() {
474
512
  fileSetsToSave: fileSetsToSave,
475
513
  fileSetsContentToSave: fileSetsContentToSave,
476
514
  filesToStream: slices.fromSet(filesToStream),
477
- wdProcessors: wdProcessors
515
+ wdProcessors: wdProcessors,
516
+ assets: smart.createMapResource(assetInfos),
517
+ assetDescriptors: assetDescriptors,
518
+ assetRules: assetRules
478
519
  }
479
520
 
480
521
  if !is_undefined(soft) {
@@ -24,6 +24,12 @@ binaryDescriptorScheme := {
24
24
  runEnv: runEnvDescriptorScheme
25
25
  }
26
26
 
27
+ assetDescriptorScheme := {
28
+ registry: "string",
29
+ package: "string",
30
+ url: "string"
31
+ }
32
+
27
33
  localDescriptorScheme := {
28
34
  hash: "string",
29
35
  path: "string",
@@ -34,6 +40,7 @@ localDescriptorScheme := {
34
40
  descriptorScheme := {
35
41
  "isDev,omitempty": "bool",
36
42
  "binary,omitempty": binaryDescriptorScheme,
43
+ "asset,omitempty": assetDescriptorScheme,
37
44
  "local,omitempty": localDescriptorScheme
38
45
  }
39
46
 
@@ -135,22 +142,21 @@ use := func(settings) {
135
142
  return self
136
143
  }
137
144
 
138
-
139
145
  _RTYPE_PACKAGE_PUT_ASSET := { Name: "PackagePutAssetsToWD", Version: "1" }
140
146
  _INPUT_PACKAGE := "package"
141
147
  _INPUT_WD_IN := "workdirIn"
142
148
  _OUTPUT_WD_OUT := "workdirOut"
143
149
 
144
150
  /**
145
- * Load assets and provide them as files or raw content.
151
+ * Load static files from software package into working directory or value resources.
146
152
  *
147
153
  * @param pkg: package - installed package (usually from package.install(<archive>))
148
154
  *
149
- * @return loader: assetsLoader - builder that provides requested files and content after load() call
155
+ * @return loader: pkgFileLoader - builder that provides requested files and content after load() call
150
156
  */
151
- assetsLoader := func(pkg) {
157
+ pkgFileLoader := func(pkg) {
152
158
  ll.assert(smart.isReference(pkg),
153
- "package.loadAssets: <pkg> must be reference to installed package. It is usually obtained from package.install().package")
159
+ "package.pkgFileLoader: <pkg> must be reference to installed package. It is usually obtained from package.install().package")
154
160
 
155
161
  filePaths := []
156
162
  fileContentPaths := []
@@ -159,14 +165,14 @@ assetsLoader := func(pkg) {
159
165
 
160
166
  self = {
161
167
  saveFile: func(path) {
162
- validation.assertJsonSchema(path, "string", "package.assetsLoader.saveFile: <path> must be string")
168
+ validation.assertJsonSchema(path, "string", "package.pkgFileLoader.saveFile: <path> must be string")
163
169
  filePaths = append(filePaths, path)
164
170
 
165
171
  return self
166
172
  },
167
173
 
168
174
  saveFiles: func(paths) {
169
- validation.assertJsonSchema(paths, ["string"], "package.assetsLoader.saveFiles: <paths> must be array of strings")
175
+ validation.assertJsonSchema(paths, ["string"], "package.pkgFileLoader.saveFiles: <paths> must be array of strings")
170
176
  for p in paths {
171
177
  self.saveFile(p)
172
178
  }
@@ -175,13 +181,13 @@ assetsLoader := func(pkg) {
175
181
  },
176
182
 
177
183
  saveFileContent: func(path) {
178
- validation.assertJsonSchema(path, "string", "package.assetsLoader.saveFileContent: <path> must be string")
184
+ validation.assertJsonSchema(path, "string", "package.pkgFileLoader.saveFileContent: <path> must be string")
179
185
  fileContentPaths = append(fileContentPaths, path)
180
186
  return self
181
187
  },
182
188
 
183
189
  saveFilesContent: func(paths) {
184
- validation.assertJsonSchema(paths, ["string"], "package.assetsLoader.saveFilesContent: <paths> must be array of strings")
190
+ validation.assertJsonSchema(paths, ["string"], "package.pkgFileLoader.saveFilesContent: <paths> must be array of strings")
185
191
  for p in paths {
186
192
  self.saveFileContent(p)
187
193
  }
@@ -252,8 +258,8 @@ assetsLoader := func(pkg) {
252
258
  *
253
259
  * @return file: singleAsset - object with reference to the file resource and progress report
254
260
  */
255
- loadAssetFile := func(pkg, assetPath) {
256
- assets := assetsLoader(pkg).
261
+ loadPackageFile := func(pkg, assetPath) {
262
+ assets := pkgFileLoader(pkg).
257
263
  saveFile(assetPath).
258
264
  load()
259
265
 
@@ -271,8 +277,8 @@ loadAssetFile := func(pkg, assetPath) {
271
277
  *
272
278
  * @return content: singleAsset - object with reference to the asset content resource and progress report
273
279
  */
274
- loadAssetContent := func(pkg, assetPath) {
275
- assets := assetsLoader(pkg).
280
+ loadPackageFileContent := func(pkg, assetPath) {
281
+ assets := pkgFileLoader(pkg).
276
282
  saveFileContent(assetPath).
277
283
  load()
278
284
 
@@ -285,6 +291,8 @@ loadAssetContent := func(pkg, assetPath) {
285
291
 
286
292
  export ll.toStrict({
287
293
  softwareInfoScheme : softwareInfoScheme,
294
+ descriptorScheme : descriptorScheme,
295
+ assetDescriptorScheme : assetDescriptorScheme,
288
296
  binaryDescriptorScheme : binaryDescriptorScheme,
289
297
  localDescriptorScheme : localDescriptorScheme,
290
298
  runEnvDescriptorScheme : runEnvDescriptorScheme,
@@ -294,7 +302,7 @@ export ll.toStrict({
294
302
  get : get,
295
303
  install : install,
296
304
  use : use,
297
- assetsLoader : assetsLoader,
298
- loadAssetFile : loadAssetFile,
299
- loadAssetContent : loadAssetContent
305
+ pkgFileLoader : pkgFileLoader,
306
+ loadPackageFile : loadPackageFile,
307
+ loadPackageFileContent : loadPackageFileContent
300
308
  })
@@ -113,7 +113,7 @@ loadDependencyFiles := func(packageRef, dependencies) {
113
113
 
114
114
  depsMap := {}
115
115
 
116
- loader := pkg.assetsLoader(packageRef)
116
+ loader := pkg.pkgFileLoader(packageRef)
117
117
  for depName, fileName in dependencies {
118
118
  loader.saveFile(fileName)
119
119
  }
@@ -0,0 +1,101 @@
1
+ /**
2
+ * Internal functions to work with blobs.
3
+ *
4
+ * This file contains functions intended for use only within workflow tengo SDK.
5
+ * Their parameters list, results and other parts of the interface may change from
6
+ * vertion to version without prior notice.
7
+ *
8
+ * DO NOT USE them in workflows or templates directly.
9
+ *
10
+ */
11
+
12
+ ll := import("@platforma-sdk/workflow-tengo:ll")
13
+ smart := import("@platforma-sdk/workflow-tengo:smart")
14
+ validation := import("@platforma-sdk/workflow-tengo:validation")
15
+
16
+ json := import("json")
17
+
18
+ //
19
+ // Resource types:
20
+ //
21
+
22
+ RTYPE_BLOB_IMPORT_EXTERNAL := { Name: "BlobImportExternal", Version: "1" }
23
+ RTYPE_BLOB_IMPORT_INTERNAL := { Name: "BlobImportInternal", Version: "1" }
24
+ RTYPE_BLOB_UPLOAD := { Name: "BlobUpload", Version: "1" }
25
+ RTYPE_BLOB_DOWNLOAD := { Name: "BlobDownload", Version: "2" }
26
+
27
+ /**
28
+ * Create a task for importing blob from external service by URL
29
+ * Produces 2 fields:
30
+ * 'file' - reference to blob to appear once import gets finished.
31
+ * 'handle' - reference to resource with 'progress' API.
32
+ */
33
+ createBlobImportExternal := func(settings) {
34
+ validation.assertJsonSchema(settings, {
35
+ "dataURL": "string",
36
+ "dataHeaders,omitempty": [["string"]],
37
+ "dataVersion,omitempty": "string"
38
+ })
39
+
40
+ importer := smart.structBuilder(RTYPE_BLOB_IMPORT_EXTERNAL, json.encode(settings)).lockAndBuild()
41
+
42
+ return ll.toStrict({
43
+ file: importer.getField("blob"),
44
+ handle: importer.getField("handle")
45
+ })
46
+ }
47
+
48
+ /**
49
+ * Create a task for importing blob from one of preconfigured storages.
50
+ * Produces 2 fields:
51
+ * 'file' - reference to blob to appear once import gets finished.
52
+ * 'handle' - reference to resource with 'progress' API.
53
+ */
54
+ createBlobImportInternal := func(settings) {
55
+ validation.assertJsonSchema(settings, {
56
+ "path": "string",
57
+ "storageId": "string",
58
+ "maxSpeedBytes,omitempty": "number",
59
+ "dataVersion,omitempty": "string"
60
+ })
61
+
62
+ importer := smart.structBuilder(RTYPE_BLOB_IMPORT_INTERNAL, json.encode(settings)).lockAndBuild()
63
+
64
+ return ll.toStrict({
65
+ file: importer.getField("blob"),
66
+ handle: importer.getField("handle")
67
+ })
68
+ }
69
+
70
+ /**
71
+ * Create a task for getting a blob directly from the user via 'upload' procedure.
72
+ * Produces 2 fields:
73
+ * 'file' - reference to blob to appear once upload gets finished.
74
+ * 'handle' - reference to resource with 'progress' and 'upload' APIs.
75
+ */
76
+ createBlobUpload := func(settings) {
77
+ validation.assertJsonSchema(settings, {
78
+ "modificationTime": "string",
79
+ "localPath": "string",
80
+ "pathSignature": "string",
81
+ "sizeBytes": "string"
82
+ })
83
+
84
+ uploader := smart.structBuilder(RTYPE_BLOB_UPLOAD, json.encode(settings)).lockAndBuild()
85
+
86
+ return ll.toStrict({
87
+ file: uploader.getField("blob"),
88
+ handle: uploader.getField("handle")
89
+ })
90
+ }
91
+
92
+ export ll.toStrict({
93
+ RTYPE_BLOB_IMPORT_EXTERNAL : RTYPE_BLOB_IMPORT_EXTERNAL,
94
+ RTYPE_BLOB_IMPORT_INTERNAL : RTYPE_BLOB_IMPORT_INTERNAL,
95
+ RTYPE_BLOB_UPLOAD : RTYPE_BLOB_UPLOAD,
96
+ RTYPE_BLOB_DOWNLOAD : RTYPE_BLOB_DOWNLOAD,
97
+
98
+ createBlobImportExternal : createBlobImportExternal,
99
+ createBlobImportInternal : createBlobImportInternal,
100
+ createBlobUpload : createBlobUpload
101
+ })
@@ -14,49 +14,10 @@
14
14
  ll := import("@platforma-sdk/workflow-tengo:ll")
15
15
  smart := import("@platforma-sdk/workflow-tengo:smart")
16
16
  validation := import("@platforma-sdk/workflow-tengo:validation")
17
+ blobs := import("@platforma-sdk/workflow-tengo:file.internal")
17
18
 
18
19
  json := import("json")
19
20
 
20
- //
21
- // Constants:
22
- //
23
-
24
- _RTYPE_BLOB_IMPORT_INTERNAL := { Name: "BlobImportInternal", Version: "1" }
25
- _RTYPE_BLOB_UPLOAD := { Name: "BlobUpload", Version: "1" }
26
- _RTYPE_BLOB_DOWNLOAD := { Name: "BlobDownload", Version: "2" }
27
-
28
- createBlobImportInternal := func(settings) {
29
- validation.assertJsonSchema(settings, {
30
- "path": "string",
31
- "storageId": "string",
32
- "maxSpeedBytes,omitempty": "number",
33
- "dataVersion,omitempty": "string"
34
- })
35
-
36
- importer := smart.structBuilder(_RTYPE_BLOB_IMPORT_INTERNAL, json.encode(settings)).lockAndBuild()
37
-
38
- return ll.toStrict({
39
- file: importer.getField("blob"),
40
- handle: importer.getField("handle")
41
- })
42
- }
43
-
44
- createBlobUpload := func(settings) {
45
- validation.assertJsonSchema(settings, {
46
- "modificationTime": "string",
47
- "localPath": "string",
48
- "pathSignature": "string",
49
- "sizeBytes": "string"
50
- })
51
-
52
- uploader := smart.structBuilder(_RTYPE_BLOB_UPLOAD, json.encode(settings)).lockAndBuild()
53
-
54
- return ll.toStrict({
55
- file: uploader.getField("blob"),
56
- handle: uploader.getField("handle")
57
- })
58
- }
59
-
60
21
  /**
61
22
  * Imports an external file into the workflow, either from pre-configured storage,
62
23
  * available from the pl instance, or from the client machine via upload.
@@ -82,11 +43,14 @@ importFile := func(importFileHandle) {
82
43
  settings := json.decode(path)
83
44
 
84
45
  if url.Scheme == "upload" {
85
- ll.assert(url.Host == "upload", "unexpected host while parsing import file handle, expected upload found ", url.Host)
86
- return createBlobUpload(settings)
46
+ ll.assert(url.Host == "upload", "wrong 'host' in import file handle. Expected: 'upload', found: '", url.Host, "'")
47
+ return blobs.createBlobUpload(settings)
87
48
  } else if url.Scheme == "index" {
88
- ll.assert(url.Host == "index", "unexpected host while parsing import file handle, expected index found ", url.Host)
89
- return createBlobImportInternal(settings)
49
+ ll.assert(url.Host == "index", "wrong 'host' in import file handle. Expected 'index', found: '", url.Host, "'")
50
+ return blobs.createBlobImportInternal(settings)
51
+ } else if (url.Scheme == "download") {
52
+ ll.assert(url.Host == "download", "wrong 'host' in import file handle. Expected: 'download', found: '", url.Host, "'")
53
+ return blobs.createBlobImportExternal(settings)
90
54
  } else {
91
55
  ll.panic("Unknown file import handle schema: ", url.Scheme)
92
56
  }
@@ -97,7 +61,7 @@ importFile := func(importFileHandle) {
97
61
  * downloaded from the UI side.
98
62
  */
99
63
  exportFile := func(blob) {
100
- builder := smart.structBuilder(_RTYPE_BLOB_DOWNLOAD)
64
+ builder := smart.structBuilder(blobs.RTYPE_BLOB_DOWNLOAD)
101
65
 
102
66
  builder.createInputField("blob").set(blob)
103
67
 
@@ -93,15 +93,34 @@ ensureNonStrict := func(r) {
93
93
  * access to non-existing elements.
94
94
  *
95
95
  * @param m: map|strictMap
96
+ * @param recursive: boolean - convert also all child maps
96
97
  *
97
98
  * @return strict: strictMap
98
99
  */
99
- toStrict := func(v) {
100
+ toStrict := func(v, ...recursive) {
101
+ if len(recursive) > 0 && recursive[0] {
102
+ if isMap(v) {
103
+ for k, vv in v {
104
+ v[k] = toStrict(vv)
105
+ }
106
+ } else if is_array(v) {
107
+ for i, vv in v {
108
+ if isMap(vv) {
109
+ v[i] = toStrict(vv)
110
+ }
111
+ }
112
+ }
113
+
114
+ if !isMap(v) {
115
+ return v // for recursive transformation, accept the top-level value to be of any type (i.e. array of maps)
116
+ }
117
+ }
118
+
100
119
  if isStrict(v) {
101
120
  return v
102
121
  }
103
122
 
104
- if(!is_map(v)) {
123
+ if(!isMap(v)) {
105
124
  panic("toStrict(<v>): <v> is not a map", v)
106
125
  }
107
126
  return plapi.mapToStrict(v)
@@ -114,6 +133,30 @@ fieldId := func(resourceId, fieldName) {
114
133
  return plapi.newFieldID(resourceId, fieldName)
115
134
  }
116
135
 
136
+ /**
137
+ * Checks if <val> can be used as field ID
138
+ */
139
+ isFieldId := func(val) {
140
+ if !isStrict(val) {
141
+ return false
142
+ }
143
+ v := fromStrict(val)
144
+ return v["ResourceID"] != undefined && v["Name"] != undefined
145
+ }
146
+
147
+ /**
148
+ * Converts reference ID (resource ID or field ID) to stable string representation, suitable for use as map keys
149
+ */
150
+ idToString := func(referenceID) {
151
+ if isFieldId(referenceID) {
152
+ return fmt.sprintf("0x%X/%s", referenceID.ResourceID, referenceID.Name)
153
+ }
154
+ if is_int(referenceID) {
155
+ return fmt.sprintf("0x%X", referenceID)
156
+ }
157
+ panic("ll.idToString: <referenceID> cannot be used as resource or field ID")
158
+ }
159
+
117
160
  /**
118
161
  * Returns current template renderer
119
162
  */
@@ -221,6 +264,8 @@ export toStrict({
221
264
  ensureNonStrict: ensureNonStrict,
222
265
  toStrict: toStrict,
223
266
  fieldId: fieldId,
267
+ isFieldId: isFieldId,
268
+ idToString: idToString,
224
269
  getCurrentTemplateRenderer: getCurrentTemplateRenderer,
225
270
  isInitializing: isInitializing,
226
271
  getExecutorsList: getExecutorsList,
@@ -91,11 +91,6 @@ createBlobMapResource := undefined
91
91
 
92
92
  ////////////////// helpers ///////////////////////////
93
93
 
94
- // internal helper function
95
- isFieldId := func(val) {
96
- return ll.isStrict(val) && ll.fromStrict(val)["ResourceID"] != undefined
97
- }
98
-
99
94
  /**
100
95
  * Returns true if given object is a smart.Resource
101
96
  */
@@ -561,6 +556,12 @@ field = func(f) {
561
556
  self := undefined
562
557
  strictSelf := undefined
563
558
 
559
+ id := ll.isFieldId(f) ? f : f.ID
560
+
561
+ if (is_undefined(id)) {
562
+ ll.panic("undefined id in smart.field. <f>="+f)
563
+ }
564
+
564
565
  self = {
565
566
  _type: "field",
566
567
 
@@ -571,7 +572,7 @@ field = func(f) {
571
572
  * Name: string
572
573
  *
573
574
  */
574
- id: isFieldId(f) ? f : f.ID,
575
+ id: id,
575
576
 
576
577
  /**
577
578
  * Field name
@@ -292,8 +292,8 @@ tpl := func() {
292
292
  ll.assert(is_map(r), "expected map, got ", r)
293
293
 
294
294
  for name, ref in r {
295
- ll.assert(smart.isReference(ref), "expected reference, got ", ref)
296
- ll.assert(is_undefined(newInputs[name]), "prepare name is already in use: ", name)
295
+ ll.assert(smart.isReference(ref), "expected reference, got %v", ref)
296
+ ll.assert(is_undefined(newInputs[name]), "prepare name is already in use: %s", name)
297
297
 
298
298
  builder.add(name, ref)
299
299
  }
@@ -15,6 +15,7 @@ path := import("@platforma-sdk/workflow-tengo:path")
15
15
  enum := import("enum")
16
16
  validation := import("@platforma-sdk/workflow-tengo:validation")
17
17
  render := import("@platforma-sdk/workflow-tengo:render")
18
+ blobs := import("@platforma-sdk/workflow-tengo:file.internal")
18
19
 
19
20
  create := func() {
20
21
  wd := smart.ephemeralBuilder(constants.RTYPE_WORKDIR_CREATE).lockAndBuild()
@@ -23,8 +24,8 @@ create := func() {
23
24
 
24
25
  _getFileFillRule := func(filePath, extension) {
25
26
  return {
26
- path: filePath,
27
27
  type: "file",
28
+ path: filePath,
28
29
  permissions: 0o600,
29
30
  pathKey: filePath,
30
31
  blobKey: filePath,
@@ -33,6 +34,26 @@ _getFileFillRule := func(filePath, extension) {
33
34
  }
34
35
  }
35
36
 
37
+ _getArchiveFillRule := func(dstDir, whatToExtract, archiveBlobKey) {
38
+ extractRules := []
39
+ for f in whatToExtract {
40
+ extractRules = append(extractRules, {
41
+ srcPath: f,
42
+ dstPath: f,
43
+ filePerms: 0o600,
44
+ dirPerms: 0o700
45
+ })
46
+ }
47
+
48
+ return {
49
+ type: "archive",
50
+ path: dstDir,
51
+ blobKey: archiveBlobKey,
52
+ archiveType: "zip",
53
+ extractRules: extractRules
54
+ }
55
+ }
56
+
36
57
  /**
37
58
  * Generates a value fill rule for a given file path.
38
59
  *
@@ -41,8 +62,8 @@ _getFileFillRule := func(filePath, extension) {
41
62
  */
42
63
  _getValueFillRule := func(filePath) {
43
64
  return {
44
- path: filePath,
45
65
  type: "value",
66
+ path: filePath,
46
67
  permissions: 0o600,
47
68
  pathKey: filePath,
48
69
  valueKey: filePath
@@ -57,8 +78,8 @@ _getValueFillRule := func(filePath) {
57
78
  */
58
79
  _getDirFillRule := func(dir) {
59
80
  return {
60
- path: dir,
61
81
  type: "dir",
82
+ path: dir,
62
83
  permissions: 0o700,
63
84
  pathKey: ""
64
85
  }
@@ -75,7 +96,9 @@ _fill := func(workdir) {
75
96
  wd := smart.ephemeralBuilder(constants.RTYPE_WORKDIR_FILL)
76
97
  wd.getField("workdirIn").set(workdir)
77
98
 
99
+ blobs := {}
78
100
  files := {}
101
+ zipExtractions := {}
79
102
  values := {}
80
103
  dirs := {}
81
104
 
@@ -91,6 +114,49 @@ _fill := func(workdir) {
91
114
  sets.add(dirs, newDirs...)
92
115
  },
93
116
 
117
+ /**
118
+ * Adds files from given zip archive into working directory.
119
+ *
120
+ * @param zipResource: smart.reference - reference to zip archive to be extracted.
121
+ * @param destinationDir: string - path inside working directory for files to be extracted from archive.
122
+ * Consider it as 'cd' before extraction. It makes all extracted files to be
123
+ * stored inside this directory.
124
+ * @param pathsInArchive: string[] - paths inside the archive to be extracted into destinationDir.
125
+ * Empty array means 'unpack full archive'.
126
+ * Full file paths are preserved during extraction: the file 'a/b/file.txt' would
127
+ * be extracted into '<destinationDir>/a/b/file.txt', NOT '<destinationDir>/file.txt',
128
+ */
129
+ addFromZip: func(zipResource, destinationDir, pathsInArchive) {
130
+ validation.assertJsonSchema(zipResource, validation.reference, "workdir.builder.addFromZip: <zipResource> is not smart.reference")
131
+ validation.assertJsonSchema(destinationDir, "string", "workdir.builder.addFromZip: <destinationDir> is not a string. It must be a path inside working directory")
132
+
133
+ if (is_undefined(pathsInArchive)) {
134
+ pathsInArchive = []
135
+ }
136
+ validation.assertJsonSchema(pathsInArchive, ["string"], "workdir.builder.addFromZip: <pathsInArchive> must contain list of strings with paths inside archive")
137
+
138
+ blobKey := ll.idToString(zipResource.id)
139
+
140
+ if is_undefined(blobs[blobKey]) {
141
+ blobs[blobKey] = zipResource
142
+ }
143
+
144
+ if is_undefined(zipExtractions[blobKey]) {
145
+ zipExtractions[blobKey] = {}
146
+ }
147
+
148
+ if is_undefined(zipExtractions[blobKey][destinationDir]) || len(pathsInArchive) == 0 {
149
+ zipExtractions[blobKey][destinationDir] = pathsInArchive
150
+ return self
151
+ }
152
+
153
+ if (len(pathsInArchive) > 0) {
154
+ zipExtractions[blobKey][destinationDir] = append(zipExtractions[blobKey][destinationDir], pathsInArchive...)
155
+ }
156
+
157
+ return self
158
+ },
159
+
94
160
  /**
95
161
  * Adds a file to the workdir.
96
162
  *
@@ -102,6 +168,7 @@ _fill := func(workdir) {
102
168
  validation.assertJsonSchema(fileResource, validation.reference)
103
169
 
104
170
  files[fileName] = fileResource
171
+ blobs[fileName] = fileResource
105
172
  self._addDirs(fileName)
106
173
 
107
174
  return self
@@ -168,6 +235,12 @@ _fill := func(workdir) {
168
235
  rules = append(rules, _getValueFillRule(fileName))
169
236
  }
170
237
 
238
+ for blobKey, directories in zipExtractions {
239
+ for dstDir, whatToExtract in directories {
240
+ rules = append(rules, _getArchiveFillRule(dstDir, whatToExtract, blobKey))
241
+ }
242
+ }
243
+
171
244
  rulesRes := smart.createValueResource(
172
245
  constants.RTYPE_WORKDIR_FILL_RULES,
173
246
  json.encode(rules)
@@ -175,7 +248,7 @@ _fill := func(workdir) {
175
248
  self.getField("rules").set(rulesRes)
176
249
 
177
250
  self.getField("dataIn").set(smart.createBinaryMapResource(values))
178
- self.getField("blobsIn").set(smart.createBlobMapResource(files))
251
+ self.getField("blobsIn").set(smart.createBlobMapResource(blobs))
179
252
 
180
253
  return self.lockAndBuild().outputs().workdirOut
181
254
  }
Binary file
Binary file
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@platforma-sdk/workflow-tengo",
3
- "version": "1.6.4",
3
+ "version": "1.7.1",
4
4
  "description": "Tengo SDK library",
5
5
  "types": "./dist/index.d.ts",
6
6
  "main": "./dist/index.js",
@@ -16,11 +16,11 @@
16
16
  "./dist/**/*"
17
17
  ],
18
18
  "dependencies": {
19
- "@milaboratories/software-pframes-conv": "^1.6.4",
20
- "@platforma-open/milaboratories.software-small-binaries": "^1.14.4"
19
+ "@milaboratories/software-pframes-conv": "^1.6.6",
20
+ "@platforma-open/milaboratories.software-small-binaries": "^1.14.5"
21
21
  },
22
22
  "devDependencies": {
23
- "@platforma-sdk/tengo-builder": "^1.15.0"
23
+ "@platforma-sdk/tengo-builder": "^1.16.0"
24
24
  },
25
25
  "scripts": {
26
26
  "type-check": "pl-tengo check",
@@ -28,7 +28,7 @@
28
28
  "test": "pl-tengo test",
29
29
  "format": "/usr/bin/env emacs --quick --script ./format.el",
30
30
  "format-stdin": "/usr/bin/env emacs --quick --script ./format-stdin.el",
31
- "vscode-tags": "./create_tags.sh .tags false",
31
+ "vscode-tags": "cd ../../ && bash ./sdk/workflow-tengo/create_tags.sh .tags false",
32
32
  "emacs-tags": "./create_tags.sh ../TAGS false -e",
33
33
  "do-pack": "rm -f *.tgz && pnpm pack && mv *.tgz package.tgz"
34
34
  }