Skip to content

Commit

Permalink
Major re-organization
Browse files Browse the repository at this point in the history
This commit changes completely how `create-near-app` works. Now, the app will create
EITHER a smart contract or EITHER a Gateway (frontend). The structure of the smart
contracts has also completely change, since now the "integration-tests" (now known
as "sandbox tests") are embedded within the contract's folder, and not outside as
before. Moreover, the completely Rust smart contracts now only rely on Rust, and
nothing else!

Another improvement is that the division of logic (either frontend or backend) has
simplified a lot the code, while also simplifying each element created on itself.
  • Loading branch information
gagdiez committed Nov 5, 2023
1 parent 666b8a4 commit 293b68b
Show file tree
Hide file tree
Showing 155 changed files with 1,664 additions and 3,992 deletions.
23 changes: 9 additions & 14 deletions src/app.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,12 @@
import path from 'path';
import {createProject, runDepsInstall} from './make';
import {promptAndGetConfig,} from './user-input';
import { createProject, runDepsInstall } from './make';
import { promptAndGetConfig, } from './user-input';
import * as show from './messages';

(async function () {
const promptResult = await promptAndGetConfig();
if (promptResult === null) {
return;
}
const prompt = await promptAndGetConfig();
if (prompt === undefined) return;

const {
config: {
projectName,
Expand All @@ -17,7 +16,7 @@ import * as show from './messages';
install,
},
projectPath,
} = promptResult;
} = prompt;

show.creatingApp();

Expand All @@ -28,22 +27,18 @@ import * as show from './messages';
frontend,
tests,
projectName,
verbose: false,
rootDir: path.resolve(__dirname, '../templates'),
templatesDir: path.resolve(__dirname, '../templates'),
projectPath,
});
} catch (e) {
console.error(e);
createSuccess = false;
}
if (install) {
await runDepsInstall(projectPath);
}

if (createSuccess) {
install && await runDepsInstall(projectPath);
show.setupSuccess(projectName, contract, frontend, install);
} else {
show.setupFailed();
return;
return show.setupFailed();
}
})();
128 changes: 43 additions & 85 deletions src/make.ts
Original file line number Diff line number Diff line change
@@ -1,113 +1,71 @@
import {CreateProjectParams} from './types';
import { CreateContractParams, CreateGatewayParams } from './types';
import * as show from './messages';
import spawn from 'cross-spawn';
import fs from 'fs';
import {ncp} from 'ncp';
import { ncp } from 'ncp';
import path from 'path';
import {buildPackageJson} from './package-json';

export async function createProject({contract, frontend, tests, projectPath, projectName, verbose, rootDir}: CreateProjectParams): Promise<boolean> {

// Create files in the project folder
await createFiles({contract, frontend, projectName, tests, projectPath, verbose, rootDir});
export async function createProject({ contract, frontend, tests, projectPath, projectName, templatesDir }: CreateContractParams & CreateGatewayParams): Promise<boolean> {

// Create package.json
const packageJson = buildPackageJson({contract, frontend, tests, projectName});
fs.writeFileSync(path.resolve(projectPath, 'package.json'), Buffer.from(JSON.stringify(packageJson, null, 2)));
if(contract !== 'none'){
await createContract({ contract, tests, projectPath, projectName, templatesDir });
}else{
await createGateway({ frontend, projectPath, projectName, templatesDir });
}

return true;
}

export async function createFiles({contract, frontend, tests, projectPath, verbose, rootDir}: CreateProjectParams) {
// skip build artifacts and symlinks
const skip = ['.cache', 'dist', 'out', 'node_modules'];
async function createContract({ contract, tests, projectPath, projectName, templatesDir }: CreateContractParams) {
// contract folder
const sourceContractDir = path.resolve(templatesDir, 'contracts', contract);
const targetContractDir = projectPath;
fs.mkdirSync(targetContractDir, { recursive: true });
await copyDir(sourceContractDir, targetContractDir);

// copy frontend
if (frontend !== 'none') {
const sourceFrontendDir = path.resolve(`${rootDir}/frontend/${frontend}`);
const targetFrontendDir = path.resolve(`${projectPath}/frontend`);
fs.mkdirSync(targetFrontendDir, { recursive: true });
await copyDir(sourceFrontendDir, targetFrontendDir, {verbose, skip: skip.map(f => path.join(sourceFrontendDir, f))});
}
// copy sandbox-test dir
const targetTestDir = path.resolve(projectPath, `sandbox-${tests}`);
const sourceTestDir = path.resolve(`${templatesDir}/sandbox-tests/sandbox-${tests}`);

// shared files
const sourceSharedDir = path.resolve(rootDir, 'shared');
await copyDir(sourceSharedDir, projectPath, {verbose, skip: skip.map(f => path.join(sourceSharedDir, f))});
fs.mkdirSync(targetTestDir);
await copyDir(sourceTestDir, targetTestDir);

// copy contract files
if(contract !== 'none') {
const sourceContractDir = path.resolve(rootDir, 'contracts', contract);
const targetContractDir = path.resolve(projectPath, 'contract');
fs.mkdirSync(targetContractDir, { recursive: true });
await copyDir(sourceContractDir, targetContractDir, {
verbose,
skip: skip.map(f => path.join(sourceContractDir, f))
});
}
// tests dir
if(contract !== 'none') {
const targetTestDir = path.resolve(projectPath, 'integration-tests');
fs.mkdirSync(targetTestDir, { recursive: true });
if (contract === 'rs'){
if (tests === 'rs') {
// leave only one test script
fs.unlinkSync(path.resolve(projectPath, 'test-ts.sh'));
fs.renameSync(path.resolve(projectPath, 'test-rs.sh'), path.resolve(projectPath, 'test.sh'));

// copy tests - shared files
const sourceTestSharedDir = path.resolve(`${rootDir}/integration-tests/${tests}-tests`);
await copyDir(sourceTestSharedDir, targetTestDir, {
verbose,
skip: skip.map(f => path.join(sourceTestSharedDir, f))
});
// add workspace to Cargo.toml
const cargoTomlPath = path.resolve(projectPath, 'Cargo.toml');
const cargoToml = fs.readFileSync(cargoTomlPath).toString();
const cargoTomlWithWorkspace = cargoToml + '\n[workspace]\nmembers = ["sandbox-rs"]';
fs.writeFileSync(cargoTomlPath, cargoTomlWithWorkspace);
}else{
// leave only one test script
fs.unlinkSync(path.resolve(projectPath, 'test-rs.sh'));
fs.renameSync(path.resolve(projectPath, 'test-ts.sh'), path.resolve(projectPath, 'test.sh'));
}
}

// add .gitignore
await renameFile(`${projectPath}/template.gitignore`, `${projectPath}/.gitignore`);
}

export const renameFile = async function (oldPath: string, newPath: string) {
return new Promise<void>((resolve, reject) => {
fs.rename(oldPath, newPath, err => {
if (err) {
console.error(err);
reject(err);
return;
}
resolve();
});
});
};
async function createGateway({ frontend, projectPath, projectName, templatesDir }: CreateGatewayParams) {
const sourceFrontendDir = path.resolve(`${templatesDir}/frontend/${frontend}`);
const targetFrontendDir = path.resolve(`${projectPath}`);
fs.mkdirSync(targetFrontendDir, { recursive: true });
await copyDir(sourceFrontendDir, targetFrontendDir);
}

// Wrap `ncp` tool to wait for the copy to finish when using `await`
// Allow passing `skip` variable to skip copying an array of filenames
export function copyDir(source: string, dest: string, {skip, verbose}: {skip: string[], verbose: boolean}) {
export function copyDir(source: string, dest: string) {
return new Promise<void>((resolve, reject) => {
const copied: string[] = [];
const skipped: string[] = [];
const filter = skip && function(filename: string) {
const shouldCopy = !skip.find(f => filename.includes(f));
shouldCopy ? copied.push(filename) : skipped.push(filename);
return !skip.find(f => filename.includes(f));
};

ncp(source, dest, {filter}, err => {
if (err) {
reject(err);
return;
}

if (verbose) {
console.log('Copied:');
copied.forEach(f => console.log(' ' + f));
console.log('Skipped:');
skipped.forEach(f => console.log(' ' + f));
}

resolve();
});
ncp(source, dest, { }, err => err? reject(err): resolve());
});
}

export async function runDepsInstall(projectPath: string) {
show.depsInstall();
const npmCommandArgs = ['install'];
await new Promise<void>((resolve, reject) => spawn('npm', npmCommandArgs, {
await new Promise<void>((resolve, reject) => spawn('npm', ['install'], {
cwd: projectPath,
stdio: 'inherit',
}).on('close', (code: number) => {
Expand Down
50 changes: 29 additions & 21 deletions src/messages.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,44 +8,52 @@ if (process.env.NEAR_NO_COLOR) {

export const show = (...args: unknown[]) => console.log(...args);

export const welcome = () => show(chalk`{blue ======================================================}
👋 {bold {green Welcome to NEAR!}} Learn more: https://docs.near.org/
🔧 Let's get your dApp ready.
export const welcome = () => show(chalk`
{blue ======================================================}
(${trackingMessage})
`);
👋 {bold {green Welcome to Near!}} Learn more: https://docs.near.org/
🔧 Let's get your project ready.
{blue ======================================================}
(${trackingMessage})`);

export const setupFailed = () => show(chalk`{bold {red ==========================================}}
{red ⛔️ There was a problem during NEAR project setup}.
{red ⛔️ There was a problem during the project setup}.
Please refer to https://github.com/near/create-near-app README to troubleshoot.
Notice: some platforms aren't supported (yet).
{bold {red ==========================================}}`);

export const successContractToText = (contract: Contract) => contract === 'none' ? '' : chalk`with a smart contract in {bold ${contract === 'rust' ? 'Rust' : 'JavaScript'}}`;
export const successFrontendToText = (frontend: Frontend) => frontend === 'none' ? '' : chalk` and a frontend template${frontend === 'gateway' ? chalk`{bold in React.js}` : ''}`;
export const successContractToText = (contract: Contract) => contract === 'none' ? '' : chalk`a smart contract in {bold ${contract === 'rs' ? 'Rust' : 'Typescript'}}`;
export const successFrontendToText = (frontend: Frontend) => frontend === 'none' ? '' : chalk`a gateway using ${frontend === 'next' ? 'NextJS + React' : 'Vanilla-JS'}`;
export const setupSuccess = (projectName: ProjectName, contract: Contract, frontend: Frontend, install: boolean) => show(chalk`
{green ======================================================}
✅ Success! Created '${projectName}'
${successContractToText(contract)}${successFrontendToText(frontend)}.
${contract === 'rust' ? chalk`🦀 If you are new to Rust please visit {bold {green https://www.rust-lang.org }}\n` : ''}
{bold {bgYellow {black Your next steps}}}:
✅ Success! Created '${projectName}', ${successContractToText(contract)}${successFrontendToText(frontend)}.
${contract === 'rs' ? chalk`🦀 If you are new to Rust please visit {bold {green https://www.rust-lang.org }}\n` : ''}
{bold {bgYellow {black Next steps}}}:
${contractInstructions(projectName, contract, install)}${gatewayInstructions(projectName, frontend, install)}`);

export const contractInstructions = (projectName: ProjectName, contract: Contract, install: boolean) => contract === 'none' ? '' : chalk`
- {inverse Navigate to your project}:
{blue cd {bold ${projectName}}}
${!install ? chalk`- {inverse Install all dependencies}
${contract ==='ts' && !install ? chalk` - {inverse Install all dependencies}
{blue npm {bold install}}` : 'Then:'}
- {inverse Build your contract}:
{blue npm {bold run build}}
- {inverse Test your contract} in NEAR SandBox:
{blue npm {bold test}}
${contract === 'ts' ? chalk`{blue npm {bold run build}}` : chalk`{blue {bold ./build.sh}}`}
- {inverse Test your contract} in the Sandbox:
${contract === 'ts' ? chalk`{blue npm {bold run test}}` : chalk`{blue {bold ./test.sh}}`}
- {inverse Deploy your contract} to NEAR TestNet with a temporary dev account:
{blue npm {bold run deploy}}
${frontend !== 'none' ? chalk`- {inverse Start your frontend}:
{blue npm {bold start}}\n` : ''}
🧠 Read {bold {greenBright README.md}} to explore further.`);
${contract === 'ts' ? chalk`{blue npm {bold run deploy}}` : chalk`{blue {bold ./deploy.sh}}`}
🧠 Read {bold {greenBright README.md}} to explore further`;

export const gatewayInstructions = (projectName: ProjectName, frontend: Frontend, install: boolean) => frontend === 'none' ? '' : chalk`
- {inverse Navigate to your project}:
{blue cd {bold ${projectName}}}
${!install ? chalk` - {inverse Install all dependencies}
{blue pnpm {bold install}}` : 'Then:'}
- {inverse Start your app}:
{blue pnpm {bold run dev}}`;

export const argsError = () => show(chalk`{red Arguments error}
Run {blue npx create-near-app} without arguments, or use:
npx create-near-app <projectName> --contract rust|js --frontend react|vanilla|none --tests js|rust`);
npx create-near-app <projectName> --frontend next|vanilla|none --contract rs|ts|none --tests rs|ts|none`);

export const unsupportedNodeVersion = (supported: string) => show(chalk`{red We support node.js version ${supported} or later}`);

Expand Down
Loading

0 comments on commit 293b68b

Please sign in to comment.