ember-cli 7.2.0-alpha.1 → 7.2.0-beta.1

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 (33) hide show
  1. package/CHANGELOG.md +40 -46
  2. package/RELEASE.md +6 -5
  3. package/docs/build/data.json +24 -9
  4. package/lib/models/project.js +13 -5
  5. package/lib/utilities/version-utils.js +6 -1
  6. package/package.json +23 -22
  7. package/packages/addon-blueprint/package.json +5 -5
  8. package/packages/app-blueprint/files/package.json +18 -18
  9. package/packages/app-blueprint/package.json +4 -4
  10. package/tests/helpers/acceptance.js +1 -7
  11. package/tests/helpers/fixturify-project.js +29 -137
  12. package/tests/helpers/per-bundle-addon-cache.js +17 -23
  13. package/packages/blueprint-blueprint/files/blueprints/__name__/files/.gitkeep +0 -0
  14. package/packages/blueprint-blueprint/files/blueprints/__name__/index.js +0 -16
  15. package/packages/blueprint-blueprint/index.js +0 -5
  16. package/packages/blueprint-blueprint/package.json +0 -13
  17. package/packages/blueprint-model/index.js +0 -1807
  18. package/packages/blueprint-model/node_modules/.bin/markdown-it +0 -21
  19. package/packages/blueprint-model/package.json +0 -31
  20. package/packages/blueprint-model/utilities/clean-remove.js +0 -31
  21. package/packages/blueprint-model/utilities/directory-for-package-name.js +0 -31
  22. package/packages/blueprint-model/utilities/edit-file-diff.js +0 -64
  23. package/packages/blueprint-model/utilities/experiments.js +0 -63
  24. package/packages/blueprint-model/utilities/file-info.js +0 -170
  25. package/packages/blueprint-model/utilities/insert-into-file.js +0 -125
  26. package/packages/blueprint-model/utilities/is-addon.js +0 -5
  27. package/packages/blueprint-model/utilities/markdown-color.js +0 -208
  28. package/packages/blueprint-model/utilities/open-editor.js +0 -44
  29. package/packages/blueprint-model/utilities/prepend-emoji.js +0 -12
  30. package/packages/blueprint-model/utilities/print-command.js +0 -102
  31. package/packages/blueprint-model/utilities/process-template.js +0 -10
  32. package/packages/blueprint-model/utilities/sequence.js +0 -42
  33. package/packages/blueprint-model/utilities/walk-up-path.js +0 -23
@@ -1,1807 +0,0 @@
1
- 'use strict';
2
-
3
- /**
4
- @module ember-cli
5
- */
6
- const FileInfo = require('@ember-tooling/blueprint-model/utilities/file-info');
7
- const { default: chalk } = require('chalk');
8
- const MarkdownColor = require('@ember-tooling/blueprint-model/utilities/markdown-color');
9
- const sequence = require('./utilities/sequence');
10
- const printCommand = require('@ember-tooling/blueprint-model/utilities/print-command');
11
- const insertIntoFile = require('@ember-tooling/blueprint-model/utilities/insert-into-file');
12
- const cleanRemove = require('@ember-tooling/blueprint-model/utilities/clean-remove');
13
- const fs = require('fs-extra');
14
- const { pluralize } = require('inflection');
15
- const { minimatch } = require('minimatch');
16
- const path = require('path');
17
- const stringUtils = require('ember-cli-string-utils');
18
- const merge = require('lodash/merge');
19
- const zipObject = require('lodash/zipObject');
20
- const intersection = require('lodash/intersection');
21
- const cloneDeep = require('lodash/cloneDeep');
22
- const compact = require('lodash/compact');
23
- const uniq = require('lodash/uniq');
24
- const sortBy = require('lodash/sortBy');
25
- const walkSync = require('walk-sync');
26
- const SilentError = require('silent-error');
27
- const CoreObject = require('core-object');
28
- const EOL = require('os').EOL;
29
- const logger = require('heimdalljs-logger')('ember-cli:blueprint');
30
- const normalizeEntityName = require('ember-cli-normalize-entity-name');
31
- const isAddon = require('@ember-tooling/blueprint-model/utilities/is-addon');
32
-
33
- const initialIgnoredFiles = ['.DS_Store'];
34
-
35
- /**
36
- A blueprint is a bundle of template files with optional install
37
- logic.
38
-
39
- Blueprints follow a simple structure. Let's take the built-in
40
- `controller` blueprint as an example:
41
-
42
- ```
43
- blueprints/controller
44
- ├── files
45
- │ ├── app
46
- │ │ └── __path__
47
- │ │ └── __name__.js
48
- └── index.js
49
-
50
- blueprints/controller-test
51
- ├── files
52
- │ └── tests
53
- │ └── unit
54
- │ └── controllers
55
- │ └── __test__.js
56
- └── index.js
57
- ```
58
-
59
- ## Files
60
-
61
- `files` contains templates for the all the files to be
62
- installed into the target directory.
63
-
64
- The `__name__` token is subtituted with the dasherized
65
- entity name at install time. For example, when the user
66
- invokes `ember generate controller foo` then `__name__` becomes
67
- `foo`. When the `--pod` flag is used, for example `ember
68
- generate controller foo --pod` then `__name__` becomes
69
- `controller`.
70
-
71
- The `__path__` token is substituted with the blueprint
72
- name at install time. For example, when the user invokes
73
- `ember generate controller foo` then `__path__` becomes
74
- `controller`. When the `--pod` flag is used, for example
75
- `ember generate controller foo --pod` then `__path__`
76
- becomes `foo` (or `<podModulePrefix>/foo` if the
77
- podModulePrefix is defined). This token is primarily for
78
- pod support, and is only necessary if the blueprint can be
79
- used in pod structure. If the blueprint does not require pod
80
- support, simply use the blueprint name instead of the
81
- `__path__` token.
82
-
83
- The `__test__` token is substituted with the dasherized
84
- entity name and appended with `-test` at install time.
85
- This token is primarily for pod support and only necessary
86
- if the blueprint requires support for a pod structure. If
87
- the blueprint does not require pod support, simply use the
88
- `__name__` token instead.
89
-
90
- ## Template Variables (AKA Locals)
91
-
92
- Variables can be inserted into templates with
93
- `<%= someVariableName %>`.
94
-
95
- For example, the built-in `util` blueprint
96
- `files/app/utils/__name__.js` looks like this:
97
-
98
- ```js
99
- export default function <%= camelizedModuleName %>() {
100
- return true;
101
- }
102
- ```
103
-
104
- `<%= camelizedModuleName %>` is replaced with the real
105
- value at install time.
106
-
107
- The following template variables are provided by default:
108
-
109
- - `dasherizedPackageName`
110
- - `classifiedPackageName`
111
- - `dasherizedModuleName`
112
- - `classifiedModuleName`
113
- - `camelizedModuleName`
114
-
115
- `packageName` is the project name as found in the project's
116
- `package.json`.
117
-
118
- `moduleName` is the name of the entity being generated.
119
-
120
- The mechanism for providing custom template variables is
121
- described below.
122
-
123
- ## Index.js
124
-
125
- Custom installation and uninstallation behavior can be added
126
- by overriding the hooks documented below. `index.js` should
127
- export a plain object, which will extend the prototype of the
128
- `Blueprint` class. If needed, the original `Blueprint` prototype
129
- can be accessed through the `_super` property.
130
-
131
- ```js
132
- module.exports = {
133
- locals(options) {
134
- // Return custom template variables here.
135
- return {};
136
- },
137
-
138
- normalizeEntityName(entityName) {
139
- // Normalize and validate entity name here.
140
- return entityName;
141
- },
142
-
143
- fileMapTokens(options) {
144
- // Return custom tokens to be replaced in your files
145
- return {
146
- __token__(options){
147
- // logic to determine value goes here
148
- return 'value';
149
- }
150
- }
151
- },
152
-
153
- filesPath(options) {
154
- return path.join(this.path, 'files');
155
- },
156
-
157
- beforeInstall(options) {},
158
- afterInstall(options) {},
159
- beforeUninstall(options) {},
160
- afterUninstall(options) {}
161
-
162
- };
163
- ```
164
-
165
- ## Blueprint Hooks
166
-
167
- ### beforeInstall & beforeUninstall
168
-
169
- Called before any of the template files are processed and receives
170
- the `options` and `locals` hashes as parameters. Typically used for
171
- validating any additional command line options or for any asynchronous
172
- setup that is needed. As an example, the `controller` blueprint validates
173
- its `--type` option in this hook. If you need to run any asynchronous code,
174
- wrap it in a promise and return that promise from these hooks. This will
175
- ensure that your code is executed correctly.
176
-
177
- ### afterInstall & afterUninstall
178
-
179
- The `afterInstall` and `afterUninstall` hooks receives the same
180
- arguments as `locals`. Use it to perform any custom work after the
181
- files are processed. For example, the built-in `route` blueprint
182
- uses these hooks to add and remove relevant route declarations in
183
- `app/router.js`.
184
-
185
- ### Overriding Install
186
-
187
- If you don't want your blueprint to install the contents of
188
- `files` you can override the `install` method. It receives the
189
- same `options` object described above and must return a promise.
190
- See the built-in `resource` blueprint for an example of this.
191
-
192
- @class Blueprint
193
- @constructor
194
- @extends CoreObject
195
- @param {String} [blueprintPath]
196
- @param {Object} [blueprintOptions]
197
- */
198
- let Blueprint = CoreObject.extend({
199
- availableOptions: [],
200
- anonymousOptions: ['name'],
201
-
202
- _printableProperties: ['name', 'description', 'availableOptions', 'anonymousOptions', 'overridden'],
203
-
204
- /**
205
- Indicates whether or not a blueprint is a candidate for automatic transpilation from TS to JS.
206
- This property could be false in the case that the blueprint is written in JS and is not intended
207
- to work with TS at all, OR in the case that the blueprint is written in TS and the author does
208
- not intend to support transpilation to JS.
209
-
210
- @public
211
- @property shouldTransformTypeScript
212
- @type Boolean
213
- */
214
- shouldTransformTypeScript: false,
215
-
216
- init(blueprintPath, blueprintOptions) {
217
- this._super();
218
-
219
- this.path = blueprintPath;
220
- this.name = blueprintOptions?.name ?? path.basename(blueprintPath);
221
-
222
- this._processOptions(blueprintOptions);
223
- },
224
-
225
- /**
226
- Process the options object coming from either
227
- the `init`, `install` or `uninstall` hook.
228
-
229
- @private
230
- @method _processOptions
231
- @param {Object} options
232
- */
233
- _processOptions(options = {}) {
234
- this.options = options;
235
- this.dryRun = options.dryRun;
236
- this.pod = options.pod;
237
- this.project = options.project;
238
- this.ui = options.ui;
239
- },
240
-
241
- /**
242
- Hook to specify the path to the blueprint's files. By default this is
243
- `path.join(this.path, 'files)`.
244
-
245
- This can be used to customize which set of files to install based on options
246
- or environmental variables. It defaults to the `files` directory within the
247
- blueprint's folder.
248
-
249
- @public
250
- @method filesPath
251
- @param {Object} options
252
- @return {String} Path to the blueprints files directory.
253
- */
254
- filesPath(/* options */) {
255
- return path.join(this.path, 'files');
256
- },
257
-
258
- /**
259
- Used to retrieve files for blueprint.
260
-
261
- @public
262
- @method files
263
- @param {Object} options
264
- @return {Array} Contents of the blueprint's files directory
265
- */
266
- files(/* options */) {
267
- if (this._files) {
268
- return this._files;
269
- }
270
-
271
- let filesPath = this.filesPath(this.options);
272
- if (Blueprint._existsSync(filesPath)) {
273
- this._files = walkSync(filesPath);
274
- } else {
275
- this._files = [];
276
- }
277
-
278
- return this._files;
279
- },
280
-
281
- /**
282
- @method srcPath
283
- @param {String} file
284
- @return {String} Resolved path to the file
285
- */
286
- srcPath(file) {
287
- return path.resolve(this.filesPath(this.options), file);
288
- },
289
-
290
- /**
291
- Hook for normalizing entity name
292
-
293
- Use the `normalizeEntityName` hook to add custom normalization and
294
- validation of the provided entity name. The default hook does not
295
- make any changes to the entity name, but makes sure an entity name
296
- is present and that it doesn't have a trailing slash.
297
-
298
- This hook receives the entity name as its first argument. The string
299
- returned by this hook will be used as the new entity name.
300
-
301
- @public
302
- @method normalizeEntityName
303
- @param {String} entityName
304
- @return {null}
305
- */
306
- normalizeEntityName(entityName) {
307
- return normalizeEntityName(entityName);
308
- },
309
-
310
- /**
311
- Write a status and message to the UI
312
- @private
313
- @method _writeStatusToUI
314
- @param {Function} chalkColor
315
- @param {String} keyword
316
- @param {String} message
317
- */
318
- _writeStatusToUI(chalkColor, keyword, message) {
319
- if (this.ui) {
320
- this.ui.writeLine(` ${chalkColor(keyword)} ${message}`);
321
- }
322
- },
323
-
324
- /**
325
- @private
326
- @method _writeFile
327
- @param {Object} info
328
- @return {Promise}
329
- */
330
- async _writeFile(info) {
331
- if (!this.dryRun) {
332
- return fs.outputFile(info.outputPath, await info.render());
333
- }
334
- },
335
-
336
- /**
337
- Actions lookup
338
- @private
339
- @property _actions
340
- @type Object
341
- */
342
- _actions: {
343
- write(info) {
344
- this._writeStatusToUI(chalk.green, 'create', info.displayPath);
345
- return this._writeFile(info);
346
- },
347
- skip(info) {
348
- let label = 'skip';
349
-
350
- if (info.resolution === 'identical') {
351
- label = 'identical';
352
- }
353
-
354
- this._writeStatusToUI(chalk.yellow, label, info.displayPath);
355
- },
356
-
357
- overwrite(info) {
358
- this._writeStatusToUI(chalk.yellow, 'overwrite', info.displayPath);
359
- return this._writeFile(info);
360
- },
361
-
362
- edit(info) {
363
- this._writeStatusToUI(chalk.green, 'edited', info.displayPath);
364
- },
365
-
366
- remove(info) {
367
- this._writeStatusToUI(chalk.red, 'remove', info.displayPath);
368
- if (!this.dryRun) {
369
- return cleanRemove(info);
370
- }
371
- },
372
- },
373
-
374
- /**
375
- Calls an action.
376
- @private
377
- @method _commit
378
- @param {Object} result
379
- @return {Promise}
380
- @throws {Error} Action doesn't exist.
381
- */
382
- _commit(result) {
383
- let action = this._actions[result.action];
384
-
385
- if (action) {
386
- return action.call(this, result);
387
- } else {
388
- throw new Error(`Tried to call action "${result.action}" but it does not exist`);
389
- }
390
- },
391
-
392
- /**
393
- Prints warning for pod unsupported.
394
- @private
395
- @method _checkForPod
396
- */
397
- _checkForPod(verbose) {
398
- if (!this.hasPathToken && this.pod && verbose) {
399
- this.ui.writeLine(
400
- chalk.yellow(
401
- 'You specified the pod flag, but this' +
402
- ' blueprint does not support pod structure. It will be generated with' +
403
- ' the default structure.'
404
- )
405
- );
406
- }
407
- },
408
-
409
- /**
410
- @private
411
- @method _normalizeEntityName
412
- @param {Object} entity
413
- */
414
- _normalizeEntityName(entity) {
415
- if (entity) {
416
- entity.name = this.normalizeEntityName(entity.name);
417
- }
418
- },
419
-
420
- /**
421
- @private
422
- @method _checkInRepoAddonExists
423
- @param {Object} options
424
- */
425
- _checkInRepoAddonExists(options) {
426
- let addon;
427
-
428
- if (options.inRepoAddon) {
429
- addon = findAddonByName(this.project, options.inRepoAddon);
430
-
431
- if (!addon) {
432
- throw new SilentError(
433
- `You specified the 'in-repo-addon' flag, but the ` +
434
- `in-repo-addon '${options.inRepoAddon}' does not exist. Please check the name and try again.`
435
- );
436
- }
437
- }
438
-
439
- if (options.in) {
440
- if (!ensureTargetDirIsAddon(options.in)) {
441
- throw new SilentError(
442
- `You specified the 'in' flag, but the ` +
443
- `in repo addon '${options.in}' does not exist. Please check the name and try again.`
444
- );
445
- }
446
- }
447
- },
448
-
449
- /**
450
- @private
451
- @method _process
452
- @param {Object} options
453
- @param {Function} beforeHook
454
- @param {Function} process
455
- @param {Function} afterHook
456
- */
457
- async _process(options, beforeHook, process, afterHook) {
458
- let intoDir = options.target;
459
-
460
- let locals = await this._locals(options);
461
-
462
- // run beforeInstall/beforeUninstall userland hooks
463
- await beforeHook.call(this, options, locals);
464
-
465
- // gather fileInfos to be processed
466
- let fileInfos = await process.call(this, intoDir, locals, options);
467
-
468
- // commit changes for each FileInfo (with prompting as needed)
469
- await Promise.all(fileInfos.map((info) => this._commit(info)));
470
-
471
- // run afterInstall/afterUninstall userland hooks
472
- await afterHook.call(this, options);
473
- },
474
-
475
- /**
476
- @private
477
- @method shouldConvertToJS
478
- @param {Object} options
479
- @param {FileInfo} fileInfo
480
- @return {Boolean}
481
- */
482
- shouldConvertToJS(options, fileInfo) {
483
- // If this isn't turned on, it doesn't matter what else was passed, we're not touching it.
484
- if (!this.shouldTransformTypeScript) {
485
- return false;
486
- }
487
-
488
- // If the blueprint isn't a TS file to begin with, there's nothing to convert.
489
- if (!isTypeScriptFile(fileInfo.outputPath)) {
490
- return false;
491
- }
492
-
493
- // Indicates when the user explicitly passed either `--typescript` or `--no-typescript` as opposed
494
- // to not passing a flag at all and allowing for default behavior
495
- const userExplicitlySelectedTypeScriptStatus = options.typescript !== undefined;
496
-
497
- // Indicates when the user has asked for TypeScript either globally (by setting
498
- // `isTypeScriptProject` to true) or locally (by passing the `--typescript` flag when they
499
- // invoked the generator). Although ember-cli merges `.ember-cli` and all of the flag values into
500
- // one object, we thought the DX would be improved by differentiating between what is intended
501
- // to be global vs. local config.
502
- const shouldUseTypeScript = userExplicitlySelectedTypeScriptStatus
503
- ? options.typescript
504
- : options.isTypeScriptProject;
505
-
506
- // if the user wants TS output and we have a TS file available, we do *not* want to downlevel to JS
507
- if (shouldUseTypeScript) {
508
- return false;
509
- }
510
-
511
- return true;
512
- },
513
-
514
- /**
515
- @private
516
- @method convertToJS
517
- @param {FileInfo} fileInfo
518
- @return {Promise}
519
- */
520
- async convertToJS(fileInfo) {
521
- let rendered = await fileInfo.render();
522
-
523
- fileInfo.rendered = await this.removeTypes(path.extname(fileInfo.displayPath), rendered);
524
- fileInfo.displayPath = replaceTypeScriptExtension(fileInfo.displayPath);
525
- fileInfo.outputPath = replaceTypeScriptExtension(fileInfo.outputPath);
526
-
527
- return fileInfo;
528
- },
529
-
530
- /**
531
- @private
532
- @method removeTypes
533
- @param {string} extension
534
- @param {string} code
535
- @return {Promise}
536
- */
537
- async removeTypes(extension, code) {
538
- const { removeTypes } = require('babel-remove-types');
539
-
540
- if (extension === '.gts') {
541
- const { Preprocessor } = require('content-tag');
542
- const preprocessor = new Preprocessor();
543
- // Strip template tags
544
- const replacementClassMember = (i) => `template = __TEMPLATE_TAG_${i}__;`;
545
- const replacementExpression = (i) => `__TEMPLATE_TAG_${i}__`;
546
- const templateTagMatches = preprocessor.parse(code);
547
- let strippedCode = code;
548
- for (let i = 0; i < templateTagMatches.length; i++) {
549
- const match = templateTagMatches[i];
550
- const templateTag = substringBytes(code, match.range.startByte, match.range.endByte);
551
- if (match.type === 'class-member') {
552
- strippedCode = strippedCode.replace(templateTag, replacementClassMember(i));
553
- } else {
554
- strippedCode = strippedCode.replace(templateTag, replacementExpression(i));
555
- }
556
- }
557
-
558
- // Remove types
559
- const transformed = await removeTypes(strippedCode);
560
-
561
- // Readd stripped template tags
562
- let transformedWithTemplateTag = transformed;
563
- for (let i = 0; i < templateTagMatches.length; i++) {
564
- const match = templateTagMatches[i];
565
- const templateTag = substringBytes(code, match.range.startByte, match.range.endByte);
566
- if (match.type === 'class-member') {
567
- transformedWithTemplateTag = transformedWithTemplateTag.replace(replacementClassMember(i), templateTag);
568
- } else {
569
- // babel-remove-types uses prettier under the hood, and adds trailing `;` where allowed,
570
- // so we need to take that into account when restoring the template tags:
571
- transformedWithTemplateTag = transformedWithTemplateTag.replace(`${replacementExpression(i)};`, templateTag);
572
- transformedWithTemplateTag = transformedWithTemplateTag.replace(replacementExpression(i), templateTag);
573
- }
574
- }
575
-
576
- return transformedWithTemplateTag;
577
- }
578
-
579
- return await removeTypes(code);
580
- },
581
-
582
- /**
583
- @method install
584
- @param {Object} options
585
- @return {Promise}
586
- */
587
- install(options) {
588
- this._processOptions(options);
589
-
590
- this.hasPathToken = hasPathToken(this.files(this.options));
591
-
592
- this.ui.writeLine(`installing ${this.name}`);
593
-
594
- if (this.dryRun) {
595
- this.ui.writeLine(chalk.yellow('You specified the `dry-run` flag, so no changes will be written.'));
596
- }
597
-
598
- this._normalizeEntityName(options.entity);
599
- this._checkForPod(options.verbose);
600
- this._checkInRepoAddonExists(options);
601
-
602
- logger.info('START: processing blueprint: `%s`', this.name);
603
- let start = new Date();
604
- return this._process(options, this.beforeInstall, this.processFiles, this.afterInstall).finally(() =>
605
- logger.info('END: processing blueprint: `%s` in (%dms)', this.name, new Date() - start)
606
- );
607
- },
608
-
609
- /**
610
- @method uninstall
611
- @param {Object} options
612
- @return {Promise}
613
- */
614
- uninstall(options) {
615
- this._processOptions(options);
616
-
617
- this.hasPathToken = hasPathToken(this.files(this.options));
618
-
619
- this.ui.writeLine(`uninstalling ${this.name}`);
620
-
621
- if (this.dryRun) {
622
- this.ui.writeLine(chalk.yellow('You specified the `dry-run` flag, so no files will be deleted.'));
623
- }
624
-
625
- this._normalizeEntityName(options.entity);
626
- this._checkForPod(options.verbose);
627
-
628
- return this._process(options, this.beforeUninstall, this.processFilesForUninstall, this.afterUninstall);
629
- },
630
-
631
- /**
632
- Hook for running operations before install.
633
- @method beforeInstall
634
- @return {Promise|null}
635
- */
636
- beforeInstall() {},
637
-
638
- /**
639
- Hook for running operations after install.
640
- @method afterInstall
641
- @return {Promise|null}
642
- */
643
- afterInstall() {},
644
-
645
- /**
646
- Hook for running operations before uninstall.
647
- @method beforeUninstall
648
- @return {Promise|null}
649
- */
650
- beforeUninstall() {},
651
-
652
- /**
653
- Hook for running operations after uninstall.
654
- @method afterUninstall
655
- @return {Promise|null}
656
- */
657
- afterUninstall() {},
658
-
659
- filesToRemove: [],
660
-
661
- /**
662
- Hook for adding custom template variables.
663
-
664
- When the following is called on the command line:
665
-
666
- ```sh
667
- ember generate controller foo --type=array --dry-run isAdmin:true
668
- ```
669
-
670
- The object passed to `locals` looks like this:
671
-
672
- ```js
673
- {
674
- entity: {
675
- name: 'foo',
676
- options: {
677
- isAdmin: true
678
- }
679
- },
680
- dryRun: true
681
- type: "array"
682
- // more keys
683
- }
684
- ```
685
-
686
- This hook must return an object or a Promise which resolves to an object.
687
- The resolved object will be merged with the aforementioned default locals.
688
-
689
- @public
690
- @method locals
691
- @param {Object} options General and entity-specific options
692
- @return {Object|Promise|null}
693
- */
694
- locals(/* options */) {},
695
-
696
- /**
697
- Hook to add additional or override existing fileMap tokens.
698
-
699
- Use `fileMapTokens` to add custom fileMap tokens for use
700
- in the `mapFile` method. The hook must return an object in the
701
- following pattern:
702
-
703
- ```js
704
- {
705
- __token__(options){
706
- // logic to determine value goes here
707
- return 'value';
708
- }
709
- }
710
- ```
711
-
712
- It will be merged with the default `fileMapTokens`, and can be used
713
- to override any of the default tokens.
714
-
715
- Tokens are used in the files folder (see `files`), and get replaced with
716
- values when the `mapFile` method is called.
717
-
718
- @public
719
- @method fileMapTokens
720
- @return {Object|null}
721
- */
722
- fileMapTokens() {},
723
-
724
- /**
725
- @private
726
- @method _fileMapTokens
727
- @param {Object} options
728
- @return {Object}
729
- */
730
- _fileMapTokens(options) {
731
- let { project } = this;
732
- let standardTokens = {
733
- __name__(options) {
734
- if (options.pod && options.hasPathToken) {
735
- return options.blueprintName;
736
- }
737
- return options.dasherizedModuleName;
738
- },
739
- __path__(options) {
740
- let blueprintName = options.blueprintName;
741
-
742
- if (/-test/.test(blueprintName)) {
743
- blueprintName = options.blueprintName.slice(0, options.blueprintName.indexOf('-test'));
744
- }
745
- if (options.pod && options.hasPathToken) {
746
- return path.join(options.podPath, options.dasherizedModuleName);
747
- }
748
- return pluralize(blueprintName);
749
- },
750
- __root__(options) {
751
- if (options.inRepoAddon) {
752
- let addon = findAddonByName(project, options.inRepoAddon);
753
- let relativeAddonPath = path.relative(project.root, addon.root);
754
- return path.join(relativeAddonPath, 'addon');
755
- }
756
- if (options.in) {
757
- let relativeAddonPath = path.relative(project.root, options.in);
758
- return path.join(relativeAddonPath, 'addon');
759
- }
760
- if (options.inDummy) {
761
- return path.join('tests', 'dummy', 'app');
762
- }
763
- if (options.inAddon) {
764
- return 'addon';
765
- }
766
- return 'app';
767
- },
768
- __test__(options) {
769
- if (options.pod && options.hasPathToken) {
770
- return options.blueprintName;
771
- }
772
- return `${options.dasherizedModuleName}-test`;
773
- },
774
- };
775
-
776
- let customTokens = this.fileMapTokens(options) || options.fileMapTokens || {};
777
- return merge(standardTokens, customTokens);
778
- },
779
-
780
- /**
781
- Used to generate fileMap tokens for mapFile.
782
-
783
- @method generateFileMap
784
- @param {Object} fileMapVariables
785
- @return {Object}
786
- */
787
- generateFileMap(fileMapVariables) {
788
- let tokens = this._fileMapTokens(fileMapVariables);
789
- let fileMapValues = Object.values(tokens);
790
- let tokenValues = fileMapValues.map((token) => token(fileMapVariables));
791
- let tokenKeys = Object.keys(tokens);
792
- return zipObject(tokenKeys, tokenValues);
793
- },
794
-
795
- /**
796
- @method buildFileInfo
797
- @param {Function} destPath
798
- @param {Object} templateVariables
799
- @param {String} file
800
- @param {Object} options may be used when buildFileInfo is customized in a blueprint
801
- @return {FileInfo | null}
802
- */
803
- buildFileInfo(intoDir, templateVariables, file /*, options */) {
804
- let mappedPath = this.mapFile(file, templateVariables);
805
-
806
- return new FileInfo({
807
- action: 'write',
808
- outputBasePath: path.normalize(intoDir),
809
- outputPath: path.join(intoDir, mappedPath),
810
- displayPath: path.normalize(mappedPath),
811
- inputPath: this.srcPath(file),
812
- templateVariables,
813
- ui: this.ui,
814
- });
815
- },
816
-
817
- /**
818
- @method isUpdate
819
- @return {Boolean}
820
- */
821
- isUpdate() {
822
- if (this.project && this.project.isEmberCLIProject) {
823
- return this.project.isEmberCLIProject();
824
- }
825
- },
826
-
827
- /**
828
- @private
829
- @method _getFileInfos
830
- @param {Array} files
831
- @param {String} intoDir
832
- @param {Object} templateVariables
833
- @param {Object} options
834
- @return {Array} file infos
835
- */
836
- _getFileInfos(files, intoDir, templateVariables, options) {
837
- return files
838
- .map((file) => this.buildFileInfo.call(this, intoDir, templateVariables, file, options))
839
- .filter(Boolean);
840
- },
841
-
842
- /**
843
- Add update files to ignored files or reset them
844
- @private
845
- @method _ignoreUpdateFiles
846
- */
847
- _ignoreUpdateFiles() {
848
- if (this.isUpdate()) {
849
- Blueprint.ignoredFiles = Blueprint.ignoredFiles.concat(Blueprint.ignoredUpdateFiles);
850
- } else {
851
- Blueprint.ignoredFiles = initialIgnoredFiles;
852
- }
853
- },
854
-
855
- /**
856
- @private
857
- @method _getFilesForInstall
858
- @param {Array} targetFiles
859
- @return {Array} files
860
- */
861
- _getFilesForInstall(targetFiles) {
862
- let files = this.files(this.options);
863
-
864
- // if we've defined targetFiles, get file info on ones that match
865
- return (targetFiles && targetFiles.length > 0 && intersection(files, targetFiles)) || files;
866
- },
867
-
868
- /**
869
- @private
870
- @method _checkForNoMatch
871
- @param {Array} fileInfos
872
- @param {String} rawArgs
873
- */
874
- _checkForNoMatch(fileInfos, rawArgs) {
875
- if (fileInfos.filter(isFilePath).length < 1 && rawArgs) {
876
- this.ui.writeLine(
877
- chalk.yellow(`The globPattern "${rawArgs}" ` + `did not match any files, so no file updates will be made.`)
878
- );
879
- }
880
- },
881
-
882
- /**
883
- @method processFiles
884
- @param {String} intoDir
885
- @param {Object} templateVariables
886
- @param {Object} options
887
- @return {Promise<FileInfo[]>}
888
- */
889
- processFiles(intoDir, templateVariables, options) {
890
- let files = this._getFilesForInstall(templateVariables.targetFiles);
891
- let fileInfos = this._getFileInfos(files, intoDir, templateVariables, options);
892
- this._checkForNoMatch(fileInfos, templateVariables.rawArgs);
893
-
894
- this._ignoreUpdateFiles();
895
-
896
- let fileInfosToRemove = this._getFileInfos(this.filesToRemove, intoDir, templateVariables);
897
-
898
- fileInfosToRemove = finishProcessingForUninstall(fileInfosToRemove);
899
-
900
- return Promise.all(fileInfos.filter(isValidFile).map(prepareConfirm))
901
- .then(finishProcessingForInstall)
902
- .then((fileInfos) => {
903
- return Promise.all(
904
- fileInfos.map((info) => {
905
- if (this.shouldConvertToJS(this.options, info)) {
906
- return this.convertToJS(info);
907
- }
908
-
909
- return info;
910
- })
911
- );
912
- })
913
- .then((fileInfos) => fileInfos.concat(fileInfosToRemove));
914
- },
915
-
916
- /**
917
- @method processFilesForUninstall
918
- @param {String} intoDir
919
- @param {Object} templateVariables
920
- */
921
- processFilesForUninstall(intoDir, templateVariables) {
922
- let fileInfos = this._getFileInfos(this.files(this.options), intoDir, templateVariables);
923
-
924
- this._ignoreUpdateFiles();
925
-
926
- fileInfos = fileInfos.filter(isValidFile).reduce((acc, info) => {
927
- // if it's possible that this blueprint could have produced either typescript OR javascript, we have to do some
928
- // work to figure out which files to delete.
929
- if (this.shouldTransformTypeScript) {
930
- if (this.options.typescript === true) {
931
- // if the user explicitly passed `--typescript`, we only want to delete TS files, so we stick with the existing
932
- // info object since it will contain a .ts outputPath (since we know this blueprint is authored in TS because
933
- // of our check above)
934
- acc.push(info);
935
- return acc;
936
- }
937
-
938
- const jsInfo = new FileInfo({
939
- ...info,
940
- outputPath: replaceTypeScriptExtension(info.outputPath),
941
- displayPath: replaceTypeScriptExtension(info.displayPath),
942
- });
943
-
944
- if (this.options.typescript === false) {
945
- // if the user explicitly passed `--no-typescript`, we only want to delete JS file, so we return our newly
946
- // created jsInfo object since it contains the javascript version of the output path.
947
- acc.push(jsInfo);
948
- return acc;
949
- }
950
-
951
- if (this.options.typescript === undefined) {
952
- // if the user didn't specify one way or the other, then both the JS and TS paths are possibilities, so we add
953
- // both of them to the list. `finishProcessingForUninstall` will actually look to see which of them exists and
954
- // delete whatever it finds.
955
- acc.push(info, jsInfo);
956
- return acc;
957
- }
958
- }
959
-
960
- acc.push(info);
961
- return acc;
962
- }, []);
963
-
964
- return finishProcessingForUninstall(fileInfos);
965
- },
966
-
967
- /**
968
- @method mapFile
969
- @param {String} file
970
- @param locals
971
- @return {String}
972
- */
973
- mapFile(file, locals) {
974
- let pattern, i;
975
- let fileMap = locals.fileMap || { __name__: locals.dasherizedModuleName };
976
- file = Blueprint.renamedFiles[file] || file;
977
- for (i in fileMap) {
978
- pattern = new RegExp(i, 'g');
979
- file = file.replace(pattern, fileMap[i]);
980
- }
981
- return file;
982
- },
983
-
984
- /**
985
- Looks for a __root__ token in the files folder. Must be present for
986
- the blueprint to support addon tokens. The `server`, `blueprints`, and `test`
987
-
988
- @private
989
- @method supportsAddon
990
- @return {Boolean}
991
- */
992
- supportsAddon() {
993
- return /__root__/.test(this.files(this.options).join());
994
- },
995
-
996
- /**
997
- @private
998
- @method _generateFileMapVariables
999
- @param {String} moduleName
1000
- @param locals
1001
- @param {Object} options
1002
- @return {Object}
1003
- */
1004
- _generateFileMapVariables(moduleName, locals, options) {
1005
- let originBlueprintName = options.originBlueprintName || this.name;
1006
- let podModulePrefix = this.project.config().podModulePrefix || '';
1007
- let podPath = podModulePrefix.substr(podModulePrefix.lastIndexOf('/') + 1);
1008
- let inAddon = this.project.isEmberCLIAddon() || !!options.inRepoAddon;
1009
- let inDummy = this.project.isEmberCLIAddon() ? options.dummy : false;
1010
-
1011
- return {
1012
- pod: this.pod,
1013
- podPath,
1014
- hasPathToken: this.hasPathToken,
1015
- inAddon,
1016
- inRepoAddon: options.inRepoAddon,
1017
- in: options.in,
1018
- inDummy,
1019
- blueprintName: this.name,
1020
- originBlueprintName,
1021
- dasherizedModuleName: stringUtils.dasherize(moduleName),
1022
- locals,
1023
- };
1024
- },
1025
-
1026
- /**
1027
- @private
1028
- @method _locals
1029
- @param {Object} options
1030
- @return {Object}
1031
- */
1032
- _locals(options) {
1033
- let packageName = options.project.name();
1034
- let moduleName = (options.entity && options.entity.name) || packageName;
1035
- let sanitizedModuleName = moduleName.replace(/\//g, '-');
1036
-
1037
- return new Promise((resolve) => {
1038
- resolve(this.locals(options));
1039
- }).then((customLocals) => {
1040
- let fileMapVariables = this._generateFileMapVariables(moduleName, customLocals, options);
1041
- let fileMap = this.generateFileMap(fileMapVariables);
1042
- let standardLocals = {
1043
- dasherizedPackageName: stringUtils.dasherize(packageName),
1044
- classifiedPackageName: stringUtils.classify(packageName),
1045
- dasherizedModuleName: stringUtils.dasherize(moduleName),
1046
- classifiedModuleName: stringUtils.classify(sanitizedModuleName),
1047
- camelizedModuleName: stringUtils.camelize(sanitizedModuleName),
1048
- decamelizedModuleName: stringUtils.decamelize(sanitizedModuleName),
1049
- fileMap,
1050
- hasPathToken: this.hasPathToken,
1051
- targetFiles: options.targetFiles,
1052
- rawArgs: options.rawArgs,
1053
- };
1054
-
1055
- return merge({}, standardLocals, customLocals);
1056
- });
1057
- },
1058
-
1059
- /**
1060
- Used to add a package to the project's `package.json`.
1061
-
1062
- Generally, this would be done from the `afterInstall` hook, to
1063
- ensure that a package that is required by a given blueprint is
1064
- available.
1065
-
1066
- @method addPackageToProject
1067
- @param {String} packageName
1068
- @param {String} target
1069
- @return {Promise}
1070
- */
1071
- addPackageToProject(packageName, target) {
1072
- let packageObject = { name: packageName };
1073
-
1074
- if (target) {
1075
- packageObject.target = target;
1076
- }
1077
-
1078
- return this.addPackagesToProject([packageObject]);
1079
- },
1080
-
1081
- /**
1082
- Used to add multiple packages to the project's `package.json`.
1083
-
1084
- Generally, this would be done from the `afterInstall` hook, to
1085
- ensure that a package that is required by a given blueprint is
1086
- available.
1087
-
1088
- Expects each array item to be an object with a `name`. Each object
1089
- may optionally have a `target` to specify a specific version.
1090
-
1091
- @method addPackagesToProject
1092
- @param {Array} packages
1093
- @return {Promise}
1094
-
1095
- @example
1096
- ```js
1097
- this.addPackagesToProject([
1098
- { name: 'lodash' },
1099
- { name: 'moment', target: '^2.17.0' },
1100
- ]);
1101
- ```
1102
- */
1103
- addPackagesToProject(packages) {
1104
- let task = this.taskFor('npm-install');
1105
- let installText = packages.length > 1 ? 'install packages' : 'install package';
1106
- let packageNames = [];
1107
- let packageArray = [];
1108
-
1109
- for (let i = 0; i < packages.length; i++) {
1110
- packageNames.push(packages[i].name);
1111
-
1112
- let packageNameAndVersion = packages[i].name;
1113
-
1114
- if (packages[i].target) {
1115
- packageNameAndVersion += `@${packages[i].target}`;
1116
- }
1117
-
1118
- packageArray.push(packageNameAndVersion);
1119
- }
1120
-
1121
- this._writeStatusToUI(chalk.green, installText, packageNames.join(', '));
1122
-
1123
- return task.run({
1124
- 'save-dev': true,
1125
- verbose: false,
1126
- packages: packageArray,
1127
- });
1128
- },
1129
-
1130
- /**
1131
- Used to remove a package from the project's `package.json`.
1132
-
1133
- Generally, this would be done from the `afterInstall` hook, to
1134
- ensure that any package conflicts can be resolved before the
1135
- addon is used.
1136
-
1137
- @method removePackageFromProject
1138
- @param {String} packageName
1139
- @return {Promise}
1140
- */
1141
- removePackageFromProject(packageName) {
1142
- let packageObject = { name: packageName };
1143
-
1144
- return this.removePackagesFromProject([packageObject]);
1145
- },
1146
-
1147
- /**
1148
- Used to remove multiple packages from the project's `package.json`.
1149
-
1150
- Generally, this would be done from the `afterInstall` hook, to
1151
- ensure that any package conflicts can be resolved before the
1152
- addon is used.
1153
-
1154
- Expects each array item to be an object with a `name` property.
1155
-
1156
- @method removePackagesFromProject
1157
- @param {Array} packages
1158
- @return {Promise}
1159
- */
1160
- removePackagesFromProject(packages) {
1161
- let task = this.taskFor('npm-uninstall');
1162
- let installText = packages.length > 1 ? 'uninstall packages' : 'uninstall package';
1163
- let packageNames = [];
1164
-
1165
- let projectDependencies = this.project.dependencies();
1166
-
1167
- for (let i = 0; i < packages.length; i++) {
1168
- let packageName = packages[i].name;
1169
- if (packageName in projectDependencies) {
1170
- packageNames.push(packageName);
1171
- }
1172
- }
1173
-
1174
- if (packageNames.length === 0) {
1175
- this._writeStatusToUI(chalk.yellow, 'remove', 'Skipping uninstall because no matching package is installed.');
1176
- return Promise.resolve();
1177
- }
1178
-
1179
- this._writeStatusToUI(chalk.green, installText, packageNames.join(', '));
1180
-
1181
- return task.run({
1182
- 'save-dev': true,
1183
- verbose: false,
1184
- packages: packageNames,
1185
- });
1186
- },
1187
-
1188
- /**
1189
- Used to add an addon to the project's `package.json` and run it's
1190
- `defaultBlueprint` if it provides one.
1191
-
1192
- Generally, this would be done from the `afterInstall` hook, to
1193
- ensure that a package that is required by a given blueprint is
1194
- available.
1195
-
1196
- @method addAddonToProject
1197
- @param {Object} options
1198
- @return {Promise}
1199
- */
1200
- addAddonToProject(options) {
1201
- return this.addAddonsToProject({
1202
- packages: [options],
1203
- extraArgs: options.extraArgs || {},
1204
- blueprintOptions: options.blueprintOptions || {},
1205
- });
1206
- },
1207
-
1208
- /**
1209
- Used to add multiple addons to the project's `package.json` and run their
1210
- `defaultBlueprint` if they provide one.
1211
-
1212
- Generally, this would be done from the `afterInstall` hook, to
1213
- ensure that a package that is required by a given blueprint is
1214
- available.
1215
-
1216
- @method addAddonsToProject
1217
- @param {Object} options
1218
- @return {Promise}
1219
- */
1220
- addAddonsToProject(options) {
1221
- let taskOptions = {
1222
- packages: [],
1223
- extraArgs: options.extraArgs || [],
1224
- blueprintOptions: options.blueprintOptions || {},
1225
- };
1226
-
1227
- let packages = options.packages;
1228
- if (packages && packages.length) {
1229
- taskOptions.packages = packages.map((pkg) => {
1230
- if (typeof pkg === 'string') {
1231
- return pkg;
1232
- }
1233
-
1234
- if (!pkg.name) {
1235
- throw new SilentError('You must provide a package `name` to addAddonsToProject');
1236
- }
1237
-
1238
- if (pkg.target) {
1239
- pkg.name += `@${pkg.target}`;
1240
- }
1241
-
1242
- return pkg.name;
1243
- });
1244
- } else {
1245
- throw new SilentError('You must provide package to addAddonsToProject');
1246
- }
1247
-
1248
- let installText = packages.length > 1 ? 'install addons' : 'install addon';
1249
- this._writeStatusToUI(chalk.green, installText, taskOptions['packages'].join(', '));
1250
-
1251
- let previousCwd;
1252
- if (!this.project.isEmberCLIProject()) {
1253
- // The install task adds dependencies based on the current working directory.
1254
- // But in case we created the new project by calling the blueprint with a custom target directory (options.target),
1255
- // the current directory will *not* be the one the project is created in, so we must adjust this here.
1256
- previousCwd = process.cwd();
1257
- process.chdir(options.blueprintOptions.target);
1258
- }
1259
-
1260
- let result = this.taskFor('addon-install').run(taskOptions);
1261
- if (previousCwd) {
1262
- return result.then(() => process.chdir(previousCwd));
1263
- }
1264
-
1265
- return result;
1266
- },
1267
-
1268
- /**
1269
- Used to retrieve a task with the given name. Passes the new task
1270
- the standard information available (like `ui`, `project`, etc).
1271
-
1272
- @method taskFor
1273
- @param dasherizedName
1274
- @public
1275
- */
1276
- taskFor(dasherizedName) {
1277
- const Task = require(`../tasks/${dasherizedName}`);
1278
-
1279
- return new Task({
1280
- ui: this.ui,
1281
- project: this.project,
1282
- });
1283
- },
1284
-
1285
- /**
1286
- Inserts the given content into a file. If the `contentsToInsert` string is already
1287
- present in the current contents, the file will not be changed unless `force` option
1288
- is passed.
1289
-
1290
- If `options.before` is specified, `contentsToInsert` will be inserted before
1291
- the first instance of that string. If `options.after` is specified, the
1292
- contents will be inserted after the first instance of that string.
1293
- If the string specified by options.before or options.after is not in the file,
1294
- no change will be made.
1295
-
1296
- If neither `options.before` nor `options.after` are present, `contentsToInsert`
1297
- will be inserted at the end of the file.
1298
-
1299
- Example:
1300
- ```
1301
- // app/router.js
1302
- Router.map(function () {
1303
- });
1304
- ```
1305
-
1306
- ```
1307
- insertIntoFile('app/router.js', ' this.route("admin");', {
1308
- after: 'Router.map(function () {' + EOL
1309
- }).then(function() {
1310
- // file has been inserted into!
1311
- });
1312
-
1313
-
1314
- ```
1315
-
1316
- ```
1317
- // app/router.js
1318
- Router.map(function () {
1319
- this.route("admin");
1320
- });
1321
- ```
1322
-
1323
- @method insertIntoFile
1324
- @param {String} pathRelativeToProjectRoot
1325
- @param {String} contentsToInsert
1326
- @param {Object} providedOptions
1327
- @return {Promise}
1328
- */
1329
- insertIntoFile(pathRelativeToProjectRoot, contentsToInsert, providedOptions) {
1330
- let fullPath = path.join(this.project.root, pathRelativeToProjectRoot);
1331
- return insertIntoFile(fullPath, contentsToInsert, providedOptions);
1332
- },
1333
-
1334
- _printCommand: printCommand,
1335
-
1336
- printBasicHelp(verbose) {
1337
- let initialMargin = ' ';
1338
- let output = initialMargin;
1339
- if (this.overridden) {
1340
- output += chalk.grey(`(overridden) ${this.name}`);
1341
- } else {
1342
- output += this.name;
1343
-
1344
- output += this._printCommand(initialMargin, true);
1345
-
1346
- if (verbose) {
1347
- output += EOL + this.printDetailedHelp(this.availableOptions);
1348
- }
1349
- }
1350
-
1351
- return output;
1352
- },
1353
-
1354
- printDetailedHelp() {
1355
- let markdownColor = new MarkdownColor();
1356
- let filePath = getDetailedHelpPath(this.path);
1357
-
1358
- if (Blueprint._existsSync(filePath)) {
1359
- return markdownColor.renderFile(filePath, { indent: ' ' });
1360
- }
1361
- return '';
1362
- },
1363
-
1364
- getJson(verbose) {
1365
- let json = {};
1366
- this._printableProperties.forEach((key) => {
1367
- let value = this[key];
1368
- if (key === 'availableOptions') {
1369
- value = cloneDeep(value);
1370
- value.forEach((option) => {
1371
- if (typeof option.type === 'function') {
1372
- option.type = option.type.name;
1373
- }
1374
- });
1375
- }
1376
- json[key] = value;
1377
- });
1378
-
1379
- if (verbose) {
1380
- let detailedHelp = this.printDetailedHelp(this.availableOptions);
1381
- if (detailedHelp) {
1382
- json.detailedHelp = detailedHelp;
1383
- }
1384
- }
1385
-
1386
- return json;
1387
- },
1388
-
1389
- /**
1390
- Used to retrieve a blueprint with the given name.
1391
-
1392
- @method lookupBlueprint
1393
- @param {String} dasherizedName
1394
- @return {Blueprint}
1395
- @public
1396
- */
1397
- lookupBlueprint(dasherizedName) {
1398
- let projectPaths = this.project ? this.project.blueprintLookupPaths() : [];
1399
-
1400
- return Blueprint.lookup(dasherizedName, {
1401
- paths: projectPaths,
1402
- });
1403
- },
1404
- });
1405
-
1406
- const builtInBlueprints = new Map([
1407
- ['app', '@ember-tooling/classic-build-app-blueprint'],
1408
- ['addon', '@ember-tooling/classic-build-addon-blueprint'],
1409
- ['blueprint', '@ember-tooling/blueprint-blueprint'],
1410
- ]);
1411
-
1412
- /**
1413
- @static
1414
- @method lookup
1415
- @namespace Blueprint
1416
- @param {String} name
1417
- @param {Object} [options]
1418
- @param {Array} [options.paths] Extra paths to search for blueprints
1419
- @param {Boolean} [options.ignoreMissing] Throw a `SilentError` if a
1420
- matching Blueprint could not be found
1421
- @param {Object} [options.blueprintOptions] Options object that will be passed
1422
- along to the Blueprint instance on creation.
1423
- @return {Blueprint}
1424
- */
1425
- Blueprint.lookup = function (name, options) {
1426
- options = options || {};
1427
-
1428
- let lookupPaths = generateLookupPaths(options.paths);
1429
-
1430
- let lookupPath;
1431
- for (let i = 0; (lookupPath = lookupPaths[i]); i++) {
1432
- let blueprintPath = path.resolve(lookupPath, name);
1433
-
1434
- if (Blueprint._existsSync(blueprintPath)) {
1435
- return Blueprint.load(blueprintPath, options.blueprintOptions);
1436
- }
1437
- }
1438
-
1439
- // Check if `name` itself is a path to a blueprint:
1440
- if (name.includes(path.sep)) {
1441
- let blueprintPath = path.resolve(name);
1442
-
1443
- if (Blueprint._existsSync(blueprintPath)) {
1444
- return Blueprint.load(blueprintPath, options.blueprintOptions);
1445
- }
1446
- }
1447
-
1448
- // Check for the built in blueprints
1449
- if (builtInBlueprints.has(name)) {
1450
- let blueprintPath = require.resolve(builtInBlueprints.get(name));
1451
-
1452
- if (Blueprint._existsSync(blueprintPath)) {
1453
- return Blueprint.load(path.dirname(blueprintPath), options.blueprintOptions);
1454
- }
1455
- }
1456
-
1457
- if (!options.ignoreMissing) {
1458
- throw new SilentError(`Unknown blueprint: ${name}`);
1459
- }
1460
- };
1461
-
1462
- /**
1463
- Loads a blueprint from given path.
1464
- @static
1465
- @method load
1466
- @namespace Blueprint
1467
- @param {String} blueprintPath
1468
- @param {Object} [blueprintOptions]
1469
- @return {Blueprint} blueprint instance
1470
- */
1471
- Blueprint.load = function (blueprintPath, blueprintOptions) {
1472
- if (fs.lstatSync(blueprintPath).isDirectory()) {
1473
- let Constructor = Blueprint;
1474
-
1475
- let constructorPath = path.resolve(blueprintPath, 'index.js');
1476
- if (Blueprint._existsSync(constructorPath)) {
1477
- let blueprintModule = require(constructorPath);
1478
-
1479
- if (blueprintModule.__esModule) {
1480
- blueprintModule = blueprintModule.default;
1481
- }
1482
-
1483
- if (typeof blueprintModule === 'function') {
1484
- Constructor = blueprintModule;
1485
- } else {
1486
- Constructor = Blueprint.extend(blueprintModule);
1487
- }
1488
- }
1489
-
1490
- return new Constructor(blueprintPath, blueprintOptions);
1491
- }
1492
- };
1493
-
1494
- /**
1495
- @static
1496
- @method list
1497
- @namespace Blueprint
1498
- @param {Object} [options]
1499
- @param {Array} [options.paths] Extra paths to search for blueprints
1500
- @return {Array}
1501
- */
1502
- Blueprint.list = function (options) {
1503
- options = options || {};
1504
-
1505
- let lookupPaths = generateLookupPaths(options.paths);
1506
- let seen = [];
1507
-
1508
- return lookupPaths.map((lookupPath) => {
1509
- let source;
1510
- let packagePath = path.join(lookupPath, '../package.json');
1511
- if (Blueprint._existsSync(packagePath)) {
1512
- source = require(packagePath).name;
1513
- } else {
1514
- source = path.basename(path.join(lookupPath, '..'));
1515
- }
1516
-
1517
- let blueprints = dir(lookupPath).map((blueprintPath) => {
1518
- let blueprint = Blueprint.load(blueprintPath);
1519
- if (blueprint) {
1520
- let name = blueprint.name;
1521
- blueprint.overridden = seen.includes(name);
1522
- seen.push(name);
1523
-
1524
- return blueprint;
1525
- }
1526
- });
1527
-
1528
- // if we're listing blueprints from ember-cli then we should add the built-in blueprints
1529
- if (source === 'ember-cli') {
1530
- for (let [blueprintName, blueprintPackage] of builtInBlueprints.entries()) {
1531
- let blueprint = Blueprint.load(path.dirname(require.resolve(blueprintPackage)), {
1532
- name: blueprintName,
1533
- });
1534
-
1535
- let name = blueprintName;
1536
- blueprint.overridden = seen.includes(name);
1537
- seen.push(name);
1538
-
1539
- blueprints.push(blueprint);
1540
- }
1541
- }
1542
-
1543
- return {
1544
- source,
1545
- blueprints: sortBy(compact(blueprints), 'name'),
1546
- };
1547
- });
1548
- };
1549
-
1550
- Blueprint._existsSync = function (path, parent) {
1551
- return fs.existsSync(path, parent);
1552
- };
1553
-
1554
- Blueprint._readdirSync = function (path) {
1555
- return fs.readdirSync(path);
1556
- };
1557
-
1558
- /**
1559
- Files that are renamed when installed into the target directory.
1560
- This allows including files in the blueprint that would have an effect
1561
- on another process, such as a file named `.gitignore`.
1562
-
1563
- The keys are the filenames used in the files folder.
1564
- The values are the filenames used in the target directory.
1565
-
1566
- @static
1567
- @property renamedFiles
1568
- */
1569
- Blueprint.renamedFiles = {
1570
- gitignore: '.gitignore',
1571
- };
1572
-
1573
- /**
1574
- @static
1575
- @property ignoredFiles
1576
- */
1577
- Blueprint.ignoredFiles = initialIgnoredFiles;
1578
-
1579
- /**
1580
- @static
1581
- @property ignoredUpdateFiles
1582
- */
1583
- Blueprint.ignoredUpdateFiles = ['.gitkeep', 'app.css', 'LICENSE.md'];
1584
-
1585
- /**
1586
- @static
1587
- @property defaultLookupPaths
1588
- */
1589
- Blueprint.defaultLookupPaths = function () {
1590
- return [path.resolve(__dirname, '..', '..', 'blueprints')];
1591
- };
1592
-
1593
- /**
1594
- @private
1595
- @method prepareConfirm
1596
- @param {FileInfo} info
1597
- @return {Promise}
1598
- */
1599
- function prepareConfirm(info) {
1600
- return info.checkForConflict().then((resolution) => {
1601
- info.resolution = resolution;
1602
- return info;
1603
- });
1604
- }
1605
-
1606
- /**
1607
- @private
1608
- @method markIdenticalToBeSkipped
1609
- @param {FileInfo} info
1610
- */
1611
- function markIdenticalToBeSkipped(info) {
1612
- if (info.resolution === 'identical') {
1613
- info.action = 'skip';
1614
- }
1615
- }
1616
-
1617
- /**
1618
- @private
1619
- @method markToBeRemoved
1620
- @param {FileInfo} info
1621
- */
1622
- function markToBeRemoved(info) {
1623
- info.action = 'remove';
1624
- }
1625
-
1626
- /**
1627
- @private
1628
- @method gatherConfirmationMessages
1629
- @param {Array} collection
1630
- @param {FileInfo} info
1631
- @return {Array}
1632
- */
1633
- function gatherConfirmationMessages(collection, info) {
1634
- if (info.resolution === 'confirm') {
1635
- collection.push(info.confirmOverwriteTask());
1636
- }
1637
- return collection;
1638
- }
1639
-
1640
- /**
1641
- @private
1642
- @method isIgnored
1643
- @param {FileInfo} info
1644
- @return {Boolean}
1645
- */
1646
- function isIgnored(info) {
1647
- let fn = info.inputPath;
1648
-
1649
- return Blueprint.ignoredFiles.some((ignoredFile) => minimatch(fn, ignoredFile, { matchBase: true }));
1650
- }
1651
-
1652
- /**
1653
- Combines provided lookup paths with defaults and removes
1654
- duplicates.
1655
-
1656
- @private
1657
- @method generateLookupPaths
1658
- @param {Array} lookupPaths
1659
- @return {Array}
1660
- */
1661
- function generateLookupPaths(lookupPaths) {
1662
- lookupPaths = lookupPaths || [];
1663
- lookupPaths = lookupPaths.concat(Blueprint.defaultLookupPaths());
1664
- return uniq(lookupPaths);
1665
- }
1666
-
1667
- /**
1668
- Looks for a __path__ token in the files folder. Must be present for
1669
- the blueprint to support pod tokens.
1670
-
1671
- @private
1672
- @method hasPathToken
1673
- @param {files} files
1674
- @return {Boolean}
1675
- */
1676
- function hasPathToken(files) {
1677
- return /__path__/.test(files.join());
1678
- }
1679
-
1680
- function findAddonByName(addonOrProject, name) {
1681
- let addon = addonOrProject.addons.find((addon) => addon.name === name);
1682
-
1683
- if (addon) {
1684
- return addon;
1685
- }
1686
-
1687
- return addonOrProject.addons.find((addon) => findAddonByName(addon, name));
1688
- }
1689
-
1690
- function ensureTargetDirIsAddon(addonPath) {
1691
- let projectInfo;
1692
-
1693
- try {
1694
- projectInfo = require(path.join(addonPath, 'package.json'));
1695
- } catch (err) {
1696
- if (err.code === 'MODULE_NOT_FOUND') {
1697
- throw new Error(`The directory ${addonPath} does not appear to be a valid addon directory.`);
1698
- } else {
1699
- throw err;
1700
- }
1701
- }
1702
-
1703
- return isAddon(projectInfo.keywords);
1704
- }
1705
-
1706
- /**
1707
- @private
1708
- @method isValidFile
1709
- @param {Object} fileInfo
1710
- @return {Promise}
1711
- */
1712
- function isValidFile(fileInfo) {
1713
- if (isIgnored(fileInfo)) {
1714
- return false;
1715
- } else {
1716
- return isFilePath(fileInfo);
1717
- }
1718
- }
1719
-
1720
- /**
1721
- @private
1722
- @method isFilePath
1723
- @param {Object} fileInfo
1724
- @return {Promise}
1725
- */
1726
- function isFilePath(fileInfo) {
1727
- return fs.statSync(fileInfo.inputPath).isFile();
1728
- }
1729
-
1730
- /**
1731
- @private
1732
- @method dir
1733
- @return {Array} list of files in the given directory or and empty array if no directory exists
1734
- */
1735
- function dir(fullPath) {
1736
- if (Blueprint._existsSync(fullPath)) {
1737
- return Blueprint._readdirSync(fullPath).map((fileName) => path.join(fullPath, fileName));
1738
- } else {
1739
- return [];
1740
- }
1741
- }
1742
-
1743
- /**
1744
- @private
1745
- @method getDetailedHelpPath
1746
- @param {String} thisPath
1747
- @return {String} help path
1748
- */
1749
- function getDetailedHelpPath(thisPath) {
1750
- return path.join(thisPath, './HELP.md');
1751
- }
1752
-
1753
- function finishProcessingForInstall(infos) {
1754
- infos.forEach(markIdenticalToBeSkipped);
1755
-
1756
- let infosNeedingConfirmation = infos.reduce(gatherConfirmationMessages, []);
1757
-
1758
- return sequence(infosNeedingConfirmation).then(() => infos);
1759
- }
1760
-
1761
- function finishProcessingForUninstall(infos) {
1762
- let validInfos = infos.filter((info) => fs.existsSync(info.outputPath));
1763
- validInfos.forEach(markToBeRemoved);
1764
-
1765
- return validInfos;
1766
- }
1767
-
1768
- function replaceExtension(filePath, newExt) {
1769
- const { dir, name } = path.parse(filePath);
1770
-
1771
- return path.format({
1772
- dir,
1773
- name,
1774
- ext: newExt,
1775
- });
1776
- }
1777
-
1778
- function replaceTypeScriptExtension(filePath) {
1779
- const extensionMap = {
1780
- '.ts': '.js',
1781
- '.gts': '.gjs',
1782
- };
1783
- const ext = path.extname(filePath);
1784
- const newExt = extensionMap[ext];
1785
-
1786
- return replaceExtension(filePath, newExt);
1787
- }
1788
-
1789
- function isTypeScriptFile(filePath) {
1790
- return path.extname(filePath) === '.ts' || path.extname(filePath) === '.gts';
1791
- }
1792
-
1793
- /**
1794
- * Takes a substring of a string based on byte offsets.
1795
- * @private
1796
- * @method substringBytes
1797
- * @param {string} value : The input string.
1798
- * @param {number} start : The byte index of the substring start.
1799
- * @param {number} end : The byte index of the substring end.
1800
- * @return {string} : The substring.
1801
- */
1802
- function substringBytes(value, start, end) {
1803
- let buf = Buffer.from(value);
1804
- return buf.subarray(start, end).toString();
1805
- }
1806
-
1807
- module.exports = Blueprint;