scaffolder: add cookiecutter compat mode to fetch:template
Signed-off-by: Mike Lewis <mtlewis@users.noreply.github.com>
This commit is contained in:
committed by
Himanshu Mishra
parent
9fba3e22b1
commit
a8ded61923
@@ -218,4 +218,58 @@ describe('fetch:template', () => {
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('cookiecutter compatibility mode', () => {
|
||||
let context: ActionContext<FetchTemplateInput>;
|
||||
|
||||
beforeEach(async () => {
|
||||
context = mockContext({
|
||||
values: {
|
||||
name: 'test-project',
|
||||
count: 1234,
|
||||
itemList: ['first', 'second', 'third'],
|
||||
},
|
||||
cookieCutterCompat: true,
|
||||
});
|
||||
|
||||
mockFetchContents.mockImplementation(({ outputPath }) => {
|
||||
mockFs({
|
||||
[outputPath]: {
|
||||
'{{ cookiecutter.name }}.txt': 'static content',
|
||||
subdir: {
|
||||
'templated-content.txt':
|
||||
'{{ cookiecutter.name }}: {{ cookiecutter.count }}',
|
||||
},
|
||||
'{{ cookiecutter.name }}.json':
|
||||
'{{ cookiecutter.itemList | jsonify }}',
|
||||
},
|
||||
});
|
||||
|
||||
return Promise.resolve();
|
||||
});
|
||||
|
||||
await action.handler(context);
|
||||
});
|
||||
|
||||
it('copies files with cookiecutter-style templated names successfully', async () => {
|
||||
await expect(
|
||||
fs.readFile(`${workspacePath}/target/test-project.txt`, 'utf-8'),
|
||||
).resolves.toEqual('static content');
|
||||
});
|
||||
|
||||
it('copies files with cookiecutter-style templated content successfully', async () => {
|
||||
await expect(
|
||||
fs.readFile(
|
||||
`${workspacePath}/target/subdir/templated-content.txt`,
|
||||
'utf-8',
|
||||
),
|
||||
).resolves.toEqual('test-project: 1234');
|
||||
});
|
||||
|
||||
it('includes the jsonify filter', async () => {
|
||||
await expect(
|
||||
fs.readFile(`${workspacePath}/target/test-project.json`, 'utf-8'),
|
||||
).resolves.toEqual('["first","second","third"]');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -24,11 +24,25 @@ import globby from 'globby';
|
||||
import nunjucks from 'nunjucks';
|
||||
import fs from 'fs-extra';
|
||||
|
||||
/*
|
||||
* Maximise compatibility with Jinja (and therefore cookiecutter)
|
||||
* using nunjucks jinja compat mode. Since this method mutates
|
||||
* the global nunjucks instance, we can't enable this per-template,
|
||||
* or only for templates with cookiecutter compat enabled, so the
|
||||
* next best option is to explicitly enable it globally and allow
|
||||
* folks to rely on jinja compatibility behaviour in fetch:template
|
||||
* templates if they wish.
|
||||
*
|
||||
* cf. https://mozilla.github.io/nunjucks/api.html#installjinjacompat
|
||||
*/
|
||||
nunjucks.installJinjaCompat();
|
||||
|
||||
export type FetchTemplateInput = {
|
||||
url: string;
|
||||
targetPath?: string;
|
||||
values: any;
|
||||
copyWithoutRender?: string[];
|
||||
cookieCutterCompat?: boolean;
|
||||
};
|
||||
|
||||
export function createFetchTemplateAction(options: {
|
||||
@@ -72,6 +86,13 @@ export function createFetchTemplateAction(options: {
|
||||
type: 'string',
|
||||
},
|
||||
},
|
||||
cookieCutterCompat: {
|
||||
title: 'Cookiecutter compatibility mode',
|
||||
// TODO(mtlewis): documentation for cookiecutter compat mode
|
||||
description:
|
||||
'Enable features to maximise compatibility with templates built for fetch:cookiecutter',
|
||||
type: 'boolean',
|
||||
},
|
||||
// TODO(mtlewis/orkohunter): do we need to replicate the template extensions support
|
||||
// from fetch:cookiecutter?
|
||||
},
|
||||
@@ -109,8 +130,6 @@ export function createFetchTemplateAction(options: {
|
||||
outputPath: templateDir,
|
||||
});
|
||||
|
||||
// at this point the templateDir contains the unprocessed contents of the skeleton directory
|
||||
|
||||
ctx.logger.info(
|
||||
'Fetched template, beginning templating process with values',
|
||||
ctx.input.values,
|
||||
@@ -138,39 +157,51 @@ export function createFetchTemplateAction(options: {
|
||||
).flat(),
|
||||
);
|
||||
|
||||
// Nice for Cookiecutter compat
|
||||
//
|
||||
// TODO(mtlewis/orkohunter): parameterize all jinja2/cookiecutter compat
|
||||
// TODO(mtlewis): introduce "cookiecutter" prefix for input variables when
|
||||
// compat is enabled?
|
||||
nunjucks.installJinjaCompat();
|
||||
ctx.logger.info(allEntriesInTemplate);
|
||||
|
||||
// Create a templater
|
||||
const templater = nunjucks.configure({
|
||||
// TODO(mtlewis/orkohunter): Document Why we are changing the literals? Not here, but on scaffolder docs. ADR?
|
||||
tags: {
|
||||
variableStart: '${{',
|
||||
variableEnd: '}}',
|
||||
},
|
||||
...(ctx.input.cookieCutterCompat
|
||||
? {}
|
||||
: {
|
||||
tags: {
|
||||
variableStart: '${{',
|
||||
variableEnd: '}}',
|
||||
},
|
||||
}),
|
||||
// We don't want this builtin auto-escaping, since uses HTML escape sequences
|
||||
// like `"` - the correct way to escape strings in our case depends on
|
||||
// the file type.
|
||||
autoescape: false,
|
||||
});
|
||||
|
||||
// TODO(mtlewis/orkohunter) Evaluate whether this behavior is still appropriate when using nunjucks.
|
||||
// As of now jsonify seems to be the most reliable way to do escaping,
|
||||
// but is there a builtin filter to do this inside nunjucks
|
||||
// (other than `autoescape` inside `configure` which escapes strings as HTML, which isn't right.).
|
||||
templater.addFilter('jsonify', s => JSON.stringify(s));
|
||||
if (ctx.input.cookieCutterCompat) {
|
||||
// The "jsonify" filter built into cookiecutter is common
|
||||
// in fetch:cookiecutter templates, so when compat mode
|
||||
// is enabled we alias the "dump" filter from nunjucks as
|
||||
// jsonify. Dump accepts an optional `spaces` parameter
|
||||
// which enables indented output, but when this parameter
|
||||
// is not supplied it works identically to jsonify.
|
||||
//
|
||||
// cf. https://cookiecutter.readthedocs.io/en/latest/advanced/template_extensions.html?highlight=jsonify#jsonify-extension
|
||||
// cf. https://mozilla.github.io/nunjucks/templating.html#dump
|
||||
templater.addFilter('jsonify', templater.getFilter('dump'));
|
||||
}
|
||||
|
||||
// Cookiecutter prefixes all parameters in templates with
|
||||
// `cookiecutter.`. To replicate this, we wrap our parameters
|
||||
// in an object with a `cookiecutter` property when compat
|
||||
// mode is enabled.
|
||||
const parameters = ctx.input.cookieCutterCompat
|
||||
? { cookiecutter: ctx.input.values }
|
||||
: ctx.input.values;
|
||||
|
||||
for (const location of allEntriesInTemplate) {
|
||||
const isTemplated = !nonTemplatedEntries.has(location);
|
||||
const outputPath = resolvePath(
|
||||
outputDir,
|
||||
isTemplated
|
||||
? templater.renderString(location, ctx.input.values)
|
||||
: location,
|
||||
isTemplated ? templater.renderString(location, parameters) : location,
|
||||
);
|
||||
|
||||
if (location.endsWith('/')) {
|
||||
@@ -184,7 +215,7 @@ export function createFetchTemplateAction(options: {
|
||||
await fs.outputFile(
|
||||
outputPath,
|
||||
isTemplated
|
||||
? templater.renderString(inputFileContents, ctx.input.values)
|
||||
? templater.renderString(inputFileContents, parameters)
|
||||
: inputFileContents,
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user