@tawaship/pixi-animate-runtime 0.0.0-stage → 0.1.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.
Files changed (38) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +452 -2
  3. package/bin/pixi-animate-runtime.js +27 -0
  4. package/dist/generator.cjs.js +1056 -0
  5. package/dist/generator.cjs.js.map +1 -0
  6. package/dist/pixi-animate-runtime.cjs.js +3678 -0
  7. package/dist/pixi-animate-runtime.cjs.js.map +1 -0
  8. package/dist/pixi-animate-runtime.esm.js +3613 -0
  9. package/dist/pixi-animate-runtime.esm.js.map +1 -0
  10. package/dist/pixi-animate-runtime.js +3680 -0
  11. package/dist/pixi-animate-runtime.js.map +1 -0
  12. package/dist-skills/animate-data-converter-guide/SKILL.md +86 -0
  13. package/dist-skills/pixi-animate-runtime-guide/SKILL.md +146 -0
  14. package/package.json +70 -4
  15. package/tools/pixi-animate-runtime.bat +8 -0
  16. package/types/generator/generator/cli.d.ts +17 -0
  17. package/types/generator/generator/converter-version.d.ts +7 -0
  18. package/types/generator/generator/generate.d.ts +33 -0
  19. package/types/generator/generator/index.d.ts +6 -0
  20. package/types/generator/generator/plan.d.ts +87 -0
  21. package/types/generator/player/data.d.ts +141 -0
  22. package/types/generator/player/ease.d.ts +11 -0
  23. package/types/generator/player/semver.d.ts +27 -0
  24. package/types/generator/player/version.d.ts +2 -0
  25. package/types/player/animate-container.d.ts +71 -0
  26. package/types/player/content.d.ts +170 -0
  27. package/types/player/data.d.ts +141 -0
  28. package/types/player/display.d.ts +186 -0
  29. package/types/player/ease.d.ts +11 -0
  30. package/types/player/events.d.ts +60 -0
  31. package/types/player/geom.d.ts +19 -0
  32. package/types/player/index.d.ts +20 -0
  33. package/types/player/loader.d.ts +53 -0
  34. package/types/player/movieclip.d.ts +191 -0
  35. package/types/player/objects.d.ts +320 -0
  36. package/types/player/semver.d.ts +27 -0
  37. package/types/player/transform.d.ts +32 -0
  38. package/types/player/version.d.ts +2 -0
@@ -0,0 +1,1056 @@
1
+ /*!
2
+ * @tawaship/pixi-animate-runtime - v0.1.0
3
+ *
4
+ * @author tawaship (makazu.mori@gmail.com)
5
+ * @license MIT
6
+ */
7
+
8
+ 'use strict';
9
+
10
+ var path = require('path');
11
+ var animateDataConverter = require('@tawaship/animate-data-converter');
12
+ var fs = require('fs');
13
+
14
+ function _interopNamespaceDefault(e) {
15
+ var n = Object.create(null);
16
+ if (e) {
17
+ Object.keys(e).forEach(function (k) {
18
+ if (k !== 'default') {
19
+ var d = Object.getOwnPropertyDescriptor(e, k);
20
+ Object.defineProperty(n, k, d.get ? d : {
21
+ enumerable: true,
22
+ get: function () { return e[k]; }
23
+ });
24
+ }
25
+ });
26
+ }
27
+ n.default = e;
28
+ return Object.freeze(n);
29
+ }
30
+
31
+ var path__namespace = /*#__PURE__*/_interopNamespaceDefault(path);
32
+ var fs__namespace = /*#__PURE__*/_interopNamespaceDefault(fs);
33
+
34
+ /**
35
+ * Semantic version numbers and ranges, as far as the version checks of
36
+ * definitions and runtime content need them: versions are MAJOR.MINOR.PATCH,
37
+ * and ranges are written as npm writes them (`1.2.3`, `1.x`, `^1.2.0`,
38
+ * `~1.2.0`, `>=1.0.0 <2.0.0`, `0.x || 1.x`). Pre-release tags are not handled.
39
+ */
40
+ /** A version as its three numbers, or null when it is not MAJOR.MINOR.PATCH */
41
+ function parseVersion(text) {
42
+ const match = /^\s*v?(\d+)\.(\d+)\.(\d+)\s*$/.exec(text);
43
+ return match ? [Number(match[1]), Number(match[2]), Number(match[3])] : null;
44
+ }
45
+ function compareParsed(a, b) {
46
+ for (let i = 0; i < 3; i++) {
47
+ if (a[i] !== b[i]) {
48
+ return a[i] < b[i] ? -1 : 1;
49
+ }
50
+ }
51
+ return 0;
52
+ }
53
+ /** -1, 0 or 1 as a is lower than, the same as or higher than b (both have to be versions) */
54
+ function compareVersions(a, b) {
55
+ const pa = parseVersion(a);
56
+ const pb = parseVersion(b);
57
+ if (!pa || !pb) {
58
+ throw new Error('not a version: ' + (pa ? b : a));
59
+ }
60
+ return compareParsed(pa, pb);
61
+ }
62
+ /** A partial version (1, 1.2, 1.x, *): the numbers given, the rest left open */
63
+ function parsePartial(text) {
64
+ if (text === '*' || text === 'x' || text === 'X') {
65
+ return [];
66
+ }
67
+ const parts = text.split('.');
68
+ if (parts.length > 3) {
69
+ return null;
70
+ }
71
+ const numbers = [];
72
+ for (let i = 0; i < parts.length; i++) {
73
+ const part = parts[i];
74
+ if (part === 'x' || part === 'X' || part === '*') {
75
+ break;
76
+ }
77
+ if (!/^\d+$/.test(part)) {
78
+ return null;
79
+ }
80
+ numbers.push(Number(part));
81
+ }
82
+ return numbers;
83
+ }
84
+ function lowest(numbers) {
85
+ return [numbers[0] || 0, numbers[1] || 0, numbers[2] || 0];
86
+ }
87
+ /** The first version above every version the given numbers start (1.2 -> 1.3.0); null for none (*) */
88
+ function above(numbers) {
89
+ if (numbers.length === 0) {
90
+ return null;
91
+ }
92
+ if (numbers.length === 1) {
93
+ return [numbers[0] + 1, 0, 0];
94
+ }
95
+ if (numbers.length === 2) {
96
+ return [numbers[0], numbers[1] + 1, 0];
97
+ }
98
+ return [numbers[0], numbers[1], numbers[2] + 1];
99
+ }
100
+ /** The tests one word of a range stands for, or null when the word is not understood */
101
+ function parseComparator(word) {
102
+ const match = /^(\^|~|>=|<=|>|<|=)?(.+)$/.exec(word);
103
+ if (!match) {
104
+ return null;
105
+ }
106
+ const op = match[1] || '=';
107
+ const numbers = parsePartial(match[2]);
108
+ if (!numbers) {
109
+ return null;
110
+ }
111
+ const low = lowest(numbers);
112
+ const result = [];
113
+ const atLeast = (bound) => {
114
+ result.push({ op: '>=', bound });
115
+ };
116
+ const below = (bound) => {
117
+ if (bound) {
118
+ result.push({ op: '<', bound });
119
+ }
120
+ };
121
+ if (op === '=') {
122
+ atLeast(low);
123
+ below(above(numbers));
124
+ }
125
+ else if (op === '^') {
126
+ // The left-most non-zero number given stays the same (^1.2.3 <2.0.0, ^0.2.3 <0.3.0, ^0.0.3 <0.0.4)
127
+ let fixed = 0;
128
+ while (fixed < numbers.length - 1 && numbers[fixed] === 0) {
129
+ fixed++;
130
+ }
131
+ atLeast(low);
132
+ below(above(numbers.slice(0, fixed + 1)));
133
+ }
134
+ else if (op === '~') {
135
+ atLeast(low);
136
+ below(above(numbers.slice(0, numbers.length > 1 ? 2 : 1)));
137
+ }
138
+ else if (op === '>=') {
139
+ atLeast(low);
140
+ }
141
+ else if (op === '>') {
142
+ const next = above(numbers);
143
+ if (!next) {
144
+ return null;
145
+ }
146
+ atLeast(next);
147
+ }
148
+ else if (op === '<') {
149
+ below(low);
150
+ }
151
+ else {
152
+ // <=
153
+ below(above(numbers));
154
+ }
155
+ return result;
156
+ }
157
+ /** Whether a version is in a range (npm's notation, see above); a range not understood throws */
158
+ function satisfies(version, range) {
159
+ const parsed = parseVersion(version);
160
+ if (!parsed) {
161
+ return false;
162
+ }
163
+ return range.split('||').some(set => {
164
+ const words = set.trim().split(/\s+/).filter(word => word !== '');
165
+ if (!words.length) {
166
+ throw new Error('not a version range: ' + range);
167
+ }
168
+ return words.every(word => {
169
+ const comparators = parseComparator(word);
170
+ if (!comparators) {
171
+ throw new Error('not a version range: ' + range);
172
+ }
173
+ return comparators.every(c => {
174
+ const order = compareParsed(parsed, c.bound);
175
+ return c.op === '>=' ? order >= 0 : order < 0;
176
+ });
177
+ });
178
+ });
179
+ }
180
+ function checkVersion(version, range, known) {
181
+ const parsed = parseVersion(version);
182
+ if (!parsed) {
183
+ return { error: 'no version (' + JSON.stringify(version) + ')', warning: null, newer: false };
184
+ }
185
+ const newer = compareVersions(version, known) > 0;
186
+ if (!satisfies(version, range)) {
187
+ return { error: version + ' is outside ' + range, warning: null, newer };
188
+ }
189
+ return { error: null, warning: newer ? version + ' is newer than ' + known : null, newer };
190
+ }
191
+
192
+ /** The version of this package (written in from package.json when the package is built) */
193
+ const VERSION = "0.1.0";
194
+
195
+ /**
196
+ * The animate-data-converter versions this generator reads definitions of:
197
+ * the range this package depends on, and the version it was built with
198
+ * (written in from package.json when the package is built).
199
+ */
200
+ const CONVERTER_RANGE = "^0.1.0";
201
+ const CONVERTER_KNOWN = "0.1.0";
202
+
203
+ /**
204
+ * The eases of CreateJS (createjs.Ease), computed as CreateJS computes them,
205
+ * by name. Runtime content names the ease of an interpolated stretch of a
206
+ * tween; the player computes the stretch's values with it once, when a symbol's
207
+ * class is made. `get:<amount>` is Ease.get(amount) (Animate's classic ease).
208
+ */
209
+ function powIn(pow) {
210
+ return t => Math.pow(t, pow);
211
+ }
212
+ function powOut(pow) {
213
+ return t => 1 - Math.pow(1 - t, pow);
214
+ }
215
+ function powInOut(pow) {
216
+ return t => ((t *= 2) < 1 ? 0.5 * Math.pow(t, pow) : 1 - 0.5 * Math.abs(Math.pow(2 - t, pow)));
217
+ }
218
+ function backIn(amount) {
219
+ return t => t * t * ((amount + 1) * t - amount);
220
+ }
221
+ function backOut(amount) {
222
+ return t => --t * t * ((amount + 1) * t + amount) + 1;
223
+ }
224
+ function backInOut(amount) {
225
+ amount *= 1.525;
226
+ return t => ((t *= 2) < 1 ? 0.5 * (t * t * ((amount + 1) * t - amount)) : 0.5 * ((t -= 2) * t * ((amount + 1) * t + amount) + 2));
227
+ }
228
+ function elasticIn(amplitude, period) {
229
+ const pi2 = 2 * Math.PI;
230
+ return t => {
231
+ if (t == 0 || t == 1) {
232
+ return t;
233
+ }
234
+ const s = period / pi2 * Math.asin(1 / amplitude);
235
+ return -(amplitude * Math.pow(2, 10 * (t -= 1)) * Math.sin((t - s) * pi2 / period));
236
+ };
237
+ }
238
+ function elasticOut(amplitude, period) {
239
+ const pi2 = 2 * Math.PI;
240
+ return t => {
241
+ if (t == 0 || t == 1) {
242
+ return t;
243
+ }
244
+ const s = period / pi2 * Math.asin(1 / amplitude);
245
+ return amplitude * Math.pow(2, -10 * t) * Math.sin((t - s) * pi2 / period) + 1;
246
+ };
247
+ }
248
+ function elasticInOut(amplitude, period) {
249
+ const pi2 = 2 * Math.PI;
250
+ return t => {
251
+ const s = period / pi2 * Math.asin(1 / amplitude);
252
+ return (t *= 2) < 1
253
+ ? -0.5 * (amplitude * Math.pow(2, 10 * (t -= 1)) * Math.sin((t - s) * pi2 / period))
254
+ : amplitude * Math.pow(2, -10 * (t -= 1)) * Math.sin((t - s) * pi2 / period) * 0.5 + 1;
255
+ };
256
+ }
257
+ function bounceOut(t) {
258
+ return 1 / 2.75 > t ? 7.5625 * t * t
259
+ : 2 / 2.75 > t ? 7.5625 * (t -= 1.5 / 2.75) * t + 0.75
260
+ : 2.5 / 2.75 > t ? 7.5625 * (t -= 2.25 / 2.75) * t + 0.9375
261
+ : 7.5625 * (t -= 2.625 / 2.75) * t + 0.984375;
262
+ }
263
+ function bounceIn(t) {
264
+ return 1 - bounceOut(1 - t);
265
+ }
266
+ /** Ease.get(amount): Animate's classic ease, amount from -1 (in) to 1 (out) */
267
+ function classic(amount) {
268
+ amount = amount < -1 ? -1 : amount > 1 ? 1 : amount;
269
+ return t => (amount == 0 ? t : amount < 0 ? t * (t * -amount + 1 + amount) : t * ((2 - t) * amount + (1 - amount)));
270
+ }
271
+ const EASES = {
272
+ linear: t => t,
273
+ quadIn: powIn(2), quadOut: powOut(2), quadInOut: powInOut(2),
274
+ cubicIn: powIn(3), cubicOut: powOut(3), cubicInOut: powInOut(3),
275
+ quartIn: powIn(4), quartOut: powOut(4), quartInOut: powInOut(4),
276
+ quintIn: powIn(5), quintOut: powOut(5), quintInOut: powInOut(5),
277
+ sineIn: t => 1 - Math.cos(t * Math.PI / 2),
278
+ sineOut: t => Math.sin(t * Math.PI / 2),
279
+ sineInOut: t => -0.5 * (Math.cos(Math.PI * t) - 1),
280
+ backIn: backIn(1.7), backOut: backOut(1.7), backInOut: backInOut(1.7),
281
+ circIn: t => -(Math.sqrt(1 - t * t) - 1),
282
+ circOut: t => Math.sqrt(1 - --t * t),
283
+ circInOut: t => ((t *= 2) < 1 ? -0.5 * (Math.sqrt(1 - t * t) - 1) : 0.5 * (Math.sqrt(1 - (t -= 2) * t) + 1)),
284
+ bounceIn, bounceOut,
285
+ bounceInOut: t => (0.5 > t ? 0.5 * bounceIn(2 * t) : 0.5 * bounceOut(2 * t - 1) + 0.5),
286
+ elasticIn: elasticIn(1, 0.3), elasticOut: elasticOut(1, 0.3), elasticInOut: elasticInOut(1, 0.3 * 1.5)
287
+ };
288
+ /** The ease runtime content names (null for a name the player does not know) */
289
+ function easeOf(id) {
290
+ if (id.indexOf('get:') === 0) {
291
+ return classic(Number(id.slice(4)));
292
+ }
293
+ return Object.prototype.hasOwnProperty.call(EASES, id) ? EASES[id] : null;
294
+ }
295
+ /** The eases the generator tries for a stretch of a tween, by name: the named ones, then Ease.get(-1 .. 1) in hundredths */
296
+ function easeIds() {
297
+ const ids = Object.keys(EASES);
298
+ for (let n = -100; n <= 100; n++) {
299
+ ids.push('get:' + n / 100);
300
+ }
301
+ return ids;
302
+ }
303
+
304
+ /**
305
+ * The data of runtime content (what the generator writes and the player reads): the definition
306
+ * extracted by @tawaship/animate-data-converter, with every frame script
307
+ * taken out of the data and referenced by its index in the runtime content's
308
+ * script list (the scripts themselves are code in the runtime content), and with
309
+ * the writes of each tween and of each instance's initial values compiled
310
+ * into writer functions (code in the runtime content as well, referenced by index).
311
+ * Drawings are gathered into one table for the whole content, identical
312
+ * ones merged, and a symbol refers to them by index.
313
+ */
314
+ /**
315
+ * The values at a position of a step, as CreateJS's Tween computes them: the
316
+ * ratio of the position in the step, eased, then each value from its start
317
+ * toward its end (a value that is not a number, or does not change, keeps its
318
+ * start until the eased ratio reaches 1).
319
+ */
320
+ function interpolated(from, to, position, length, ease) {
321
+ let ratio = position / length;
322
+ if (ease) {
323
+ ratio = ease(ratio);
324
+ }
325
+ return from.map((v0, i) => {
326
+ const v1 = to[i];
327
+ return v0 !== v1 && typeof v0 === 'number' && typeof v1 === 'number' ? v0 + (v1 - v0) * ratio : ratio >= 1 ? v1 : v0;
328
+ });
329
+ }
330
+ /** The values a slot's fields have when runtime content leaves them out */
331
+ const SLOT_DEFAULTS = {
332
+ symbol: null, mode: null, startPosition: null, loop: null, graphics: null, sprite: null, bitmap: null, text: null,
333
+ mask: null, hitArea: null, filters: null, cache: null, button: null, filterCacheListener: false, parentSet: false,
334
+ initWriter: null, initValues: null
335
+ };
336
+
337
+ /** Manifest entries kept as images (sounds and other files are left out) */
338
+ const IMAGE_SOURCE = /\.(png|jpe?g|gif|webp|svg)(\?.*)?$/i;
339
+ const IDENTIFIER = /^[A-Za-z_$][A-Za-z0-9_$]*$/;
340
+ /** A property access written out in code: o.name, or o["name"] for other names */
341
+ function access(name) {
342
+ return IDENTIFIER.test(name) ? 'o.' + name : 'o[' + JSON.stringify(name) + ']';
343
+ }
344
+ /** The ease ids tried for a stretch, with their eases (made once) */
345
+ const EASE_CANDIDATES = easeIds().map(id => ({ id, ease: easeOf(id) }));
346
+ /**
347
+ * A tween's frame values as written: each step (from one keyframe to the next)
348
+ * that interpolation reproduces exactly (the player's own interpolated, with
349
+ * a CreateJS ease, gives every position's values as recorded) is written as a
350
+ * stretch [from, to, length, ease]; any other position as the index of its
351
+ * values, written once each. What cannot be reproduced exactly (a motion
352
+ * guide, a step cut short by a jump) stays as recorded values.
353
+ */
354
+ function interpolatedFrames(frames, keyframes) {
355
+ const values = [];
356
+ const indexes = new Map();
357
+ const indexOf = (frame) => {
358
+ const key = JSON.stringify(frame);
359
+ let index = indexes.get(key);
360
+ if (index === undefined) {
361
+ index = values.push(frame) - 1;
362
+ indexes.set(key, index);
363
+ }
364
+ return index;
365
+ };
366
+ const last = frames.length - 1;
367
+ const starts = keyframes.filter((key, i) => key > 0 && key < last && keyframes.indexOf(key) === i).sort((a, b) => a - b);
368
+ const bounds = [0].concat(starts, [last]);
369
+ const refs = [];
370
+ for (let i = 0; i + 1 < bounds.length; i++) {
371
+ const start = bounds[i];
372
+ const length = bounds[i + 1] - start;
373
+ const found = length > 1 ? EASE_CANDIDATES.find(candidate => {
374
+ for (let position = 0; position < length; position++) {
375
+ if (JSON.stringify(interpolated(frames[start], frames[start + length], position, length, candidate.ease)) !== JSON.stringify(frames[start + position])) {
376
+ return false;
377
+ }
378
+ }
379
+ return true;
380
+ }) : undefined;
381
+ if (found) {
382
+ refs.push([indexOf(frames[start]), indexOf(frames[start + length]), length, found.id]);
383
+ }
384
+ else {
385
+ for (let position = start; position < start + length; position++) {
386
+ refs.push(indexOf(frames[position]));
387
+ }
388
+ }
389
+ }
390
+ refs.push(indexOf(frames[last]));
391
+ return { values, frames: refs };
392
+ }
393
+ /**
394
+ * A tween's distinct values as written: in a display list (the `state` prop),
395
+ * an entry that places a slot as it is (no props) is written as the slot's
396
+ * index. The player turns them back into entries once per symbol.
397
+ */
398
+ function writtenFrames(props, frames, keyframes) {
399
+ const distinct = interpolatedFrames(frames, keyframes);
400
+ const state = props.indexOf('state');
401
+ return {
402
+ frames: distinct.frames,
403
+ values: distinct.values.map(frame => frame.map((value, i) => {
404
+ if (i !== state || !Array.isArray(value)) {
405
+ return value;
406
+ }
407
+ return value.map(entry => entry.target.type === 'slot' && entry.props === null ? entry.target.slot : entry);
408
+ }))
409
+ };
410
+ }
411
+ /** A recorded Graphics reference ({ graphics: n }): the value has to be resolved to the instance's Graphics */
412
+ function isGraphicsReference(value) {
413
+ return value !== null && typeof value === 'object' && !Array.isArray(value);
414
+ }
415
+ /**
416
+ * The writer of a tween: one assignment per property, the name written in the
417
+ * code (so the engine resolves each write once instead of looking a name up
418
+ * on every frame). values[i] goes to props[i]; a property that ever holds a
419
+ * Graphics reference goes through resolve.
420
+ */
421
+ function tweenWriter(props, frames) {
422
+ const lines = props.map((name, i) => {
423
+ const resolved = frames.some(frame => isGraphicsReference(frame[i]));
424
+ return access(name) + ' = ' + (resolved ? 'g(v[' + i + '])' : 'v[' + i + ']') + ';';
425
+ });
426
+ return 'function (o, v, g) { ' + lines.join(' ') + ' }';
427
+ }
428
+ /**
429
+ * A slot's template as written in runtime content: the definition's template
430
+ * with the writer of its initial values, the fields at their defaults
431
+ * (SLOT_DEFAULTS) left out, the player filling them in. Its initial values are
432
+ * not in it (the slot gives them).
433
+ */
434
+ function slotTemplate(template, initWriter) {
435
+ const full = Object.assign({}, SLOT_DEFAULTS, template, { initWriter, initValues: null });
436
+ const data = { kind: full.kind };
437
+ Object.keys(SLOT_DEFAULTS).forEach(key => {
438
+ const value = Reflect.get(full, key);
439
+ if (JSON.stringify(value) !== JSON.stringify(Reflect.get(SLOT_DEFAULTS, key))) {
440
+ Reflect.set(data, key, value);
441
+ }
442
+ });
443
+ return data;
444
+ }
445
+ /** A table of values written once each, identical ones (compared as JSON) sharing one index */
446
+ class JsonTable {
447
+ constructor() {
448
+ this.list = [];
449
+ this._indexes = new Map();
450
+ }
451
+ add(value) {
452
+ const key = JSON.stringify(value);
453
+ const known = this._indexes.get(key);
454
+ if (known !== undefined) {
455
+ return known;
456
+ }
457
+ this.list.push(value);
458
+ this._indexes.set(key, this.list.length - 1);
459
+ return this.list.length - 1;
460
+ }
461
+ }
462
+ /**
463
+ * Initial values a display object of the player already has when it is made
464
+ * (as a createjs one has): writing them changes nothing, so they are left out.
465
+ * `name` is not among them: writing null gives the Pixi node the name ''
466
+ * (its own default is null).
467
+ */
468
+ const INIT_DEFAULTS = {
469
+ x: 0, y: 0, scaleX: 1, scaleY: 1, rotation: 0, skewX: 0, skewY: 0, regX: 0, regY: 0,
470
+ alpha: 1, visible: true, compositeOperation: null
471
+ };
472
+ /**
473
+ * The initial values of a slot to write (those not at INIT_DEFAULTS), and the
474
+ * writer that writes them: one assignment per property, the name written in
475
+ * the code and the value taken from the values, so that slots setting the
476
+ * same properties share one writer.
477
+ */
478
+ function initWrite(init) {
479
+ const names = Object.keys(init).filter(name => init[name] !== undefined && !(name in INIT_DEFAULTS && init[name] === INIT_DEFAULTS[name]));
480
+ if (!names.length) {
481
+ return null;
482
+ }
483
+ return {
484
+ source: 'function (o, v) { ' + names.map((name, i) => access(name) + ' = v[' + i + '];').join(' ') + ' }',
485
+ values: names.map(name => init[name])
486
+ };
487
+ }
488
+ /**
489
+ * The member writer of a symbol: one assignment per named child, the name
490
+ * written in the code (as the published constructor's `this.<name> = ...`),
491
+ * so that no name is looked up when an instance is made.
492
+ */
493
+ function memberWriter(members) {
494
+ return 'function (o, s) { ' + members.map(member => access(member.name) + ' = s[' + member.slot + '];').join(' ') + ' }';
495
+ }
496
+ /** Function sources written once each, identical ones sharing one index */
497
+ class CodeTable {
498
+ constructor() {
499
+ this.list = [];
500
+ this._indexes = new Map();
501
+ }
502
+ /** The index of a function's source: functions written the same are one entry (they hold no state) */
503
+ add(source) {
504
+ const known = this._indexes.get(source);
505
+ if (known !== undefined) {
506
+ return known;
507
+ }
508
+ this.list.push(source);
509
+ this._indexes.set(source, this.list.length - 1);
510
+ return this.list.length - 1;
511
+ }
512
+ }
513
+ /** A display list entry from its compact form (a slot placed as it is is the slot's index) */
514
+ function stateEntryOf(entry) {
515
+ return typeof entry === 'number' ? { target: { type: 'slot', slot: entry }, props: null } : entry;
516
+ }
517
+ /** Whether a compact value is a frame value as it is (all but a display list with slot indexes) */
518
+ function isFrameValue(value) {
519
+ return !Array.isArray(value) || value.every(entry => typeof entry !== 'number');
520
+ }
521
+ /**
522
+ * A tween's values at every position (frames[position][i] is the value of
523
+ * props[i]), from its compact form: interpolated stretches are found among
524
+ * these. Display list entries are whole.
525
+ */
526
+ function framesOf(tween) {
527
+ const state = tween.props.indexOf('state');
528
+ const values = tween.values.map(row => row.map((value, i) => {
529
+ if (i === state && Array.isArray(value)) {
530
+ return value.map(stateEntryOf);
531
+ }
532
+ return isFrameValue(value) ? value : null;
533
+ }));
534
+ return tween.frames.map(index => values[index]);
535
+ }
536
+ function convertMovieClip(def, templates, scripts, writers, memberWriters, slotTemplates) {
537
+ const add = (source) => writers.add(source);
538
+ return {
539
+ kind: def.kind, id: def.id, nominalBounds: def.nominalBounds, frameBounds: def.frameBounds,
540
+ defaults: def.defaults, labels: def.labels, duration: def.duration,
541
+ // A slot is its template (shared by the slots that differ only in their initial values) and its initial values
542
+ slots: def.slots.map((slot) => {
543
+ const write = initWrite(typeof slot === 'number' ? {} : slot[1]);
544
+ const template = slotTemplates.add(slotTemplate(templates[typeof slot === 'number' ? slot : slot[0]], write ? add(write.source) : null));
545
+ return write ? [template, write.values] : template;
546
+ }),
547
+ memberWriter: def.members.length ? memberWriters.add(memberWriter(def.members)) : null,
548
+ unmanaged: def.unmanaged, graphics: def.graphics, filterCacheList: def.filterCacheList,
549
+ tweens: def.tweens.map(tween => {
550
+ const frames = framesOf(tween);
551
+ return Object.assign({
552
+ target: tween.target, duration: tween.duration, props: tween.props,
553
+ keyframes: tween.keyframes,
554
+ actions: tween.actions.map(action => {
555
+ scripts.push(action.source);
556
+ return { position: action.position, script: scripts.length - 1, params: action.params };
557
+ }),
558
+ writer: tween.props.length ? add(tweenWriter(tween.props, frames)) : null
559
+ }, writtenFrames(tween.props, frames, tween.keyframes));
560
+ })
561
+ };
562
+ }
563
+ /**
564
+ * The runtime content file: a factory that takes the names frame scripts
565
+ * refer to (cjs, createjs, lib, exportRoot, stage, playSound) and returns the
566
+ * data, the scripts and the writers. The scripts are written as code, as
567
+ * published (not rewritten), and the writers are code too, so no code is
568
+ * compiled at run time. Loaded (by a script tag or any other way), the file
569
+ * registers the factory in `window.PixiAnimateRuntimeContents` under the
570
+ * content's composition id. One registered under the same id before is
571
+ * replaced, with a warning: which content an id stands for is the
572
+ * application's to decide (one id may be given contents of the same make in
573
+ * turn); contents already built keep working.
574
+ */
575
+ function runtimeContentSource(data, scripts, writers, memberWriters, id, sourceName) {
576
+ const comment = sourceName.replace(/\*\//g, '* /');
577
+ return [
578
+ '/* Generated by @tawaship/pixi-animate-runtime from Adobe Animate HTML5 Canvas output.',
579
+ ' * Runtime content ' + JSON.stringify(id).replace(/\*\//g, '* /') + ' (composition id), from ' + comment + ' */',
580
+ '(function (contents, id, source, factory) {',
581
+ '\tvar known = contents[id];',
582
+ '\tif (known && typeof console !== "undefined") {',
583
+ '\t\tconsole.warn("Runtime content " + JSON.stringify(id) + " (" + source + ") replaces the one registered under the same composition id (" + (known.source || "unknown") + ").");',
584
+ '\t}',
585
+ '\tfactory.source = source;',
586
+ '\tcontents[id] = factory;',
587
+ '})(window.PixiAnimateRuntimeContents = window.PixiAnimateRuntimeContents || {}, ' + JSON.stringify(id) + ', ' + JSON.stringify(sourceName) + ', function (cjs, createjs, lib, exportRoot, stage, playSound) {',
588
+ '\treturn {',
589
+ '\t\tdata: ' + JSON.stringify(data) + ',',
590
+ '\t\tscripts: [',
591
+ scripts.map(script => '\t\t\t' + script).join(',\n'),
592
+ '\t\t],',
593
+ '\t\twriters: [',
594
+ writers.map(writer => '\t\t\t' + writer).join(',\n'),
595
+ '\t\t],',
596
+ '\t\tmemberWriters: [',
597
+ memberWriters.map(writer => '\t\t\t' + writer).join(',\n'),
598
+ '\t\t]',
599
+ '\t};',
600
+ '});',
601
+ ''
602
+ ].join('\n');
603
+ }
604
+ /**
605
+ * Whether this generator reads a definition: an error when the version of
606
+ * animate-data-converter that extracted it is outside the range this package
607
+ * depends on, a warning when it is newer than the converter this generator
608
+ * was built with (it may hold what the generator does not know and leaves out).
609
+ */
610
+ function checkDefinition(definition) {
611
+ const made = typeof definition.converterVersion === 'string' ? definition.converterVersion : '';
612
+ const check = checkVersion(made, CONVERTER_RANGE, CONVERTER_KNOWN);
613
+ const by = 'The definition was extracted by animate-data-converter ' + (made || '(no version)');
614
+ if (check.error) {
615
+ throw new Error(by + ', which this generator (pixi-animate-runtime ' + VERSION + ') does not read (it reads definitions from ' + CONVERTER_RANGE + '). '
616
+ + (check.newer ? 'Update pixi-animate-runtime.' : 'Extract the definition again with animate-data-converter ' + CONVERTER_RANGE + '.'));
617
+ }
618
+ return check.warning ? [by + ', newer than the version this generator knows of (' + CONVERTER_KNOWN + '). What the newer version added may be left out; update pixi-animate-runtime.'] : [];
619
+ }
620
+ /** The composition id of a content (lib.properties.id): the id its runtime content is registered and loaded under */
621
+ function compositionId(definition) {
622
+ const id = definition.properties.id;
623
+ if (typeof id !== 'string' || !id) {
624
+ throw new Error('The published content has no composition id (lib.properties.id): its runtime content could not be registered.');
625
+ }
626
+ return id;
627
+ }
628
+ /**
629
+ * Generates the runtime content of a content from its extracted definition.
630
+ * It is registered under the content's composition id; `sourceName` (the
631
+ * published file it came from) is written into it to tell contents apart in
632
+ * messages.
633
+ */
634
+ function generate(definition, sourceName) {
635
+ const warnings = checkDefinition(definition);
636
+ if (definition.form !== 'compact') {
637
+ throw new Error('The definition is not in its compact form (form: ' + JSON.stringify(definition.form) + '): generate takes the definition as animate-data-converter extracts it (compact() of animate-data-converter gives it from an expanded one).');
638
+ }
639
+ const id = compositionId(definition);
640
+ const scripts = [];
641
+ // Writers written the same (the same values set, the same names given) are written once and shared
642
+ const writers = new CodeTable();
643
+ const memberWriters = new CodeTable();
644
+ const slotTemplates = new JsonTable();
645
+ const symbols = {};
646
+ Object.keys(definition.symbols).forEach(key => {
647
+ const def = definition.symbols[key];
648
+ symbols[key] = def.kind === 'movieclip' ? convertMovieClip(def, definition.slotTemplates, scripts, writers, memberWriters, slotTemplates) : def;
649
+ });
650
+ const data = {
651
+ runtimeVersion: VERSION,
652
+ properties: definition.properties,
653
+ images: definition.manifest.filter(entry => IMAGE_SOURCE.test(entry.src)),
654
+ spriteSheets: definition.spriteSheets,
655
+ graphics: definition.graphics,
656
+ commandKinds: definition.commandKinds,
657
+ slotTemplates: slotTemplates.list,
658
+ symbols
659
+ };
660
+ const source = runtimeContentSource(data, scripts, writers.list, memberWriters.list, id, sourceName || '(unknown)');
661
+ return { id, data, scripts, writers: writers.list, memberWriters: memberWriters.list, source, issues: definition.issues, warnings };
662
+ }
663
+ /**
664
+ * Extracts a published file and generates its runtime content.
665
+ */
666
+ function generateFromPublishedFile(publishedPath) {
667
+ return animateDataConverter.extractPublishedFile(publishedPath).then(definition => generate(definition, path__namespace.basename(publishedPath)));
668
+ }
669
+
670
+ /** Whether a JavaScript file is Animate HTML5 Canvas output (it registers a composition) */
671
+ function isPublished(file) {
672
+ return /\.compositions\s*\[/.test(fs__namespace.readFileSync(file, 'utf8'));
673
+ }
674
+ function publishedFiles(dir) {
675
+ return fs__namespace.readdirSync(dir, { withFileTypes: true })
676
+ .filter(entry => entry.isFile() && /\.js$/i.test(entry.name))
677
+ .map(entry => path__namespace.join(dir, entry.name))
678
+ .filter(isPublished)
679
+ .sort();
680
+ }
681
+ /** Directories the recursive search does not enter: packages, and hidden ones (.git and the like) */
682
+ function isSkipped(name) {
683
+ return name === 'node_modules' || name.charAt(0) === '.';
684
+ }
685
+ /** The published files in a directory and in the directories below it */
686
+ function publishedFilesBelow(dir) {
687
+ const files = publishedFiles(dir);
688
+ fs__namespace.readdirSync(dir, { withFileTypes: true })
689
+ .filter(entry => entry.isDirectory() && !isSkipped(entry.name))
690
+ .map(entry => entry.name)
691
+ .sort()
692
+ .forEach(name => {
693
+ publishedFilesBelow(path__namespace.join(dir, name)).forEach(file => files.push(file));
694
+ });
695
+ return files;
696
+ }
697
+ /** The local file of an image path of the manifest (its query and hash left out), or null for a URL that is not a file */
698
+ function localImagePath(src) {
699
+ if (/^(data:|blob:|[a-z][a-z0-9+.-]*:\/\/)/i.test(src) || src.charAt(0) === '/') {
700
+ return null;
701
+ }
702
+ return src.split('#')[0].split('?')[0];
703
+ }
704
+ /** Whether a relative path stays inside a directory (not absolute, and not leading up out of it) */
705
+ function isBelow(dir, relativePath) {
706
+ if (path__namespace.isAbsolute(relativePath)) {
707
+ return false;
708
+ }
709
+ const fromDir = path__namespace.relative(dir, path__namespace.resolve(dir, relativePath));
710
+ return fromDir !== '' && !fromDir.startsWith('..') && !path__namespace.isAbsolute(fromDir);
711
+ }
712
+ function listFiles(dir) {
713
+ if (!fs__namespace.existsSync(dir)) {
714
+ return [];
715
+ }
716
+ const files = [];
717
+ fs__namespace.readdirSync(dir, { withFileTypes: true }).forEach(entry => {
718
+ const full = path__namespace.join(dir, entry.name);
719
+ if (entry.isDirectory()) {
720
+ listFiles(full).forEach(file => files.push(file));
721
+ }
722
+ else {
723
+ files.push(full);
724
+ }
725
+ });
726
+ return files;
727
+ }
728
+ /** A path from a directory, "/" separated */
729
+ function relativePath(from, file) {
730
+ return path__namespace.relative(from, file).split(path__namespace.sep).join('/') || '.';
731
+ }
732
+ /**
733
+ * The published files an input names, and the directory their names are
734
+ * taken from. Only reads the files (nothing is run). Throws when the input
735
+ * does not exist or has no published file.
736
+ */
737
+ function findPublished(input, recursive, cwd) {
738
+ const inputPath = path__namespace.resolve(cwd || process.cwd(), input);
739
+ if (!fs__namespace.existsSync(inputPath)) {
740
+ throw new Error('not found: ' + inputPath);
741
+ }
742
+ const single = fs__namespace.statSync(inputPath).isFile();
743
+ const files = single ? [inputPath] : recursive ? publishedFilesBelow(inputPath) : publishedFiles(inputPath);
744
+ if (!files.length) {
745
+ throw new Error('no published content (Animate HTML5 Canvas JavaScript) in ' + inputPath + (recursive ? ' or below it' : ''));
746
+ }
747
+ return { root: single ? path__namespace.dirname(inputPath) : inputPath, files };
748
+ }
749
+ /** The local images a generated content uses (paths from its published directory), and why it cannot be written if it cannot */
750
+ function imagesOf(generated, publishedDir) {
751
+ const images = [];
752
+ const missing = [];
753
+ const outside = [];
754
+ generated.data.images.forEach(entry => {
755
+ const local = localImagePath(entry.src);
756
+ if (local === null) {
757
+ return;
758
+ }
759
+ // Animate writes image paths relative to the published file, below its directory: a path
760
+ // that leads elsewhere (up out of it, or absolute) is neither read nor copied
761
+ if (!isBelow(publishedDir, local)) {
762
+ outside.push(entry.src);
763
+ }
764
+ else if (fs__namespace.existsSync(path__namespace.join(publishedDir, local))) {
765
+ images.push(local);
766
+ }
767
+ else {
768
+ missing.push(entry.src);
769
+ }
770
+ });
771
+ if (outside.length) {
772
+ return { images, problem: 'image paths that lead out of its directory: ' + outside.join(', ') };
773
+ }
774
+ if (missing.length) {
775
+ return { images, problem: 'images it uses are missing: ' + missing.join(', ') };
776
+ }
777
+ return { images, problem: null };
778
+ }
779
+ /**
780
+ * Finds the published files, makes their runtime content and works out where
781
+ * everything would be written, without writing anything. Each published file
782
+ * is run in a sandbox of its own, one after another. What cannot be written
783
+ * (a file that cannot be read, unsupported features without force, missing
784
+ * images) is in the plan with its problem. Throws when the input does not
785
+ * exist, has no published file, or the output would be a published directory.
786
+ * {@link writeGeneration} writes the plan.
787
+ */
788
+ async function planGeneration(options) {
789
+ const cwd = options.cwd || process.cwd();
790
+ const found = findPublished(options.input, options.recursive, cwd);
791
+ const root = found.root;
792
+ const outRoot = options.outDir !== undefined && options.outDir !== null ? path__namespace.resolve(cwd, options.outDir) : null;
793
+ // Below the output directory at the path of its published file from the root, or into generated/ next to its published file
794
+ const outDirOf = (file) => outRoot !== null ? path__namespace.join(outRoot, path__namespace.relative(root, path__namespace.dirname(file))) : path__namespace.join(path__namespace.dirname(file), 'generated');
795
+ if (found.files.some(file => path__namespace.resolve(outDirOf(file)) === path__namespace.resolve(path__namespace.dirname(file)))) {
796
+ throw new Error('the output directory is the published directory: runtime content would overwrite the published files of the same names');
797
+ }
798
+ const contents = [];
799
+ // One after another: each is run in a sandbox of its own
800
+ for (const file of found.files) {
801
+ const outDir = outDirOf(file);
802
+ const out = path__namespace.join(outDir, path__namespace.basename(file));
803
+ const content = {
804
+ published: file,
805
+ name: relativePath(root, file),
806
+ id: null,
807
+ out,
808
+ exists: fs__namespace.existsSync(out),
809
+ images: [],
810
+ source: null,
811
+ issues: [],
812
+ warnings: [],
813
+ problem: null
814
+ };
815
+ contents.push(content);
816
+ try {
817
+ const definition = await animateDataConverter.extractPublishedFile(file);
818
+ content.id = compositionId(definition);
819
+ const generated = generate(definition, path__namespace.basename(file));
820
+ content.issues = generated.issues;
821
+ content.warnings = generated.warnings;
822
+ const images = imagesOf(generated, path__namespace.dirname(file));
823
+ content.images = images.images.map(image => ({ from: path__namespace.join(path__namespace.dirname(file), image), to: path__namespace.join(outDir, image) }));
824
+ if (generated.issues.length && !options.force) {
825
+ content.problem = generated.issues.length + ' unsupported feature(s)';
826
+ }
827
+ else if (images.problem !== null) {
828
+ content.problem = images.problem;
829
+ }
830
+ else {
831
+ content.source = generated.source;
832
+ }
833
+ }
834
+ catch (e) {
835
+ content.problem = (content.id === null ? 'could not read: ' : '') + (e instanceof Error ? e.message : String(e));
836
+ }
837
+ }
838
+ const existing = [];
839
+ (outRoot !== null ? [outRoot] : contents.map(content => path__namespace.dirname(content.out)).filter((dir, i, all) => all.indexOf(dir) === i)).forEach(dir => {
840
+ listFiles(dir).forEach(file => existing.push(file));
841
+ });
842
+ return { root, outDir: outRoot, contents, existing };
843
+ }
844
+ /**
845
+ * Writes a plan made by {@link planGeneration}: the runtime content it made
846
+ * (as it was when the plan was made) and copies of the images. The contents
847
+ * with a problem are not written. Nothing is deleted.
848
+ */
849
+ function writeGeneration(plan) {
850
+ const written = {};
851
+ const files = [];
852
+ const add = (file) => {
853
+ written[path__namespace.resolve(file)] = true;
854
+ files.push(file);
855
+ };
856
+ plan.contents.forEach(content => {
857
+ if (content.source === null || content.problem !== null) {
858
+ return;
859
+ }
860
+ fs__namespace.mkdirSync(path__namespace.dirname(content.out), { recursive: true });
861
+ fs__namespace.writeFileSync(content.out, content.source);
862
+ add(content.out);
863
+ content.images.forEach(image => {
864
+ if (!written[path__namespace.resolve(image.to)]) {
865
+ fs__namespace.mkdirSync(path__namespace.dirname(image.to), { recursive: true });
866
+ fs__namespace.copyFileSync(image.from, image.to);
867
+ add(image.to);
868
+ }
869
+ });
870
+ });
871
+ return { written: files, left: plan.existing.filter(file => !written[path__namespace.resolve(file)]) };
872
+ }
873
+
874
+ const USAGE = [
875
+ 'Usage: pixi-animate-runtime [<published directory or file>] [-o <directory>] [-r] [-y] [-f]',
876
+ '',
877
+ ' <published directory> a directory Animate published HTML5 Canvas content into: every published',
878
+ ' JavaScript file directly in it is turned into runtime content',
879
+ ' <published file> one published JavaScript file',
880
+ ' -o, --out-dir <directory> where to write (default: generated/ in the published directory)',
881
+ ' -r, --recursive also look in the directories below it (not node_modules, nor directories',
882
+ ' whose name starts with "."); with -o, each content is written below it at',
883
+ ' the same relative path, and otherwise into generated/ next to its file',
884
+ ' -y, --yes write without asking anything (needed when not run in a terminal)',
885
+ ' -f, --force write runtime content even when the content uses unsupported features',
886
+ ' -h, --help show this',
887
+ '',
888
+ 'Each runtime content file gets the name of its published file, and the images it uses are copied',
889
+ 'next to it at the same relative paths. What would be written is listed first, and written only',
890
+ 'when you answer "y" (or with -y; run from a script or CI without -y, nothing is written). In a',
891
+ 'terminal, you are also asked for the directories that are not given. Nothing is ever deleted:',
892
+ 'files already in the output directory that this run did not write are listed.'
893
+ ].join('\n');
894
+ /** A composition id as listed: its first characters (the whole id is in the published file and at the top of the runtime content) */
895
+ function shortId(id) {
896
+ return id === null ? '-' : id.length > 8 ? id.slice(0, 8) + '...' : id;
897
+ }
898
+ function pad(text, width) {
899
+ return text.length >= width ? text : text + ' '.repeat(width - text.length);
900
+ }
901
+ /**
902
+ * Lists a plan as a table: where from and where to once, then a row for each
903
+ * published file (its composition id shortened, the number of images, and
904
+ * whether it is new, replaces a file, or is not written and why).
905
+ * Unsupported features and warnings go to stderr.
906
+ */
907
+ function showPlan(plan, force, cwd, io) {
908
+ plan.contents.forEach(content => {
909
+ content.warnings.forEach(warning => {
910
+ io.stderr(content.name + ': warning: ' + warning + '\n');
911
+ });
912
+ content.issues.forEach(issue => {
913
+ io.stderr(content.name + ': unsupported: ' + (issue.symbol ? issue.symbol : '(content)') + (issue.slot !== null ? ' slot ' + issue.slot : '') + ': ' + issue.message + '\n');
914
+ });
915
+ });
916
+ io.stdout('From: ' + relativePath(cwd, plan.root) + '\n');
917
+ io.stdout('To: ' + (plan.outDir !== null ? relativePath(cwd, plan.outDir) + ' (each at its path from "From")' : 'generated/ next to each published file') + '\n\n');
918
+ const header = ['published', 'composition id', 'images', ''];
919
+ const rows = plan.contents.map(content => [
920
+ content.name,
921
+ shortId(content.id),
922
+ content.images.length ? String(content.images.length) : '',
923
+ content.problem !== null ? 'not written: ' + content.problem + (content.issues.length && !force ? ' (--force writes it anyway)' : '') : content.exists ? 'replace' : 'new'
924
+ ]);
925
+ const widths = [0, 1, 2].map(column => Math.max(header[column].length, ...rows.map(row => row[column].length)));
926
+ [header].concat(rows).forEach(row => {
927
+ io.stdout((' ' + pad(row[0], widths[0]) + ' ' + pad(row[1], widths[1]) + ' ' + ' '.repeat(widths[2] - row[2].length) + row[2] + ' ' + row[3]).replace(/\s+$/, '') + '\n');
928
+ });
929
+ const writes = plan.contents.filter(content => content.problem === null).length;
930
+ const notWritten = plan.contents.length - writes;
931
+ const counts = {};
932
+ plan.contents.forEach(content => {
933
+ if (content.id !== null) {
934
+ counts[content.id] = (counts[content.id] || 0) + 1;
935
+ }
936
+ });
937
+ const shared = Object.keys(counts).filter(id => counts[id] > 1).length;
938
+ io.stdout('\n' + writes + ' to write' + (notWritten ? ', ' + notWritten + ' not written' : '') + '.' +
939
+ (shared ? ' ' + shared + ' composition id(s) shared by several files (on one page, the later replaces the earlier).' : '') + '\n');
940
+ }
941
+ /**
942
+ * The command line tool: {@link planGeneration}, the plan listed (and, in a
943
+ * terminal, confirmed; without one, written only with -y), then
944
+ * {@link writeGeneration}. Returns the exit code: 0 when every content was
945
+ * written, 1 when some could not be (unsupported features without --force,
946
+ * images the content uses that are missing) or nothing was written for want of
947
+ * a "y", 2 for usage and loading errors.
948
+ */
949
+ async function runCli(args, io) {
950
+ const cwd = io.cwd || process.cwd();
951
+ let input = null;
952
+ let outDir = null;
953
+ let force = false;
954
+ let recursive = false;
955
+ let yes = false;
956
+ for (let i = 0; i < args.length; i++) {
957
+ const arg = args[i];
958
+ if (arg === '--out-dir' || arg === '-o') {
959
+ const value = args[++i];
960
+ if (value === undefined) {
961
+ io.stderr(arg + ' needs a directory\n\n' + USAGE + '\n');
962
+ return 2;
963
+ }
964
+ outDir = value;
965
+ }
966
+ else if (arg === '--force' || arg === '-f') {
967
+ force = true;
968
+ }
969
+ else if (arg === '--recursive' || arg === '-r') {
970
+ recursive = true;
971
+ }
972
+ else if (arg === '--yes' || arg === '-y') {
973
+ yes = true;
974
+ }
975
+ else if (arg === '--help' || arg === '-h') {
976
+ io.stdout(USAGE + '\n');
977
+ return 0;
978
+ }
979
+ else if (arg.charAt(0) === '-' || input !== null) {
980
+ io.stderr('unexpected argument: ' + arg + '\n\n' + USAGE + '\n');
981
+ return 2;
982
+ }
983
+ else {
984
+ input = arg;
985
+ }
986
+ }
987
+ // -y: no questions at all (what is not given takes its default)
988
+ const ask = yes ? undefined : io.ask;
989
+ if (input === null) {
990
+ input = ask ? (await ask('Directory of the published content (empty: the current directory): ')).trim() : '';
991
+ }
992
+ input = input || '.';
993
+ if (outDir === null && ask) {
994
+ // The published files found (by name: they are run when the plan is made), then where to write them
995
+ let found;
996
+ try {
997
+ found = findPublished(input, recursive, cwd);
998
+ }
999
+ catch (e) {
1000
+ io.stderr((e instanceof Error ? e.message : String(e)) + '\n');
1001
+ return 2;
1002
+ }
1003
+ io.stdout('Published content in ' + relativePath(cwd, found.root) + ':\n');
1004
+ found.files.forEach(file => {
1005
+ io.stdout(' ' + relativePath(found.root, file) + '\n');
1006
+ });
1007
+ const answer = (await ask('Output directory (empty: ' + (recursive ? 'generated/ next to each published file' : relativePath(cwd, path__namespace.join(found.root, 'generated'))) + '): ')).trim();
1008
+ outDir = answer || null;
1009
+ }
1010
+ let plan;
1011
+ try {
1012
+ plan = await planGeneration({ input, outDir, recursive, force, cwd });
1013
+ }
1014
+ catch (e) {
1015
+ io.stderr((e instanceof Error ? e.message : String(e)) + '\n');
1016
+ return 2;
1017
+ }
1018
+ showPlan(plan, force, cwd, io);
1019
+ if (plan.contents.every(content => content.id === null)) {
1020
+ return 2;
1021
+ }
1022
+ const failed = plan.contents.some(content => content.problem !== null);
1023
+ if (plan.contents.every(content => content.problem !== null)) {
1024
+ io.stdout('Nothing to write.\n');
1025
+ return 1;
1026
+ }
1027
+ if (!yes) {
1028
+ // Nobody to ask (not a terminal): written only with -y
1029
+ if (!ask) {
1030
+ io.stdout('Nothing written: not run in a terminal, so nobody could be asked. Run with -y to write.\n');
1031
+ return 1;
1032
+ }
1033
+ const answer = (await ask('Write these? (y/N): ')).trim().toLowerCase();
1034
+ if (answer !== 'y' && answer !== 'yes') {
1035
+ io.stdout('Nothing written.\n');
1036
+ return 1;
1037
+ }
1038
+ }
1039
+ const result = writeGeneration(plan);
1040
+ io.stdout('Written: ' + result.written.length + ' file(s).\n');
1041
+ if (result.left.length) {
1042
+ io.stdout('Not written by this run (remove them if no longer needed):\n');
1043
+ result.left.forEach(file => {
1044
+ io.stdout(' ' + relativePath(cwd, file) + '\n');
1045
+ });
1046
+ }
1047
+ return failed ? 1 : 0;
1048
+ }
1049
+
1050
+ exports.compositionId = compositionId;
1051
+ exports.generate = generate;
1052
+ exports.generateFromPublishedFile = generateFromPublishedFile;
1053
+ exports.planGeneration = planGeneration;
1054
+ exports.runCli = runCli;
1055
+ exports.writeGeneration = writeGeneration;
1056
+ //# sourceMappingURL=generator.cjs.js.map