Skip to content

Commit ab78d6b

Browse files
Do up the readme and start with some zod scaffolding
1 parent cc59343 commit ab78d6b

File tree

10 files changed

+1942
-92
lines changed

10 files changed

+1942
-92
lines changed

.gitignore

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
/node_modules
22
/build
33
/app
4-
routes.yml
4+
routes.yml
5+
src/index.ts

.vscode/launch.json

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
{
2+
// Use IntelliSense to learn about possible attributes.
3+
// Hover to view descriptions of existing attributes.
4+
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
5+
"version": "0.2.0",
6+
"configurations": [
7+
{
8+
"type": "node",
9+
"request": "launch",
10+
"name": "Testing",
11+
"args": ["${relativeFile}"],
12+
"runtimeArgs": ["--nolazy", "-r", "ts-node/register/transpile-only"],
13+
"resolveSourceMapLocations": ["${workspaceFolder}/**", "!**/node_modules/**"],
14+
"env": { "TS_NODE_PROJECT": "${workspaceFolder}\\tsconfig.json" },
15+
"cwd": "${workspaceRoot}",
16+
"protocol": "inspector"
17+
}
18+
]
19+
}

CONTRIBUTING.md

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1-
# Contribution
1+
# Contributing to Remix Scaffold
22

3-
Just do it
3+
👍🎉 First off, thanks for taking the time to look here! 🎉👍
4+
5+
Right now there are no hard or fast rules while this project kicks off.
6+
7+
Just reach out if you want to get involved, raise PR's or tickets, and make sure to be a nice human! :D

README.md

Lines changed: 74 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,85 @@
11
![](/assets/header.png)
22

3-
# Remix Scaffold - WORK IN PROGRESS
3+
# Remix Scaffold
4+
>🚧 *This package is still in development and non-functional, reach out if you'd like to help make it reality.* 🚧
45
5-
### Simple scaffolding tool for [Remix](https://remix.run/)
6+
Remix scaffold, is a code-gen tool for [Remix](https://remix.run/) designed to increase your productivity, and manage your route heirarchy.
67

7-
## Features
8+
To do this, we provide a `routes.yml` file at the root of your app, which allows you to define the structure and behaviour of your site (Or utilise pre-built structures from the community) from which your routes and views are automatically generated.
89

9-
- TODO
10+
## Getting Started
1011

11-
## Overview
12+
```bash
13+
#Install globally
14+
npm install -g remix-scaffold
1215

13-
TODO
16+
#initialise
17+
remix-scaffold init
1418

15-
## Getting Started
19+
#Or simply use npx without installing
20+
npx remix-scaffold init
21+
```
22+
23+
This will initialise a `routes.yml` file at the root of your project:
24+
25+
```yml
26+
structure: co-locate
27+
routes:
28+
- name: Home
29+
type: index
30+
- name: Login
31+
- name: Jokes
32+
children:
33+
- name: New
34+
- name: Joke
35+
slug: jokeId
36+
37+
```
38+
>*Note: The default structure is based off the [remix jokes](https://remix.run/docs/en/v1/tutorials/jokes) tutorial, check it out if you are new to Remix*
39+
40+
This is where you lay out the structure of your site, but for now simply run the following to generate your routes
1641

1742
```bash
18-
npm install -D remix-scaffold
43+
remix-scaffold generate
1944
```
45+
46+
Take a look in your `/app/routes` folder and you should see some new routes
47+
48+
![](/assets/generate.png)
49+
50+
And it's really as simple as that!
51+
52+
## Full structure for `routes.yml`
53+
54+
```yml
55+
structure: split | co-locate #Default - co-locate
56+
templates: /pathToTemplates #TODO - Define your own templates
57+
routes:
58+
- name: Home #Becomes the route name in lowercase
59+
type: basic | index | layout | pathless #Default - basic
60+
- name: Blog
61+
type: layout
62+
children:
63+
- name: Post
64+
slug: postId #Defines a dynamic route $postId.tsx
65+
- name: BlogAuthors
66+
route: blog.authors #This will generate Dot Delimeter route
67+
- name: CatchAll
68+
slug: . #This will generate a $.tsx Splat route
69+
```
70+
71+
### Route references
72+
- [Dynamic routes](https://remix.run/docs/en/v1/api/conventions#dynamic-route-parameters)
73+
- [Layout routes](https://remix.run/docs/en/v1/api/conventions#layout-routes)
74+
- [Pathless layout routes](https://remix.run/docs/en/v1/api/conventions#pathless-layout-routes)
75+
- [Dot Delimeters](https://remix.run/docs/en/v1/api/conventions#dot-delimeters)
76+
- [Splat routes](https://remix.run/docs/en/v1/api/conventions#splat-routes)
77+
78+
## CLI Options (Planned)
79+
80+
### `remix-scaffold init`
81+
- `--template` - Use a predefined `routes.yml` template from the community (TODO)
82+
83+
### `remix-scaffold generate`
84+
- `--clear` - Clears folders and routes that no longer exist in the `routes.yml` (TODO)
85+
- `--reset` - Override existing routes, and clears non-existent routes (TODO)

assets/generate.png

10.5 KB
Loading

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@
4343
"dependencies": {
4444
"chalk": "^4.1.2",
4545
"js-yaml": "^4.1.0",
46-
"yargs": "^17.3.0"
46+
"yargs": "^17.3.0",
47+
"zod": "^3.11.6"
4748
}
4849
}

src/commands/generate.ts

Lines changed: 59 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -1,50 +1,59 @@
1-
import type { Arguments, CommandBuilder } from 'yargs';
2-
import path from 'path';
3-
import fs from 'fs';
4-
import yaml from 'js-yaml';
5-
import chalk from 'chalk';
6-
import { YamlConfig } from '../typings/Config';
7-
8-
type Options = {
9-
definition: string;
10-
// clear: boolean;
11-
};
12-
13-
export const command: string = 'generate';
14-
export const desc: string = 'Generate the structure defined in your YAML routes definition file';
15-
16-
export const builder: CommandBuilder<Options, Options> = yargs =>
17-
yargs.options({
18-
definition: {
19-
type: 'string',
20-
alias: 'd',
21-
default: './routes.yml',
22-
description: 'Path to your YAML routes definition',
23-
},
24-
// clear: {
25-
// type: 'boolean',
26-
// alias: 'c',
27-
// default: false,
28-
// description: 'Clear folders that no longer exist in the YAML routes definition',
29-
// },
30-
});
31-
32-
export const handler = (argv: Arguments<Options>): void => {
33-
try {
34-
const { definition } = argv;
35-
36-
let defPath = path.join(process.cwd(), definition);
37-
if (!defPath.endsWith('.yml')) defPath = `${defPath}.yml`;
38-
39-
if (!fs.existsSync(defPath)) {
40-
throw `Missing YAML route definition at ${defPath}`;
41-
}
42-
43-
const doc = yaml.load(fs.readFileSync(defPath, 'utf8'), { json: true }) as YamlConfig;
44-
console.log(JSON.stringify(doc))
45-
46-
} catch (e) {
47-
console.log(chalk.red(e));
48-
process.exit();
49-
}
50-
};
1+
import type { Arguments, CommandBuilder } from 'yargs';
2+
import path from 'path';
3+
import fs from 'fs';
4+
import yaml from 'js-yaml';
5+
import chalk from 'chalk';
6+
import { YamlConfig } from '../typings/Config';
7+
import { routeSchema } from '../utils/routeSchema';
8+
9+
type Options = {
10+
definition: string;
11+
// clear: boolean;
12+
// reset: boolean;
13+
};
14+
15+
export const command: string = 'generate';
16+
export const desc: string = 'Generate the structure defined in your YAML routes definition file';
17+
18+
export const builder: CommandBuilder<Options, Options> = yargs =>
19+
yargs.options({
20+
definition: {
21+
type: 'string',
22+
alias: 'd',
23+
default: './routes.yml',
24+
description: 'Path to your YAML routes definition',
25+
},
26+
// clear: {
27+
// type: 'boolean',
28+
// alias: 'c',
29+
// default: false,
30+
// description: 'Clear folders and files that no longer exist in the YAML routes definition',
31+
// },
32+
// reset: {
33+
// type: 'boolean',
34+
// alias: 'r',
35+
// default: false,
36+
// description: 'Override existing route folders and files, and clear missing ones',
37+
// },
38+
});
39+
40+
export const handler = (argv: Arguments<Options>): void => {
41+
try {
42+
const { definition } = argv;
43+
44+
let defPath = path.join(process.cwd(), definition);
45+
if (!defPath.endsWith('.yml')) defPath = `${defPath}.yml`;
46+
47+
if (!fs.existsSync(defPath)) {
48+
throw `Missing YAML route definition at ${defPath}`;
49+
}
50+
51+
const doc = yaml.load(fs.readFileSync(defPath, 'utf8'), { json: true }) as YamlConfig;
52+
const checks = routeSchema.safeParse(doc);
53+
console.log(checks.success);
54+
55+
} catch (e) {
56+
console.log(chalk.red(e));
57+
process.exit();
58+
}
59+
};

src/commands/init.ts

Lines changed: 30 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,30 @@
1-
import type { Arguments, CommandBuilder } from 'yargs';
2-
import path from 'path';
3-
import fs from 'fs';
4-
import chalk from 'chalk';
5-
6-
export const command: string = 'init [type]';
7-
export const desc: string = 'Creates a pre-defined YAML definition based off the provided type.';
8-
9-
type Options = {
10-
type: 'blog' | 'portal'
11-
}
12-
13-
const getDefinitionOptions = () => {
14-
const files = fs.readdirSync(path.join(__dirname, '../templates/definitions'));
15-
return files.map(file => path.basename(file, '.yml'));
16-
}
17-
18-
export const builder: CommandBuilder<Options> = yargs =>
19-
yargs.positional('type', { choices: getDefinitionOptions(), default: 'default' });
20-
21-
export const handler = (argv: Arguments<Options>): void => {
22-
try {
23-
const { type } = argv;
24-
console.log(type);
25-
26-
} catch (e) {
27-
console.log(chalk.red(e));
28-
process.exit();
29-
}
30-
};
1+
import type { Arguments, CommandBuilder } from 'yargs';
2+
import path from 'path';
3+
import fs from 'fs';
4+
import chalk from 'chalk';
5+
6+
export const command: string = 'init [type]';
7+
export const desc: string = 'Creates a pre-defined YAML definition based off the provided type.';
8+
9+
type Options = {
10+
type: 'blog' | 'portal'
11+
}
12+
13+
const getDefinitionOptions = () => {
14+
const files = fs.readdirSync(path.join(__dirname, '../templates/definitions'));
15+
return files.map(file => path.basename(file, '.yml'));
16+
}
17+
18+
export const builder: CommandBuilder<Options> = yargs =>
19+
yargs.positional('type', { choices: getDefinitionOptions(), default: 'default' });
20+
21+
export const handler = (argv: Arguments<Options>): void => {
22+
try {
23+
const { type } = argv;
24+
console.log(type);
25+
26+
} catch (e) {
27+
console.log(chalk.red(e));
28+
process.exit();
29+
}
30+
};

src/utils/routeSchema.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import { z } from 'zod';
2+
3+
interface RouteObject {
4+
slug?: string;
5+
type?: "basic" | "layout" | "pathless" | "index";
6+
name: string;
7+
children?: RouteObject[]
8+
}
9+
10+
const routeObject: z.Schema<RouteObject> = z.late.object(() => ({
11+
name: z.string().regex(/^[A-Z]([A-Za-z]+)/g, 'Name should be PascalCase with no numbers or special characters'),
12+
type: z.enum(['basic', 'layout', 'pathless', 'index']).default('basic'),
13+
slug: z.string().regex(/^[a-z]([A-Za-z]+)/g, 'Slug should be camelCase with no numbers or special characters').optional(),
14+
children: z.array(routeObject).optional()
15+
}));
16+
17+
export const routeSchema = z.object({
18+
structure: z.enum(['co-locate', 'split'],).default('co-locate'),
19+
routes: z.array(routeObject)
20+
})

0 commit comments

Comments
 (0)