Skip to content

Commit

Permalink
refactor: move create-keystone-app inside monorepo
Browse files Browse the repository at this point in the history
  • Loading branch information
iamandrewluca committed Apr 17, 2024
1 parent 3b6ba78 commit c5f6d71
Show file tree
Hide file tree
Showing 18 changed files with 1,819 additions and 231 deletions.
21 changes: 21 additions & 0 deletions packages/create/LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
MIT License

Copyright (c) 2021 Thinkmill Labs Pty Ltd

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
6 changes: 6 additions & 0 deletions packages/create/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
# @keystone-6/create

Keystone-6 is the latest version of Keystone.
To get help with this package join the conversation in [Slack](https://community.keystonejs.com/), or [Github](https://github.com/keystonejs/keystone/).

Visit <https://keystonejs.com/> for docs, and [follow @keystonejs on Twitter](https://twitter.com/keystonejs) for the latest updates.
2 changes: 2 additions & 0 deletions packages/create/cli.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
#!/usr/bin/env node
import './dist/create-keystone-app.esm.js'
43 changes: 43 additions & 0 deletions packages/create/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
{
"name": "create-keystone-app",
"version": "9.0.1",
"license": "MIT",
"type": "module",
"main": "dist/create-keystone-app.cjs.js",
"module": "dist/create-keystone-app.esm.js",
"repository": "https://github.com/keystonejs/keystone/tree/main/packages/create",
"bin": "./cli.js",
"exports": {
".": {
"module": "./dist/create-keystone-app.esm.js",
"default": "./dist/create-keystone-app.cjs.js"
},
"./package.json": "./package.json"
},
"preconstruct": {
"entrypoints": [
"index.ts"
]
},
"dependencies": {
"chalk": "^4.1.2",
"enquirer": "^2.4.1",
"execa": "^5.1.1",
"fs-extra": "^11.0.0",
"meow": "^9.0.0",
"ora": "^8.0.1",
"package-json": "^10.0.0",
"path": "^0.12.7",
"semver": "^7.6.0",
"terminal-link": "^3.0.0"
},
"devDependencies": {
"@types/fs-extra": "^11.0.0",
"@types/semver": "^7.5.8"
},
"files": [
"dist",
"starter",
"cli.js"
]
}
24 changes: 24 additions & 0 deletions packages/create/src/checkVersion.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import getPackageJson from 'package-json';
import currentPkgJson from '../package.json';
import * as semver from 'semver';

export async function checkVersion() {
try {
const { version } = await getPackageJson('create-keystone-app');
if (typeof version !== 'string') {
throw new Error(
'version from package metadata was expected to be a string but was not'
);
}
if (semver.lt(currentPkgJson.version, version)) {
console.error(
`⚠️ You're running an old version of create-keystone-app, please update to ${version}`
);
}
} catch (err) {
console.error(
'A problem occurred fetching the latest version of create-keystone-app'
);
console.error(err);
}
}
135 changes: 135 additions & 0 deletions packages/create/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
import fs from 'fs-extra';
import path from 'path';
import meow from 'meow';
import enquirer from 'enquirer';
import execa from 'execa';
import ora from 'ora';
import c from 'chalk';
import terminalLink from 'terminal-link';
import { checkVersion } from './checkVersion';
import { UserError } from './utils';
import { fileURLToPath } from 'url';

const __dirname = path.dirname(fileURLToPath(import.meta.url));
const starterDir = path.normalize(`${__dirname}/../starter`);

const cli = meow(
`
Usage
$ create-keystone-app [directory]
`
);

type Args = {
directory: string;
};

const versionInfo = () => {
process.stdout.write('\n');
console.log(`✨ You're about to generate a project using ${c.bold(
'Keystone 6'
)} packages.
`);
};

async function normalizeArgs(): Promise<Args> {
let directory = cli.input[0];
if (!directory) {
({ directory } = await enquirer.prompt({
type: 'input',
name: 'directory',
message:
'What directory should create-keystone-app generate your app into?',
validate: (x) => !!x,
}));
process.stdout.write('\n');
}
return {
directory: path.resolve(directory),
};
}

function pkgManagerFromUserAgent(userAgent: string | undefined) {
if (!userAgent) return 'npm';
const pkgSpec = userAgent.split(' ')[0];
const [name, _version] = pkgSpec.split('/');
return name ?? 'npm';
}

const installDeps = async (cwd: string): Promise<string> => {
const pkgManager = pkgManagerFromUserAgent(process.env.npm_config_user_agent);
const spinner = ora(
`Installing dependencies with ${pkgManager}. This may take a few minutes.`
).start();
try {
await execa(pkgManager, ['install'], { cwd });
spinner.succeed(`Installed dependencies with ${pkgManager}.`);
return pkgManager;
} catch (err) {
spinner.fail(`Failed to install with ${pkgManager}.`);
throw err;
}
};

(async () => {
versionInfo();
await checkVersion();
const normalizedArgs = await normalizeArgs();
await fs.mkdir(normalizedArgs.directory);
await Promise.all([
...[
'_gitignore',
'schema.ts',
'package.json',
'tsconfig.json',
'schema.graphql',
'schema.prisma',
'keystone.ts',
'auth.ts',
'README.md',
].map((filename) =>
fs.copyFile(
path.join(starterDir, filename),
path.join(normalizedArgs.directory, filename.replace(/^_/, '.'))
)
),
]);
const packageManager = await installDeps(normalizedArgs.directory);
const relativeProjectDir = path.relative(
process.cwd(),
normalizedArgs.directory
);
process.stdout.write('\n');
console.log(`🎉 Keystone created a starter project in: ${c.bold(
relativeProjectDir
)}
${c.bold('To launch your app, run:')}
- cd ${relativeProjectDir}
- ${packageManager} run dev
${c.bold('Next steps:')}
- Read ${c.bold(
`${relativeProjectDir}${path.sep}README.md`
)} for additional getting started details.
- Edit ${c.bold(
`${relativeProjectDir}${path.sep}keystone.ts`
)} to customize your app.
- ${terminalLink('Open the Admin UI', 'http://localhost:3000')}
- ${terminalLink('Open the Graphql API', 'http://localhost:3000/api/graphql')}
- ${terminalLink('Read the docs', 'https://keystonejs.com')}
- ${terminalLink(
'Star Keystone on GitHub',
'https://github.com/keystonejs/keystone'
)}
`);
})().catch((err) => {
if (err instanceof UserError) {
console.error(err.message);
} else {
console.error(err);
}
process.exit(1);
});
1 change: 1 addition & 0 deletions packages/create/src/utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export class UserError extends Error {}
17 changes: 17 additions & 0 deletions packages/create/starter/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# keystone-app

## 1.0.2

### Patch Changes

- [`3b4360a`](https://github.com/keystonejs/create-keystone-app/commit/3b4360a114f00094e40fdc89dd4c82e1456b9ae5) Thanks [@dcousens](https://github.com/dcousens)! - Fix graphql@^15.8.0 and next@12.2.4 as pseudo-peer dependencies until next `@keystone-6/core` release

## 1.0.1

### Patch Changes

- [#278](https://github.com/keystonejs/create-keystone-app/pull/278) [`26f9a79`](https://github.com/keystonejs/create-keystone-app/commit/26f9a79ef913915bac85657884f85ff7e4da46c2) Thanks [@Noviny](https://github.com/Noviny)! - Improve schema options for linking authors to posts:
- Add `inlineConnect: true` to the post's relationship to users
- Remove authors from being inline-creatable

* [#319](https://github.com/keystonejs/create-keystone-app/pull/319) [`94a859e`](https://github.com/keystonejs/create-keystone-app/commit/94a859e43123d2f348d5e21551d59bd7e257aa81) Thanks [@Achisingh](https://github.com/Achisingh)! - Fix dependencies and update schemas for the latest `keystone-6` release
52 changes: 52 additions & 0 deletions packages/create/starter/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
# Keystone Project Starter

Welcome to Keystone!

Run

```
yarn dev
```

To view the config for your new app, look at [./keystone.ts](./keystone.ts)

This project starter is designed to give you a sense of the power Keystone can offer you, and show off some of its main features. It's also a pretty simple setup if you want to build out from it.

We recommend you use this alongside our [getting started walkthrough](https://keystonejs.com/docs/walkthroughs/getting-started-with-create-keystone-app) which will walk you through what you get as part of this starter.

If you want an overview of all the features Keystone offers, check out our [features](https://keystonejs.com/why-keystone#features) page.

## Some Quick Notes On Getting Started

### Changing the database

We've set you up with an [SQLite database](https://keystonejs.com/docs/apis/config#sqlite) for ease-of-use. If you're wanting to use PostgreSQL, you can!

Just change the `db` property on line 16 of the Keystone file [./keystone.ts](./keystone.ts) to

```typescript
db: {
provider: 'postgresql',
url: process.env.DATABASE_URL || 'DATABASE_URL_TO_REPLACE',
}
```

And provide your database url from PostgreSQL.

For more on database configuration, check out or [DB API Docs](https://keystonejs.com/docs/apis/config#db)

### Auth

We've put auth into its own file to make this humble starter easier to navigate. To explore it without auth turned on, comment out the `isAccessAllowed` on line 21 of the Keystone file [./keystone.ts](./keystone.ts).

For more on auth, check out our [Authentication API Docs](https://keystonejs.com/docs/apis/auth#authentication-api)

### Adding a frontend

As a Headless CMS, Keystone can be used with any frontend that uses GraphQL. It provides a GraphQL endpoint you can write queries against at `/api/graphql` (by default [http://localhost:3000/api/graphql](http://localhost:3000/api/graphql)). At Thinkmill, we tend to use [Next.js](https://nextjs.org/) and [Apollo GraphQL](https://www.apollographql.com/docs/react/get-started/) as our frontend and way to write queries, but if you have your own favourite, feel free to use it.

A walkthrough on how to do this is forthcoming, but in the meantime our [todo example](https://github.com/keystonejs/keystone-react-todo-demo) shows a Keystone set up with a frontend. For a more full example, you can also look at an example app we built for [Prisma Day 2021](https://github.com/keystonejs/prisma-day-2021-workshop)

### Embedding Keystone in a Next.js frontend

While Keystone works as a standalone app, you can embed your Keystone app into a [Next.js](https://nextjs.org/) app. This is quite a different setup to the starter, and we recommend checking out our walkthrough for that [here](https://keystonejs.com/docs/walkthroughs/embedded-mode-with-sqlite-nextjs#how-to-embed-keystone-sq-lite-in-a-next-js-app).
4 changes: 4 additions & 0 deletions packages/create/starter/_gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
node_modules
.keystone/
keystone.db
*.log
66 changes: 66 additions & 0 deletions packages/create/starter/auth.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
// Welcome to some authentication for Keystone
//
// This is using @keystone-6/auth to add the following
// - A sign-in page for your Admin UI
// - A cookie-based stateless session strategy
// - Using a User email as the identifier
// - 30 day cookie expiration
//
// This file does not configure what Users can do, and the default for this starter
// project is to allow anyone - logged-in or not - to do anything.
//
// If you want to prevent random people on the internet from accessing your data,
// you can find out how by reading https://keystonejs.com/docs/guides/auth-and-access-control
//
// If you want to learn more about how our out-of-the-box authentication works, please
// read https://keystonejs.com/docs/apis/auth#authentication-api

import { randomBytes } from 'crypto';
import { createAuth } from '@keystone-6/auth';

// see https://keystonejs.com/docs/apis/session for the session docs
import { statelessSessions } from '@keystone-6/core/session';

// for a stateless session, a SESSION_SECRET should always be provided
// especially in production (statelessSessions will throw if SESSION_SECRET is undefined)
let sessionSecret = process.env.SESSION_SECRET;
if (!sessionSecret && process.env.NODE_ENV !== 'production') {
sessionSecret = randomBytes(32).toString('hex');
}

// withAuth is a function we can use to wrap our base configuration
const { withAuth } = createAuth({
listKey: 'User',
identityField: 'email',

// this is a GraphQL query fragment for fetching what data will be attached to a context.session
// this can be helpful for when you are writing your access control functions
// you can find out more at https://keystonejs.com/docs/guides/auth-and-access-control
sessionData: 'name createdAt',
secretField: 'password',

// WARNING: remove initFirstItem functionality in production
// see https://keystonejs.com/docs/config/auth#init-first-item for more
initFirstItem: {
// if there are no items in the database, by configuring this field
// you are asking the Keystone AdminUI to create a new user
// providing inputs for these fields
fields: ['name', 'email', 'password'],

// it uses context.sudo() to do this, which bypasses any access control you might have
// you shouldn't use this in production
},
});

// statelessSessions uses cookies for session tracking
// these cookies have an expiry, in seconds
// we use an expiry of 30 days for this starter
const sessionMaxAge = 60 * 60 * 24 * 30;

// you can find out more at https://keystonejs.com/docs/apis/session#session-api
const session = statelessSessions({
maxAge: sessionMaxAge,
secret: sessionSecret!,
});

export { withAuth, session };
Loading

0 comments on commit c5f6d71

Please sign in to comment.