Skip to content

Commit

Permalink
feat: Add create-fedimint-app (#105)
Browse files Browse the repository at this point in the history
* chore: bump react version

* feat: Create Fedimint App script

* feat: updated readme

* chore: update lockfile

* feat: polish, update versions, fix tests

* fix: CI tests - build before testing
  • Loading branch information
alexlwn123 authored Dec 17, 2024
1 parent feb89ed commit 4943332
Show file tree
Hide file tree
Showing 25 changed files with 3,552 additions and 362 deletions.
5 changes: 5 additions & 0 deletions .changeset/big-lies-wonder.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@fedimint/create-fedimint-app': patch
---

Introducing the create-fedimint-app script! Your helper to quickly get started with the fedimint-web-sdk.
3 changes: 3 additions & 0 deletions .github/workflows/verify.yml
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,9 @@ jobs:
# Install dependencies
pnpm install
# Build packages
pnpm build
# Run Tests
pnpm test
EOF
Expand Down
2 changes: 1 addition & 1 deletion examples/vite-core/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
"clean:deep": "pnpm run clean && rm -rf node_modules"
},
"dependencies": {
"@fedimint/core-web": "canary",
"@fedimint/core-web": "^0.0.10",
"react": "^18.3.1",
"react-dom": ">=18.3.1"
},
Expand Down
10 changes: 8 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
{
"name": "@fedimint/fedimint-web-sdk",
"private": true,
"type": "module",
"scripts": {
Expand All @@ -19,17 +20,19 @@
"lint:repo": "sherif",
"preinstall": "pnpx only-allow pnpm",
"prepare": "pnpm simple-git-hooks",
"postinstall": "patch-package",
"preview": "pnpm --filter vite-core preview",
"release": "pnpm run build && pnpm changeset publish",
"reset": "pnpm clean && pnpm build && pnpm preview",
"changeset": "changeset",
"version": "changeset version",
"typecheck": "pnpm run --r --parallel typecheck",
"coverage": "vitest run --coverage",
"test": "pnpm run test:setup pnpm run test:headless",
"test": "pnpm run test:setup pnpm run test:lib",
"test:setup": "bash testing/setup_test_shell.sh",
"test:coverage": "vitest run --coverage",
"test:headless": "vitest",
"test:lib": "vitest",
"test:create": "vitest --watch=false create-fedimint",
"test:ui": "vitest --browser.headless=false --ui",
"format": "prettier --write .",
"watch": "pnpm run --r --parallel --filter \"./packages/**\" watch"
Expand All @@ -41,13 +44,16 @@
"@vitest/browser": "^2.1.2",
"@vitest/coverage-v8": "^2.1.2",
"@vitest/ui": "^2.1.2",
"execa": "^9.5.2",
"glob": "^10.4.5",
"happy-dom": "^15.7.4",
"patch-package": "^8.0.0",
"playwright": "1.40.0",
"prettier": "^3.3.3",
"sherif": "^0.8.4",
"simple-git-hooks": "^2.11.1",
"typescript": "5.5.2",
"unicorn-magic": "^0.3.0",
"vite-plugin-wasm": "^3.3.0",
"vitest": "^2.1.2"
},
Expand Down
62 changes: 62 additions & 0 deletions packages/create-fedimint-app/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
# create-fedimint-app

## Scaffolding Your First Fedimint App

> **Compatibility Note:**
> Requires [Node.js](https://nodejs.org/en/) version 18+, 20+
With NPM:

```bash
$ npm create fedimint-app@latest
```

With Yarn:

```bash
$ yarn create fedimint-app
```

With PNPM:

```bash
$ pnpm create fedimint-app
```

Then follow the prompts!

You can also directly specify the project name and the template you want to use via additional command line options. For example, to scaffold a Fedimint + React project, run:

```bash
# npm 7+, extra double-dash is needed:
npm create fedimint-app@latest my-fedimint-app -- --template vite-react-ts

# yarn
yarn create fedimint-app my-fedimint-app --template vite-react-ts

# pnpm
pnpm create fedimint-app my-fedimint-app --template vite-react-ts
```

Currently supported template presets include:

- `vite-react-ts` - React + TypeScript template with Fedimint integration

You can use `.` for the project name to scaffold in the current directory.

## Getting Started

After creating your project, install dependencies and start the dev server:

```bash
cd my-fedimint-app
npm install
npm run dev
```

This will start a development server with hot module replacement. The template includes:

- React + TypeScript setup
- Fedimint wallet integration
- Basic styling with CSS
- Vite for fast development and building
157 changes: 157 additions & 0 deletions packages/create-fedimint-app/__tests__/cli.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
import fs from 'node:fs'
import path from 'node:path'
import type { SyncOptions, SyncResult } from 'execa'
import { execaCommandSync } from 'execa'
import { afterEach, beforeAll, expect, test } from 'vitest'

const CLI_PATH = path.join(__dirname, '..')

const projectName = 'test-app'
const genPath = path.join(__dirname, projectName)
const genPathWithSubfolder = path.join(__dirname, 'subfolder', projectName)

const run = <SO extends SyncOptions>(
args: string[],
options?: SO,
): SyncResult<SO> => {
return execaCommandSync(`node ${CLI_PATH} ${args.join(' ')}`, options)
}

// Helper to create a non-empty directory
const createNonEmptyDir = (overrideFolder?: string) => {
// Create the temporary directory
const newNonEmptyFolder = overrideFolder || genPath
fs.mkdirSync(newNonEmptyFolder, { recursive: true })

// Create a package.json file
const pkgJson = path.join(newNonEmptyFolder, 'package.json')
fs.writeFileSync(pkgJson, '{ "foo": "bar" }')
}

// Vue 3 starter template
// const templateFiles = fs
// .readdirSync(path.join(CLI_PATH, 'template-vue'))
// // _gitignore is renamed to .gitignore
// .map((filePath) => (filePath === '_gitignore' ? '.gitignore' : filePath))
// .sort()

// React starter template
const templateFilesReact = fs
.readdirSync(path.join(CLI_PATH, 'template-vite-react-ts'))
// _gitignore is renamed to .gitignore
.map((filePath) => (filePath === '_gitignore' ? '.gitignore' : filePath))
.sort()

const clearAnyPreviousFolders = () => {
if (fs.existsSync(genPath)) {
fs.rmSync(genPath, { recursive: true, force: true })
}
if (fs.existsSync(genPathWithSubfolder)) {
fs.rmSync(genPathWithSubfolder, { recursive: true, force: true })
}
}

beforeAll(() => clearAnyPreviousFolders())
afterEach(() => clearAnyPreviousFolders())

test('prompts for the project name if none supplied', () => {
const { stdout } = run([])
expect(stdout).toContain('Project name:')
})

test('prompts for the framework if none supplied when target dir is current directory', () => {
fs.mkdirSync(genPath, { recursive: true })
const { stdout } = run(['.'], { cwd: genPath })
expect(stdout).toContain('Select a framework:')
})

test('prompts for the framework if none supplied', () => {
const { stdout } = run([projectName])
expect(stdout).toContain('Select a framework:')
})

test('prompts for the framework on not supplying a value for --template', () => {
const { stdout } = run([projectName, '--template'])
expect(stdout).toContain('Select a framework:')
})

test('prompts for the framework on supplying an invalid template', () => {
const { stdout } = run([projectName, '--template', 'unknown'])
expect(stdout).toContain(
`"unknown" isn't a valid template. Please choose from below:`,
)
})

test('asks to overwrite non-empty target directory', () => {
createNonEmptyDir()
const { stdout } = run([projectName], { cwd: __dirname })
expect(stdout).toContain(`Target directory "${projectName}" is not empty.`)
})

test('asks to overwrite non-empty target directory with subfolder', () => {
createNonEmptyDir(genPathWithSubfolder)
const { stdout } = run([`subfolder/${projectName}`], { cwd: __dirname })
expect(stdout).toContain(
`Target directory "subfolder/${projectName}" is not empty.`,
)
})

test('asks to overwrite non-empty current directory', () => {
createNonEmptyDir()
const { stdout } = run(['.'], { cwd: genPath })
expect(stdout).toContain(`Current directory is not empty.`)
})

// test('successfully scaffolds a project based on vue starter template', () => {
// const { stdout } = run([projectName, '--template', 'vue'], {
// cwd: __dirname,
// })
// const generatedFiles = fs.readdirSync(genPath).sort()

// // Assertions
// expect(stdout).toContain(`Scaffolding project in ${genPath}`)
// expect(templateFiles).toEqual(generatedFiles)
// })

test('successfully scaffolds a project with subfolder based on react starter template', () => {
const { stdout } = run(
[`subfolder/${projectName}`, '--template', 'vite-react-ts'],
{
cwd: __dirname,
},
)
const generatedFiles = fs.readdirSync(genPathWithSubfolder).sort()

// Assertions
expect(stdout).toContain(`Scaffolding project in ${genPathWithSubfolder}`)
expect(templateFilesReact).toEqual(generatedFiles)
})

test('works with the -t alias', () => {
const { stdout } = run([projectName, '-t', 'vite-react-ts'], {
cwd: __dirname,
})
const generatedFiles = fs.readdirSync(genPath).sort()

// Assertions
expect(stdout).toContain(`Scaffolding project in ${genPath}`)
expect(templateFilesReact).toEqual(generatedFiles)
})

test('accepts command line override for --overwrite', () => {
createNonEmptyDir()
const { stdout } = run(['.', '--overwrite', 'ignore'], { cwd: genPath })
expect(stdout).not.toContain(`Current directory is not empty.`)
})

test('return help usage how to use create-fedimint-app', () => {
const { stdout } = run(['--help'], { cwd: __dirname })
const message = 'Usage: create-fedimint-app [OPTION]... [DIRECTORY]'
expect(stdout).toContain(message)
})

test('return help usage how to use create-fedimint-app with -h alias', () => {
const { stdout } = run(['--h'], { cwd: __dirname })
const message = 'Usage: create-fedimint-app [OPTION]... [DIRECTORY]'
expect(stdout).toContain(message)
})
17 changes: 17 additions & 0 deletions packages/create-fedimint-app/build.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { defineBuildConfig } from 'unbuild'

export default defineBuildConfig({
entries: ['src/index'],
clean: true,
rollup: {
inlineDependencies: true,
esbuild: {
target: 'node18',
minify: true,
},
},
alias: {
// we can always use non-transpiled code since we support node 18+
prompts: 'prompts/lib/index.js',
},
})
3 changes: 3 additions & 0 deletions packages/create-fedimint-app/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
#!/usr/bin/env node

import './dist/index.mjs'
45 changes: 45 additions & 0 deletions packages/create-fedimint-app/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
{
"name": "@fedimint/create-fedimint-app",
"type": "module",
"version": "0.0.1",
"main": "index.js",
"author": "Alex Lewin",
"license": "MIT",
"bin": {
"create-fedimint-app": "index.js",
"cfa": "index.js"
},
"files": [
"index.js",
"template-*",
"dist"
],
"scripts": {
"dev": "unbuild --stub",
"build": "unbuild",
"typecheck": "tsc --noEmit",
"prepublishOnly": "npm run build"
},
"engines": {
"node": "^18.0.0 || ^20.0.0 || >=22.0.0"
},
"repository": {
"type": "git",
"url": "git+https://github.com/fedimint/fedimint-web-sdk.git",
"directory": "packages/create-fedimint"
},
"bugs": {
"url": "https://github.com/fedimint/fedimint-web-sdk/issues"
},
"homepage": "https://github.com/fedimint/fedimint-web-sdk/tree/main/packages/create-fedimint#readme",
"devDependencies": {
"@types/cross-spawn": "^6.0.6",
"@types/minimist": "^1.2.5",
"@types/prompts": "^2.4.9",
"cross-spawn": "^7.0.6",
"minimist": "^1.2.8",
"picocolors": "^1.1.1",
"prompts": "^2.4.2",
"unbuild": "^2.0.0"
}
}
Loading

0 comments on commit 4943332

Please sign in to comment.