Skip to content

feat(templates): support nested templates to create nested routes #123

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Apr 1, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
140 changes: 132 additions & 8 deletions __tests__/templating/filesystem.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -85,12 +85,19 @@ test('installation with basic functions', async () => {
name: 'hello.js',
type: 'functions',
content: 'https://example.com/hello.js',
directory: '',
},
{
name: '.env',
type: '.env',
content: 'https://example.com/.env',
directory: '',
},
{ name: '.env', type: '.env', content: 'https://example.com/.env' },
{
name: 'README.md',
type: 'README.md',
content: 'https://example.com/README.md',
directory: '',
},
],
'./testing/',
Expand Down Expand Up @@ -135,13 +142,20 @@ test('installation with functions and assets', async () => {
name: 'hello.js',
type: 'functions',
content: 'https://example.com/hello.js',
directory: '',
},
{
name: 'hello.wav',
type: 'assets',
content: 'https://example.com/hello.wav',
directory: '',
},
{
name: '.env',
type: '.env',
content: 'https://example.com/.env',
directory: '',
},
{ name: '.env', type: '.env', content: 'https://example.com/.env' },
],
'./testing/',
'example',
Expand Down Expand Up @@ -185,18 +199,26 @@ test('installation with functions and assets and blank namespace', async () => {
name: 'hello.js',
type: 'functions',
content: 'https://example.com/hello.js',
directory: '',
},
{
name: 'hello.wav',
type: 'assets',
content: 'https://example.com/hello.wav',
directory: '',
},
{
name: 'README.md',
type: 'README.md',
content: 'https://example.com/README.md',
directory: '',
},
{
name: '.env',
type: '.env',
content: 'https://example.com/.env',
directory: '',
},
{ name: '.env', type: '.env', content: 'https://example.com/.env' },
],
'./testing/',
'',
Expand Down Expand Up @@ -269,8 +291,14 @@ test('installation with an empty dependency file', async () => {
name: 'package.json',
type: 'package.json',
content: 'https://example.com/package.json',
directory: '',
},
{
name: '.env',
type: '.env',
content: 'https://example.com/.env',
directory: '',
},
{ name: '.env', type: '.env', content: 'https://example.com/.env' },
],
'./testing/',
'example',
Expand Down Expand Up @@ -314,8 +342,14 @@ test('installation with a dependency file', async () => {
name: 'package.json',
type: 'package.json',
content: 'https://example.com/package.json',
directory: '',
},
{
name: '.env',
type: '.env',
content: 'https://example.com/.env',
directory: '',
},
{ name: '.env', type: '.env', content: 'https://example.com/.env' },
],
'./testing/',
'example',
Expand Down Expand Up @@ -359,7 +393,14 @@ test('installation with an existing dot-env file', async () => {
);

await writeFiles(
[{ name: '.env', type: '.env', content: 'https://example.com/.env' }],
[
{
name: '.env',
type: '.env',
content: 'https://example.com/.env',
directory: '',
},
],
'./testing/',
'example',
'hello'
Expand Down Expand Up @@ -398,8 +439,14 @@ test('installation with overlapping function files throws errors before writing'
name: 'hello.js',
type: 'functions',
content: 'https://example.com/hello.js',
directory: '',
},
{
name: '.env',
type: '.env',
content: 'https://example.com/.env',
directory: '',
},
{ name: '.env', type: '.env', content: 'https://example.com/.env' },
],
'./',
'example',
Expand Down Expand Up @@ -431,13 +478,20 @@ test('installation with overlapping asset files throws errors before writing', a
name: 'hello.js',
type: 'functions',
content: 'https://example.com/hello.js',
directory: '',
},
{
name: 'hello.wav',
type: 'assets',
content: 'https://example.com/hello.wav',
directory: '',
},
{
name: '.env',
type: '.env',
content: 'https://example.com/.env',
directory: '',
},
{ name: '.env', type: '.env', content: 'https://example.com/.env' },
],
'./',
'example',
Expand All @@ -450,3 +504,73 @@ test('installation with overlapping asset files throws errors before writing', a
expect(downloadFile).toHaveBeenCalledTimes(0);
expect(writeFile).toHaveBeenCalledTimes(0);
});

test('installation with functions and assets in nested directories', async () => {
// For this test, getFirstMatchingDirectory never errors.
mocked(
fsHelpers.getFirstMatchingDirectory
).mockImplementation((basePath: string, directories: Array<string>): string =>
path.join(basePath, directories[0])
);

await writeFiles(
[
{
name: 'hello.js',
type: 'functions',
content: 'https://example.com/hello.js',
directory: 'admin',
},
{
name: 'woohoo.jpg',
type: 'assets',
content: 'https://example.com/woohoo.jpg',
directory: 'success',
},
{
name: '.env',
type: '.env',
content: 'https://example.com/.env',
directory: '',
},
{
name: 'README.md',
type: 'README.md',
content: 'https://example.com/README.md',
directory: '',
},
],
'./testing/',
'example',
'hello'
);

expect(downloadFile).toHaveBeenCalledTimes(4);
expect(downloadFile).toHaveBeenCalledWith(
'https://example.com/.env',
'testing/.env'
);
expect(downloadFile).toHaveBeenCalledWith(
'https://example.com/hello.js',
'testing/functions/example/admin/hello.js'
);
expect(downloadFile).toHaveBeenCalledWith(
'https://example.com/README.md',
'testing/readmes/example/hello.md'
);
expect(downloadFile).toHaveBeenCalledWith(
'https://example.com/woohoo.jpg',
'testing/assets/example/success/woohoo.jpg'
);

expect(mkdir).toHaveBeenCalledTimes(3);
expect(mkdir).toHaveBeenCalledWith('testing/functions/example', {
recursive: true,
});
expect(mkdir).toHaveBeenCalledWith('testing/readmes/example', {
recursive: true,
});
expect(mkdir).toHaveBeenCalledWith('testing/assets/example', {
recursive: true,
});
});
73 changes: 58 additions & 15 deletions src/templating/data.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import path from 'path';
import { stripIndent } from 'common-tags';
import got from 'got';
import { OutgoingHttpHeaders } from 'http';
Expand Down Expand Up @@ -31,6 +32,7 @@ export async function fetchListOfTemplates(): Promise<Template[]> {

export type TemplateFileInfo = {
name: string;
directory: string;
type: string;
content: string;
};
Expand All @@ -57,27 +59,67 @@ type GitHubError = {
documentation_url?: string;
};

function getFromUrl(url: string) {
const headers = buildHeader();
return got(url, {
json: true,
headers,
});
}

async function getNestedRepoContents(
url: string,
dirName: string,
directory: string
): Promise<TemplateFileInfo[]> {
const response = await getFromUrl(url);
const repoContents = response.body as RawContentsPayload;
return (
await Promise.all(
repoContents.map(async file => {
if (file.type === 'dir') {
return await getNestedRepoContents(
file.url,
path.join(dirName, file.name),
directory
);
} else {
return {
name: file.name,
directory: dirName,
type: directory,
content: file.download_url,
};
}
})
)
).flat();
}

async function getFiles(
templateId: string,
directory: string
): Promise<TemplateFileInfo[]> {
const headers = buildHeader();
const response = await got(
CONTENT_BASE_URL +
`/${templateId}/${directory}?ref=${TEMPLATE_BASE_BRANCH}`,
{
json: true,
headers,
}
const response = await getFromUrl(
CONTENT_BASE_URL + `/${templateId}/${directory}?ref=${TEMPLATE_BASE_BRANCH}`
);
const repoContents = response.body as RawContentsPayload;
return repoContents.map(file => {
return {
name: file.name,
type: directory,
content: file.download_url,
};
});
return (
await Promise.all(
repoContents.map(async file => {
if (file.type === 'dir') {
return await getNestedRepoContents(file.url, file.name, directory);
} else {
return {
name: file.name,
type: directory,
content: file.download_url,
directory: '',
};
}
})
)
).flat();
}

export async function getTemplateFiles(
Expand Down Expand Up @@ -114,6 +156,7 @@ export async function getTemplateFiles(
name: file.name,
type: file.name,
content: file.download_url,
directory: '',
};
});
const files = otherFiles.concat(
Expand Down
13 changes: 8 additions & 5 deletions src/templating/filesystem.ts
Original file line number Diff line number Diff line change
Expand Up @@ -116,15 +116,15 @@ export async function writeFiles(

for (let file of files) {
if (file.type === 'functions') {
let filepath = path.join(functionsTargetDir, file.name);
let filepath = path.join(functionsTargetDir, file.directory, file.name);

if (await fileExists(filepath)) {
throw new Error(
`Template with name "${namespace}" has duplicate file "${file.name}" in "${functionsDir}"`
);
}
} else if (file.type === 'assets') {
let filepath = path.join(assetsTargetDir, file.name);
let filepath = path.join(assetsTargetDir, file.directory, file.name);

if (await fileExists(filepath)) {
throw new Error(
Expand All @@ -138,18 +138,21 @@ export async function writeFiles(
.map(file => {
if (file.type === 'functions') {
return {
title: `Creating function: ${file.name}`,
title: `Creating function: ${path.join(file.directory, file.name)}`,
task: () =>
downloadFile(
file.content,
path.join(functionsTargetDir, file.name)
path.join(functionsTargetDir, file.directory, file.name)
),
};
} else if (file.type === 'assets') {
return {
title: `Creating asset: ${file.name}`,
task: () =>
downloadFile(file.content, path.join(assetsTargetDir, file.name)),
downloadFile(
file.content,
path.join(assetsTargetDir, file.directory, file.name)
),
};
} else if (file.type === '.env') {
return {
Expand Down
Loading