Skip to content
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

chore: add infra to support multiple create fuels templates #2851

Merged
merged 23 commits into from
Aug 7, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
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
5 changes: 5 additions & 0 deletions .changeset/wet-elephants-impress.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"create-fuels": patch
---

chore: add infra to support multiple `create fuels` templates
4 changes: 4 additions & 0 deletions apps/docs/src/guide/creating-a-fuel-dapp/options.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,10 @@ bunx --bun create-fuels@{{fuels}} --bun [project-name] [options]

:::

## `--template <template-name>`

Specifies the template to use for your project. The available templates are: `nextjs`.

## `--pnpm`

Notifies the tool to use pnpm as the package manager to install the necessary dependencies.
Expand Down
1 change: 0 additions & 1 deletion packages/create-fuels/src/bin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import { runScaffoldCli, setupProgram } from './cli';

runScaffoldCli({
program: setupProgram(),
templateName: 'nextjs',
args: process.argv,
})
.then(() => process.exit(0))
Expand Down
22 changes: 17 additions & 5 deletions packages/create-fuels/src/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,11 @@ import ora from 'ora';
import { join } from 'path';

import { tryInstallFuelUp } from './lib';
import { doesTemplateExist } from './lib/doesTemplateExist';
import { getPackageManager } from './lib/getPackageManager';
import { getPackageVersion } from './lib/getPackageVersion';
import type { ProgramOptions } from './lib/setupProgram';
import { defaultTemplate, templates } from './lib/setupProgram';
import type { Template, ProgramOptions } from './lib/setupProgram';
import { promptForProjectPath } from './prompts';
import { error, log } from './utils/logger';

Expand All @@ -39,18 +41,28 @@ function writeEnvFile(envFilePath: string) {

export const runScaffoldCli = async ({
program,
templateName = 'nextjs',
args = process.argv,
}: {
program: Command;
args: string[];
templateName: string;
}) => {
program.parse(args);
const opts = program.opts<ProgramOptions>();

const templateOfChoice = (opts.template ?? defaultTemplate) as Template;

if (!doesTemplateExist(templateOfChoice)) {
error(`Template '${templateOfChoice}' does not exist.`);
log();
log('Available templates:');
for (const template of templates) {
log(` - ${template}`);
}
process.exit(1);
}

let projectPath = program.args[0] ?? (await promptForProjectPath());

const opts = program.opts<ProgramOptions>();
const verboseEnabled = opts.verbose ?? false;
const packageManager = getPackageManager(opts);

Expand Down Expand Up @@ -93,7 +105,7 @@ export const runScaffoldCli = async ({

await mkdir(projectPath);

const templateDir = join(__dirname, '..', 'templates', templateName);
const templateDir = join(__dirname, '..', 'templates', templateOfChoice);
petertonysmith94 marked this conversation as resolved.
Show resolved Hide resolved
await cp(templateDir, projectPath, {
recursive: true,
filter: (filename) => !filename.includes('CHANGELOG.md'),
Expand Down
13 changes: 13 additions & 0 deletions packages/create-fuels/src/lib/doesTemplateExist.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { doesTemplateExist } from './doesTemplateExist';

/**
* @group node
*/
test('doesTemplateExist should return true if the template exists', () => {
expect(doesTemplateExist('nextjs')).toBeTruthy();
});

test('doesTemplateExist should return false if the template does not exist', () => {
// @ts-expect-error intentionally passing in a non-existent template
expect(doesTemplateExist('non-existent-template')).toBeFalsy();
});
4 changes: 4 additions & 0 deletions packages/create-fuels/src/lib/doesTemplateExist.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import type { Template } from './setupProgram';
import { templates } from './setupProgram';

export const doesTemplateExist = (templateName: Template): boolean => templates.has(templateName);
12 changes: 11 additions & 1 deletion packages/create-fuels/src/lib/setupProgram.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,22 @@ import { setupProgram } from './setupProgram';
describe('setupProgram', () => {
test('setupProgram takes in args properly', () => {
const program = setupProgram();
program.parse(['', '', 'test-project-name', '--pnpm', '--npm', '--bun']);
program.parse([
'',
'',
'test-project-name',
'--template',
'nextjs',
'--pnpm',
'--npm',
'--bun',
]);
expect(program.args[0]).toBe('test-project-name');
expect(program.opts().pnpm).toBe(true);
expect(program.opts().npm).toBe(true);
expect(program.opts().bun).toBe(true);
expect(program.opts().install).toBe(true);
expect(program.opts().template).toBe('nextjs');
});

test('setupProgram - no args', () => {
Expand Down
6 changes: 6 additions & 0 deletions packages/create-fuels/src/lib/setupProgram.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@ import { Command } from 'commander';

import packageJson from '../../package.json';

export type Template = 'nextjs';
export const templates: Set<Template> = new Set(['nextjs']);
export const defaultTemplate: Template = 'nextjs';

export interface ProgramOptions {
contract?: boolean;
predicate?: boolean;
Expand All @@ -11,6 +15,7 @@ export interface ProgramOptions {
bun?: boolean;
verbose?: boolean;
install?: boolean;
template?: Template;
}

export const setupProgram = () => {
Expand All @@ -22,6 +27,7 @@ export const setupProgram = () => {
.option('--bun', 'Use bun to install dependencies')
.option('--verbose', 'Enable verbose logging')
.option('--no-install', 'Do not install dependencies')
.option('--template <template>', 'Specify a template to use', defaultTemplate)
.addHelpCommand()
.showHelpAfterError(true);
return program;
Expand Down
53 changes: 44 additions & 9 deletions packages/create-fuels/test/cli.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import { mkdirSync, readFileSync } from 'fs';
import { join } from 'path';

import { runScaffoldCli, setupProgram } from '../src/cli';
import * as doesTemplateExistMod from '../src/lib/doesTemplateExist';
import { templates } from '../src/lib/setupProgram';

import type { ProjectPaths } from './utils/bootstrapProject';
import {
Expand All @@ -20,7 +22,7 @@ import { filterOriginalTemplateFiles, getAllFiles } from './utils/templateFiles'
* @group node
*/
describe('CLI', { timeout: 15_000 }, () => {
const { error } = mockLogger();
const { error, log } = mockLogger();
let paths: ProjectPaths;

beforeEach(() => {
Expand All @@ -39,11 +41,12 @@ describe('CLI', { timeout: 15_000 }, () => {
});

test('create-fuels extracts the template to the specified directory', async () => {
const args = generateArgv(paths.projectRoot);
const args = generateArgv({ projectName: paths.projectRoot, template: paths.templateName });

vi.spyOn(doesTemplateExistMod, 'doesTemplateExist').mockReturnValueOnce(true);

await runScaffoldCli({
program: setupProgram(),
templateName: paths.templateName,
args,
});

Expand All @@ -56,11 +59,12 @@ describe('CLI', { timeout: 15_000 }, () => {
});

test('create-fuels checks the versions on the fuel-toolchain file', async () => {
const args = generateArgv(paths.projectRoot);
const args = generateArgv({ projectName: paths.projectRoot, template: paths.templateName });

vi.spyOn(doesTemplateExistMod, 'doesTemplateExist').mockReturnValueOnce(true);

await runScaffoldCli({
program: setupProgram(),
templateName: paths.templateName,
args,
});

Expand All @@ -75,11 +79,16 @@ describe('CLI', { timeout: 15_000 }, () => {
});

test('should rewrite for the appropriate package manager', async () => {
const args = generateArgv(paths.projectRoot, 'bun');
const args = generateArgv({
projectName: paths.projectRoot,
packageManager: 'bun',
template: paths.templateName,
});

vi.spyOn(doesTemplateExistMod, 'doesTemplateExist').mockReturnValueOnce(true);

await runScaffoldCli({
program: setupProgram(),
templateName: paths.templateName,
args,
});

Expand All @@ -94,15 +103,16 @@ describe('CLI', { timeout: 15_000 }, () => {
});

test('create-fuels reports an error if the project directory already exists', async () => {
const args = generateArgv(paths.projectRoot);
const args = generateArgv({ projectName: paths.projectRoot, template: paths.templateName });

// Generate the project once
mkdirSync(paths.projectRoot, { recursive: true });

vi.spyOn(doesTemplateExistMod, 'doesTemplateExist').mockReturnValueOnce(true);

// Generate the project again
await runScaffoldCli({
program: setupProgram(),
templateName: paths.templateName,
args,
}).catch((e) => {
expect(e).toBeInstanceOf(Error);
Expand All @@ -112,4 +122,29 @@ describe('CLI', { timeout: 15_000 }, () => {
expect.stringContaining(`A folder already exists at ${paths.projectRoot}`)
);
});

test('create-fuels reports an error if the template does not exist', async () => {
const args = generateArgv({
projectName: paths.projectRoot,
template: 'non-existent-template',
});

vi.spyOn(doesTemplateExistMod, 'doesTemplateExist').mockReturnValueOnce(false);

await runScaffoldCli({
program: setupProgram(),
args,
}).catch((e) => {
expect(e).toBeInstanceOf(Error);
});

expect(error).toHaveBeenCalledWith(
expect.stringContaining(`Template 'non-existent-template' does not exist.`)
);
expect(log).toHaveBeenCalledWith();
expect(log).toHaveBeenCalledWith('Available templates:');
for (const template of templates) {
expect(log).toHaveBeenCalledWith(` - ${template}`);
}
});
});
2 changes: 1 addition & 1 deletion packages/create-fuels/test/integration.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ describe('`create fuels` package integrity', () => {
`"fuels": "[0-9]+.[0-9]+.[0-9]+-${PUBLISHED_NPM_TAG}-[0-9]+"`
);

const args = generateArgs(paths.projectRoot, packageManager).join(' ');
const args = generateArgs({ projectName: paths.projectRoot, packageManager }).join(' ');
const expectedTemplateFiles = await getAllFiles(paths.templateSource).then((files) =>
filterOriginalTemplateFiles(files).filter(filterForcBuildFiles)
);
Expand Down
28 changes: 22 additions & 6 deletions packages/create-fuels/test/utils/generateArgs.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,34 @@
export const generateArgs = (projectName?: string, packageManager: string = 'pnpm'): string[] => {
export const generateArgs = ({
projectName,
packageManager = 'pnpm',
template,
}: {
projectName?: string;
packageManager: string;
template?: string;
}): string[] => {
const args = [];
if (packageManager === 'npm') {
args.push('--');
}
if (projectName) {
args.push(projectName);
}
if (template) {
args.push(`--template`);
args.push(template);
}
args.push(`--${packageManager}`);
args.push(`--no-install`);
return args;
};

export const generateArgv = (projectName?: string, packageManager: string = 'pnpm'): string[] => [
'',
'',
...generateArgs(projectName, packageManager),
];
export const generateArgv = ({
projectName,
packageManager = 'pnpm',
template,
}: {
projectName?: string;
packageManager?: string;
template?: string;
}): string[] => ['', '', ...generateArgs({ projectName, packageManager, template })];
Loading