@nexrender/core 1.46.1 → 1.46.4

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/package.json CHANGED
@@ -1,13 +1,14 @@
1
1
  {
2
2
  "name": "@nexrender/core",
3
- "version": "1.46.1",
3
+ "version": "1.46.4",
4
4
  "main": "src/index.js",
5
5
  "author": "Inlife",
6
6
  "homepage": "https://www.nexrender.com",
7
7
  "bugs": "https://github.com/inlife/nexrender/issues",
8
8
  "repository": "https://github.com/inlife/nexrender.git",
9
9
  "scripts": {
10
- "pkg-prelink": "node ../../misc/prelink.js"
10
+ "pkg-prelink": "node ../../misc/prelink.js",
11
+ "test": "mocha"
11
12
  },
12
13
  "dependencies": {
13
14
  "@nexrender/types": "^1.45.6",
@@ -15,13 +16,12 @@
15
16
  "file-uri-to-path": "^2.0.0",
16
17
  "is-wsl": "^2.2.0",
17
18
  "make-fetch-happen": "^11.0.2",
18
- "match-all": "^1.2.5",
19
19
  "mime-types": "^2.1.29",
20
20
  "mkdirp": "^1.0.4",
21
21
  "nanoid": "^3.2.0",
22
- "posthog-node": "^3.1.1",
23
22
  "requireg": "^0.2.1",
24
23
  "rimraf": "^3.0.2",
24
+ "strip-comments": "^2.0.1",
25
25
  "systeminformation": "^5.18.3"
26
26
  },
27
27
  "peerDependencies": {
@@ -36,5 +36,5 @@
36
36
  "publishConfig": {
37
37
  "access": "public"
38
38
  },
39
- "gitHead": "9286b0383ee0c7f54fec91a7c37b1e6714b71e3e"
39
+ "gitHead": "e997bcd889a5d6ea61f2dd30cfd1fd382dcaf1e5"
40
40
  }
@@ -5,7 +5,6 @@ const crypto = require('crypto')
5
5
 
6
6
  const si = require('systeminformation')
7
7
  const { nanoid } = require('nanoid')
8
- const {PostHog} = require('posthog-node')
9
8
  const childProcess = require('child_process')
10
9
 
11
10
  const { version } = require('../../package.json')
@@ -15,13 +14,6 @@ const hash = (data, salt) => crypto
15
14
  .update(data)
16
15
  .digest('hex');
17
16
 
18
- const analyticsPublicKey = 'phc_AWcZMlCOqHJiFyFKSoxT9WSrRkdKDFxpiFn8Ww0ZMHu';
19
- const analytics = new PostHog(analyticsPublicKey, {
20
- host: 'https://eu.posthog.com',
21
- flushAt: 1,
22
- flushInterval: 0,
23
- disableGeoip: true,
24
- });
25
17
 
26
18
  /**
27
19
  * A helper function to force syncronous tracking
@@ -186,8 +178,11 @@ const track = async (settings, event, properties = {}) => {
186
178
 
187
179
  // console.log('tracking event:', params)
188
180
 
189
- analytics.capture(params);
190
- await analytics.flush();
181
+
182
+ // removed posthog for now
183
+ params.foo = 1;
184
+ // analytics.capture(params);
185
+ // await analytics.flush();
191
186
  }
192
187
 
193
188
  if (process.argv[2] === 'child') {
@@ -0,0 +1,280 @@
1
+ const fs = require('fs')
2
+ const strip = require('strip-comments');
3
+
4
+ const escapeForRegex = (str) => {
5
+ return str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); // $& means the whole matched string
6
+ }
7
+
8
+ // Find instances of a function inside a single-line string.
9
+ const REGEX_FN_DETECT = new RegExp(/(?:(?:(?:function *(?:[a-zA-Z0-9_]+)?) *(?:\((?:.*)\)) *(?:\{(?:.*)\}))|(?:(?:.*) *(?:=>) *(?:\{)(?:.*?)?\}))/);
10
+
11
+
12
+ // This regex will detect a self-invoking function like (function(){})() and will catch the invoking parameters in a single string for further inspection.
13
+ const REGEX_SELF_INVOKING_FN = new RegExp(/(?:(?:^\() *(?:.*?)(?:} *\)))(?: *(?:\() *(.*?) *(?:\) *$))/, "gm");
14
+
15
+ /**
16
+ * Parse method from parameter
17
+ * @description Given a parameter detect whether it should be casted as a function in the final argument injection.
18
+ * @param param (string|number|boolean|null|object|array) The parameter to parse a method from.
19
+ * @return bool (Boolean) If a method is detected then it's stripped from String quotes, else it's returned in it's original type.
20
+ */
21
+ const isMethod = (param) => {
22
+ if (typeof param == "string") {
23
+ return (param.match(REGEX_FN_DETECT) ? true : false);
24
+ }
25
+ return false
26
+ }
27
+
28
+ const getSearchUsageByMethodRegex = (keyword = 'NX', method = "set", flags = "g") => {
29
+ const str = `(?<!(?:[\\/\\s]))(?<!(?:[\\*\\s]))(?:\\s*${keyword}\\s*\\.)\\s*(${method})\\s*(?:\\()(?:["']{1})([a-zA-Z0-9._-]+)(?:["']{1})(?:\\))`;
30
+ return new RegExp(str, flags);
31
+ }
32
+
33
+ function EnhancedScript (
34
+ dest,
35
+ src,
36
+ parameters = [],
37
+ keyword = "NX",
38
+ defaults = {
39
+ global: "null",
40
+ string: "",
41
+ number: 0,
42
+ array: [],
43
+ boolean: false,
44
+ object: {},
45
+ function: function (){},
46
+ null: null
47
+ },
48
+ jobID,
49
+ logger
50
+ ) {
51
+ this.scriptPath = src;
52
+ this.script = fs.readFileSync(dest, 'utf8');
53
+ this.keyword = keyword;
54
+ this.defaults = defaults;
55
+ this.jobID = jobID;
56
+ this.logger = logger;
57
+ this.jsonParameters = parameters;
58
+
59
+ /** @type { key: string
60
+ * isVar: boolean
61
+ * isFn: boolean
62
+ * needsDefault: boolean.
63
+ * }
64
+ */
65
+ this.missingJSONParams = [];
66
+ }
67
+
68
+ /**
69
+ * Get Default Value
70
+ * @description Retrieves a default value parameter based on a key.
71
+ * @param key (String)("string"|"number"|"array"|"object"|"null"|"function") The key to the required default parameter. Defaults to "null".
72
+ * @returns default The value from this.defaults array.
73
+ */
74
+ EnhancedScript.prototype.getDefaultValue = function(key) { return key in this.defaults ? this.defaults[key] : this.defaults[this.defaults.global]; }
75
+
76
+ EnhancedScript.prototype.parseMethod = function (parameter) {
77
+ const selfInvokingFn = [...parameter.value.matchAll(REGEX_SELF_INVOKING_FN)];
78
+ if (selfInvokingFn ) {
79
+ return this.parseMethodWithArgs(parameter);
80
+ }
81
+ return parameter.value;
82
+ }
83
+
84
+ EnhancedScript.prototype.matchAsJSONParameterKey = function( key ) {
85
+ const parameterMatch = this.jsonParameters.find(o => o.key == key);
86
+ return parameterMatch ? parameterMatch.value : key;
87
+ }
88
+
89
+ EnhancedScript.prototype.parseMethodWithArgs = function (parameter) {
90
+ let value = parameter.value;
91
+ const methodArgs = [...parameter.value.matchAll(getSearchUsageByMethodRegex(this.keyword, 'arg', "gm"))];
92
+
93
+ if (methodArgs.length > 0 ) {
94
+ this.logger.log("We found a self-invoking method with arguments!");
95
+ this.logger.log(JSON.stringify(methodArgs));
96
+
97
+ let arg, fullMatch;
98
+ const foundArgument = methodArgs.filter( argMatch => {
99
+ fullMatch = argMatch[0]
100
+ arg = argMatch[2];
101
+
102
+ return parameter.arguments && parameter.arguments.find(o => o.key == arg);
103
+ });
104
+
105
+ if (foundArgument) {
106
+ // Search if argument is present in JSON and has `arguments` array to match against the results.
107
+ // And do a replacement with either the found argument on the array or a global default value.
108
+ let argReplacement = parameter.arguments && parameter.arguments.find(o => o.key == arg).value || this.getStringifiedDefaultValue(this.defaults.global);
109
+ const fullMatchRegex = new RegExp(escapeForRegex(fullMatch), "gm");
110
+ value = parameter.value.replace(fullMatchRegex, argReplacement);
111
+ }
112
+ }
113
+
114
+ return value;
115
+ }
116
+
117
+ EnhancedScript.prototype.detectValueType = function (parameter) {
118
+ return isMethod(parameter.value) ? this.parseMethod(parameter) : JSON.stringify(parameter.value);
119
+ }
120
+
121
+ /**
122
+ * Get Default Value as String
123
+ * @description Retrieves a default value parameter based on a key.
124
+ * @param key (String)("string"|"number"|"array"|"object"|"null"|"function") The key to the required default parameter. Defaults to "null".
125
+ * @returns default (String) A template literal string with the embedded default parameter. If it's a string then it's wrapped with quotes.
126
+ */
127
+ EnhancedScript.prototype.getStringifiedDefaultValue = function (key) {
128
+ return JSON.stringify(this.getDefaultValue(key))
129
+ }
130
+
131
+ /**
132
+ * Find Missing Matches in JSX Script
133
+ * ====================
134
+ * @description RegEx Searches a given JSX script to find occurences and saves an object with keys
135
+ * @return bool (Boolean) Whether or not there are any variables to inject. Defaults to false.
136
+ */
137
+ EnhancedScript.prototype.findMissingMatchesInJSX = function () {
138
+ const script = strip(this.script);
139
+
140
+ // Parse all occurrences of the usage of NX on the provided script.
141
+ const nxMatches = [...script.matchAll(getSearchUsageByMethodRegex(this.keyword, "get", "gm"))];
142
+
143
+ if (nxMatches && nxMatches.length > 0 ) {
144
+ nxMatches.forEach( match => {
145
+ const keyword = match[2];
146
+
147
+ var nxMatch = {
148
+ key: keyword.replace(/\s/g, ''),
149
+ isVar: false,
150
+ isFn: false,
151
+ value: this.getDefaultValue(match.default ? match.default : this.defaults.global)
152
+ };
153
+ if (this.jsonParameters.filter( o => o.key == nxMatch.key ).length == 0) { // If the parameter doesn't have a value defined in JSON
154
+ this.missingJSONParams.push(nxMatch);
155
+ }
156
+ });
157
+ }
158
+
159
+ const numMissing = this.missingJSONParams.length;
160
+ if (numMissing > 0) {
161
+ this.logger.log(`[${this.jobID}] ${this.displayAlert()}`);
162
+ }
163
+ return numMissing > 0;
164
+ }
165
+
166
+
167
+ /**
168
+ * Generated Placeholder Parameters
169
+ * ================================
170
+ * @description Generates placeholder for "parameters" JSON Object based on keys from an array.
171
+ * @return string (String) JSON "parameters" object.
172
+ */
173
+ EnhancedScript.prototype.generatePlaceholderParameters = function ( ) {
174
+ const template = (key) => `
175
+ {
176
+ "key" : "${key}",
177
+ "value" : ${this.getStringifiedDefaultValue(this.defaults.global)}
178
+ }\n
179
+ `;
180
+ return `
181
+ "parameters" : [
182
+ ${this.missingJSONParams.map((k) => template(k.key)).join("\n")}
183
+ ]
184
+ `
185
+ }
186
+
187
+ /**
188
+ * Display Missing Alert
189
+ * =====================
190
+ * @description Display a log message if theres any missing parameter set on the JSON configuration but is being referred in the script.
191
+ * @return string (String) The template literal string displaying all the occurences if any.
192
+ */
193
+ EnhancedScript.prototype.displayAlert = function () {
194
+ const keyword = this.keyword;
195
+
196
+ return ` -- W A R N I N G --
197
+ The following parameters used in the script were NOT found on the JSON "parameters" object of your script asset ${this.scriptPath }
198
+
199
+ ${this.missingJSONParams.map(o => o.key).join(", ")}
200
+
201
+ Please set defaults in your JSX script (see documentation) or copy the following placeholder JSON code snippet and replace the values with your own:
202
+
203
+ ${this.generatePlaceholderParameters()}
204
+
205
+ Remember to always use a fallback default value for any use of the ${keyword} object to have the ability to run this script on After Effects directly.
206
+ Example:
207
+ const dogName = ${keyword} && ${keyword}.get("doggo") || "Doggo";
208
+ `
209
+ }
210
+
211
+ EnhancedScript.prototype.injectParameters = function () {
212
+ return [...this.jsonParameters, ...this.missingJSONParams].map( param => {
213
+ let value = param.type ? this.getStringifiedDefaultValue(param.type) : this.getDefaultValue(this.defaults.global);
214
+
215
+ if (param.value ) {
216
+ value = this.detectValueType(param);
217
+ }
218
+
219
+ return `${this.keyword}.set('${param.key}', ${value});`
220
+ }).join("\n");
221
+ }
222
+
223
+ EnhancedScript.prototype.buildParameterConfigurator = function () {
224
+ const defaultGlobalValue = this.getStringifiedDefaultValue( this.defaults.global );
225
+ // const defaultFnValue = this.getDefaultValue( this.defaults.function );
226
+ const createParameterConfigurator = () => `
227
+ function ParameterConfigurator () {
228
+ this.params = [];
229
+ }
230
+
231
+ ParameterConfigurator.prototype.set = function (k, v) {
232
+ this.params.push({
233
+ key: k,
234
+ value: v
235
+ });
236
+ };
237
+
238
+ ParameterConfigurator.prototype.call = function ( key, args ) {
239
+ for (var i = 0; i < this.params.length; i++) {
240
+ if (this.params[i].key == key) {
241
+ if (typeof this.params[i].value == "function") return this.params[i].value.apply(this, args && args.length > 0 ? args : []);
242
+ }
243
+ }
244
+ return null;
245
+ }
246
+
247
+ ParameterConfigurator.prototype.get = function ( key, args ) {
248
+ for (var i = 0; i < this.params.length; i++) {
249
+ if (this.params[i].key == key) {
250
+ if (typeof this.params[i].value == "function") return this.call(key, args || []);
251
+ return this.params[i].value;
252
+ };
253
+ }
254
+ return ${ defaultGlobalValue }
255
+ };
256
+ var ${ this.keyword } = new ParameterConfigurator();
257
+
258
+ // Parameter injection from job configuration
259
+ ${ this.injectParameters() }
260
+ `;
261
+
262
+ return createParameterConfigurator();
263
+ }
264
+
265
+ EnhancedScript.prototype.build = function () {
266
+ this.findMissingMatchesInJSX();
267
+
268
+ // Et voilà!
269
+ const enhancedScript = `(function() {
270
+ ${this.buildParameterConfigurator()}
271
+ ${this.script}
272
+ })();\n`;
273
+
274
+ // do not log the script (can be uncommented for debugging)
275
+ // this.logger.log(enhancedScript);
276
+ return enhancedScript;
277
+ }
278
+
279
+
280
+ module.exports = EnhancedScript
@@ -0,0 +1,115 @@
1
+ // simple syntax test
2
+ const path = require('path')
3
+ const fs = require('fs')
4
+ const os = require("os");
5
+ const chai = require('chai')
6
+ const expect = chai.expect
7
+
8
+ describe('tasks/script/EnhancedScript', () => {
9
+ const EnhancedScript = require('./EnhancedScript')
10
+
11
+ const testJsxFilePath = path.join(os.tmpdir(), 'unittest.jsx')
12
+
13
+ it("Finds parameters in jsx with missing values in json and injects", () => {
14
+ fs.writeFileSync(testJsxFilePath,`
15
+ /* var c = NX.get('unittest_undefined_parameter_multi_line_comment')
16
+ */
17
+ // var d = NX.get('unittest_undefined_parameter_single_line_comment')
18
+ var a = NX.get('unittest_undefined_parameter')
19
+ var b = NX.get('unittest_defined_parameter')
20
+ `);
21
+
22
+ const enhancedScript = new EnhancedScript(testJsxFilePath, 'src', [
23
+ {
24
+ key: 'unittest_defined_parameter',
25
+ value: 'VALUE'
26
+ },
27
+ ], 'NX', {}, 'unittest', console);
28
+
29
+ const anyMissing = enhancedScript.findMissingMatchesInJSX();
30
+ expect(anyMissing).true;
31
+ expect(enhancedScript.missingJSONParams.map((o) => { return o.key})).to.have.members(['unittest_undefined_parameter'])
32
+
33
+ expect(enhancedScript.injectParameters()).to.equal(
34
+ [`NX.set('unittest_defined_parameter', "VALUE");`,
35
+ `NX.set('unittest_undefined_parameter', undefined);`].join('\n'));
36
+
37
+ })
38
+
39
+ it("injects functions and functions with arguments", () => {
40
+
41
+ //FIXME: NX.get('unittest_undefined_function', 'arg1') is currently not found and no warning is given
42
+ fs.writeFileSync(testJsxFilePath,`
43
+ var a = NX.get('unittest_undefined_function', 'arg1')
44
+ var b = NX.get('eventInvitation')
45
+ `);
46
+
47
+ const enhancedScript = new EnhancedScript(testJsxFilePath, 'src', [
48
+ {
49
+ "key" : "invitees",
50
+ "value": ["Steve", "Natasha", "Tony", "Bruce", "Wanda", "Thor", "Peter", "Clint" ]
51
+ },
52
+ {
53
+ "key" : "eventInvitation",
54
+ "value": `(function (venue) { alert( 'This years' Avengers Gala is on the prestigious ' + venue.name + ' located at ' + venue.location + '. Our special guests ' + NX.get('invitees').value.map(function (a, i) { return (i == NX.get('invitees').value.length - 1) ? ' and ' + a + ' (whoever that is)' : a + ', '; }).join('') + ' going to be present for the ceremony!');
55
+ })({ name: NX.arg('venue'), location: NX.arg('location') })`,
56
+ "arguments": [
57
+ {
58
+ "key" : "venue",
59
+ "value" : "Smithsonian Museum of Natural History"
60
+ },
61
+ {
62
+ "key" : "location",
63
+ "value": "10th St. & Constitution Ave."
64
+ },
65
+ ]
66
+ },
67
+ {
68
+ "type": "array",
69
+ "key" : "dogs",
70
+ "value": [ "Captain Sparkles", "Summer", "Neptune"]
71
+ },
72
+ {
73
+ "type" : "number",
74
+ "key" : "anAmount"
75
+ },
76
+ {
77
+ "type": "function",
78
+ "key": "getDogsCount",
79
+ "value" : "function() { return NX.get('dogs').length; }"
80
+ },
81
+ {
82
+ "type": "function",
83
+ "key": "exampleFn",
84
+ "value": "function ( parameter ) { return parameter; }"
85
+ },
86
+ {
87
+ "type" : "function",
88
+ "key" : "dogCount",
89
+ "value" : "(function(length) { return length })(NX.arg('dogCount'))",
90
+ "arguments": [
91
+ {
92
+ "key" : "dogCount",
93
+ "value": ["NX.call('exampleFn', [NX.call('getDogsCount') + NX.get('anAmount')])"]
94
+ }
95
+ ]
96
+ }
97
+ ], 'NX', {}, 'unittest', console);
98
+
99
+ const anyMissing = enhancedScript.findMissingMatchesInJSX();
100
+ expect(anyMissing).false;
101
+
102
+ const injected = enhancedScript.injectParameters();
103
+
104
+ expect(injected).to.equal([
105
+ `NX.set('invitees', ["Steve","Natasha","Tony","Bruce","Wanda","Thor","Peter","Clint"]);`,
106
+ `NX.set('eventInvitation', (function (venue) { alert( 'This years' Avengers Gala is on the prestigious ' + venue.name + ' located at ' + venue.location + '. Our special guests ' + NX.get('invitees').value.map(function (a, i) { return (i == NX.get('invitees').value.length - 1) ? ' and ' + a + ' (whoever that is)' : a + ', '; }).join('') + ' going to be present for the ceremony!');
107
+ })({ name: NX.arg('venue'), location:10th St. & Constitution Ave. }));`,
108
+ `NX.set('dogs', ["Captain Sparkles","Summer","Neptune"]);`,
109
+ `NX.set('anAmount', undefined);`,
110
+ `NX.set('getDogsCount', function() { return NX.get('dogs').length; });`,
111
+ `NX.set('exampleFn', function ( parameter ) { return parameter; });`,
112
+ `NX.set('dogCount', (function(length) { return length })(NX.call('exampleFn', [NX.call('getDogsCount') + NX.get('anAmount')])));`
113
+ ].join('\n'))
114
+ })
115
+ })
@@ -0,0 +1,18 @@
1
+ const escape = str => {
2
+ str = JSON.stringify(str)
3
+ str = str.substring(1, str.length-1)
4
+ str = `'${str.replace(/'/g, '\\\'')}'`
5
+ return str
6
+ }
7
+
8
+ const selectLayers = (job, { composition, layerName, layerIndex }, callbackString) => {
9
+ const method = layerName ? 'selectLayersByName' : 'selectLayersByIndex';
10
+ const compo = composition === undefined ? 'null' : escape(composition);
11
+ const value = layerName ? escape(layerName) : layerIndex;
12
+ return (`nexrender.${method}(${compo}, ${value}, ${callbackString}, null, ${job.template.continueOnMissing});`);
13
+ }
14
+
15
+ module.exports = {
16
+ escape,
17
+ selectLayers,
18
+ }
@@ -0,0 +1,52 @@
1
+ const fs = require('fs')
2
+ const path = require('path')
3
+ const script = require('../../assets/nexrender.jsx')
4
+
5
+ const wrapFootage = require('./wrap-footage')
6
+ const wrapData = require('./wrap-data')
7
+ const wrapEnhancedScript = require('./wrap-enhanced-script')
8
+
9
+ module.exports = (job, settings) => {
10
+ settings.logger.log(`[${job.uid}] running script assemble...`);
11
+
12
+ const data = [];
13
+ const base = job.workpath;
14
+
15
+ job.assets.map(asset => {
16
+ settings.trackCombined('Asset Script Wraps', {
17
+ job_id: job.uid, // anonymized internally
18
+ script_type: asset.type,
19
+ script_compostion_set: asset.composition !== undefined,
20
+ script_layer_strat: asset.layerName ? 'name' : 'index',
21
+ script_value_strat:
22
+ asset.value !== undefined ? 'value' : // eslint-disable-line no-nested-ternary, multiline-ternary
23
+ asset.expression !== undefined ? 'expression' : // eslint-disable-line multiline-ternary
24
+ undefined,
25
+ })
26
+
27
+ switch (asset.type) {
28
+ case 'video':
29
+ case 'audio':
30
+ case 'image':
31
+ data.push(wrapFootage(job, settings, asset));
32
+ break;
33
+
34
+ case 'data':
35
+ data.push(wrapData(job, settings, asset));
36
+ break;
37
+
38
+ case 'script':
39
+ data.push(wrapEnhancedScript(job, settings, asset));
40
+ break;
41
+ }
42
+ });
43
+
44
+ /* write out assembled custom script file in the workpath */
45
+ job.scriptfile = path.join(base, `nexrender-${job.uid}-script.jsx`);
46
+ fs.writeFileSync(job.scriptfile, script
47
+ .replace('/*COMPOSITION*/', job.template.composition)
48
+ .replace('/*USERSCRIPT*/', () => data.join('\n'))
49
+ );
50
+
51
+ return Promise.resolve(job)
52
+ }
@@ -0,0 +1,22 @@
1
+ const { selectLayers } = require('./helpers')
2
+
3
+ const renderIf = (value, string) => {
4
+ const encoded = typeof value == 'string' ? escape(value) : JSON.stringify(value);
5
+ return value === undefined ? '' : string.replace('$value', () => encoded);
6
+ }
7
+
8
+ const partsOfKeypath = (keypath) => {
9
+ var parts = keypath.split('->');
10
+ return (parts.length === 1) ? keypath.split('.') : parts
11
+ }
12
+
13
+ const wrapData = (job, settings, { property, value, expression, ...asset }) => (`(function() {
14
+ ${selectLayers(job, asset, `function(layer) {
15
+ var parts = ${JSON.stringify(partsOfKeypath(property))};
16
+ ${renderIf(value, `var value = { "value": $value }`)}
17
+ ${renderIf(expression, `var value = { "expression": $value }`)}
18
+ nexrender.changeValueForKeypath(layer, parts, value);
19
+ }`)}
20
+ })();\n`)
21
+
22
+ module.exports = wrapData
@@ -0,0 +1,25 @@
1
+ const EnhancedScript = require('./EnhancedScript')
2
+
3
+ /**
4
+ * Wrap Enhanced Script
5
+ * ====================
6
+ * @author Dilip Ramírez (https://github.com/dukuo | https://notimetoexplain.co)
7
+ * @autor Based on the work of Potat (https://github.com/dukuo/potat)
8
+ * @description Parse a script from a source, and injects a configuration object named ${keyword} based on the "parameters" array of the script asset if any.
9
+ *
10
+ * If parameters or functions deriving from the configuration object are being used in the script, but no parameters are set, then it succeeds but
11
+ * displays a warning with the missing JSX/JSON matches, and sets all the missing ones to null for a soft fault tolerance at runtime.
12
+ *
13
+ * @param src The JSX script
14
+ * @param parameters (Array<Object>) Argument array described in the Asset JSON object inside the Job description
15
+ * @param keyword (String) Name for the exported variable holding configuration parameters. Defaults to NX as in NeXrender.
16
+ * @param defaults.null (Any) The default value in case the user setted key name on any given `parameter` child object. Defaults to `null`
17
+ *
18
+ * @return string (String) The compiled script with parameter injection outside its original scope to avoid user-defined defaults collision.
19
+ */
20
+ const wrapEnhancedScript = (job, settings, { dest, src, parameters = [], keyword, defaults, /* ...asset */ }) => {
21
+ const enhancedScript = new EnhancedScript(dest, src, parameters, keyword, defaults, job.uid, settings.logger);
22
+ return enhancedScript.build();
23
+ }
24
+
25
+ module.exports = wrapEnhancedScript
@@ -0,0 +1,10 @@
1
+ const { selectLayers } = require('./helpers')
2
+ const { checkForWSL } = require('../../helpers/path')
3
+
4
+ const wrapFootage = (job, settings, { dest, ...asset }) => (`(function() {
5
+ ${selectLayers(job, asset, `function(layer) {
6
+ nexrender.replaceFootage(layer, '${checkForWSL(dest.replace(/\\/g, "\\\\"), settings)}')
7
+ }`)}
8
+ })();\n`)
9
+
10
+ module.exports = wrapFootage
package/test/index.js CHANGED
@@ -1,2 +1,2 @@
1
1
  // simple syntax test
2
- require('../src')
2
+ require('../src')
@@ -1,471 +0,0 @@
1
- const fs = require('fs')
2
- const path = require('path')
3
- const script = require('../assets/nexrender.jsx')
4
- const matchAll = require('match-all')
5
- const { checkForWSL } = require('../helpers/path')
6
-
7
- /* helpers */
8
- const escape = str => {
9
- str = JSON.stringify(str)
10
- str = str.substring(1, str.length-1)
11
- str = `'${str.replace(/'/g, '\\\'')}'`
12
- return str
13
- }
14
-
15
- const selectLayers = (job, settings, { composition, layerName, layerIndex }, callbackString) => {
16
- const method = layerName ? 'selectLayersByName' : 'selectLayersByIndex';
17
- const compo = composition === undefined ? 'null' : escape(composition);
18
- const value = layerName ? escape(layerName) : layerIndex;
19
- return (`nexrender.${method}(${compo}, ${value}, ${callbackString}, null, ${job.template.continueOnMissing});`);
20
- }
21
-
22
- const renderIf = (value, string) => {
23
- const encoded = typeof value == 'string' ? escape(value) : JSON.stringify(value);
24
- return value === undefined ? '' : string.replace('$value', () => encoded);
25
- }
26
-
27
- const partsOfKeypath = (keypath) => {
28
- var parts = keypath.split('->');
29
- return (parts.length === 1) ? keypath.split('.') : parts
30
- }
31
-
32
- /* scripting wrappers */
33
- const wrapFootage = (job, settings, { dest, ...asset }) => (`(function() {
34
- ${selectLayers(job, settings, asset, `function(layer) {
35
- nexrender.replaceFootage(layer, '${checkForWSL(dest.replace(/\\/g, "\\\\"), settings)}')
36
- }`)}
37
- })();\n`)
38
-
39
- const wrapData = (job, settings, { property, value, expression, ...asset }) => (`(function() {
40
- ${selectLayers(job, settings, asset, `function(layer) {
41
- var parts = ${JSON.stringify(partsOfKeypath(property))};
42
- ${renderIf(value, `var value = { "value": $value }`)}
43
- ${renderIf(expression, `var value = { "expression": $value }`)}
44
- nexrender.changeValueForKeypath(layer, parts, value);
45
- }`)}
46
- })();\n`)
47
-
48
- /**
49
- * Wrap Enhanced Script
50
- * ====================
51
- * @author Dilip Ramírez (https://github.com/dukuo | https://notimetoexplain.co)
52
- * @autor Based on the work of Potat (https://github.com/dukuo/potat)
53
- * @description Parse a script from a source, and injects a configuration object named ${keyword} based on the "parameters" array of the script asset if any.
54
- *
55
- * If parameters or functions deriving from the configuration object are being used in the script, but no parameters are set, then it succeeds but
56
- * displays a warning with the missing JSX/JSON matches, and sets all the missing ones to null for a soft fault tolerance at runtime.
57
- *
58
- * @param src The JSX script
59
- * @param parameters (Array<Object>) Argument array described in the Asset JSON object inside the Job description
60
- * @param keyword (String) Name for the exported variable holding configuration parameters. Defaults to NX as in NeXrender.
61
- * @param defaults.null (Any) The default value in case the user setted key name on any given `parameter` child object. Defaults to `null`
62
- *
63
- * @return string (String) The compiled script with parameter injection outside its original scope to avoid user-defined defaults collision.
64
- */
65
- const wrapEnhancedScript = (job, settings, { dest, src, parameters = [], keyword, defaults, /* ...asset */ }) => {
66
- const jobID = job.uid;
67
- let arg, fullMatch;
68
-
69
- function EnhancedScript (
70
- dest,
71
- src,
72
- parameters = [],
73
- keyword = "NX",
74
- defaults = {
75
- global: "null",
76
- string: "",
77
- number: 0,
78
- array: [],
79
- boolean: false,
80
- object: {},
81
- function: function (){},
82
- null: null
83
- },
84
- jobID,
85
- logger
86
- ) {
87
- this.scriptPath = src;
88
- this.script = fs.readFileSync(dest, 'utf8');
89
- this.keyword = keyword;
90
- this.defaults = defaults;
91
- this.jobID = jobID;
92
- this.logger = logger;
93
- this.jsonParameters = parameters;
94
-
95
- this.regexes = {
96
- keywordUsage: null,
97
- keywordInit: null,
98
- fnDetect: null
99
- };
100
-
101
- this.missingJSONParams = [];
102
-
103
- // Setup
104
- this.setupRegexes();
105
- }
106
-
107
- /**
108
- * Utilities, one-liners
109
- */
110
- EnhancedScript.prototype.getScriptPath = function() { return this.scriptPath; }
111
- EnhancedScript.prototype.getGetterMethod = function () { return "get"; }
112
- EnhancedScript.prototype.getSetterMethod = function () { return "set"; }
113
- EnhancedScript.prototype.getLogger = function () { return this.logger; }
114
- EnhancedScript.prototype.getJSXScript = function () { return this.script; }
115
- EnhancedScript.prototype.getJSONParams = function () { return this.jsonParameters; }
116
- EnhancedScript.prototype.jsonParametersCount = function () { return this.jsonParameters.length; }
117
- EnhancedScript.prototype.getMissingJSONParams = function() { return this.missingJSONParams; }
118
- EnhancedScript.prototype.countMissingJSONParams = function() { return this.getMissingJSONParams().length; }
119
- EnhancedScript.prototype.getKeyword = function () { return this.keyword; }
120
- EnhancedScript.prototype.getFnArgsKeyword = function () { return this.fnArgsKeyword; }
121
- EnhancedScript.prototype.getRegex = function (key) { return this.regexes[key]; }
122
-
123
- /**
124
- * Get Default Value
125
- * @description Retrieves a default value parameter based on a key.
126
- * @param key (String)("string"|"number"|"array"|"object"|"null"|"function") The key to the required default parameter. Defaults to "null".
127
- * @returns default The value from this.defaults array.
128
- */
129
- EnhancedScript.prototype.getDefaultValue = function(key) { return key in this.defaults ? this.defaults[key] : this.defaults[this.defaults.global]; }
130
-
131
- /**
132
- * Add Missing JSON Parameter
133
- * =====================
134
- * @description Adds an object with the key of a parameter being used in JSX code but with no default in the JSON Asset.
135
- * @param nxMatch (Object) The object to add to the missing array.
136
- * Required format:
137
- * {
138
- * key: string
139
- * isVar: boolean
140
- * isFn: boolean
141
- * needsDefault: boolean.
142
- * }
143
- * @returns object The recently added object.
144
- */
145
- EnhancedScript.prototype.addToMissingJsonParameter = function(nxMatch) { return this.missingJSONParams.push(nxMatch); }
146
-
147
- /**
148
- * End one-liners
149
- */
150
- EnhancedScript.prototype.setupRegexes = function() {
151
- this.regexes.searchUsageByMethod = (method = "set", flags = "g") => {
152
- const str = `(?<!(?:[\\/\\s]))(?<!(?:[\\*\\s]))(?:\\s*${this.getKeyword()}\\s*\\.)\\s*(${method})\\s*(?:\\()(?:["']{1})([a-zA-Z0-9._-]+)(?:["']{1})(?:\\))`;
153
- return this.buildRegex(str, flags);
154
- }
155
-
156
- // Find instances of a function inside a single-line string.
157
- this.regexes.fnDetect = new RegExp(/(?:(?:(?:function *(?:[a-zA-Z0-9_]+)?) *(?:\((?:.*)\)) *(?:\{(?:.*)\}))|(?:(?:.*) *(?:=>) *(?:\{)(?:.*?)?\}))/);
158
-
159
- // This regex will detect a self-invoking function like (function(){})() and will catch the invoking parameters in a single string for further inspection.
160
- this.regexes.selfInvokingFn = new RegExp(/(?:(?:^\() *(?:.*?)(?:} *\)))(?: *(?:\() *(.*?) *(?:\) *$))/);
161
- }
162
-
163
- EnhancedScript.prototype.escapeForRegex = function(str) {
164
- return str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); // $& means the whole matched string
165
- }
166
-
167
- EnhancedScript.prototype.stripComments = function (templateLiteral) {
168
- return templateLiteral.replace(/\/\*[\s\S]*?\*\/|([^:]|^)\/\/.*$/gm, '');
169
- }
170
-
171
- EnhancedScript.prototype.buildRegex = function(templateLiteral, flags) {
172
- return new RegExp(this.stripComments(templateLiteral).replace(/(\r\n|r\|\n|\s)/gm, ''), flags);
173
- }
174
-
175
- /**
176
- * Parse method from parameter
177
- * @description Given a parameter detect whether it should be casted as a function in the final argument injection.
178
- * @param param (string|number|boolean|null|object|array) The parameter to parse a method from.
179
- * @return bool (Boolean) If a method is detected then it's stripped from String quotes, else it's returned in it's original type.
180
- */
181
- EnhancedScript.prototype.isMethod = function (param) {
182
- if (typeof param == "string") {
183
- return (param.match(this.getRegex("fnDetect")) ? true : false);
184
- }
185
- return false
186
- }
187
-
188
- EnhancedScript.prototype.parseMethod = function (parameter) {
189
- const selfInvokingFn = matchAll(parameter.value, this.getRegex('selfInvokingFn'));
190
- if (selfInvokingFn ) {
191
- this.getLogger().log(Array.from(selfInvokingFn));
192
- return this.parseMethodWithArgs(parameter);
193
- }
194
- return parameter.value;
195
- }
196
-
197
- EnhancedScript.prototype.matchAsJSONParameterKey = function( key ) {
198
- const parameterMatch = this.getJSONParams().find(o => o.key == key);
199
- return parameterMatch ? parameterMatch.value : key;
200
- }
201
-
202
- EnhancedScript.prototype.parseMethodWithArgs = function (parameter) {
203
- let value = parameter.value;
204
- const methodArgs = matchAll(parameter.value, this.getRegex('searchUsageByMethod')('arg', "gm")).toArray();
205
-
206
- if (methodArgs.length > 0 ) {
207
- this.getLogger().log("We found a self-invoking method with arguments!");
208
- this.getLogger().log(JSON.stringify(methodArgs));
209
- const foundArgument = methodArgs.filter( argMatch => {
210
- fullMatch = argMatch[0]
211
- arg = argMatch[2]
212
-
213
- return parameter.arguments && parameter.arguments.find(o => o.key == arg);
214
- });
215
-
216
-
217
- if (foundArgument) {
218
- // Search if argument is present in JSON and has `arguments` array to match against the results.
219
- // And do a replacement with either the found argument on the array or a global default value.
220
- let argReplacement = parameter.arguments && parameter.arguments.find(o => o.key == arg).value || this.getStringifiedDefaultValue(this.defaults.global);
221
- const fullMatchRegex = this.buildRegex(this.escapeForRegex(fullMatch), "gm");
222
- value = parameter.value.replace(fullMatchRegex, argReplacement);
223
- }
224
- }
225
-
226
- return value;
227
- }
228
-
229
- EnhancedScript.prototype.detectValueType = function (parameter) {
230
- return this.isMethod(parameter.value) ? this.parseMethod(parameter) : JSON.stringify(parameter.value);
231
- }
232
-
233
- /**
234
- * Get Default Value as String
235
- * @description Retrieves a default value parameter based on a key.
236
- * @param key (String)("string"|"number"|"array"|"object"|"null"|"function") The key to the required default parameter. Defaults to "null".
237
- * @returns default (String) A template literal string with the embedded default parameter. If it's a string then it's wrapped with quotes.
238
- */
239
- EnhancedScript.prototype.getStringifiedDefaultValue = function (key) {
240
- return JSON.stringify(this.getDefaultValue(key))
241
- }
242
-
243
- /**
244
- * Strip comment blocks from Script [EXPERIMENTAL]
245
- * ================================
246
- * @description Removes /* * / comments from script to avoid mismatching occurrences.
247
- * @param script (String) The target script to strip.
248
- * @returns string (String) A one-line version of the original script without comment blocks.
249
- */
250
- EnhancedScript.prototype.stripCommentsFromScript = function (script) {
251
- // https://levelup.gitconnected.com/advanced-regex-find-and-remove-multi-line-comments-in-your-code-c162ba6e5811
252
- return script
253
- .replace(/\n/g, " ")
254
- .replace(/\/\*.*\*\//g, " ")
255
- .replace(/\s+/g, " ")
256
- .trim();
257
- }
258
-
259
- /*
260
- * End Utilities
261
- */
262
-
263
- /**
264
- * Find Missing Matches in JSX Script
265
- * ====================
266
- * @description RegEx Searches a given JSX script to find occurences and saves an object with keys
267
- * @param script (String) JSX script to find occurences in.
268
- * @param regex (Object) RegEx object to match against JSX script.
269
- * @param parameters (Array<Object>) Array with the parameters to compare against the matches.
270
- * @return bool (Boolean) Whether or not there are any variables to inject. Defaults to false.
271
- */
272
- EnhancedScript.prototype.findMissingMatchesInJSX = function () {
273
- const script = this.stripCommentsFromScript(this.getJSXScript());
274
-
275
- // Parse all occurrences of the usage of NX on the provided script.
276
- const nxMatches = matchAll(script, this.getRegex("searchUsageByMethod")("get", "gm")).toArray();
277
-
278
- if (nxMatches && nxMatches.length > 0 ) {
279
- nxMatches.forEach( match => {
280
- const keyword = match[2];
281
-
282
- var nxMatch = {
283
- key: keyword.replace(/\s/g, ''),
284
- isVar: false,
285
- isFn: false,
286
- value: this.getDefaultValue(match.default ? match.default : this.defaults.global)
287
- };
288
- if (this.getJSONParams().filter( o => o.key == nxMatch.key ).length == 0) { // If the parameter doesn't have a value defined in JSON
289
- this.addToMissingJsonParameter(nxMatch);
290
- }
291
- });
292
- }
293
-
294
- const missingJSONParameters = this.countMissingJSONParams();
295
- if (missingJSONParameters > 0) {
296
- this.getLogger().log(`[${jobID}] ${this.displayAlert()}`);
297
- }
298
- return missingJSONParameters > 0;
299
- }
300
-
301
-
302
- /**
303
- * Generated Placeholder Parameters
304
- * ================================
305
- * @description Generates placeholder for "parameters" JSON Object based on keys from an array.
306
- * @param keys (Array) Array of strings. These should be the occurences of `keyword` variable use on the JSX script.
307
- *
308
- * @return string (String) JSON "parameters" object.
309
- */
310
- EnhancedScript.prototype.generatePlaceholderParameters = function ( ) {
311
- const missingParams = this.getMissingJSONParams();
312
-
313
- const template = (key) => `
314
- {
315
- "key" : "${key}",
316
- "value" : ${this.getStringifiedDefaultValue(this.defaults.global)}
317
- }\n
318
- `;
319
-
320
- return `
321
- "parameters" : [
322
- ${missingParams.map((k) => template(k.key)).join("\n")}
323
- ]
324
- `
325
- }
326
-
327
- /**
328
- * Display Missing Alert
329
- * =====================
330
- * @description Display a log message if theres any missing parameter set on the JSON configuration but is being referred in the script.
331
- *
332
- * @param missingParam (Object) Missing Parameters object. See below for its construction. Must have child objects `fn` and `vars`
333
- * @param showJSXWarning (Boolean) Flag for whether or not to display warning about not initializing variable in JSX script. Defaults to false.
334
- * @param injectionVar (String) Variable initialized with placeholder values. Defaults to "".
335
- *
336
- * @return string (String) The template literal string displaying all the occurences if any.
337
- */
338
- EnhancedScript.prototype.displayAlert = function () {
339
- const keyword = this.getKeyword();
340
-
341
- return ` -- W A R N I N G --
342
- The following parameters used in the script were NOT found on the JSON "parameters" object of your script asset ${this.getScriptPath() }
343
-
344
- ${this.getMissingJSONParams().map(o => o.key).join(", ")}
345
-
346
- Please set defaults in your JSX script (see documentation) or copy the following placeholder JSON code snippet and replace the values with your own:
347
-
348
- ${this.generatePlaceholderParameters()}
349
-
350
- Remember to always use a fallback default value for any use of the ${keyword} object to have the ability to run this script on After Effects directly.
351
- Example:
352
- const dogName = ${keyword} && ${keyword}.${this.getGetterMethod()}("doggo") || "Doggo";
353
- `
354
- }
355
-
356
- EnhancedScript.prototype.injectParameters = function () {
357
- return [...this.getJSONParams(), ...this.getMissingJSONParams()].map( param => {
358
- let value = param.type ? this.getStringifiedDefaultValue(param.type) : this.getDefaultValue(this.defaults.global);
359
-
360
- if (param.value ) {
361
- value = this.detectValueType(param);
362
- }
363
-
364
- return `${this.getKeyword()}.${this.getSetterMethod()}('${param.key}', ${value});`
365
- }).join("\n");
366
- }
367
-
368
- EnhancedScript.prototype.buildParameterConfigurator = function () {
369
- const defaultGlobalValue = this.getStringifiedDefaultValue( this.defaults.global );
370
- // const defaultFnValue = this.getDefaultValue( this.defaults.function );
371
- const createParameterConfigurator = () => `
372
- function ParameterConfigurator () {
373
- this.params = [];
374
- }
375
-
376
- ParameterConfigurator.prototype.set = function (k, v) {
377
- this.params.push({
378
- key: k,
379
- value: v
380
- });
381
- };
382
-
383
- ParameterConfigurator.prototype.call = function ( key, args ) {
384
- for (var i = 0; i < this.params.length; i++) {
385
- if (this.params[i].key == key) {
386
- if (typeof this.params[i].value == "function") return this.params[i].value.apply(this, args && args.length > 0 ? args : []);
387
- }
388
- }
389
- return null;
390
- }
391
-
392
- ParameterConfigurator.prototype.get = function ( key, args ) {
393
- for (var i = 0; i < this.params.length; i++) {
394
- if (this.params[i].key == key) {
395
- if (typeof this.params[i].value == "function") return this.call(key, args || []);
396
- return this.params[i].value;
397
- };
398
- }
399
- return ${ defaultGlobalValue }
400
- };
401
- var ${ this.getKeyword() } = new ParameterConfigurator();
402
-
403
- // Parameter injection from job configuration
404
- ${ this.injectParameters() }
405
- `;
406
-
407
- return createParameterConfigurator();
408
- }
409
-
410
- EnhancedScript.prototype.build = function () {
411
- this.findMissingMatchesInJSX();
412
-
413
- // Et voilà!
414
- const enhancedScript = `(function() {
415
- ${this.buildParameterConfigurator()}
416
- ${this.getJSXScript()}
417
- })();\n`;
418
-
419
- // do not log the script (can be uncommented for debugging)
420
- // this.getLogger().log(enhancedScript);
421
- return enhancedScript;
422
- }
423
-
424
- const enhancedScript = new EnhancedScript(dest, src, parameters, keyword, defaults, jobID, settings.logger);
425
- return enhancedScript.build();
426
- }
427
-
428
- module.exports = (job, settings) => {
429
- settings.logger.log(`[${job.uid}] running script assemble...`);
430
-
431
- const data = [];
432
- const base = job.workpath;
433
-
434
- job.assets.map(asset => {
435
- settings.trackCombined('Asset Script Wraps', {
436
- job_id: job.uid, // anonymized internally
437
- script_type: asset.type,
438
- script_compostion_set: asset.composition !== undefined,
439
- script_layer_strat: asset.layerName ? 'name' : 'index',
440
- script_value_strat:
441
- asset.value !== undefined ? 'value' : // eslint-disable-line no-nested-ternary, multiline-ternary
442
- asset.expression !== undefined ? 'expression' : // eslint-disable-line multiline-ternary
443
- undefined,
444
- })
445
-
446
- switch (asset.type) {
447
- case 'video':
448
- case 'audio':
449
- case 'image':
450
- data.push(wrapFootage(job, settings, asset));
451
- break;
452
-
453
- case 'data':
454
- data.push(wrapData(job, settings, asset));
455
- break;
456
-
457
- case 'script':
458
- data.push(wrapEnhancedScript(job, settings, asset));
459
- break;
460
- }
461
- });
462
-
463
- /* write out assembled custom script file in the workpath */
464
- job.scriptfile = path.join(base, `nexrender-${job.uid}-script.jsx`);
465
- fs.writeFileSync(job.scriptfile, script
466
- .replace('/*COMPOSITION*/', job.template.composition)
467
- .replace('/*USERSCRIPT*/', () => data.join('\n'))
468
- );
469
-
470
- return Promise.resolve(job)
471
- }