Skip to content
This repository was archived by the owner on Apr 26, 2022. It is now read-only.

Commit 8645656

Browse files
authored
Merge pull request #155 from jacksteamdev/add-action.transform
Add transform and when functions to actions
2 parents 288c8e9 + 9f06a31 commit 8645656

12 files changed

+989
-15
lines changed

index.d.ts

Lines changed: 34 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -181,6 +181,19 @@ export interface ActionConfig<TData extends object = object> {
181181
* @default true
182182
*/
183183
abortOnFail?: boolean;
184+
/**
185+
* Skip an action if this function returns a string,
186+
* which is the reason it should be skipped.
187+
*
188+
* May also return a Promise which resolves to a string.
189+
*
190+
* The action will continue if action.skip()
191+
* returns or resolves to anything other than a string,
192+
* and the return value will be ignored.
193+
*
194+
* @default () => true
195+
*/
196+
skip?: (data: TData) => void | string | Promise<void | string>;
184197
}
185198

186199
/**
@@ -211,6 +224,10 @@ export interface AddActionConfig<TData extends object = object>
211224
* @default false
212225
*/
213226
skipIfExists?: boolean;
227+
/**
228+
* Transform the template result before writing the file.
229+
*/
230+
transform?: (templateResult: string, data: TData) => string;
214231
}
215232

216233
/**
@@ -258,12 +275,19 @@ export interface AddManyActionConfig<TData extends object = object>
258275
* @default true
259276
*/
260277
verbose?: boolean;
278+
/**
279+
* Transform the template result before writing the file.
280+
*/
281+
transform?: (templateResult: string, data: TData) => string;
261282
}
262283

263284
/**
264-
* The `modify` action will use a `pattern` property to find/replace text in the
265-
* file located at the `path` specified. More details on modify can be found in
266-
* the example folder.
285+
* The `modify` action will use a `pattern` property and/or a `transform` function
286+
* to find/replace or transform text in the file located at the `path` specified.
287+
*
288+
* `pattern` and `transform` can be used together or individually.
289+
*
290+
* More details on modify can be found in the example folder.
267291
*/
268292
export interface ModifyActionConfig<TData extends object = object>
269293
extends ActionConfig<TData> {
@@ -280,16 +304,20 @@ export interface ModifyActionConfig<TData extends object = object>
280304
* Used to match text that should be replaced.
281305
* @default end-of-file
282306
*/
283-
pattern: string | RegExp;
307+
pattern?: string | RegExp;
284308
/**
285309
* Handlebars template that should replace what was matched by the `pattern`.
286310
* Capture groups are available as `$1`, `$2`, etc.
287311
*/
288-
template: string;
312+
template?: string;
289313
/**
290314
* Path a file containing the `template`.
291315
*/
292-
templateFile: string;
316+
templateFile?: string;
317+
/**
318+
* Transform the file contents immediately before writing to disk.
319+
*/
320+
transform?: (fileContents: string, data: TData) => string;
293321
}
294322

295323
/**

src/actions/_common-action-add-file.js

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import path from 'path';
22
import del from 'del';
33
import {
44
getRenderedTemplate,
5+
getTransformedTemplate,
56
makeDestPath,
67
throwStringifiedError,
78
getRelativeToBasePath
@@ -38,7 +39,14 @@ export default async function addFile(data, cfg, plop) {
3839
await fspp.writeFileRaw(fileDestPath, rawTemplate);
3940
} else {
4041
const renderedTemplate = await getRenderedTemplate(data, cfg, plop);
41-
await fspp.writeFile(fileDestPath, renderedTemplate);
42+
43+
const transformedTemplate = await getTransformedTemplate(
44+
renderedTemplate,
45+
data,
46+
cfg
47+
);
48+
49+
await fspp.writeFile(fileDestPath, transformedTemplate);
4250
}
4351

4452
// keep the executable flags

src/actions/_common-action-interface-check.js

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,5 +16,17 @@ export default function(action, {checkPath=true, checkAbortOnFail=true} = {}) {
1616
return `Invalid value for abortOnFail (${abortOnFail} is not a Boolean)`;
1717
}
1818

19+
if ('transform' in action && typeof action.transform !== 'function') {
20+
return `Invalid value for transform (${typeof action.transform} is not a function)`;
21+
}
22+
23+
if (action.type === 'modify' && !('pattern' in action) && !('transform' in action)) {
24+
return 'Invalid modify action (modify must have a pattern or transform function)';
25+
}
26+
27+
if ('skip' in action && typeof action.skip !== 'function') {
28+
return `Invalid value for skip (${typeof action.skip} is not a function)`;
29+
}
30+
1931
return true;
2032
}

src/actions/_common-action-utils.js

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,3 +49,21 @@ export const throwStringifiedError = err => {
4949
throw err.message || JSON.stringify(err);
5050
}
5151
};
52+
53+
export async function getTransformedTemplate(template, data, cfg) {
54+
// transform() was already typechecked at runtime in interface check
55+
if ('transform' in cfg) {
56+
const result = await cfg.transform(template, data);
57+
58+
if (typeof result !== 'string')
59+
throw new TypeError(
60+
`Invalid return value for transform (${JSON.stringify(
61+
result
62+
)} is not a string)`
63+
);
64+
65+
return result;
66+
} else {
67+
return template;
68+
}
69+
}

src/actions/modify.js

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,8 @@ import {
44
makeDestPath,
55
throwStringifiedError,
66
getRelativeToBasePath,
7-
getRenderedTemplatePath
7+
getRenderedTemplatePath,
8+
getTransformedTemplate
89
} from './_common-action-utils';
910

1011
import actionInterfaceTest from './_common-action-interface-check';
@@ -26,7 +27,8 @@ export default async function (data, cfg, plop) {
2627
cfg.templateFile = getRenderedTemplatePath(data, cfg, plop);
2728
const replacement = await getRenderedTemplate(data, cfg, plop);
2829
fileData = fileData.replace(cfg.pattern, replacement);
29-
await fspp.writeFile(fileDestPath, fileData);
30+
const transformed = await getTransformedTemplate(fileData, data, cfg);
31+
await fspp.writeFile(fileDestPath, transformed);
3032
}
3133
return getRelativeToBasePath(fileDestPath, plop);
3234
} catch (err) {

src/generator-runner.js

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,14 @@ export default function (plopfileApi, flags) {
114114
// data can also be a function that returns a data object
115115
if (typeof cfgData === 'function') { cfgData = await cfgData(); }
116116

117+
// check if action should run
118+
if (typeof cfg.skip === 'function') {
119+
const reasonToSkip = await cfg.skip(data);
120+
if (typeof reasonToSkip === 'string') {
121+
return reasonToSkip;
122+
}
123+
}
124+
117125
// track keys that can be applied to the main data scope
118126
const cfgDataKeys = Object.keys(cfgData).filter(k => typeof data[k] === 'undefined');
119127
// copy config data into main data scope so it's available for templates
@@ -135,16 +143,19 @@ export default function (plopfileApi, flags) {
135143
}
136144
)
137145
// cleanup main data scope so config data doesn't leak
138-
.finally(() => cfgDataKeys.forEach(k => {delete data[k];}));
146+
.finally(() =>
147+
cfgDataKeys.forEach(k => {
148+
delete data[k];
149+
})
150+
);
139151
};
140152

141153
// request the list of custom actions from the plopfile
142154
function getCustomActionTypes() {
143-
return plopfileApi.getActionTypeList()
144-
.reduce(function (types, name) {
145-
types[name] = plopfileApi.getActionType(name);
146-
return types;
147-
}, {});
155+
return plopfileApi.getActionTypeList().reduce(function(types, name) {
156+
types[name] = plopfileApi.getActionType(name);
157+
return types;
158+
}, {});
148159
}
149160

150161
return {

0 commit comments

Comments
 (0)