• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

IgniteUI / igniteui-cli / 27742220407

18 Jun 2026 06:53AM UTC coverage: 87.817% (-0.07%) from 87.891%
27742220407

Pull #1719

github

web-flow
Merge a437b108a into ffc46000e
Pull Request #1719: feat: add Blazor project scaffolding options and templates

1160 of 1494 branches covered (77.64%)

Branch coverage included in aggregate %.

103 of 120 new or added lines in 7 files covered. (85.83%)

15 existing lines in 1 file now uncovered.

5832 of 6468 relevant lines covered (90.17%)

86.77 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

80.06
/packages/core/util/Util.ts
1
import chalk from "chalk";
12✔
2
import { execSync, ExecSyncOptions, spawnSync, SpawnSyncOptions } from "child_process";
12✔
3
import * as fs from "fs";
12✔
4
import * as glob from "glob";
12✔
5
import * as path from "path";
12✔
6
import through2 = require("through2");
12✔
7
import { BaseComponent } from "../templates/BaseComponent";
12✔
8
import { Component, ComponentGroup, Delimiter, FS_TOKEN, IFileSystem, Template, TemplateDelimiters } from "../types";
12✔
9
import { App } from "./App";
12✔
10
import { GoogleAnalytics } from "./GoogleAnalytics";
12✔
11

12
const imageExtensions = [".png", ".jpg", ".jpeg", ".gif", ".bmp", ".ico"];
12✔
13
const applyConfig = (configuration: { [key: string]: string }) => {
12✔
14
        return through2((data, enc, cb) => {
360✔
15
                cb(null, Buffer.from(Util.applyConfigTransformation(data.toString(), configuration)));
354✔
16
        });
17
};
18

19
const noop = () => through2.obj();
22✔
20
export const defaultDelimiters: TemplateDelimiters = {
12✔
21
        content: {
22
                start: `$(`,
23
                end: `)`
24
        },
25
        path: {
26
                start: `__`,
27
                end: `__`
28
        }
29
};
30

31
export type ChoiceItem = Pick<Template | ComponentGroup, "name" | "description"> | Component;
32

33
export class Util {
12✔
34
        public static getCurrentDirectoryBase() {
35
                return path.basename(process.cwd());
×
36
        }
37

38
        public static directoryExists(dirPath) {
39
                return App.container.get<IFileSystem>(FS_TOKEN).directoryExists(dirPath);
223✔
40
        }
41

42
        public static fileExists(filePath) {
43
                return App.container.get<IFileSystem>(FS_TOKEN).fileExists(filePath);
137✔
44
        }
45

46
        public static isDirectory(dirPath): boolean {
47
                return fs.lstatSync(dirPath).isDirectory();
×
48
        }
49

50
        public static isFile(filePath): boolean {
51
                return fs.lstatSync(filePath).isFile();
×
52
        }
53

54
        public static ensureDirectoryExists(dirPath) {
55
                if (!this.directoryExists(dirPath)) {
×
56
                        fs.mkdirSync(dirPath);
×
57
                }
58
        }
59

60
        public static getDirectoryNames(rootPath: string): string[] {
61
                // TODO: add https://github.com/davetemplin/async-file
62
                let folders: string[] = [];
200✔
63
                if (this.directoryExists(rootPath)) {
200✔
64
                        folders = fs.readdirSync(rootPath).filter(file => fs.lstatSync(path.join(rootPath, file)).isDirectory());
1,086✔
65
                }
66
                return folders;
200✔
67
        }
68

69
        public static async processTemplates(
70
                sourcePath: string,
48✔
71
                destinationPath: string, configuration: { [key: string]: string },
72
                delimiters: TemplateDelimiters, validate = true): Promise<boolean> {
17✔
73

74
                sourcePath = sourcePath.replace(/\\/g, "/");
48✔
75
                destinationPath = destinationPath.replace(/\\/g, "/");
48✔
76

77
                if (validate && !this.validateTemplate(sourcePath, destinationPath, configuration, delimiters)) {
48✔
78
                        return false;
3✔
79
                }
80

81
                return new Promise((resolve, reject) => {
45✔
82
                        const filePaths: string[] = glob.sync(sourcePath + "/**/*", { nodir: true })
45✔
83
                                .map(filePath => filePath.replace(/\\/g, "/"));
382✔
84
                        let fileCount = filePaths.length;
45✔
85
                        // if no files should be created, resolve
86
                        if (fileCount === 0) {
45✔
87
                                resolve(false);
13✔
88
                        }
89
                        for (const filePath of filePaths) {
45✔
90
                                let targetPath = filePath.replace(sourcePath, destinationPath);
382✔
91
                                targetPath = Util.applyConfigTransformation(targetPath, Util.applyDelimiters(configuration,
382✔
92
                                        delimiters.path || defaultDelimiters.path));
393✔
93
                                Util.createDirectory(path.dirname(targetPath));
382✔
94
                                const writeStream = fs.createWriteStream(targetPath);
382✔
95
                                const isImage = imageExtensions.indexOf(path.extname(targetPath)) !== -1;
382✔
96
                                fs.createReadStream(filePath)
382✔
97
                                        // for image files, just copy the content
98
                                        .pipe(!isImage ?
382✔
99
                                                applyConfig(Util.applyDelimiters(configuration, delimiters.content || defaultDelimiters.content))
371✔
100
                                                : noop())
101
                                        .pipe(writeStream);
102
                                writeStream.on("finish", () => {
382✔
103
                                        if (--fileCount === 0) {
382✔
104
                                                resolve(true);
32✔
105
                                        }
106
                                });
107
                        }
108
                });
109
        }
110

111
        public static applyConfigTransformation = (data: string, configuration: { [key: string]: string }): string => {
12✔
112
                let key;
113
                for (key in configuration) {
9,641✔
114
                        data = data.replace(new RegExp(Util.escapeRegExp(key), "g"), configuration[key]);
17,772✔
115
                }
116

117
                return data;
9,641✔
118
        }
119

120
        public static escapeRegExp(str): string {
121
                return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); // $& means the whole matched string
17,909✔
122
        }
123

124
        /**
125
         * Simple log with optional color.
126
         * @param message Text to log
127
         * @param colorKeyword Optional color (CSS keyword like red, green, etc.)
128
         */
129
        public static log(message: string, colorKeyword?: string) {
130
                if (colorKeyword) {
427!
131
                        const color = chalk.keyword(colorKeyword);
×
132
                        message = color(message);
×
133
                }
134
                // eslint-disable-next-line no-console
135
                console.log(message);
427✔
136
        }
137

138
        /**
139
         * Error log with optional color.
140
         * @param message Error to log
141
         * @param colorKeyword Optional color (CSS keyword like red, green, etc.)
142
         */
143
        public static error(message: string, colorKeyword?: string) {
144
                GoogleAnalytics.post({
13✔
145
                        t: "screenview",
146
                        cd: `error: ${message}`
147
                });
148

149
                if (colorKeyword) {
13✔
150
                        const color = chalk.keyword(colorKeyword);
13✔
151
                        message = color(message);
13✔
152
                }
153
                // eslint-disable-next-line no-console
154
                console.error(message);
13✔
155
        }
156

157
        /**
158
         * Log a warning with optional color.
159
         * @param message warn to log
160
         * @param colorKeyword Optional color (CSS keyword like red, green, etc.)
161
         */
162
        public static warn(message: string, colorKeyword?: string) {
163
                if (colorKeyword) {
16✔
164
                        const color = chalk.keyword(colorKeyword);
16✔
165
                        message = color(message);
16✔
166
                }
167
                // eslint-disable-next-line no-console
168
                console.warn(message);
16✔
169
        }
170

171
        public static greenCheck() {
172
                if (process.platform.startsWith("win")) {
62!
173
                        return chalk.green("√");
×
174
                } else {
175
                        return chalk.green("✔");
62✔
176
                }
177
        }
178

179
        public static formatPackageJson(json: { dependencies: { [key: string]: string } }, sort = true): string {
29✔
180
                if (sort) {
29✔
181
                        json.dependencies =
29✔
182
                        Object.keys(json.dependencies)
183
                                .sort()
184
                                .reduce((result, key) => (result[key] = json.dependencies[key]) && result, {});
95✔
185
                }
186
                return JSON.stringify(json, null, 2) + "\n";
29✔
187
        }
188

189
        public static formatAngularJsonOptions(json:
190
                {
191
                        projects: {
192
                                architect: {
193
                                        build: {
194
                                                options: {
195
                                                        styles: any[],
196
                                                        scripts: Array<{ input: string, bundleName: string }>
197
                                                }
198
                                        }
199
                                }
200
                        }
201
                }): string {
202
                return JSON.stringify(json, null, 2) + "\n";
×
203
        }
204

205
        public static version(filePath?: string): string {
206
                const configuration = require(filePath || "../package.json");
51✔
207
                return configuration.version;
51✔
208
        }
209

210
        public static showVersion(filePath) {
211
                const logo = fs.readFileSync(filePath);
×
212
                logo.toString().split("\n").forEach(line => {
×
213
                        this.log(line);
×
214
                });
215
                this.log("Ignite UI CLI version: " + this.version());
×
216
                this.log("OS: " + this.getOSFriendlyName(process.platform));
×
217
        }
218

219
        public static getOSFriendlyName(platform: string): string {
220
                let os = "";
×
221
                switch (platform) {
×
222
                        case "win32":
223
                                os = "Windows";
×
224
                                break;
×
225
                        case "darwin":
226
                                os = "Mac OS";
×
227
                                break;
×
228
                        case "freebsd":
229
                                os = "FreeBSD";
×
230
                                break;
×
231
                        default:
232
                                os = "Unknown OS";
×
233
                                break;
×
234
                }
235
                return os;
×
236
        }
237

238
        /**
239
         * lower-dashed string
240
         * Add dash on the place of empty spaces and between lower and upper case letters.
241
         */
242
        public static lowerDashed(text: string) {
243
                const regex = new RegExp("[\\s]+|([\\p{Ll}\\p{Nd}](?=[\\p{Lu}]))", "gu");
116✔
244
                const result = text.trim()
116✔
245
                                .replace(regex, "$1-")
246
                                .toLowerCase();
247
                return result;
116✔
248
        }
249

250
        /**
251
         * Checks if a giver string consists of alphanumeric characters, dashes and spaces only
252
         * and also starts with a letter.
253
         * @param name Text to check
254
         */
255
        public static isAlphanumericExt(name: string) {
256
                return /^[\sa-zA-Z][\w\s\-]*$/.test(name);
77✔
257
        }
258

259
        /**
260
         * Separate provided name to words on each space and/or dash and capitalize first letter of each
261
         * resulting word.
262
         * @param name Text to convert to proper class name
263
         */
264
        public static className(name: string): string {
265
                return name.replace(/\w[^-\s]*/g, txt => txt.charAt(0).toUpperCase() + txt.substr(1)).replace(/[-\s]/g, "");
75✔
266
        }
267

268
        /**
269
         * Simple object merge - deep nested objects and arrays (of primitive values only)
270
         * @param target Object to merge values into
271
         * @param source Object to merge values from
272
         */
273
        public static merge(target: any, source: any) {
274
                if (!source) {
531!
UNCOV
275
                        return target;
×
276
                }
277

278
                for (const key of Object.keys(source)) {
531✔
279
                        const sourceKeyIsArray = Array.isArray(source[key]);
879✔
280
                        const targetHasThisKey = target.hasOwnProperty(key);
879✔
281

282
                        if (typeof source[key] === "object" && !sourceKeyIsArray) {
879✔
283
                                // object value:
284
                                if (!targetHasThisKey) {
36✔
285
                                        target[key] = {};
36✔
286
                                }
287
                                this.merge(target[key], source[key]);
36✔
288
                        } else if (sourceKeyIsArray) {
843✔
289
                                //        array value:
290
                                if (targetHasThisKey) {
206✔
291
                                        // skip array merge on target type mismatch:
292
                                        if (!Array.isArray(target[key])) {
11!
UNCOV
293
                                                continue;
×
294
                                        }
295
                                        for (const item of source[key]) {
11✔
296
                                                if (target[key].indexOf(item) === -1) {
2✔
297
                                                        target[key].push(item);
2✔
298
                                                }
299
                                        }
300
                                } else {
301
                                        target[key] = (source[key] as any[]).slice(0);
195✔
302
                                }
303
                        } else {
304
                                // primitive value:
305
                                if (source.hasOwnProperty(key)) {
637✔
306
                                        target[key] = source[key];
637✔
307
                                }
308
                        }
309
                }
310
        }
311

312
        /**
313
         * Checks if the terminal is TTY and is not in CI env
314
         */
315
        public static canPrompt() {
316
                return process.stdout.isTTY && process.stdin.isTTY && !process.env.CI;
16✔
317
        }
318

319
        /**
320
         * Fairly aggressive sanitize removing anything but ASCII, numbers and a few needed chars that have no action:
321
         * - semicolons (:), dots (.), underscores (_) for paths/URLs
322
         * - dashes (-) & forward slashes (/) for packages and paths/URLs
323
         * - at (@), non-leading tilde (~) for package scope & version
324
         * @remarks
325
         * Most shells should be UTF-enabled, but current usage is very limited thus no need for `\p{L}`
326
         */
327
        public static sanitizeShellArg(arg: string): string {
328
                return arg
18✔
329
                        .replace(/[^a-z0-9@:_~\^\/\.\-]/gi, '') // remove unsafe chars
330
                        .replace(/\^(?!\d)/g, '') // keep only ^<digit>
331
                        .replace(/^~/, ''); // remove leading ~
332
        }
333

334
        /**
335
         * Execute synchronous command with options
336
         * @param command Command to be executed
337
         * @param options Command options
338
         * @throws {Error} On timeout or non-zero exit code. Error has 'status', 'signal', 'output', 'stdout', 'stderr'
339
         */
340
        public static execSync(command: string, options?: ExecSyncOptions) {
341
                try {
36✔
342
                        return execSync(command, options);
36✔
343
                } catch (error) {
344
                        // execSync may throw an error during process interruption
345
                        // if this happens - stderr will end with "^C" which was appended in the checkExecSyncError function
346
                        // this means that a SIGINT was attempted and failed
347
                        // npm may be involved in this as it works just fine with any other node process
348
                        if (error.stderr && error.stderr.toString().endsWith() === "^C") {
1!
UNCOV
349
                                return process.exit();
×
350
                        }
351

352
                        // if SIGINT killed the process with no errors
353
                        // 3221225786 - cmd- Ctrl+C
354
                        // 128 - bash - invalid argument to exit
355
                        // 130 - bash - Ctrl+C
356
                        // 255 - bash - exit status out of range
357
                        if (error.status === 3221225786 || error.status > 128) {
1!
358
                                return process.exit();
×
359
                        }
360

361
                        throw error;
1✔
362
                }
363
        }
364

365
        /**
366
         * Execute synchronous command with options using spawnSync
367
         * @param command Command to be executed
368
         * NOTE: `spawn` without `shell` (unsafe) is **not** equivalent to `exec` & requires direct path to run the correct process on win,
369
         * e.g. `npm.cmd` but that is also blocked in node@24+ for security reasons
370
         * Do not call with/add commands that are not known binaries without validating first.
371
         * Allowed binaries: `node`, `git`, `dotnet` (all real PATH binaries on every OS, no `.cmd`-shim issue).
372
         * @param args Command arguments
373
         * @param options Command options
374
         * @returns {SpawnSyncReturns} object with status and stdout
375
         * @remarks Consuming code MUST handle the result and check for failure status!
376
         */
377
        public static spawnSync(command: 'node' | 'git' | 'dotnet', args: string[], options?: Omit<SpawnSyncOptions, 'shell'>) {
378
                return spawnSync(command, args, options);
11✔
379
        }
380

381
        /**
382
         * Initialize git for a project, located in the provided directory and commit it.
383
         * @param parentRoot Parent directory root of the project.
384
         * @param projectName Project name.
385
         */
386
        public static gitInit(parentRoot, projectName) {
387
                try {
9✔
388
                        const options: any = { cwd: path.join(parentRoot, projectName), stdio: [process.stdin, "ignore", "ignore"] };
9✔
389
                        Util.execSync("git init", options);
9✔
390
                        Util.execSync("git add .", options);
9✔
391
                        Util.execSync("git commit -m " + "\"Initial commit for project\"", options);
9✔
392
                        Util.log(Util.greenCheck() + " Git Initialized and Project '" + projectName + "' Committed");
9✔
393
                } catch (error) {
UNCOV
394
                        Util.error("Git initialization failed. Install Git in order to automatically commit the project.", "yellow");
×
395
                }
396
        }
397

398
        /**
399
         * Truncating text to fit console viewPort and appending specified truncate characters at the end
400
         * to indicate text is truncated.
401
         * @param text Text to be used.
402
         * @param limit max viewPort.
403
         * @param truncateCount How many characters to be replaced at the text end with a specified truncateChar.
404
         * @param truncateChar Char to use for truncated text.
405
         */
406
        public static truncate(text: string, limit: number, count = 3, truncateChar = ".") {
×
407
                //adjust for console characters prior description
408
                if (text.length > limit) {
59!
UNCOV
409
                        text = text.slice(0, (limit - count)).trim() + truncateChar.repeat(count);
×
410
                }
411
                return text;
59✔
412
        }
413

414
        /**
415
         * to indicate text is truncated.
416
         * @param text Text to be used.
417
         * @param startIndex Apply color from this index on.
418
         */
419
        public static addColor(text: string, startIndex: number) {
420
                const name = text.slice(0, startIndex);
118✔
421
                const separatedDescription = text.slice(startIndex);
118✔
422
                return name + chalk.gray(`${separatedDescription}`);
118✔
423
        }
424

425
        /**
426
         * Returns a colored text
427
         */
428
        public static color(text: string, colorKeyword: string) {
429
                const color = chalk.keyword(colorKeyword);
17✔
430
                return color(text);
17✔
431
        }
432

433
        public static getAvailableName(
434
                defaultName: string, isApp: boolean, framework?: string, _projectType?: string): string {
435

436
                const baseLength = defaultName.length;
17✔
437
                let specificPath = "";
17✔
438

439
                if (["angular", "react", "webcomponents"].includes(framework)) {
17✔
440
                        specificPath = path.join("src", "app");
1✔
441
                }
442

443
                if (isApp) {
17✔
444
                        while (Util.directoryExists(path.join(App.workDir, defaultName))) {
6✔
445
                                defaultName = Util.incrementName(defaultName, baseLength);
2✔
446
                        }
447
                } else {
448
                        while (Util.directoryExists(path.join(App.workDir, specificPath, Util.lowerDashed(defaultName)))) {
11✔
449
                                defaultName = Util.incrementName(defaultName, baseLength);
8✔
450
                        }
451
                }
452
                return defaultName;
17✔
453
        }
454

455
        /**
456
         * Creates all folders in a given absolute path. Starts from cwd
457
         * @param targetDir Absolute path to folder to create
458
         * @throws Throws on `EACCES`, `EISDIR`
459
         */
460
        public static createDirectory(targetDir: string) {
461
                // start from current
462
                let curDir = process.cwd();
382✔
463
                if (path.isAbsolute(targetDir)) {
382✔
464
                        // strip target to relative
465
                        targetDir = path.relative(curDir, targetDir);
382✔
466
                }
467

468
                // split target into parts and go through
469
                targetDir.split(path.sep).forEach(childDir => {
382✔
470
                        curDir = path.resolve(curDir, childDir);
968✔
471
                        try {
968✔
472
                                fs.mkdirSync(curDir);
968✔
473
                        } catch (err) {
474
                                // reuse catch rather than another one from `this.directoryExists`
475
                                if (err.code === "EEXIST") {
862✔
476
                                        return;
862✔
477
                                }
UNCOV
478
                                this.error(`Failed to create ${curDir}`, "red");
×
UNCOV
479
                                this.error(err.message, "red");
×
UNCOV
480
                                throw err;
×
481
                        }
482
                });
483
        }
484

485
        /**
486
         * Extracts the name (last part) from a path and trims.
487
         * @param fileName Path-like name, e.g. /path/to/my component
488
         */
489
        public static nameFromPath(fileName: string) {
490
                const parts = path.parse(fileName);
133✔
491
                const name = parts.name + parts.ext;
133✔
492
                // trim name itself to avoid creating awkward component names
493
                return name.trim();
133✔
494
        }
495

496
        public static camelCase(str: string) {
497
                if (!str) {
18✔
498
                        return null;
8✔
499
                }
500
                const result = this.className(str);
10✔
501
                return result[0].toLowerCase() + result.substring(1, result.length);
10✔
502
        }
503

504
        /**
505
         * Generate relative path from target file to another
506
         * Adds "./" to avoid node module resolution conflicts
507
         * @param targetPath Target file (root path)
508
         * @param filePath File to generate relative path to
509
         * @param posix Require path in posix style (/-separated)
510
         * @param removeExt Strip file extension
511
         */
512
        public static relativePath(targetPath: string, filePath: string, posix: boolean, removeExt = true): string {
6✔
513
                if (!targetPath.endsWith(path.win32.sep) && !targetPath.endsWith(path.posix.sep)) {
26✔
514
                        // path.relative splits by fragments, must be dirname w/ trailing to work both down and up
515
                        targetPath = path.win32.dirname(targetPath) + path.sep;
22✔
516
                }
517

518
                // use win32 api as it handles both formats
519
                let relativePath: string = path.win32.relative(targetPath, filePath);
26✔
520

521
                if (removeExt) {
26✔
522
                        relativePath = relativePath.replace(path.win32.extname(relativePath), "");
24✔
523
                }
524

525
                if (posix) {
26✔
526
                        relativePath = path.posix.join(...relativePath.split(path.win32.sep));
25✔
527
                        relativePath = relativePath.startsWith(".") ? relativePath : "./" + relativePath;
25✔
528
                } else {
529
                        relativePath = path.win32.join(...relativePath.split(path.win32.sep));
1✔
530
                        relativePath = relativePath.startsWith(".") ? relativePath : ".\\" + relativePath;
1!
531
                }
532

533
                return relativePath;
26✔
534
        }
535

536
        public static formatChoices(items: ChoiceItem[], padding = 3): Array<{name: string, value: string, short: string}> {
23✔
537
                const choiceItems = [];
33✔
538
                const leftPadding = 2;
33✔
539
                const rightPadding = 1;
33✔
540

541
                const maxNameLength = Math.max(...items.map(x => x.name.length)) + padding;
73✔
542
                const targetNameLength = Math.max(18, maxNameLength);
33✔
543
                let description: string;
544
                for (const item of items) {
33✔
545
                        const choiceItem = {
73✔
546
                                name: "",
547
                                short: item.name,
548
                                value: item.name
549
                        };
550
                        choiceItem.name = item.name;
73✔
551
                        if (item instanceof BaseComponent && item.templates.length <= 1) {
73!
UNCOV
552
                                description = item.templates[0].description || "";
×
553
                        } else {
554
                                description = item.description || "";
73✔
555
                        }
556
                        if (description !== "") {
73✔
557
                                choiceItem.name = item.name  +  Util.addColor(".".repeat(targetNameLength - item.name.length), 0);
59✔
558
                                const max = process.stdout.columns - targetNameLength - leftPadding - rightPadding;
59✔
559
                                description = Util.truncate(description, max, 3, ".");
59✔
560
                                description = Util.addColor(description, 0);
59✔
561
                                choiceItem.name += description;
59✔
562
                        }
563
                        choiceItems.push(choiceItem);
73✔
564
                }
565
                return choiceItems;
33✔
566
        }
567

568
        public static applyDelimiters(config: {[key: string]: string }, delimiter: Delimiter) {
569
                const obj = {};
9,662✔
570
                for (const key of Object.keys(config)) {
9,662✔
571
                        obj[`${delimiter.start}${key}${delimiter.end}`] = config[key];
17,957✔
572
                }
573
                return obj;
9,662✔
574
        }
575

576
        private static incrementName(name: string, baseLength: number): string {
577
                const text: string = name.slice(0, baseLength);
10✔
578
                const number: number = parseInt(name.slice(baseLength + 1), 10) || 0;
10✔
579
                return `${text} ${number + 1}`;
10✔
580
        }
581

582
        private static propertyByPath(object: any, propPath: string) {
UNCOV
583
                if (!propPath) {
×
UNCOV
584
                        return object;
×
585
                }
UNCOV
586
                const pathParts = propPath.split(".");
×
UNCOV
587
                const currentProp = pathParts.shift();
×
UNCOV
588
                if (currentProp in object) {
×
UNCOV
589
                        return this.propertyByPath(object[currentProp], pathParts.join("."));
×
590
                }
591
        }
592

593
        private static validateTemplate(
594
                sourcePath: string,
595
                destinationPath: string, configuration: { [key: string]: string },
596
                delimiters: TemplateDelimiters): boolean {
597

598
                sourcePath = sourcePath.replace(/\\/g, "/");
2,771✔
599
                destinationPath = destinationPath.replace(/\\/g, "/");
2,771✔
600

601
                let paths: string[] = glob.sync(sourcePath + "/**/*", { nodir: true })
2,771✔
602
                        .map(filePath => filePath.replace(/\\/g, "/"));
11,057✔
603
                // TODO: D.P Temporary ignoring asset files
604
                const ignorePaths: string[] = glob.sync(sourcePath + "/**/+(assets|data)/**/*", { nodir: true })
2,771✔
605
                        .map(filePath => filePath.replace(/\\/g, "/"));
2,149✔
606
                paths = paths.filter(x => ignorePaths.indexOf(x) === -1);
11,057✔
607

608
                for (let filePath of paths) {
2,771✔
609
                        filePath = filePath.replace(sourcePath, destinationPath);
8,905✔
610
                        filePath = Util.applyConfigTransformation(filePath, Util.applyDelimiters(configuration,
8,905✔
611
                                delimiters.path || defaultDelimiters.path));
17,784✔
612
                        if (fs.existsSync(filePath)) {
8,905✔
613
                                this.error(path.relative(process.cwd(), filePath) + " already exists!", "red");
3✔
614
                                return false;
3✔
615
                        }
616
                }
617
                return true;
2,768✔
618
        }
619
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE TRIAL · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc