firebase-tools 11.20.0 → 11.21.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.
@@ -8,7 +8,7 @@ if (!semver.satisfies(nodeVersion, pkg.engines.node)) {
8
8
  console.error(`Firebase CLI v${pkg.version} is incompatible with Node.js ${nodeVersion} Please upgrade Node.js to version ${pkg.engines.node}`);
9
9
  process.exit(1);
10
10
  }
11
- const updateNotifierPkg = require("update-notifier");
11
+ const updateNotifierPkg = require("update-notifier-cjs");
12
12
  const clc = require("colorette");
13
13
  const TerminalRenderer = require("marked-terminal");
14
14
  const updateNotifier = updateNotifierPkg({ pkg });
@@ -90,6 +90,9 @@ exports.command = new command_1.Command("appdistribution:distribute <release-bin
90
90
  default:
91
91
  utils.logSuccess(`uploaded release ${release.displayVersion} (${release.buildVersion}) successfully!`);
92
92
  }
93
+ utils.logSuccess(`View this release in the Firebase console: ${release.firebaseConsoleUri}`);
94
+ utils.logSuccess(`Share this release with testers who have access: ${release.testingUri}`);
95
+ utils.logSuccess(`Download the release binary (link expires in 1 hour): ${release.binaryDownloadUri}`);
93
96
  releaseName = uploadResponse.release.name;
94
97
  }
95
98
  catch (err) {
@@ -32,6 +32,7 @@ exports.command = new command_1.Command("ext:configure <extensionInstanceId>")
32
32
  .before(checkMinRequiredVersion_1.checkMinRequiredVersion, "extMinVersion")
33
33
  .before(extensionsHelper_1.diagnoseAndFixProject)
34
34
  .action(async (instanceId, options) => {
35
+ var _a;
35
36
  const projectId = (0, projectUtils_1.getProjectId)(options);
36
37
  if (options.nonInteractive) {
37
38
  throw new error_1.FirebaseError(`Command not supported in non-interactive mode, edit ./extensions/${instanceId}.env directly instead. ` +
@@ -56,7 +57,7 @@ exports.command = new command_1.Command("ext:configure <extensionInstanceId>")
56
57
  instanceId,
57
58
  projectDir: config.projectDir,
58
59
  });
59
- const [immutableParams, tbdParams] = (0, functional_1.partition)(spec.params, (param) => { var _a; return (_a = param.immutable) !== null && _a !== void 0 ? _a : false; });
60
+ const [immutableParams, tbdParams] = (0, functional_1.partition)(spec.params.concat((_a = spec.systemParams) !== null && _a !== void 0 ? _a : []), (param) => { var _a; return (_a = param.immutable) !== null && _a !== void 0 ? _a : false; });
60
61
  infoImmutableParams(immutableParams, oldParamValues);
61
62
  paramHelper.setNewDefaults(tbdParams, oldParamValues);
62
63
  const mutableParamsBindingOptions = await paramHelper.getParams({
@@ -120,7 +120,7 @@ async function infoExtensionVersion(args) {
120
120
  await (0, warnings_1.displayWarningPrompts)(ref.publisherId, extension.registryLaunchStage, args.extensionVersion);
121
121
  }
122
122
  async function installToManifest(options) {
123
- var _a;
123
+ var _a, _b;
124
124
  const { projectId, extensionName, extVersion, source, paramsEnvPath, nonInteractive, force } = options;
125
125
  const isLocalSource = (0, extensionsHelper_1.isLocalPath)(extensionName);
126
126
  const spec = (_a = extVersion === null || extVersion === void 0 ? void 0 : extVersion.spec) !== null && _a !== void 0 ? _a : source === null || source === void 0 ? void 0 : source.spec;
@@ -134,7 +134,7 @@ async function installToManifest(options) {
134
134
  }
135
135
  const paramBindingOptions = await paramHelper.getParams({
136
136
  projectId,
137
- paramSpecs: spec.params,
137
+ paramSpecs: spec.params.concat((_b = spec.systemParams) !== null && _b !== void 0 ? _b : []),
138
138
  nonInteractive,
139
139
  paramsEnvPath,
140
140
  instanceId,
@@ -8,7 +8,9 @@ const error_1 = require("../../error");
8
8
  const extensionsHelper_1 = require("../../extensions/extensionsHelper");
9
9
  const logger_1 = require("../../logger");
10
10
  const manifest_1 = require("../../extensions/manifest");
11
+ const paramHelper_1 = require("../../extensions/paramHelper");
11
12
  const specHelper_1 = require("../../extensions/emulator/specHelper");
13
+ const functional_1 = require("../../functional");
12
14
  async function getExtensionVersion(i) {
13
15
  if (!i.extensionVersion) {
14
16
  if (!i.ref) {
@@ -49,9 +51,11 @@ exports.getExtensionSpec = getExtensionSpec;
49
51
  async function have(projectId) {
50
52
  const instances = await extensionsApi.listInstances(projectId);
51
53
  return instances.map((i) => {
54
+ var _a;
52
55
  const dep = {
53
56
  instanceId: i.name.split("/").pop(),
54
57
  params: i.config.params,
58
+ systemParams: (_a = i.config.systemParams) !== null && _a !== void 0 ? _a : {},
55
59
  allowedEventTypes: i.config.allowedEventTypes,
56
60
  eventarcChannel: i.config.eventarcChannel,
57
61
  etag: i.etag,
@@ -71,7 +75,7 @@ async function want(args) {
71
75
  for (const e of Object.entries(args.extensions)) {
72
76
  try {
73
77
  const instanceId = e[0];
74
- const params = (0, manifest_1.readInstanceParam)({
78
+ const rawParams = (0, manifest_1.readInstanceParam)({
75
79
  projectDir: args.projectDir,
76
80
  instanceId,
77
81
  projectId: args.projectId,
@@ -80,18 +84,20 @@ async function want(args) {
80
84
  checkLocal: args.emulatorMode,
81
85
  });
82
86
  const autoPopulatedParams = await (0, extensionsHelper_1.getFirebaseProjectParams)(args.projectId, args.emulatorMode);
83
- const subbedParams = (0, extensionsHelper_1.substituteParams)(params, autoPopulatedParams);
84
- const allowedEventTypes = subbedParams.ALLOWED_EVENT_TYPES !== undefined
85
- ? subbedParams.ALLOWED_EVENT_TYPES.split(",").filter((e) => e !== "")
87
+ const subbedParams = (0, extensionsHelper_1.substituteParams)(rawParams, autoPopulatedParams);
88
+ const [systemParams, params] = (0, functional_1.partitionRecord)(subbedParams, paramHelper_1.isSystemParam);
89
+ const allowedEventTypes = params.ALLOWED_EVENT_TYPES !== undefined
90
+ ? params.ALLOWED_EVENT_TYPES.split(",").filter((e) => e !== "")
86
91
  : undefined;
87
- const eventarcChannel = subbedParams.EVENTARC_CHANNEL;
88
- delete subbedParams["EVENTARC_CHANNEL"];
89
- delete subbedParams["ALLOWED_EVENT_TYPES"];
92
+ const eventarcChannel = params.EVENTARC_CHANNEL;
93
+ delete params["EVENTARC_CHANNEL"];
94
+ delete params["ALLOWED_EVENT_TYPES"];
90
95
  if ((0, extensionsHelper_1.isLocalPath)(e[1])) {
91
96
  instanceSpecs.push({
92
97
  instanceId,
93
98
  localPath: e[1],
94
- params: subbedParams,
99
+ params,
100
+ systemParams,
95
101
  allowedEventTypes: allowedEventTypes,
96
102
  eventarcChannel: eventarcChannel,
97
103
  });
@@ -102,7 +108,8 @@ async function want(args) {
102
108
  instanceSpecs.push({
103
109
  instanceId,
104
110
  ref,
105
- params: subbedParams,
111
+ params,
112
+ systemParams,
106
113
  allowedEventTypes: allowedEventTypes,
107
114
  eventarcChannel: eventarcChannel,
108
115
  });
@@ -32,6 +32,7 @@ function createExtensionInstanceTask(projectId, instanceSpec, validateOnly = fal
32
32
  projectId,
33
33
  instanceId: instanceSpec.instanceId,
34
34
  params: instanceSpec.params,
35
+ systemParams: instanceSpec.systemParams,
35
36
  extensionVersionRef: refs.toExtensionVersionRef(instanceSpec.ref),
36
37
  allowedEventTypes: instanceSpec.allowedEventTypes,
37
38
  eventarcChannel: instanceSpec.eventarcChannel,
@@ -44,6 +45,7 @@ function createExtensionInstanceTask(projectId, instanceSpec, validateOnly = fal
44
45
  projectId,
45
46
  instanceId: instanceSpec.instanceId,
46
47
  params: instanceSpec.params,
48
+ systemParams: instanceSpec.systemParams,
47
49
  extensionSource,
48
50
  allowedEventTypes: instanceSpec.allowedEventTypes,
49
51
  eventarcChannel: instanceSpec.eventarcChannel,
@@ -71,6 +73,7 @@ function updateExtensionInstanceTask(projectId, instanceSpec, validateOnly = fal
71
73
  instanceId: instanceSpec.instanceId,
72
74
  extRef: refs.toExtensionVersionRef(instanceSpec.ref),
73
75
  params: instanceSpec.params,
76
+ systemParams: instanceSpec.systemParams,
74
77
  canEmitEvents: !!instanceSpec.allowedEventTypes,
75
78
  allowedEventTypes: instanceSpec.allowedEventTypes,
76
79
  eventarcChannel: instanceSpec.eventarcChannel,
@@ -84,6 +87,8 @@ function updateExtensionInstanceTask(projectId, instanceSpec, validateOnly = fal
84
87
  instanceId: instanceSpec.instanceId,
85
88
  extensionSource,
86
89
  validateOnly,
90
+ params: instanceSpec.params,
91
+ systemParams: instanceSpec.systemParams,
87
92
  canEmitEvents: !!instanceSpec.allowedEventTypes,
88
93
  allowedEventTypes: instanceSpec.allowedEventTypes,
89
94
  eventarcChannel: instanceSpec.eventarcChannel,
@@ -109,6 +114,7 @@ function configureExtensionInstanceTask(projectId, instanceSpec, validateOnly =
109
114
  projectId,
110
115
  instanceId: instanceSpec.instanceId,
111
116
  params: instanceSpec.params,
117
+ systemParams: instanceSpec.systemParams,
112
118
  canEmitEvents: !!instanceSpec.allowedEventTypes,
113
119
  allowedEventTypes: instanceSpec.allowedEventTypes,
114
120
  eventarcChannel: instanceSpec.eventarcChannel,
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.prepareFunctionsUpload = exports.getFunctionsConfig = void 0;
3
+ exports.convertToSortedKeyValueArray = exports.prepareFunctionsUpload = exports.getFunctionsConfig = void 0;
4
4
  const archiver = require("archiver");
5
5
  const clc = require("colorette");
6
6
  const filesize = require("filesize");
@@ -66,8 +66,9 @@ async function packageSource(sourceDir, config, runtimeConfig) {
66
66
  });
67
67
  }
68
68
  if (typeof runtimeConfig !== "undefined") {
69
+ const runtimeConfigHashString = JSON.stringify(convertToSortedKeyValueArray(runtimeConfig));
70
+ hashes.push(runtimeConfigHashString);
69
71
  const runtimeConfigString = JSON.stringify(runtimeConfig, null, 2);
70
- hashes.push(runtimeConfigString);
71
72
  archive.append(runtimeConfigString, {
72
73
  name: CONFIG_DEST_FILE,
73
74
  mode: 420,
@@ -94,3 +95,13 @@ async function prepareFunctionsUpload(sourceDir, config, runtimeConfig) {
94
95
  return packageSource(sourceDir, config, runtimeConfig);
95
96
  }
96
97
  exports.prepareFunctionsUpload = prepareFunctionsUpload;
98
+ function convertToSortedKeyValueArray(config) {
99
+ if (typeof config !== "object" || config === null)
100
+ return config;
101
+ return Object.keys(config)
102
+ .sort()
103
+ .map((key) => {
104
+ return { key, value: convertToSortedKeyValueArray(config[key]) };
105
+ });
106
+ }
107
+ exports.convertToSortedKeyValueArray = convertToSortedKeyValueArray;
@@ -5,7 +5,7 @@ const node = require("./node");
5
5
  const validate = require("../validate");
6
6
  const error_1 = require("../../../error");
7
7
  const RUNTIMES = ["nodejs10", "nodejs12", "nodejs14", "nodejs16", "nodejs18"];
8
- const EXPERIMENTAL_RUNTIMES = ["go113"];
8
+ const EXPERIMENTAL_RUNTIMES = [];
9
9
  const DEPRECATED_RUNTIMES = ["nodejs6", "nodejs8"];
10
10
  function isDeprecatedRuntime(runtime) {
11
11
  return DEPRECATED_RUNTIMES.includes(runtime);
@@ -23,7 +23,6 @@ const MESSAGE_FRIENDLY_RUNTIMES = {
23
23
  nodejs14: "Node.js 14",
24
24
  nodejs16: "Node.js 16",
25
25
  nodejs18: "Node.js 18",
26
- go113: "Go 1.13",
27
26
  };
28
27
  function getHumanFriendlyRuntimeName(runtime) {
29
28
  return MESSAGE_FRIENDLY_RUNTIMES[runtime] || runtime;
@@ -63,7 +63,9 @@ class Delegate {
63
63
  if (Object.keys(config || {}).length) {
64
64
  env.CLOUD_RUNTIME_CONFIG = JSON.stringify(config);
65
65
  }
66
- const childProcess = spawn("./node_modules/.bin/firebase-functions", [this.sourceDir], {
66
+ const sdkPath = require.resolve("firebase-functions", { paths: [this.sourceDir] });
67
+ const binPath = sdkPath.substring(0, sdkPath.lastIndexOf("node_modules") + 12);
68
+ const childProcess = spawn(path.join(binPath, ".bin", "firebase-functions"), [this.sourceDir], {
67
69
  env,
68
70
  cwd: this.sourceDir,
69
71
  stdio: ["ignore", "pipe", "inherit"],
@@ -502,7 +502,7 @@ function createSessionCookie(state, reqBody) {
502
502
  const { payload } = parseIdToken(state, reqBody.idToken);
503
503
  const issuedAt = (0, utils_1.toUnixTimestamp)(new Date());
504
504
  const expiresAt = issuedAt + validDuration;
505
- const sessionCookie = (0, jsonwebtoken_1.sign)(Object.assign(Object.assign({}, payload), { iat: issuedAt, exp: expiresAt, iss: `https://session.firebase.google.com/${payload.aud}` }), "", {
505
+ const sessionCookie = (0, jsonwebtoken_1.sign)(Object.assign(Object.assign({}, payload), { iat: issuedAt, exp: expiresAt, iss: `https://session.firebase.google.com/${payload.aud}` }), "fake-secret", {
506
506
  algorithm: "none",
507
507
  });
508
508
  return { sessionCookie };
@@ -1552,7 +1552,7 @@ function generateJwt(user, { projectId, signInProvider, expiresInSeconds, extraC
1552
1552
  tenant: tenantId,
1553
1553
  sign_in_attributes: signInAttributes,
1554
1554
  } });
1555
- const jwtStr = (0, jsonwebtoken_1.sign)(customPayloadFields, "", {
1555
+ const jwtStr = (0, jsonwebtoken_1.sign)(customPayloadFields, "fake-secret", {
1556
1556
  algorithm: "none",
1557
1557
  expiresIn: expiresInSeconds,
1558
1558
  subject: user.localId,
@@ -2129,7 +2129,7 @@ function generateBlockingFunctionJwt(state, event, url, timeoutMs, user, options
2129
2129
  if (state.shouldForwardCredentialToBlockingFunction("refreshToken")) {
2130
2130
  jwt.oauth_refresh_token = oauthTokens.oauthRefreshToken;
2131
2131
  }
2132
- const jwtStr = (0, jsonwebtoken_1.sign)(jwt, "", {
2132
+ const jwtStr = (0, jsonwebtoken_1.sign)(jwt, "fake-secret", {
2133
2133
  algorithm: "none",
2134
2134
  });
2135
2135
  return jwtStr;
@@ -28,9 +28,9 @@ const EMULATOR_UPDATE_DETAILS = {
28
28
  expectedChecksum: "4f41d24a3c0f3b55ea22804a424cc0ee",
29
29
  },
30
30
  storage: {
31
- version: "1.1.2",
32
- expectedSize: 47028740,
33
- expectedChecksum: "983b4415b1e72b109864f1b8e7ea7546",
31
+ version: "1.1.3",
32
+ expectedSize: 52892936,
33
+ expectedChecksum: "2ca11ec1193003bea89f806cc085fa25",
34
34
  },
35
35
  ui: experiments.isEnabled("emulatoruisnapshot")
36
36
  ? { version: "SNAPSHOT", expectedSize: -1, expectedChecksum: "" }
@@ -229,6 +229,7 @@ function createFirebaseEndpoints(emulator) {
229
229
  throw err;
230
230
  }
231
231
  res.header("X-Goog-Upload-Size-Received", upload.size.toString());
232
+ res.header("x-goog-upload-status", upload.status);
232
233
  return res.sendStatus(200);
233
234
  }
234
235
  if (uploadCommand === "cancel") {
@@ -52,7 +52,7 @@ class DefaultStorageRulesManager {
52
52
  this._logger.log("WARN", `${parsedIssue.description_.replace(/\.$/, "")} in ${parsedIssue.sourcePosition_.fileName_}:${parsedIssue.sourcePosition_.line_}`);
53
53
  }
54
54
  catch (_a) {
55
- this._logger.log("WARN", issue);
55
+ this._logger.logLabeled("WARN", "storage", issue);
56
56
  }
57
57
  });
58
58
  return issues;
@@ -24,8 +24,8 @@ function createApp(defaultProjectId, emulator) {
24
24
  exposedHeaders: [
25
25
  "content-type",
26
26
  "x-firebase-storage-version",
27
+ "X-Goog-Upload-Size-Received",
27
28
  "x-goog-upload-url",
28
- "x-goog-upload-status",
29
29
  "x-goog-upload-command",
30
30
  "x-gupload-uploadid",
31
31
  "x-goog-upload-header-content-length",
@@ -11,9 +11,9 @@ var UploadType;
11
11
  })(UploadType = exports.UploadType || (exports.UploadType = {}));
12
12
  var UploadStatus;
13
13
  (function (UploadStatus) {
14
- UploadStatus[UploadStatus["ACTIVE"] = 0] = "ACTIVE";
15
- UploadStatus[UploadStatus["CANCELLED"] = 1] = "CANCELLED";
16
- UploadStatus[UploadStatus["FINISHED"] = 2] = "FINISHED";
14
+ UploadStatus["ACTIVE"] = "active";
15
+ UploadStatus["CANCELLED"] = "cancelled";
16
+ UploadStatus["FINISHED"] = "final";
17
17
  })(UploadStatus = exports.UploadStatus || (exports.UploadStatus = {}));
18
18
  class UploadNotActiveError extends Error {
19
19
  }
@@ -34,9 +34,10 @@ async function createInstanceHelper(projectId, instanceId, config, validateOnly
34
34
  return pollRes;
35
35
  }
36
36
  async function createInstance(args) {
37
- var _a, _b;
37
+ var _a, _b, _c;
38
38
  const config = {
39
39
  params: args.params,
40
+ systemParams: (_a = args.systemParams) !== null && _a !== void 0 ? _a : {},
40
41
  allowedEventTypes: args.allowedEventTypes,
41
42
  eventarcChannel: args.eventarcChannel,
42
43
  };
@@ -44,12 +45,12 @@ async function createInstance(args) {
44
45
  throw new error_1.FirebaseError("ExtensionSource and ExtensionVersion both provided, but only one should be.");
45
46
  }
46
47
  else if (args.extensionSource) {
47
- config.source = { name: (_a = args.extensionSource) === null || _a === void 0 ? void 0 : _a.name };
48
+ config.source = { name: (_b = args.extensionSource) === null || _b === void 0 ? void 0 : _b.name };
48
49
  }
49
50
  else if (args.extensionVersionRef) {
50
51
  const ref = refs.parse(args.extensionVersionRef);
51
52
  config.extensionRef = refs.toExtensionRef(ref);
52
- config.extensionVersion = (_b = ref.version) !== null && _b !== void 0 ? _b : "";
53
+ config.extensionVersion = (_c = ref.version) !== null && _c !== void 0 ? _c : "";
53
54
  }
54
55
  else {
55
56
  throw new error_1.FirebaseError("No ExtensionVersion or ExtensionSource provided but one is required.");
@@ -128,6 +129,10 @@ async function configureInstance(args) {
128
129
  reqBody.data.config.eventarcChannel = args.eventarcChannel;
129
130
  }
130
131
  reqBody.updateMask += ",config.allowed_event_types,config.eventarc_channel";
132
+ if (args.systemParams) {
133
+ reqBody.data.config.systemParams = args.systemParams;
134
+ reqBody.updateMask += ",config.system_params";
135
+ }
131
136
  return patchInstance(reqBody);
132
137
  }
133
138
  exports.configureInstance = configureInstance;
@@ -143,6 +148,10 @@ async function updateInstance(args) {
143
148
  body.config.params = args.params;
144
149
  updateMask += ",config.params";
145
150
  }
151
+ if (args.systemParams) {
152
+ body.config.systemParams = args.systemParams;
153
+ updateMask += ",config.system_params";
154
+ }
146
155
  if (args.canEmitEvents) {
147
156
  if (args.allowedEventTypes === undefined || args.eventarcChannel === undefined) {
148
157
  throw new error_1.FirebaseError(`This instance is configured to emit events, but either allowed event types or eventarc channel is undefined.`);
@@ -174,6 +183,10 @@ async function updateInstanceFromRegistry(args) {
174
183
  body.config.params = args.params;
175
184
  updateMask += ",config.params";
176
185
  }
186
+ if (args.systemParams) {
187
+ body.config.systemParams = args.systemParams;
188
+ updateMask += ",config.system_params";
189
+ }
177
190
  if (args.canEmitEvents) {
178
191
  if (args.allowedEventTypes === undefined || args.eventarcChannel === undefined) {
179
192
  throw new error_1.FirebaseError(`This instance is configured to emit events, but either allowed event types or eventarc channel is undefined.`);
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.readEnvFile = exports.promptForNewParams = exports.getParamsForUpdate = exports.getParams = exports.getParamsWithCurrentValuesAsDefaults = exports.setNewDefaults = exports.buildBindingOptionsWithBaseValue = exports.getBaseParamBindings = void 0;
3
+ exports.isSystemParam = exports.readEnvFile = exports.promptForNewParams = exports.getParamsForUpdate = exports.getParams = exports.getParamsWithCurrentValuesAsDefaults = exports.setNewDefaults = exports.buildBindingOptionsWithBaseValue = exports.getBaseParamBindings = void 0;
4
4
  const path = require("path");
5
5
  const clc = require("colorette");
6
6
  const fs = require("fs-extra");
@@ -172,3 +172,8 @@ function readEnvFile(envPath) {
172
172
  return result.envs;
173
173
  }
174
174
  exports.readEnvFile = readEnvFile;
175
+ function isSystemParam(paramName) {
176
+ const regex = /^firebaseextensions\.[a-zA-Z0-9\.]*\//;
177
+ return regex.test(paramName);
178
+ }
179
+ exports.isSystemParam = isSystemParam;
package/lib/functional.js CHANGED
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.nullsafeVisitor = exports.mapObject = exports.partition = exports.assertExhaustive = exports.zipIn = exports.zip = exports.reduceFlat = exports.flatten = exports.flattenArray = exports.flattenObject = void 0;
3
+ exports.nullsafeVisitor = exports.mapObject = exports.partitionRecord = exports.partition = exports.assertExhaustive = exports.zipIn = exports.zip = exports.reduceFlat = exports.flatten = exports.flattenArray = exports.flattenObject = void 0;
4
4
  function* flattenObject(obj) {
5
5
  function* helper(path, obj) {
6
6
  for (const [k, v] of Object.entries(obj)) {
@@ -56,13 +56,20 @@ function assertExhaustive(val) {
56
56
  throw new Error(`Never has a value (${val}). This should be impossible`);
57
57
  }
58
58
  exports.assertExhaustive = assertExhaustive;
59
- function partition(arr, callbackFn) {
59
+ function partition(arr, predicate) {
60
60
  return arr.reduce((acc, elem) => {
61
- acc[callbackFn(elem) ? 0 : 1].push(elem);
61
+ acc[predicate(elem) ? 0 : 1].push(elem);
62
62
  return acc;
63
63
  }, [[], []]);
64
64
  }
65
65
  exports.partition = partition;
66
+ function partitionRecord(rec, predicate) {
67
+ return Object.entries(rec).reduce((acc, [key, val]) => {
68
+ acc[predicate(key, val) ? 0 : 1][key] = val;
69
+ return acc;
70
+ }, [{}, {}]);
71
+ }
72
+ exports.partitionRecord = partitionRecord;
66
73
  function mapObject(input, transform) {
67
74
  const result = {};
68
75
  for (const [k, v] of Object.entries(input)) {
@@ -32,7 +32,7 @@ const RESERVED_KEYS = [
32
32
  ];
33
33
  const LINE_RE = new RegExp("^" +
34
34
  "\\s*" +
35
- "(\\w+)" +
35
+ "([\\w./]+)" +
36
36
  "\\s*=[\\f\\t\\v]*" +
37
37
  "(" +
38
38
  "\\s*'(?:\\\\'|[^'])*'|" +