@promptbook/node 0.85.0-1 → 0.85.0-11

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.
@@ -47,6 +47,7 @@ import { parseKeywords } from '../utils/normalization/parseKeywords';
47
47
  import { parseKeywordsFromString } from '../utils/normalization/parseKeywordsFromString';
48
48
  import { removeDiacritics } from '../utils/normalization/removeDiacritics';
49
49
  import { searchKeywords } from '../utils/normalization/searchKeywords';
50
+ import { suffixUrl } from '../utils/normalization/suffixUrl';
50
51
  import { titleToName } from '../utils/normalization/titleToName';
51
52
  import { spaceTrim } from '../utils/organization/spaceTrim';
52
53
  import { extractParameterNames } from '../utils/parameters/extractParameterNames';
@@ -128,6 +129,7 @@ export { parseKeywords };
128
129
  export { parseKeywordsFromString };
129
130
  export { removeDiacritics };
130
131
  export { searchKeywords };
132
+ export { suffixUrl };
131
133
  export { titleToName };
132
134
  export { spaceTrim };
133
135
  export { extractParameterNames };
@@ -8,7 +8,6 @@ import type { Command as Program } from 'commander';
8
8
  */
9
9
  export declare function $initializeAboutCommand(program: Program): void;
10
10
  /**
11
- * TODO: !!! Test this in `deno`
12
11
  * TODO: [🗽] Unite branding and make single place for it
13
12
  * Note: [💞] Ignore a discrepancy between file name and entity name
14
13
  * Note: [🟡] Code in this file should never be published outside of `@promptbook/cli`
@@ -1,7 +1,7 @@
1
1
  import type { Observable } from 'rxjs';
2
2
  import { PartialDeep } from 'type-fest';
3
- import type { string_SCREAMING_CASE } from '../utils/normalization/normalizeTo_SCREAMING_CASE';
4
3
  import type { task_id } from '../types/typeAliases';
4
+ import type { string_SCREAMING_CASE } from '../utils/normalization/normalizeTo_SCREAMING_CASE';
5
5
  import type { AbstractTaskResult } from './AbstractTaskResult';
6
6
  import type { PipelineExecutorResult } from './PipelineExecutorResult';
7
7
  /**
@@ -30,7 +30,7 @@ export declare function createTask<TTaskResult extends AbstractTaskResult>(optio
30
30
  */
31
31
  export type ExecutionTask = AbstractTask<PipelineExecutorResult> & {
32
32
  readonly taskType: 'EXECUTION';
33
- readonly taskId: `execution-${task_id}`;
33
+ readonly taskId: `exec-${task_id}`;
34
34
  };
35
35
  /**
36
36
  * Represents a task that prepares a pipeline
@@ -38,7 +38,7 @@ export type ExecutionTask = AbstractTask<PipelineExecutorResult> & {
38
38
  */
39
39
  export type PreparationTask = AbstractTask<PipelineExecutorResult> & {
40
40
  readonly taskType: 'PREPARATION';
41
- readonly taskId: `preparation-${task_id}`;
41
+ readonly taskId: `prep-${task_id}`;
42
42
  };
43
43
  /**
44
44
  * Base interface for all task types
@@ -62,6 +62,10 @@ export type AbstractTask<TTaskResult extends AbstractTaskResult> = {
62
62
  * Gets an observable stream of partial task results
63
63
  */
64
64
  asObservable(): Observable<PartialDeep<TTaskResult>>;
65
+ /**
66
+ * Gets just the current value which is mutated during the task processing
67
+ */
68
+ currentValue: PartialDeep<TTaskResult>;
65
69
  };
66
70
  export type Task = ExecutionTask | PreparationTask;
67
71
  export {};
@@ -1,5 +1,5 @@
1
1
  /// <reference types="node" />
2
- import type fs from 'node:fs/promises';
2
+ import type fs from 'fs/promises';
3
3
  /**
4
4
  * Container for all the tools needed to manipulate with filesystem
5
5
  */
@@ -11,8 +11,7 @@ import type { RemoteServerOptions } from './types/RemoteServerOptions';
11
11
  */
12
12
  export declare function startRemoteServer<TCustomOptions = undefined>(options: RemoteServerOptions<TCustomOptions>): IDestroyable;
13
13
  /**
14
- * TODO: !!!!!!! CORS and security
15
- * TODO: !!!!!!! Allow to pass tokem here
14
+ * TODO: !! Add CORS and security - probbably via `helmet`
16
15
  * TODO: [👩🏾‍🤝‍🧑🏾] Allow to pass custom fetch function here - PromptbookFetch
17
16
  * TODO: Split this file into multiple functions - handler for each request
18
17
  * TODO: Maybe use `$exportJson`
@@ -0,0 +1,7 @@
1
+ import type { string_url } from '../../types/typeAliases';
2
+ /**
3
+ * Adds suffix to the URL
4
+ *
5
+ * @public exported from `@promptbook/utils`
6
+ */
7
+ export declare function suffixUrl(value: URL, suffix: `/${string}`): string_url;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@promptbook/node",
3
- "version": "0.85.0-1",
3
+ "version": "0.85.0-11",
4
4
  "description": "It's time for a paradigm shift. The future of software in plain English, French or Latin",
5
5
  "private": false,
6
6
  "sideEffects": false,
@@ -47,7 +47,7 @@
47
47
  "module": "./esm/index.es.js",
48
48
  "typings": "./esm/typings/src/_packages/node.index.d.ts",
49
49
  "peerDependencies": {
50
- "@promptbook/core": "0.85.0-1"
50
+ "@promptbook/core": "0.85.0-11"
51
51
  },
52
52
  "dependencies": {
53
53
  "colors": "1.4.0",
package/umd/index.umd.js CHANGED
@@ -1,8 +1,8 @@
1
1
  (function (global, factory) {
2
- typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('colors'), require('node:fs/promises'), require('node:path'), require('spacetrim'), require('prettier'), require('prettier/parser-html'), require('rxjs'), require('crypto'), require('waitasecond'), require('papaparse'), require('crypto-js/enc-hex'), require('crypto-js/sha256'), require('crypto-js'), require('mime-types'), require('node:child_process'), require('dotenv')) :
3
- typeof define === 'function' && define.amd ? define(['exports', 'colors', 'node:fs/promises', 'node:path', 'spacetrim', 'prettier', 'prettier/parser-html', 'rxjs', 'crypto', 'waitasecond', 'papaparse', 'crypto-js/enc-hex', 'crypto-js/sha256', 'crypto-js', 'mime-types', 'node:child_process', 'dotenv'], factory) :
4
- (global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global["promptbook-node"] = {}, global.colors, global.promises, global.node_path, global.spaceTrim, global.prettier, global.parserHtml, global.rxjs, global.crypto, global.waitasecond, global.papaparse, global.hexEncoder, global.sha256, global.cryptoJs, global.mimeTypes, global.node_child_process, global.dotenv));
5
- })(this, (function (exports, colors, promises, node_path, spaceTrim, prettier, parserHtml, rxjs, crypto, waitasecond, papaparse, hexEncoder, sha256, cryptoJs, mimeTypes, node_child_process, dotenv) { 'use strict';
2
+ typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('colors'), require('fs/promises'), require('path'), require('spacetrim'), require('prettier'), require('prettier/parser-html'), require('rxjs'), require('crypto'), require('waitasecond'), require('papaparse'), require('crypto-js/enc-hex'), require('crypto-js/sha256'), require('crypto-js'), require('mime-types'), require('child_process'), require('dotenv')) :
3
+ typeof define === 'function' && define.amd ? define(['exports', 'colors', 'fs/promises', 'path', 'spacetrim', 'prettier', 'prettier/parser-html', 'rxjs', 'crypto', 'waitasecond', 'papaparse', 'crypto-js/enc-hex', 'crypto-js/sha256', 'crypto-js', 'mime-types', 'child_process', 'dotenv'], factory) :
4
+ (global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global["promptbook-node"] = {}, global.colors, global.promises, global.path, global.spaceTrim, global.prettier, global.parserHtml, global.rxjs, global.crypto, global.waitasecond, global.papaparse, global.hexEncoder, global.sha256, global.cryptoJs, global.mimeTypes, global.child_process, global.dotenv));
5
+ })(this, (function (exports, colors, promises, path, spaceTrim, prettier, parserHtml, rxjs, crypto, waitasecond, papaparse, hexEncoder, sha256, cryptoJs, mimeTypes, child_process, dotenv) { 'use strict';
6
6
 
7
7
  function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; }
8
8
 
@@ -45,7 +45,7 @@
45
45
  * @generated
46
46
  * @see https://github.com/webgptorg/promptbook
47
47
  */
48
- var PROMPTBOOK_ENGINE_VERSION = '0.85.0-0';
48
+ var PROMPTBOOK_ENGINE_VERSION = '0.85.0-10';
49
49
  /**
50
50
  * TODO: string_promptbook_version should be constrained to the all versions of Promptbook engine
51
51
  * Note: [💞] Ignore a discrepancy between file name and entity name
@@ -2060,11 +2060,27 @@
2060
2060
  */
2061
2061
  function createTask(options) {
2062
2062
  var taskType = options.taskType, taskProcessCallback = options.taskProcessCallback;
2063
- var taskId = "".concat(taskType.toLowerCase(), "-").concat($randomToken(256 /* <- TODO: !!! To global config */));
2063
+ var taskId = "".concat(taskType.toLowerCase().substring(0, 4), "-").concat($randomToken(8 /* <- TODO: To global config + Use Base58 to avoid simmilar char conflicts */));
2064
2064
  var partialResultSubject = new rxjs.BehaviorSubject({});
2065
2065
  var finalResultPromise = /* not await */ taskProcessCallback(function (newOngoingResult) {
2066
2066
  partialResultSubject.next(newOngoingResult);
2067
2067
  });
2068
+ finalResultPromise
2069
+ .catch(function (error) {
2070
+ partialResultSubject.error(error);
2071
+ })
2072
+ .then(function (value) {
2073
+ if (value) {
2074
+ try {
2075
+ assertsTaskSuccessful(value);
2076
+ partialResultSubject.next(value);
2077
+ }
2078
+ catch (error) {
2079
+ partialResultSubject.error(error);
2080
+ }
2081
+ }
2082
+ partialResultSubject.complete();
2083
+ });
2068
2084
  function asPromise(options) {
2069
2085
  return __awaiter(this, void 0, void 0, function () {
2070
2086
  var _a, isCrashedOnError, finalResult;
@@ -2088,9 +2104,10 @@
2088
2104
  taskId: taskId,
2089
2105
  asPromise: asPromise,
2090
2106
  asObservable: function () {
2091
- return rxjs.concat(partialResultSubject.asObservable(), rxjs.from(asPromise({
2092
- isCrashedOnError: true,
2093
- })));
2107
+ return partialResultSubject.asObservable();
2108
+ },
2109
+ get currentValue() {
2110
+ return partialResultSubject.value;
2094
2111
  },
2095
2112
  };
2096
2113
  }
@@ -5596,7 +5613,7 @@
5596
5613
  value = value.replace(/\.html$/, '');
5597
5614
  }
5598
5615
  else if (isValidFilePath(value)) {
5599
- value = node_path.basename(value);
5616
+ value = path.basename(value);
5600
5617
  // Note: Keeping extension in the name
5601
5618
  }
5602
5619
  value = value.split('/').join('-');
@@ -5702,9 +5719,9 @@
5702
5719
  }
5703
5720
  basename = url.split('/').pop() || titleToName(url);
5704
5721
  hash = sha256__default["default"](hexEncoder__default["default"].parse(url)).toString( /* hex */);
5705
- rootDirname_1 = node_path.join(process.cwd(), DEFAULT_DOWNLOAD_CACHE_DIRNAME);
5706
- filepath = node_path.join.apply(void 0, __spreadArray(__spreadArray([], __read(nameToSubfolderPath(hash /* <- TODO: [🎎] Maybe add some SHA256 prefix */)), false), ["".concat(basename.substring(0, MAX_FILENAME_LENGTH), ".").concat(mimeTypeToExtension(mimeType))], false));
5707
- return [4 /*yield*/, tools.fs.mkdir(node_path.dirname(node_path.join(rootDirname_1, filepath)), { recursive: true })];
5722
+ rootDirname_1 = path.join(process.cwd(), DEFAULT_DOWNLOAD_CACHE_DIRNAME);
5723
+ filepath = path.join.apply(void 0, __spreadArray(__spreadArray([], __read(nameToSubfolderPath(hash /* <- TODO: [🎎] Maybe add some SHA256 prefix */)), false), ["".concat(basename.substring(0, MAX_FILENAME_LENGTH), ".").concat(mimeTypeToExtension(mimeType))], false));
5724
+ return [4 /*yield*/, tools.fs.mkdir(path.dirname(path.join(rootDirname_1, filepath)), { recursive: true })];
5708
5725
  case 2:
5709
5726
  _h.sent();
5710
5727
  _g = (_f = Buffer).from;
@@ -5714,7 +5731,7 @@
5714
5731
  if (fileContent.length > DEFAULT_MAX_FILE_SIZE /* <- TODO: Allow to pass different value to remote server */) {
5715
5732
  throw new LimitReachedError("File is too large (".concat(Math.round(fileContent.length / 1024 / 1024), "MB). Maximum allowed size is ").concat(Math.round(DEFAULT_MAX_FILE_SIZE / 1024 / 1024), "MB."));
5716
5733
  }
5717
- return [4 /*yield*/, tools.fs.writeFile(node_path.join(rootDirname_1, filepath), fileContent)];
5734
+ return [4 /*yield*/, tools.fs.writeFile(path.join(rootDirname_1, filepath), fileContent)];
5718
5735
  case 4:
5719
5736
  _h.sent();
5720
5737
  // TODO: [💵] Check the file security
@@ -5730,7 +5747,7 @@
5730
5747
  throw new EnvironmentMismatchError('Can not import file knowledge in non-file pipeline');
5731
5748
  // <- TODO: [🧠] What is the best error type here`
5732
5749
  }
5733
- filename_1 = node_path.join(rootDirname, knowledgeSourceContent).split('\\').join('/');
5750
+ filename_1 = path.join(rootDirname, knowledgeSourceContent).split('\\').join('/');
5734
5751
  fileExtension = getFileExtension(filename_1);
5735
5752
  mimeType = extensionToMimeType(fileExtension || '');
5736
5753
  return [4 /*yield*/, isFileExisting(filename_1, tools.fs)];
@@ -9726,7 +9743,7 @@
9726
9743
  console.info(colors__default["default"].yellow(cwd) + ' ' + colors__default["default"].green(command) + ' ' + colors__default["default"].blue(args.join(' ')));
9727
9744
  }
9728
9745
  try {
9729
- var commandProcess = node_child_process.spawn(command, args, { cwd: cwd, shell: true });
9746
+ var commandProcess = child_process.spawn(command, args, { cwd: cwd, shell: true });
9730
9747
  if (isVerbose) {
9731
9748
  commandProcess.on('message', function (message) {
9732
9749
  console.info({ message: message });
@@ -9936,7 +9953,7 @@
9936
9953
  function locateAppOnWindows(_a) {
9937
9954
  var appName = _a.appName, windowsSuffix = _a.windowsSuffix;
9938
9955
  return __awaiter(this, void 0, void 0, function () {
9939
- var prefixes, prefixes_1, prefixes_1_1, prefix, path, e_1_1, error_1;
9956
+ var prefixes, prefixes_1, prefixes_1_1, prefix, path$1, e_1_1, error_1;
9940
9957
  var e_1, _b;
9941
9958
  return __generator(this, function (_c) {
9942
9959
  switch (_c.label) {
@@ -9944,7 +9961,7 @@
9944
9961
  _c.trys.push([0, 9, , 10]);
9945
9962
  prefixes = [
9946
9963
  process.env.LOCALAPPDATA,
9947
- node_path.join(process.env.LOCALAPPDATA || '', 'Programs'),
9964
+ path.join(process.env.LOCALAPPDATA || '', 'Programs'),
9948
9965
  process.env.PROGRAMFILES,
9949
9966
  process.env['PROGRAMFILES(X86)'],
9950
9967
  ];
@@ -9956,11 +9973,11 @@
9956
9973
  case 2:
9957
9974
  if (!!prefixes_1_1.done) return [3 /*break*/, 5];
9958
9975
  prefix = prefixes_1_1.value;
9959
- path = prefix + windowsSuffix;
9960
- return [4 /*yield*/, isExecutable(path, $provideFilesystemForNode())];
9976
+ path$1 = prefix + windowsSuffix;
9977
+ return [4 /*yield*/, isExecutable(path$1, $provideFilesystemForNode())];
9961
9978
  case 3:
9962
9979
  if (_c.sent()) {
9963
- return [2 /*return*/, path];
9980
+ return [2 /*return*/, path$1];
9964
9981
  }
9965
9982
  _c.label = 4;
9966
9983
  case 4:
@@ -10356,7 +10373,7 @@
10356
10373
  case 3:
10357
10374
  if (!!envFilePatterns_1_1.done) return [3 /*break*/, 6];
10358
10375
  pattern = envFilePatterns_1_1.value;
10359
- envFilename = node_path.join(rootDirname, pattern);
10376
+ envFilename = path.join(rootDirname, pattern);
10360
10377
  return [4 /*yield*/, isFileExisting(envFilename, $provideFilesystemForNode())];
10361
10378
  case 4:
10362
10379
  if (_b.sent()) {
@@ -10384,7 +10401,7 @@
10384
10401
  return [3 /*break*/, 11];
10385
10402
  }
10386
10403
  // Note: If the directory does not exist, try the parent directory
10387
- rootDirname = node_path.join(rootDirname, '..');
10404
+ rootDirname = path.join(rootDirname, '..');
10388
10405
  _b.label = 10;
10389
10406
  case 10:
10390
10407
  i++;
@@ -11131,19 +11148,19 @@
11131
11148
  * @returns List of all files in the directory
11132
11149
  * @private internal function of `createCollectionFromDirectory`
11133
11150
  */
11134
- function listAllFiles(path, isRecursive, fs) {
11151
+ function listAllFiles(path$1, isRecursive, fs) {
11135
11152
  return __awaiter(this, void 0, void 0, function () {
11136
11153
  var dirents, fileNames, _a, _b, dirent, subPath, _c, _d, _e, _f, e_1_1;
11137
11154
  var e_1, _g;
11138
11155
  return __generator(this, function (_h) {
11139
11156
  switch (_h.label) {
11140
- case 0: return [4 /*yield*/, isDirectoryExisting(path, fs)];
11157
+ case 0: return [4 /*yield*/, isDirectoryExisting(path$1, fs)];
11141
11158
  case 1:
11142
11159
  if (!(_h.sent())) {
11143
- throw new Error("Directory \"".concat(path, "\" does not exist or is not readable"));
11160
+ throw new Error("Directory \"".concat(path$1, "\" does not exist or is not readable"));
11144
11161
  // <- TODO: Use some custom error class
11145
11162
  }
11146
- return [4 /*yield*/, fs.readdir(path, {
11163
+ return [4 /*yield*/, fs.readdir(path$1, {
11147
11164
  withFileTypes: true /* Note: This is not working: recursive: isRecursive */,
11148
11165
  })];
11149
11166
  case 2:
@@ -11152,7 +11169,7 @@
11152
11169
  .filter(function (dirent) { return dirent.isFile(); })
11153
11170
  .map(function (_a) {
11154
11171
  var name = _a.name;
11155
- return node_path.join(path, name).split('\\').join('/');
11172
+ return path.join(path$1, name).split('\\').join('/');
11156
11173
  });
11157
11174
  if (!isRecursive) return [3 /*break*/, 10];
11158
11175
  _h.label = 3;
@@ -11163,7 +11180,7 @@
11163
11180
  case 4:
11164
11181
  if (!!_b.done) return [3 /*break*/, 7];
11165
11182
  dirent = _b.value;
11166
- subPath = node_path.join(path, dirent.name);
11183
+ subPath = path.join(path$1, dirent.name);
11167
11184
  _d = (_c = fileNames.push).apply;
11168
11185
  _e = [fileNames];
11169
11186
  _f = [[]];
@@ -11294,7 +11311,7 @@
11294
11311
  * @returns PipelineCollection
11295
11312
  * @public exported from `@promptbook/node`
11296
11313
  */
11297
- function createCollectionFromDirectory(path, tools, options) {
11314
+ function createCollectionFromDirectory(path$1, tools, options) {
11298
11315
  return __awaiter(this, void 0, void 0, function () {
11299
11316
  var madeLibraryFilePath, _a, _b, isRecursive, _c, isVerbose, _d, isLazyLoaded, _e, isCrashedOnError, rootUrl, collection;
11300
11317
  var _this = this;
@@ -11311,7 +11328,7 @@
11311
11328
  throw new EnvironmentMismatchError('Can not create collection without filesystem tools');
11312
11329
  // <- TODO: [🧠] What is the best error type here`
11313
11330
  }
11314
- madeLibraryFilePath = node_path.join(path, "".concat(DEFAULT_PIPELINE_COLLECTION_BASE_FILENAME
11331
+ madeLibraryFilePath = path.join(path$1, "".concat(DEFAULT_PIPELINE_COLLECTION_BASE_FILENAME
11315
11332
  // <- TODO: [🦒] Allow to override (pass different value into the function)
11316
11333
  , ".json"));
11317
11334
  return [4 /*yield*/, isFileExisting(madeLibraryFilePath, tools.fs)];
@@ -11330,9 +11347,9 @@
11330
11347
  switch (_b.label) {
11331
11348
  case 0:
11332
11349
  if (isVerbose) {
11333
- console.info(colors__default["default"].cyan("Creating pipeline collection from path ".concat(path.split('\\').join('/'))));
11350
+ console.info(colors__default["default"].cyan("Creating pipeline collection from path ".concat(path$1.split('\\').join('/'))));
11334
11351
  }
11335
- return [4 /*yield*/, listAllFiles(path, isRecursive, tools.fs)];
11352
+ return [4 /*yield*/, listAllFiles(path$1, isRecursive, tools.fs)];
11336
11353
  case 1:
11337
11354
  fileNames = _b.sent();
11338
11355
  // Note: First load all .book.json and then .book.md files
@@ -11353,7 +11370,7 @@
11353
11370
  switch (_f.label) {
11354
11371
  case 0:
11355
11372
  sourceFile = './' + fileName.split('\\').join('/');
11356
- rootDirname = node_path.dirname(sourceFile).split('\\').join('/');
11373
+ rootDirname = path.dirname(sourceFile).split('\\').join('/');
11357
11374
  _f.label = 1;
11358
11375
  case 1:
11359
11376
  _f.trys.push([1, 8, , 9]);
@@ -11547,7 +11564,7 @@
11547
11564
  var name = titleToName(key);
11548
11565
  var hash = sha256__default["default"](hexEncoder__default["default"].parse(name)).toString( /* hex */);
11549
11566
  // <- TODO: [🥬] Encapsulate sha256 to some private utility function
11550
- return node_path.join.apply(void 0, __spreadArray(__spreadArray([this.options.rootFolderPath], __read(nameToSubfolderPath(hash /* <- TODO: [🎎] Maybe add some SHA256 prefix */)), false), ["".concat(name.substring(0, MAX_FILENAME_LENGTH), ".json")], false));
11567
+ return path.join.apply(void 0, __spreadArray(__spreadArray([this.options.rootFolderPath], __read(nameToSubfolderPath(hash /* <- TODO: [🎎] Maybe add some SHA256 prefix */)), false), ["".concat(name.substring(0, MAX_FILENAME_LENGTH), ".json")], false));
11551
11568
  };
11552
11569
  /**
11553
11570
  * @@@ Returns the current value associated with the given key, or null if the given key does not exist in the list associated with the object.
@@ -11588,7 +11605,7 @@
11588
11605
  throw new UnexpectedError("The \"".concat(key, "\" you want to store in JSON file is not serializable as JSON"));
11589
11606
  }
11590
11607
  fileContent = stringifyPipelineJson(value);
11591
- return [4 /*yield*/, promises.mkdir(node_path.dirname(filename), { recursive: true })];
11608
+ return [4 /*yield*/, promises.mkdir(path.dirname(filename), { recursive: true })];
11592
11609
  case 1:
11593
11610
  _a.sent(); // <- [0]
11594
11611
  return [4 /*yield*/, promises.writeFile(filename, fileContent, 'utf-8')];