@fynjs/run 1.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.
package/cli/xrun.js ADDED
@@ -0,0 +1,8 @@
1
+ import { xrunMain } from "./xrun-main.js";
2
+
3
+ //
4
+ // Async: task files load through `import()` so a task file can use top-level await, which
5
+ // `require` can never support. Callers must await this - bin/xrun.js does, and so must any
6
+ // programmatic caller that wants to observe the run.
7
+ //
8
+ export default xrunMain;
package/lib/chalk.js ADDED
@@ -0,0 +1,11 @@
1
+ //
2
+ // chalk is ESM-only. This package is ESM too, so importing it is unremarkable - this module
3
+ // stays only because a dozen call sites already point at it, and it is the one place to change
4
+ // if the color library is ever swapped.
5
+ //
6
+ // Bound explicitly rather than `export { default } from "chalk"`: a bare re-export can reach
7
+ // consumers as a module namespace, and call sites assign to `chalk.level`.
8
+ //
9
+ import chalk from "chalk";
10
+
11
+ export default chalk;
@@ -0,0 +1,145 @@
1
+ /**
2
+ * CliContext encapsulates all CLI-related information from command line parsing
3
+ * This provides a clean interface for accessing command arguments, options, and metadata
4
+ * throughout the execution pipeline without tight coupling to CLI parsing details.
5
+ */
6
+ class CliContext {
7
+ constructor(cmdArgs) {
8
+ // Full cmdArgs from CLI parsing
9
+ this._cmdArgs = cmdArgs;
10
+ this._parsed = cmdArgs.parsed;
11
+ this._subCmdNodes = cmdArgs.cmdNodes || cmdArgs.parsed?.command?.subCmdNodes || {};
12
+ this._opts = cmdArgs.opts;
13
+ this._tasks = cmdArgs.tasks || [];
14
+ this._searchResult = cmdArgs.searchResult;
15
+ this._remainingArgs = cmdArgs.parsed?._ || [];
16
+ }
17
+
18
+ /**
19
+ * Get command information for a specific task
20
+ * @param {string} taskName - Name of the task
21
+ * @returns {Object} Task command object with argv, opts, etc.
22
+ */
23
+ getTaskCommand(taskName) {
24
+ return this._subCmdNodes[taskName] || {};
25
+ }
26
+
27
+ /**
28
+ * Get argv array for a specific task
29
+ * @param {string} taskName - Name of the task
30
+ * @returns {Array} Task arguments array
31
+ */
32
+ getTaskArgv(taskName) {
33
+ return this.getTaskCommand(taskName).argv || [];
34
+ }
35
+
36
+ /**
37
+ * Get global command line options
38
+ * @returns {Object} Global options object
39
+ */
40
+ getGlobalOptions() {
41
+ return this._opts;
42
+ }
43
+
44
+ /**
45
+ * Get command metadata (jsonMeta)
46
+ * @returns {Object} Command metadata
47
+ */
48
+ getMetadata() {
49
+ return this._parsed?.command?.jsonMeta || {};
50
+ }
51
+
52
+ /**
53
+ * Get the list of tasks to execute
54
+ * @returns {Array} Tasks array
55
+ */
56
+ getTasks() {
57
+ return this._tasks;
58
+ }
59
+
60
+ /**
61
+ * Get search result information
62
+ * @returns {Object} Search result with found files, directories, etc.
63
+ */
64
+ getSearchResult() {
65
+ return this._searchResult;
66
+ }
67
+
68
+ /**
69
+ * Check if quiet mode is enabled
70
+ * @returns {boolean} True if quiet mode
71
+ */
72
+ isQuiet() {
73
+ return this._opts.quiet;
74
+ }
75
+
76
+ /**
77
+ * Check if serial execution is requested
78
+ * @returns {boolean} True if serial execution
79
+ */
80
+ isSerial() {
81
+ return this._opts.serial;
82
+ }
83
+
84
+ /**
85
+ * Get stop on error setting
86
+ * @returns {string|boolean} Stop on error setting
87
+ */
88
+ getStopOnError() {
89
+ return this._opts.soe;
90
+ }
91
+
92
+ /**
93
+ * Get arguments that came after "--" in the CLI
94
+ * These are stored in parsed._ by @fynjs/cli-args
95
+ * @returns {Array} Arguments after "--"
96
+ */
97
+ getRemainingArgs() {
98
+ return this._remainingArgs;
99
+ }
100
+
101
+ /**
102
+ * Check if there are any remaining arguments after "--"
103
+ * @returns {boolean} True if there are remaining arguments
104
+ */
105
+ hasRemainingArgs() {
106
+ return this._remainingArgs && this._remainingArgs.length > 0;
107
+ }
108
+
109
+ /**
110
+ * Get raw command args for backward compatibility
111
+ * @returns {Object} Raw command args object
112
+ */
113
+ getRawCmdArgs() {
114
+ return this._cmdArgs;
115
+ }
116
+
117
+ /**
118
+ * Get raw parsed command for backward compatibility
119
+ * @returns {Object} Raw parsed command object
120
+ */
121
+ getRawParsed() {
122
+ return this._parsed;
123
+ }
124
+
125
+ /**
126
+ * Get all task names that have command nodes
127
+ * @returns {Array} Array of task names
128
+ */
129
+ getAllTaskNames() {
130
+ return Object.keys(this._subCmdNodes);
131
+ }
132
+
133
+ /**
134
+ * Check if a given task is the last task from CLI arguments
135
+ * This is used to determine where to append remaining args
136
+ * @param {string} taskName - Name of the task to check
137
+ * @param {string} taskType - Type of task (e.g., 'shell', 'function')
138
+ * @returns {boolean} True if this is the last CLI task and it's a shell task
139
+ */
140
+ isLastTask(taskName) {
141
+ return this._tasks.at(-1) === taskName;
142
+ }
143
+ }
144
+
145
+ export { CliContext };
@@ -0,0 +1,32 @@
1
+ const SERIAL_SYM = Symbol("serial");
2
+ const CONCURRENT_SYM = Symbol("concurrent");
3
+
4
+ export const NAMESPACE = "/";
5
+ export const NS_SEP = "/";
6
+ export const SERIAL_SIG = [".", "-s", "--serial", "--ser", SERIAL_SYM];
7
+ export const CONCURRENT_SIG = ["--concurrent", "-c", "--conc", CONCURRENT_SYM];
8
+ export const ANON_SHELL_SIG = ["~$", "~@"];
9
+ export const ANON_SHELL_OPT_SIG = [`~(`];
10
+ export const ANON_SHELL_OPT_CLOSE_SIG = [`)$`, ")@"];
11
+ export const SHELL_FLAGS = ["tty", "spawn", "sync", "noenv", "npm"];
12
+ export const STR_ARRAY_SIG = "~[";
13
+ export const STOP_SYM = Symbol("xrun.stop");
14
+ export const INTERNALS = Symbol("xrun.internals");
15
+
16
+ export { CONCURRENT_SYM, SERIAL_SYM };
17
+
18
+ export default {
19
+ NAMESPACE,
20
+ NS_SEP,
21
+ SERIAL_SIG,
22
+ CONCURRENT_SIG,
23
+ ANON_SHELL_SIG,
24
+ ANON_SHELL_OPT_SIG,
25
+ ANON_SHELL_OPT_CLOSE_SIG,
26
+ SHELL_FLAGS,
27
+ STR_ARRAY_SIG,
28
+ CONCURRENT_SYM,
29
+ SERIAL_SYM,
30
+ STOP_SYM,
31
+ INTERNALS
32
+ };
@@ -0,0 +1,10 @@
1
+ export default tag => {
2
+ return (
3
+ (tag ? `${tag}_` : "") +
4
+ Math.random()
5
+ .toString(36)
6
+ .substring(2, 12) +
7
+ "_" +
8
+ Date.now().toString(36)
9
+ );
10
+ };
package/lib/index.js ADDED
@@ -0,0 +1,12 @@
1
+ import container from "./xrun-instance.js";
2
+
3
+ const xrun = container.xrun;
4
+
5
+ export default xrun;
6
+
7
+ //
8
+ // Keep `require("@fynjs/run")` returning the xrun instance itself rather than the module
9
+ // namespace, so a CommonJS task file - still a first class way to use this - does not have to
10
+ // learn about `.default` because the package moved to ESM.
11
+ //
12
+ export { xrun as "module.exports" };
package/lib/logger.js ADDED
@@ -0,0 +1,105 @@
1
+ import chalk from "./chalk.js";
2
+ const MSEC_IN_SECOND = 1000;
3
+ const MSEC_IN_MINUTE = 60 * MSEC_IN_SECOND;
4
+
5
+ const pad2 = x => {
6
+ return (x < 10 ? "0" : "") + x;
7
+ };
8
+
9
+ class Logger {
10
+ constructor() {
11
+ this.coloring(true);
12
+ this.buffering(true);
13
+ this.quiet(true);
14
+ }
15
+
16
+ quiet(q) {
17
+ this._quiet = q;
18
+ }
19
+
20
+ buffering(f) {
21
+ if (f) {
22
+ if (!this._buf) this._buf = [];
23
+ } else {
24
+ this._buf = undefined;
25
+ }
26
+ }
27
+
28
+ resetBuffer(flush, buffering) {
29
+ if (this._buf) {
30
+ const buf = this._buf;
31
+ this._buf = [];
32
+ if (flush && !this._quiet) {
33
+ buf.forEach(l => this.write(`${l}\n`));
34
+ }
35
+ this.buffering(buffering);
36
+ }
37
+ }
38
+
39
+ coloring(f) {
40
+ if (f) {
41
+ this._L = `${chalk.magenta("[")}`;
42
+ this._R = `${chalk.magenta("]")}`;
43
+ this._c = true;
44
+ } else {
45
+ this._L = `[`;
46
+ this._R = `]`;
47
+ this._c = false;
48
+ }
49
+ }
50
+
51
+ get buffer() {
52
+ return this._buf || [];
53
+ }
54
+
55
+ timestamp() {
56
+ const d = new Date();
57
+ const ts = `${pad2(d.getHours())}:${pad2(d.getMinutes())}:${pad2(d.getSeconds())}`;
58
+ return ts;
59
+ }
60
+
61
+ _ts() {
62
+ return this._c
63
+ ? `${this._L}${chalk.gray(this.timestamp())}${this._R}`
64
+ : `${this._L}${this.timestamp()}${this._R}`;
65
+ }
66
+
67
+ formatElapse(elapse) {
68
+ if (elapse >= MSEC_IN_MINUTE) {
69
+ const min = elapse / MSEC_IN_MINUTE;
70
+ return `${min.toFixed(2)} min`;
71
+ } else if (elapse >= MSEC_IN_SECOND) {
72
+ const sec = elapse / MSEC_IN_SECOND;
73
+ return `${sec.toFixed(2)} sec`;
74
+ } else {
75
+ return `${elapse} ms`;
76
+ }
77
+ }
78
+
79
+ log() {
80
+ const msg = Array.prototype.join.call(arguments, " ");
81
+ const output = `${this._ts()} ${msg}`;
82
+
83
+ if (this._buf) {
84
+ this._buf.push(output);
85
+ } else if (!this._quiet) {
86
+ this.write(`${output}\n`);
87
+ }
88
+ }
89
+
90
+ error(...args) {
91
+ const x = this._quiet;
92
+ this._quiet = false;
93
+ this.log(...args);
94
+ this.quiet(x);
95
+ }
96
+
97
+ write(output) {
98
+ process.stdout.write(output);
99
+ }
100
+ }
101
+
102
+ const logger = new Logger();
103
+ logger.pad2 = pad2;
104
+
105
+ export default logger;
@@ -0,0 +1,76 @@
1
+ import each from "lodash.foreach";
2
+ import assert from "assert";
3
+
4
+ class NSOrder {
5
+ constructor() {
6
+ this._namespaces = [];
7
+ this._overrides = {};
8
+ }
9
+
10
+ //
11
+ // make name order before overrides (string or array of string)
12
+ //
13
+ // the ordering is kept by an integer
14
+ // each new namespace is reset with a init value (default 1)
15
+ // then the max of values of its overrides is looked up
16
+ // if its value is not > max, then it's set to max + 1
17
+ // process repeated until all override max are smaller
18
+ //
19
+ // finally sort namespace by order value
20
+ //
21
+ add(name, overrides, priority = 1) {
22
+ let overrideMap = this._overrides[name];
23
+ if (!overrideMap) {
24
+ overrideMap = this._overrides[name] = { initValue: priority, others: [] };
25
+ this._namespaces.push(name);
26
+ }
27
+
28
+ if (!overrides) {
29
+ overrides = [];
30
+ } else if (!Array.isArray(overrides)) {
31
+ overrides = [overrides];
32
+ }
33
+
34
+ overrideMap.others = overrideMap.others.concat(overrides);
35
+
36
+ // do a first level circular check
37
+ each(this._overrides, (ov, ns) => {
38
+ if (ns !== name && overrideMap.others.indexOf(ns) >= 0 && ov.others.indexOf(name) >= 0) {
39
+ throw new Error(`circular namespace override between '${name}' and '${ns}'`);
40
+ }
41
+ });
42
+
43
+ // reset all values
44
+ each(this._overrides, v => (v.value = v.initValue));
45
+
46
+ // assign value base on override map
47
+ let done;
48
+ let n = 0;
49
+ do {
50
+ done = true;
51
+ each(this._overrides, ov => {
52
+ if (ov.others.length === 0) return;
53
+ const max = ov.others.reduce(
54
+ (max, oname) =>
55
+ Math.max((this._overrides[oname] && this._overrides[oname].value) || 0, max),
56
+ 1
57
+ );
58
+ if (ov.value <= max) {
59
+ done = false;
60
+ ov.value = 1 + max;
61
+ }
62
+ });
63
+ n++;
64
+ assert(
65
+ n < 10,
66
+ "calculating namespace order looped too many times, there may be circular overrides"
67
+ );
68
+ } while (!done);
69
+
70
+ this._namespaces.sort((a, b) => this._overrides[b].value - this._overrides[a].value);
71
+
72
+ return this._namespaces;
73
+ }
74
+ }
75
+
76
+ export default NSOrder;
@@ -0,0 +1,105 @@
1
+ import stringify from "../stringify.js";
2
+ import chalk from "../chalk.js";
3
+ import assert from "assert";
4
+ import Path from "path";
5
+ import xsh from "xsh";
6
+
7
+ const replaceCwd = p => xsh.pathCwdNm.replace(p, null, "g");
8
+
9
+ const stringifyTask = x => {
10
+ x = stringify(x);
11
+ /* istanbul ignore next */
12
+ if (Path.sep === "\\") {
13
+ /* istanbul ignore next */
14
+ x = x.replace(/\\\\/g, "\\");
15
+ }
16
+ return replaceCwd(x);
17
+ };
18
+
19
+ const printNSTasks = (title, names, tasks) => {
20
+ let guideChar = ".";
21
+ let nameColor = "cyan";
22
+
23
+ const taskType = task => task !== null && task !== undefined && task.constructor.name;
24
+
25
+ const maxNameLen =
26
+ 2 +
27
+ names.reduce((l, x) => {
28
+ const task = tasks[x];
29
+ if (taskType(task) === "Object" && !task.desc) {
30
+ return l;
31
+ }
32
+ return x.length > l ? x.length : l;
33
+ }, 0);
34
+
35
+ const printTask = name => {
36
+ const task = tasks[name];
37
+ const tof = taskType(task);
38
+
39
+ const paddedArr = new Array(Math.max(1, maxNameLen - name.length));
40
+ const paddedName = chalk[nameColor](` ${name} ${paddedArr.join(guideChar)}`);
41
+ if (tof === "String") {
42
+ console.log(paddedName, chalk.magenta(replaceCwd(task)));
43
+ } else if (tof === "Array") {
44
+ console.log(paddedName, chalk.green(`${stringifyTask(task)}`));
45
+ } else if (tof === "Object") {
46
+ if (!task.desc) {
47
+ return;
48
+ }
49
+ console.log(paddedName, chalk.yellow(task.desc));
50
+ const spacePad = new Array(maxNameLen + 4).join(" ");
51
+ if (task.task) {
52
+ console.log(chalk.dim.green(`${spacePad} tasks: ${stringifyTask(task.task)}`));
53
+ }
54
+ if (task.dep) {
55
+ console.log(chalk.dim.cyan(`${spacePad} deps: ${stringifyTask(task.dep)}`));
56
+ }
57
+ } else if (tof === "Function") {
58
+ console.log(paddedName, "function", task.name);
59
+ } else if (tof === "XTaskSpec") {
60
+ console.log(paddedName, chalk.magenta(replaceCwd(task.toString())));
61
+ } else {
62
+ console.log(paddedName, chalk.red(`Unknown task type ${tof}`));
63
+ }
64
+ guideChar = guideChar === "." ? "-" : ".";
65
+ nameColor = nameColor === "cyan" ? "blue" : "cyan";
66
+ };
67
+
68
+ console.log(chalk.underline(title));
69
+ console.log("");
70
+
71
+ names.sort().forEach(printTask);
72
+ console.log("");
73
+ };
74
+
75
+ function printTasks(xtasks) {
76
+ const namespaces = xtasks._namespaces;
77
+ const tasks = xtasks._tasks;
78
+ namespaces.forEach(n => {
79
+ const nTasks = tasks[n];
80
+ assert(nTasks, `Task namespace ${n} is falsy`);
81
+ if (Object.keys(nTasks).length === 0) {
82
+ return;
83
+ }
84
+ console.log(chalk.inverse.bold.red(`Namespace '${n}'`));
85
+ const taskNames = Object.keys(nTasks).reduce((an, tn) => {
86
+ let lbl;
87
+ if (tn.match(/(^\.)|([$~])/)) {
88
+ lbl = "Hidden";
89
+ } else if (tn.match(/^[a-zA-Z_0-9]+$/)) {
90
+ lbl = "Primary";
91
+ } else {
92
+ lbl = "Other";
93
+ }
94
+ an[lbl] = an[lbl] || [];
95
+ an[lbl].push(tn);
96
+ return an;
97
+ }, {});
98
+ const isEmpty = x => !x || x.length === 0;
99
+ ["Primary", "Other"]
100
+ .filter(tt => !isEmpty(taskNames[tt]))
101
+ .forEach(tt => printNSTasks(chalk.bold(`${tt} Tasks`), taskNames[tt], nTasks));
102
+ });
103
+ }
104
+
105
+ export default printTasks;
@@ -0,0 +1,133 @@
1
+ import chalk from "../chalk.js";
2
+ import logger from "../logger.js";
3
+ import stringify from "../stringify.js";
4
+ import assert from "assert";
5
+ import defaults from "../defaults.js";
6
+ import xsh from "xsh";
7
+
8
+ class XReporterConsole {
9
+ constructor(xrun) {
10
+ this._xrun = xrun;
11
+ xrun.on("execute", data => this._onExecute(data));
12
+ xrun.on("done-item", data => this._onDoneItem(data));
13
+ xrun.on("run", () => (this._sep = ""));
14
+ xrun.on("not-found", err => {
15
+ this._logger.log(err.message);
16
+ });
17
+ xrun.once("warn-finally", () => {
18
+ logger.log(chalk.yellow("NOTE: finally hook is unreliable when stopOnError is set to full"));
19
+ });
20
+ this._tags = {};
21
+ this._logger = logger;
22
+ }
23
+
24
+ _log(qItem, msg) {
25
+ this._logger.log(`${this._indent(qItem)}${msg}`);
26
+ this._tags[qItem.id] = { sep: this._sep, msg };
27
+ }
28
+
29
+ _indent(qItem, sep) {
30
+ if (sep === undefined) {
31
+ this._sep = this._sep === "." ? "-" : ".";
32
+ sep = this._sep;
33
+ }
34
+ if (qItem.level) {
35
+ return chalk.magenta(new Array(qItem.level + 1).join(sep));
36
+ } else {
37
+ return "";
38
+ }
39
+ }
40
+
41
+ _onExecute(data) {
42
+ const m = `_onExeType_${data.type.replace(/-/g, "_")}`;
43
+ this[m](data);
44
+ }
45
+
46
+ _depMsg(qItem) {
47
+ const depDee = qItem.value().depDee;
48
+ const dep = depDee ? "'s dependency" : "";
49
+ return dep;
50
+ }
51
+
52
+ _onExeType_env(data) {
53
+ const qItem = data.qItem;
54
+ const name = this._itemDisplayName(qItem);
55
+ const str = data.cmdVal.toString();
56
+ const msg = `Execute ${name} setting ${chalk.blue(str)}`;
57
+ this._log(qItem, msg);
58
+ }
59
+
60
+ _onExeType_shell(data) {
61
+ const qItem = data.qItem;
62
+ const name = this._itemDisplayName(qItem);
63
+ let cmd;
64
+ if (data.cmdVal.constructor.name === "XTaskSpec") {
65
+ cmd = xsh.pathCwd.replace(data.cmdVal.toString(data.cmd2), ".", "g");
66
+ } else {
67
+ cmd = xsh.pathCwd.replace(data.cmd, ".", "g");
68
+ }
69
+ const msg = data.anon
70
+ ? `Execute ${chalk.cyan(cmd)}`
71
+ : `Execute ${name}${this._depMsg(qItem)} ${chalk.blue(cmd)}`;
72
+ this._log(qItem, msg);
73
+ }
74
+
75
+ _onExeType_lookup(_data) {
76
+ // do nothing
77
+ }
78
+
79
+ _onExeType_serial_arr(data) {
80
+ const qItem = data.qItem;
81
+ const name = this._itemDisplayName(qItem);
82
+ const ts = chalk.blue(stringify(data.array));
83
+ const msg = `Process ${name}${this._depMsg(qItem)} serial array ${ts}`;
84
+ this._log(qItem, msg);
85
+ }
86
+
87
+ _onExeType_concurrent_arr(data) {
88
+ const qItem = data.qItem;
89
+ const name = this._itemDisplayName(qItem);
90
+ const ts = chalk.blue(xsh.pathCwd.replace(stringify(data.array), ".", "g"));
91
+ const msg = `Process ${name}${this._depMsg(qItem)} concurrent array ${ts}`;
92
+ this._log(qItem, msg);
93
+ }
94
+
95
+ _onExeType_function(data) {
96
+ const qItem = data.qItem;
97
+ const name = this._itemDisplayName(qItem);
98
+ const anon = qItem.anon ? " anonymous " : " as ";
99
+ const msg = `Execute ${name}${this._depMsg(qItem)}${anon}function`;
100
+ this._log(qItem, msg);
101
+ }
102
+
103
+ _itemDisplayName(qItem) {
104
+ let ns = "";
105
+ const ix = qItem.name.indexOf(defaults.NS_SEP);
106
+ if (ix < 0) {
107
+ if (qItem.ns === defaults.NAMESPACE) {
108
+ ns = chalk.dim.cyan(qItem.ns);
109
+ } else if (qItem.ns) {
110
+ ns = chalk.dim.cyan(`${qItem.ns}${defaults.NS_SEP}`);
111
+ }
112
+ }
113
+ const finallyMsg = qItem.isFinally ? chalk.magenta(":finally") : "";
114
+ return `${ns}${chalk.cyan(qItem.name)}${finallyMsg}`;
115
+ }
116
+
117
+ _onDoneItem(data) {
118
+ // const qItem = data.qItem;
119
+ const xqItem = data.xqItem;
120
+ const elapseStr = `(${logger.formatElapse(data.elapse)})`;
121
+ const failed = !!this._xrun.failed || !!xqItem.err;
122
+ const result = xqItem.err ? "Failed" : "Done";
123
+ const status = failed ? chalk.red(result) : chalk.green(result);
124
+ const tag = this._tags[xqItem.id];
125
+ assert(tag, `console reporter no tag found for ${xqItem.name}`);
126
+
127
+ this._logger.log(
128
+ `${this._indent(xqItem, ">")}${status} ${tag.msg} ${chalk.magenta(elapseStr)}`
129
+ );
130
+ }
131
+ }
132
+
133
+ export default XReporterConsole;
@@ -0,0 +1,16 @@
1
+ export default data => {
2
+ try {
3
+ return JSON.stringify(data, (key, value) => {
4
+ if (typeof value === "function") {
5
+ return "func";
6
+ } else if (value.constructor.name === "XTaskSpec") {
7
+ return value.toString();
8
+ } else if (typeof value === "symbol") {
9
+ return `<${value.toString().match(/\(([^)]+)\)/)[1]}>`;
10
+ }
11
+ return value;
12
+ });
13
+ } catch (err) {
14
+ return `ERROR: ${err.message}`;
15
+ }
16
+ };
@@ -0,0 +1,5 @@
1
+ import * as stringArray from "string-array";
2
+
3
+ export default function pa(str) {
4
+ return stringArray.parse(str, true, true).array; // noPrefix and noExtra
5
+ };
@@ -0,0 +1,16 @@
1
+ export default function(env, target, override = true) {
2
+ target = target || process.env;
3
+ if (env) {
4
+ Object.keys(env).forEach(k => {
5
+ if (override === false && target.hasOwnProperty(k)) {
6
+ return;
7
+ }
8
+ if (env[k] === undefined || env[k] === null) {
9
+ delete target[k];
10
+ } else {
11
+ target[k] = env[k];
12
+ }
13
+ });
14
+ }
15
+ return target;
16
+ };