gaffer-generator 1.2.5 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +7 -5
- package/cli.js +43 -37
- package/lib/argv.js +9 -0
- package/lib/create.js +40 -0
- package/{src → lib}/generate/directory.js +13 -14
- package/{src → lib}/generate/file.js +13 -13
- package/{src → lib}/generate/node.js +6 -9
- package/lib/generate/root.js +95 -0
- package/{src → lib}/templateArgs.js +1 -1
- package/lib/utils/contentsDiffer.js +20 -0
- package/lib/utils/log.js +26 -0
- package/lib/utils/logChange.js +16 -0
- package/lib/utils/logError.js +10 -0
- package/lib/utils/logRemoval.js +10 -0
- package/lib/utils/lowerCaseFirst.js +11 -0
- package/lib/utils/normalizeLineEndings.js +8 -0
- package/lib/utils/normalizePath.js +10 -0
- package/lib/utils/parameterizeString.js +19 -0
- package/lib/utils/safeRead.js +17 -0
- package/lib/utils/safeWrite.js +23 -0
- package/lib/utils.js +16 -0
- package/package.json +42 -14
- package/.github/dependabot.yml +0 -8
- package/.github/workflows/codeql-analysis.yml +0 -67
- package/SECURITY.md +0 -15
- package/example/sample.json +0 -431
- package/example/sample.templateroot/date-parser.ts +0 -10
- package/example/sample.templateroot/is-set.ts +0 -3
- package/example/sample.templateroot/models/_eachEnum.fileName_.ts +0 -13
- package/example/sample.templateroot/models/_eachModel.fileName_.ts +0 -73
- package/example/sample.templateroot/models/index.ts +0 -14
- package/example/sample.templateroot/services/_eachController.fileName_.ts +0 -82
- package/example/sample.templateroot/services/index.ts +0 -11
- package/example/sample.templateroot/template.js +0 -413
- package/jest.config.js +0 -185
- package/src/__mocks__/fs.js +0 -59
- package/src/create.js +0 -38
- package/src/create.test.js +0 -5
- package/src/generate/directory.test.js +0 -5
- package/src/generate/file.test.js +0 -5
- package/src/generate/node.test.js +0 -5
- package/src/generate/root.js +0 -82
- package/src/generate/root.test.js +0 -5
- package/src/lodash.js +0 -17205
- package/src/utils.js +0 -179
- package/src/utils.test.js +0 -146
package/README.md
CHANGED
|
@@ -6,7 +6,7 @@ My old Gaffer is tired of writing code, so he generates it instead! This is how
|
|
|
6
6
|
|
|
7
7
|
## Requirements
|
|
8
8
|
|
|
9
|
-
Install the latest LTS of NodeJS. Or a version around
|
|
9
|
+
Install the latest LTS of NodeJS. Or a version around v24. Or try whatever you've got installed already, maybe it will work!
|
|
10
10
|
https://nodejs.org/en/
|
|
11
11
|
|
|
12
12
|
Also install `yarn`. Or don't, and use `npm` instead.
|
|
@@ -62,11 +62,11 @@ You should see output similar to the following:
|
|
|
62
62
|
Let's break down the discrete steps that happened above:
|
|
63
63
|
|
|
64
64
|
1. We search the current directory recursively for `.templateroot` directories.
|
|
65
|
-
2. When one is found, we read in the `template.js` file (it should be a CommonJS module).
|
|
65
|
+
2. When one is found, we read in the `template.cjs` (or `template.js`) file (it should be a CommonJS module).
|
|
66
66
|
3. Call the `download` method of this file, which should return a promise that eventually resolves to any JSON describing what you want to generate.
|
|
67
|
-
4. Using the exported `into` property from the `template.js
|
|
67
|
+
4. Using the exported `into` property from the `template.cjs` (or `template.js`), we'll recursively look at all of the other files within the `.templateroot`.
|
|
68
68
|
5. When we find a directory, recurse in to it!
|
|
69
|
-
6. When we find a file, parse it as a [lodash template](https://lodash.com/docs/4.17.11#template). The context will be whatever was returned from the `download` promise above, plus any exported methods of our `template.js` will be exposed on the `utils` object.
|
|
69
|
+
6. When we find a file, parse it as a [lodash template](https://lodash.com/docs/4.17.11#template). The context will be whatever was returned from the `download` promise above, plus any exported methods of our `template.cjs` (or `template.js`) will be exposed on the `utils` object.
|
|
70
70
|
7. When we find a variable in the path, like `_fileName_.ts`, evaluate it based on our context from `download`.
|
|
71
71
|
8. When we find a `_each` in the path, like `_eachEnum.fileName_.ts`, expand it out. This lets us generate many files from a single template.
|
|
72
72
|
|
|
@@ -90,6 +90,8 @@ There are several flags you can pass. To see them, run `gaffer-generator --help`
|
|
|
90
90
|
|
|
91
91
|
## Testing
|
|
92
92
|
|
|
93
|
+
During development on Gaffer Generator itself, you can run various tests across the codebase. This will unit test and perform basic integration tests on the generated results.
|
|
94
|
+
|
|
93
95
|
```$bash
|
|
94
|
-
|
|
96
|
+
npm run test
|
|
95
97
|
```
|
package/cli.js
CHANGED
|
@@ -1,38 +1,44 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
2
|
+
import yargs from 'yargs';
|
|
3
|
+
import { hideBin } from 'yargs/helpers';
|
|
4
|
+
import { setArgv } from './lib/argv.js';
|
|
5
|
+
|
|
6
|
+
setArgv(
|
|
7
|
+
yargs(hideBin(process.argv))
|
|
8
|
+
.command({
|
|
9
|
+
command: 'generate [directory]',
|
|
10
|
+
aliases: ['gen', 'g'],
|
|
11
|
+
desc: 'Recursively looks for .templateroots in the current directory, or supplied directory',
|
|
12
|
+
builder: yargs => yargs.default('directory', './'),
|
|
13
|
+
handler: async argv => (await import('./lib/generate/root.js')).run(argv.directory),
|
|
14
|
+
})
|
|
15
|
+
.command({
|
|
16
|
+
command: 'create [directory] [--overwrite]',
|
|
17
|
+
aliases: ['c'],
|
|
18
|
+
desc: 'Create a starting .templateroot in to the current directory, or supplied directory',
|
|
19
|
+
builder: yargs => yargs.default('directory', './example.templateroot'),
|
|
20
|
+
handler: async argv => (await import('./lib/create.js')).run(argv.directory),
|
|
21
|
+
})
|
|
22
|
+
.options({
|
|
23
|
+
'dry-run': {
|
|
24
|
+
boolean: true,
|
|
25
|
+
describe: 'Logs the changes that would be made without actually touching the file system.',
|
|
26
|
+
},
|
|
27
|
+
'into': {
|
|
28
|
+
string: true,
|
|
29
|
+
describe: 'Overrides the directory that .templateroots will target. Useful for targeting dynamic directories.',
|
|
30
|
+
},
|
|
31
|
+
'silent': {
|
|
32
|
+
boolean: true,
|
|
33
|
+
describe: 'Disables console logging.',
|
|
34
|
+
},
|
|
35
|
+
'no-colors': {
|
|
36
|
+
boolean: true,
|
|
37
|
+
describe: 'Turns off console coloring of text.',
|
|
38
|
+
},
|
|
39
|
+
})
|
|
40
|
+
.demandCommand()
|
|
41
|
+
.help()
|
|
42
|
+
.wrap(72)
|
|
43
|
+
.argv,
|
|
44
|
+
);
|
package/lib/argv.js
ADDED
package/lib/create.js
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import colors from 'colors/safe.js';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import copy from 'recursive-copy';
|
|
4
|
+
import { getArgv } from './argv.js';
|
|
5
|
+
import * as utils from './utils.js';
|
|
6
|
+
import { logChange } from './utils/logChange.js';
|
|
7
|
+
import { logError } from './utils/logError.js';
|
|
8
|
+
|
|
9
|
+
const dryRun = getArgv().dryRun || false;
|
|
10
|
+
|
|
11
|
+
/*
|
|
12
|
+
Public API.
|
|
13
|
+
*/
|
|
14
|
+
export { run };
|
|
15
|
+
|
|
16
|
+
/*
|
|
17
|
+
Implementation.
|
|
18
|
+
*/
|
|
19
|
+
function run(directory) {
|
|
20
|
+
const from = path.join(import.meta.dirname, '..', 'example', 'sample.templateroot');
|
|
21
|
+
const to = directory;
|
|
22
|
+
const options = {
|
|
23
|
+
overwrite: getArgv().overwrite,
|
|
24
|
+
};
|
|
25
|
+
logChange(to);
|
|
26
|
+
if (dryRun) {
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
copy(from, to, options)
|
|
30
|
+
.on(copy.events.ERROR, (error, copyOperation) => {
|
|
31
|
+
logError(colors.red('Unable to copy to ') + colors.magenta(copyOperation.dest));
|
|
32
|
+
})
|
|
33
|
+
.then(results => {
|
|
34
|
+
utils.log(results.length + ' file(s) copied');
|
|
35
|
+
})
|
|
36
|
+
.catch(function(error) {
|
|
37
|
+
logError(colors.red(error));
|
|
38
|
+
process.exit(1);
|
|
39
|
+
});
|
|
40
|
+
}
|
|
@@ -1,21 +1,20 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import globule from 'globule';
|
|
3
|
+
import path from 'path';
|
|
4
|
+
import { getArgv } from '../argv.js';
|
|
5
|
+
import * as utils from '../utils.js';
|
|
6
|
+
import { logRemoval } from '../utils/logRemoval.js';
|
|
7
|
+
import * as node from './node.js';
|
|
8
|
+
import difference from 'lodash.difference';
|
|
9
|
+
import uniq from 'lodash.uniq';
|
|
8
10
|
|
|
9
|
-
const
|
|
10
|
-
const
|
|
11
|
-
const node = require('./node');
|
|
12
|
-
|
|
13
|
-
const reservedItems = ['template.js', 'template.ts', 'template', '.DS_Store'];
|
|
11
|
+
const dryRun = getArgv().dryRun || false;
|
|
12
|
+
const reservedItems = ['template.cjs', 'template.js', 'template.mjs', 'template.ts', 'template', '.DS_Store'];
|
|
14
13
|
|
|
15
14
|
/*
|
|
16
15
|
Public API.
|
|
17
16
|
*/
|
|
18
|
-
|
|
17
|
+
export { visit };
|
|
19
18
|
|
|
20
19
|
/*
|
|
21
20
|
Implementation.
|
|
@@ -48,7 +47,7 @@ function cleanDirectory(changedFiles) {
|
|
|
48
47
|
}).map(utils.normalizePath);
|
|
49
48
|
const obsoleteFiles = difference(allFiles, changedFiles.map(utils.normalizePath));
|
|
50
49
|
for (let obsoleteFile of obsoleteFiles) {
|
|
51
|
-
|
|
50
|
+
logRemoval(obsoleteFile);
|
|
52
51
|
!dryRun && fs.unlinkSync(obsoleteFile);
|
|
53
52
|
}
|
|
54
53
|
});
|
|
@@ -1,16 +1,16 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
1
|
+
import colors from 'colors/safe.js';
|
|
2
|
+
import defaults from 'lodash.defaults';
|
|
3
|
+
import template from 'lodash.template';
|
|
4
|
+
import { getArgv } from '../argv.js';
|
|
5
|
+
import { templateArgs } from '../templateArgs.js';
|
|
6
|
+
import * as utils from '../utils.js';
|
|
6
7
|
|
|
7
|
-
const
|
|
8
|
-
const dryRun = argv.dryRun || false;
|
|
8
|
+
const dryRun = getArgv().dryRun || false;
|
|
9
9
|
|
|
10
10
|
/*
|
|
11
11
|
Public API.
|
|
12
12
|
*/
|
|
13
|
-
|
|
13
|
+
export { visit };
|
|
14
14
|
|
|
15
15
|
/*
|
|
16
16
|
Implementation.
|
|
@@ -24,13 +24,13 @@ function visit(items, fromPath, toPath, templateSettings, changedFiles) {
|
|
|
24
24
|
templateUtils.safeRead(fromPath),
|
|
25
25
|
defaults({
|
|
26
26
|
sourceURL: fromPath,
|
|
27
|
-
}, templateSettings.templateArgs ||
|
|
27
|
+
}, templateSettings.templateArgs || templateArgs),
|
|
28
28
|
);
|
|
29
29
|
}
|
|
30
30
|
catch (err) {
|
|
31
31
|
templateUtils.logError(
|
|
32
|
-
'Hit error when compiling template:\n'
|
|
33
|
-
+
|
|
32
|
+
colors.red('Hit error when compiling template:\n')
|
|
33
|
+
+ colors.cyan(fromPath),
|
|
34
34
|
err);
|
|
35
35
|
return;
|
|
36
36
|
}
|
|
@@ -53,8 +53,8 @@ function visit(items, fromPath, toPath, templateSettings, changedFiles) {
|
|
|
53
53
|
}
|
|
54
54
|
catch (err) {
|
|
55
55
|
templateUtils.logError(
|
|
56
|
-
'Hit error when running template:\n'
|
|
57
|
-
+
|
|
56
|
+
colors.red('Hit error when running template:\n')
|
|
57
|
+
+ colors.cyan(fromPath) + colors.gray(' => ') + colors.cyan(item.path),
|
|
58
58
|
err);
|
|
59
59
|
}
|
|
60
60
|
}
|
|
@@ -1,16 +1,13 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
const utils = require('../utils');
|
|
7
|
-
const directory = require('./directory');
|
|
8
|
-
const file = require('./file');
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import defaults from 'lodash.defaults';
|
|
3
|
+
import * as utils from '../utils.js';
|
|
4
|
+
import * as directory from './directory.js';
|
|
5
|
+
import * as file from './file.js';
|
|
9
6
|
|
|
10
7
|
/*
|
|
11
8
|
Public API.
|
|
12
9
|
*/
|
|
13
|
-
|
|
10
|
+
export { visit };
|
|
14
11
|
|
|
15
12
|
/*
|
|
16
13
|
Implementation.
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import colors from 'colors/safe.js';
|
|
2
|
+
import fs from 'fs';
|
|
3
|
+
import globule from 'globule';
|
|
4
|
+
import { createRequire } from 'module';
|
|
5
|
+
import path from 'path';
|
|
6
|
+
import { getArgv } from '../argv.js';
|
|
7
|
+
import * as utils from '../utils.js';
|
|
8
|
+
import { logError } from '../utils/logError.js';
|
|
9
|
+
import * as node from './node.js';
|
|
10
|
+
|
|
11
|
+
// Create a CommonJS-compatible require that works from ESM context
|
|
12
|
+
const requireCompat = createRequire(import.meta.url);
|
|
13
|
+
|
|
14
|
+
/*
|
|
15
|
+
Public API.
|
|
16
|
+
*/
|
|
17
|
+
export { run, visit };
|
|
18
|
+
|
|
19
|
+
/*
|
|
20
|
+
Implementation.
|
|
21
|
+
*/
|
|
22
|
+
async function run(directory) {
|
|
23
|
+
const matches = globule.find({
|
|
24
|
+
src: path.join(directory, '**/*.templateroot'),
|
|
25
|
+
filter: match => match.indexOf('node_modules') === -1,
|
|
26
|
+
dot: true,
|
|
27
|
+
});
|
|
28
|
+
for (const match of matches) {
|
|
29
|
+
await visit(path.resolve(match));
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
async function visit(rootPath) {
|
|
34
|
+
const templateSettingsPath = determineTemplateFile(rootPath);
|
|
35
|
+
if (!templateSettingsPath) {
|
|
36
|
+
logError(
|
|
37
|
+
colors.red('Found .templateroot without a template.cjs, template.mjs, template.js or template.ts file:\n')
|
|
38
|
+
+ colors.cyan(rootPath));
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
// TODO:
|
|
42
|
+
// const templateSettings = requireCompat(templateSettingsPath);
|
|
43
|
+
const templateSettings = await import(templateSettingsPath);
|
|
44
|
+
if (getArgv().into) {
|
|
45
|
+
templateSettings.into = getArgv().into;
|
|
46
|
+
}
|
|
47
|
+
if (!templateSettings.into) {
|
|
48
|
+
logError(
|
|
49
|
+
colors.red('Found .templateroot that does not have a "into" export in template file:\n')
|
|
50
|
+
+ colors.cyan(templateSettingsPath));
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
if (!templateSettings.download) {
|
|
54
|
+
logError(
|
|
55
|
+
colors.red('Found .templateroot that does not have a "download" export in template file:\n')
|
|
56
|
+
+ colors.cyan(templateSettingsPath));
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
const toPath = templateSettings.into[0] === '/' ? templateSettings.into : path.join(rootPath, templateSettings.into);
|
|
60
|
+
utils.log(colors.green('Running download from ') + colors.magenta(templateSettingsPath));
|
|
61
|
+
templateSettings.download(utils.fetch)
|
|
62
|
+
.catch(err => {
|
|
63
|
+
logError(
|
|
64
|
+
colors.red('Hit error when downloading for .templateroot:\n')
|
|
65
|
+
+ colors.cyan(templateSettingsPath),
|
|
66
|
+
err);
|
|
67
|
+
})
|
|
68
|
+
.then(json => json && node.visit(json, rootPath, toPath, templateSettings));
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function determineTemplateFile(rootPath) {
|
|
72
|
+
const cjsTemplate = path.join(rootPath, 'template.cjs');
|
|
73
|
+
const jsTemplate = path.join(rootPath, 'template.js');
|
|
74
|
+
const mjsTemplate = path.join(rootPath, 'template.mjs');
|
|
75
|
+
const tsTemplate = path.join(rootPath, 'template.ts');
|
|
76
|
+
if (fs.existsSync(cjsTemplate)) {
|
|
77
|
+
return cjsTemplate;
|
|
78
|
+
}
|
|
79
|
+
if (fs.existsSync(mjsTemplate)) {
|
|
80
|
+
return mjsTemplate;
|
|
81
|
+
}
|
|
82
|
+
if (fs.existsSync(jsTemplate)) {
|
|
83
|
+
return jsTemplate;
|
|
84
|
+
}
|
|
85
|
+
if (fs.existsSync(tsTemplate)) {
|
|
86
|
+
try {
|
|
87
|
+
requireCompat('ts-node').register();
|
|
88
|
+
}
|
|
89
|
+
catch (err) {
|
|
90
|
+
console.warn(colors.gray('Detected template.ts, but ts-node failed to register:'), err);
|
|
91
|
+
}
|
|
92
|
+
return tsTemplate;
|
|
93
|
+
}
|
|
94
|
+
return null;
|
|
95
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { normalizeLineEndings } from './normalizeLineEndings.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Look at two strings to see if they contain the same content, ignoring line endings.
|
|
5
|
+
* @param a
|
|
6
|
+
* @param b
|
|
7
|
+
* @returns {boolean}
|
|
8
|
+
*/
|
|
9
|
+
export function contentsDiffer(a, b) {
|
|
10
|
+
// Are a and b both empty?
|
|
11
|
+
if (!a && !b) {
|
|
12
|
+
return false;
|
|
13
|
+
}
|
|
14
|
+
// Is only one of them empty?
|
|
15
|
+
if (!a || !b) {
|
|
16
|
+
return true;
|
|
17
|
+
}
|
|
18
|
+
// Otherwise, compare them while ignoring line endings to see if they're equivalent.
|
|
19
|
+
return normalizeLineEndings(a) !== normalizeLineEndings(b);
|
|
20
|
+
}
|
package/lib/utils/log.js
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import colors from 'colors/safe.js';
|
|
2
|
+
import { getArgv } from '../argv.js';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Logs a message to the console with a nice prefix.
|
|
6
|
+
* @param text The text to write.
|
|
7
|
+
* @param [error] The optional error to log
|
|
8
|
+
* @param writeAsError If we should use console.error or console.log.
|
|
9
|
+
*/
|
|
10
|
+
export function log(text, error = null, writeAsError = false) {
|
|
11
|
+
if (getArgv().silent) {
|
|
12
|
+
return;
|
|
13
|
+
}
|
|
14
|
+
let prefix = colors.gray(`[${new Date().toTimeString().split(' ')[0]}] `);
|
|
15
|
+
if (writeAsError) {
|
|
16
|
+
if (error) {
|
|
17
|
+
console.error(prefix + text, error);
|
|
18
|
+
}
|
|
19
|
+
else {
|
|
20
|
+
console.error(prefix + text);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
else {
|
|
24
|
+
console.log(prefix + text);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import colors from 'colors/safe.js';
|
|
2
|
+
import fs from 'fs';
|
|
3
|
+
import path from 'path';
|
|
4
|
+
import { log } from './log.js';
|
|
5
|
+
import { normalizePath } from './normalizePath.js';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* If the file doesn't exist, logs a message saying it is being created. Otherwise, it's being updated.
|
|
9
|
+
* @param file
|
|
10
|
+
*/
|
|
11
|
+
export function logChange(file) {
|
|
12
|
+
let url = normalizePath(typeof file === 'string'
|
|
13
|
+
? file
|
|
14
|
+
: path.join(file.dirname, file.basename));
|
|
15
|
+
log(colors.cyan(`${fs.existsSync(url) ? 'updating' : 'creating'}: `) + colors.magenta(url));
|
|
16
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Lowers the first letter of the provided string, leaving the rest of it alone.
|
|
3
|
+
* @param val
|
|
4
|
+
* @returns {string}
|
|
5
|
+
*/
|
|
6
|
+
export function lowerCaseFirst(val) {
|
|
7
|
+
if (!val || !val.toLowerCase) {
|
|
8
|
+
return val;
|
|
9
|
+
}
|
|
10
|
+
return val[0].toLowerCase() + val.slice(1);
|
|
11
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import path from 'path';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Normalizes a path across platforms. (Root / paths are assumed to be on C:\ on windows).
|
|
5
|
+
* @param ref
|
|
6
|
+
* @returns {string}
|
|
7
|
+
*/
|
|
8
|
+
export function normalizePath(ref) {
|
|
9
|
+
return ref && ref.startsWith('/') ? path.resolve(ref) : path.normalize(ref);
|
|
10
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import get from 'lodash.get';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Given a string with _parameters_ within it, substitute them with the real values from the given context.
|
|
5
|
+
* @param str
|
|
6
|
+
* @param context
|
|
7
|
+
* @returns {string}
|
|
8
|
+
*/
|
|
9
|
+
export function parameterizeString(str, context) {
|
|
10
|
+
return str.replace(
|
|
11
|
+
/_[a-z]*\.?[a-z]+_/ig,
|
|
12
|
+
match => {
|
|
13
|
+
const variableName = match.slice(1, -1);
|
|
14
|
+
return get(context, variableName)
|
|
15
|
+
|| get(context, variableName.toLowerCase())
|
|
16
|
+
|| get(context, variableName.toUpperCase())
|
|
17
|
+
|| match;
|
|
18
|
+
});
|
|
19
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import { normalizePath } from './normalizePath.js';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Attempts to read in the provided file, returning the string contents if it exists, or null.
|
|
7
|
+
* @param file
|
|
8
|
+
* @returns {string}
|
|
9
|
+
*/
|
|
10
|
+
export function safeRead(file) {
|
|
11
|
+
let url = normalizePath(typeof file === 'string'
|
|
12
|
+
? file
|
|
13
|
+
: path.join(file.dirname, file.basename));
|
|
14
|
+
return fs.existsSync(url)
|
|
15
|
+
? fs.readFileSync(url, 'utf-8')
|
|
16
|
+
: null;
|
|
17
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import { normalizePath } from './normalizePath.js';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Writes the contents to the file, making sure the parent directories of the file exist.
|
|
7
|
+
* @param file
|
|
8
|
+
* @param contents
|
|
9
|
+
*/
|
|
10
|
+
export function safeWrite(file, contents) {
|
|
11
|
+
const url = normalizePath(typeof file === 'string'
|
|
12
|
+
? file
|
|
13
|
+
: path.join(file.dirname, file.basename));
|
|
14
|
+
const dirs = url.split(path.sep).slice(0, -1);
|
|
15
|
+
// Note: we start at 1 to avoid trying to create the root directory.
|
|
16
|
+
for (let i = 1; i < dirs.length; i++) {
|
|
17
|
+
let collectivePath = dirs.slice(0, i + 1).join(path.sep);
|
|
18
|
+
if (!fs.existsSync(collectivePath)) {
|
|
19
|
+
fs.mkdirSync(collectivePath);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
fs.writeFileSync(url, contents, 'utf-8');
|
|
23
|
+
}
|
package/lib/utils.js
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import fetch from 'node-fetch';
|
|
2
|
+
|
|
3
|
+
/*
|
|
4
|
+
Public API.
|
|
5
|
+
*/
|
|
6
|
+
export { contentsDiffer } from './utils/contentsDiffer.js';
|
|
7
|
+
export { safeRead } from './utils/safeRead.js';
|
|
8
|
+
export { safeWrite } from './utils/safeWrite.js';
|
|
9
|
+
export { log } from './utils/log.js';
|
|
10
|
+
export { logChange } from './utils/logChange.js';
|
|
11
|
+
export { logRemoval } from './utils/logRemoval.js';
|
|
12
|
+
export { logError } from './utils/logError.js';
|
|
13
|
+
export { parameterizeString } from './utils/parameterizeString.js';
|
|
14
|
+
export { lowerCaseFirst } from './utils/lowerCaseFirst.js';
|
|
15
|
+
export { fetch };
|
|
16
|
+
export { normalizePath } from './utils/normalizePath.js';
|