Browse Source

feat(create): Include sample Docker & compose files & docs

Michael Bromley 3 years ago
parent
commit
864314f034

+ 10 - 3
packages/create/src/create-vendure-app.ts

@@ -77,6 +77,7 @@ async function createApp(
     const root = path.resolve(name);
     const appName = path.basename(root);
     const scaffoldExists = scaffoldAlreadyExists(root, name);
+    const useYarn = useNpm ? false : shouldUseYarn();
     if (scaffoldExists) {
         console.log(
             chalk.green(
@@ -94,10 +95,12 @@ async function createApp(
         indexWorkerSource,
         migrationSource,
         readmeSource,
+        dockerfileSource,
+        dockerComposeSource,
         populateProducts,
-    } = isCi ? await gatherCiUserResponses(root) : await gatherUserResponses(root, scaffoldExists);
-
-    const useYarn = useNpm ? false : shouldUseYarn();
+    } = isCi
+        ? await gatherCiUserResponses(root, useYarn)
+        : await gatherUserResponses(root, scaffoldExists, useYarn);
     const originalDirectory = process.cwd();
     process.chdir(root);
     if (!useYarn && !checkThatNpmCanReadCwd()) {
@@ -184,6 +187,10 @@ async function createApp(
                             .then(() => fs.writeFile(srcPathScript('index-worker'), indexWorkerSource))
                             .then(() => fs.writeFile(rootPathScript('migration'), migrationSource))
                             .then(() => fs.writeFile(path.join(root, 'README.md'), readmeSource))
+                            .then(() => fs.writeFile(path.join(root, 'Dockerfile'), dockerfileSource))
+                            .then(() =>
+                                fs.writeFile(path.join(root, 'docker-compose.yml'), dockerComposeSource),
+                            )
                             .then(() => fs.mkdir(path.join(root, 'src/plugins')))
                             .then(() =>
                                 fs.copyFile(assetPath('gitignore.template'), path.join(root, '.gitignore')),

+ 25 - 38
packages/create/src/gather-user-responses.ts

@@ -4,14 +4,18 @@ import Handlebars from 'handlebars';
 import path from 'path';
 import prompts, { PromptObject } from 'prompts';
 
-import { DbType, UserResponses } from './types';
+import { DbType, FileSources, UserResponses } from './types';
 
 // tslint:disable:no-console
 
 /**
  * Prompts the user to determine how the new Vendure app should be configured.
  */
-export async function gatherUserResponses(root: string, alreadyRanScaffold: boolean): Promise<UserResponses> {
+export async function gatherUserResponses(
+    root: string,
+    alreadyRanScaffold: boolean,
+    useYarn: boolean,
+): Promise<UserResponses> {
     function onSubmit(prompt: PromptObject, answer: any) {
         if (prompt.name === 'dbType') {
             dbType = answer;
@@ -107,7 +111,7 @@ export async function gatherUserResponses(root: string, alreadyRanScaffold: bool
     });
 
     return {
-        ...(await generateSources(root, answers)),
+        ...(await generateSources(root, answers, useYarn)),
         dbType: answers.dbType,
         populateProducts: answers.populateProducts,
         superadminIdentifier: answers.superadminIdentifier,
@@ -118,7 +122,7 @@ export async function gatherUserResponses(root: string, alreadyRanScaffold: bool
 /**
  * Returns mock "user response" without prompting, for use in CI
  */
-export async function gatherCiUserResponses(root: string): Promise<UserResponses> {
+export async function gatherCiUserResponses(root: string, useYarn: boolean): Promise<UserResponses> {
     const ciAnswers = {
         dbType: 'sqlite' as const,
         dbHost: '',
@@ -132,7 +136,7 @@ export async function gatherCiUserResponses(root: string): Promise<UserResponses
     };
 
     return {
-        ...(await generateSources(root, ciAnswers)),
+        ...(await generateSources(root, ciAnswers, useYarn)),
         dbType: ciAnswers.dbType,
         populateProducts: ciAnswers.populateProducts,
         superadminIdentifier: ciAnswers.superadminIdentifier,
@@ -143,18 +147,7 @@ export async function gatherCiUserResponses(root: string): Promise<UserResponses
 /**
  * Create the server index, worker and config source code based on the options specified by the CLI prompts.
  */
-async function generateSources(
-    root: string,
-    answers: any,
-): Promise<{
-    indexSource: string;
-    indexWorkerSource: string;
-    configSource: string;
-    envSource: string;
-    envDtsSource: string;
-    migrationSource: string;
-    readmeSource: string;
-}> {
+async function generateSources(root: string, answers: any, useYarn: boolean): Promise<FileSources> {
     const assetPath = (fileName: string) => path.join(__dirname, '../assets', fileName);
 
     /**
@@ -168,6 +161,7 @@ async function generateSources(
 
     const templateContext = {
         ...answers,
+        useYarn,
         dbType: answers.dbType === 'sqlite' ? 'better-sqlite3' : answers.dbType,
         name: path.basename(root),
         isSQLite: answers.dbType === 'sqlite',
@@ -175,28 +169,21 @@ async function generateSources(
         requiresConnection: answers.dbType !== 'sqlite' && answers.dbType !== 'sqljs',
         cookieSecret: Math.random().toString(36).substr(2),
     };
-    const configTemplate = await fs.readFile(assetPath('vendure-config.hbs'), 'utf-8');
-    const configSource = Handlebars.compile(configTemplate, { noEscape: true })(templateContext);
-    const envTemplate = await fs.readFile(assetPath('.env.hbs'), 'utf-8');
-    const envSource = Handlebars.compile(envTemplate, { noEscape: true })(templateContext);
-    const envDtsTemplate = await fs.readFile(assetPath('environment.d.hbs'), 'utf-8');
-    const envDtsSource = Handlebars.compile(envDtsTemplate, { noEscape: true })(templateContext);
-    const indexTemplate = await fs.readFile(assetPath('index.hbs'), 'utf-8');
-    const indexSource = Handlebars.compile(indexTemplate)(templateContext);
-    const indexWorkerTemplate = await fs.readFile(assetPath('index-worker.hbs'), 'utf-8');
-    const indexWorkerSource = Handlebars.compile(indexWorkerTemplate)(templateContext);
-    const migrationTemplate = await fs.readFile(assetPath('migration.hbs'), 'utf-8');
-    const migrationSource = Handlebars.compile(migrationTemplate)(templateContext);
-    const readmeTemplate = await fs.readFile(assetPath('readme.hbs'), 'utf-8');
-    const readmeSource = Handlebars.compile(readmeTemplate)(templateContext);
+
+    async function createSourceFile(filename: string, noEscape = false): Promise<string> {
+        const template = await fs.readFile(assetPath(filename), 'utf-8');
+        return Handlebars.compile(template, { noEscape })(templateContext);
+    }
     return {
-        indexSource,
-        indexWorkerSource,
-        configSource,
-        envSource,
-        envDtsSource,
-        migrationSource,
-        readmeSource,
+        indexSource: await createSourceFile('index.hbs'),
+        indexWorkerSource: await createSourceFile('index-worker.hbs'),
+        configSource: await createSourceFile('vendure-config.hbs', true),
+        envSource: await createSourceFile('.env.hbs', true),
+        envDtsSource: await createSourceFile('environment.d.hbs', true),
+        migrationSource: await createSourceFile('migration.hbs'),
+        readmeSource: await createSourceFile('readme.hbs'),
+        dockerfileSource: await createSourceFile('Dockerfile.hbs'),
+        dockerComposeSource: await createSourceFile('docker-compose.hbs'),
     };
 }
 

+ 8 - 3
packages/create/src/types.ts

@@ -1,8 +1,6 @@
 export type DbType = 'mysql' | 'mariadb' | 'postgres' | 'sqlite' | 'sqljs' | 'mssql' | 'oracle';
 
-export interface UserResponses {
-    dbType: DbType;
-    populateProducts: boolean;
+export interface FileSources {
     indexSource: string;
     indexWorkerSource: string;
     configSource: string;
@@ -10,6 +8,13 @@ export interface UserResponses {
     envDtsSource: string;
     migrationSource: string;
     readmeSource: string;
+    dockerfileSource: string;
+    dockerComposeSource: string;
+}
+
+export interface UserResponses extends FileSources {
+    dbType: DbType;
+    populateProducts: boolean;
     superadminIdentifier: string;
     superadminPassword: string;
 }

+ 9 - 0
packages/create/templates/Dockerfile.hbs

@@ -0,0 +1,9 @@
+FROM node:16
+
+WORKDIR /usr/src/app
+
+COPY package.json ./
+COPY {{#if useYarn}}yarn.lock{{else}}package-lock.json{{/if}} ./
+RUN {{#if useYarn}}yarn{{else}}npm install{{/if}}
+COPY . .
+RUN {{#if useYarn}}yarn{{else}}npm run{{/if}} build

+ 39 - 0
packages/create/templates/docker-compose.hbs

@@ -0,0 +1,39 @@
+version: "3"
+services:
+  server:
+    build:
+      context: .
+      dockerfile: Dockerfile
+    ports:
+      - 3000:3000
+    command: [{{#if useYarn}}"yarn"{{else}}"npm", "run"{{/if}}, "start:server"]
+    volumes:
+      - /usr/src/app
+    environment:
+      DB_HOST: database
+      DB_PORT: 5432
+      DB_NAME: vendure
+      DB_USERNAME: postgres
+      DB_PASSWORD: password
+  worker:
+    build:
+      context: .
+      dockerfile: Dockerfile
+    command: [{{#if useYarn}}"yarn"{{else}}"npm", "run"{{/if}}, "start:worker"]
+    volumes:
+      - /usr/src/app
+    environment:
+      DB_HOST: database
+      DB_PORT: 5432
+      DB_NAME: vendure
+      DB_USERNAME: postgres
+      DB_PASSWORD: password
+  database:
+    image: postgres
+    volumes:
+      - /var/lib/postgresql/data
+    ports:
+      - 5432:5432
+    environment:
+      POSTGRES_PASSWORD: password
+      POSTGRES_DB: vendure

+ 43 - 15
packages/create/templates/readme.hbs

@@ -17,9 +17,7 @@ Useful links:
 ## Development
 
 ```
-yarn dev
-# or
-npm run dev
+{{#if useYarn}}yarn dev{{else}}npm run dev{{/if}}
 ```
 
 will start the Vendure server and [worker](https://www.vendure.io/docs/developer-guide/vendure-worker/) processes from
@@ -28,13 +26,49 @@ the `src` directory.
 ## Build
 
 ```
-yarn build
-# or
-npm run build
+{{#if useYarn}}yarn build{{else}}npm run build{{/if}}
 ```
 
 will compile the TypeScript sources into the `/dist` directory.
 
+## Production
+
+For production, there are many possibilities which depend on your operational requirements as well as your production
+hosting environment.
+
+### Running directly
+
+You can run the built files directly with the `start` script:
+
+```
+{{#if useYarn}}yarn start{{else}}npm run start{{/if}}
+```
+
+You could also consider using a process manager like [pm2](https://pm2.keymetrics.io/) to run and manage
+the server & worker processes.
+
+### Using Docker
+
+We've included a sample [Dockerfile](./Dockerfile) which you can build with the following command:
+
+```
+docker build -t vendure .
+```
+
+and then run it with:
+```
+# Run the server
+docker run -dp 3000:3000 -e "DB_HOST=host.docker.internal" --name vendure-server vendure npm run start:server
+
+# Run the worker
+docker run -dp 3000:3000 -e "DB_HOST=host.docker.internal" --name vendure-worker vendure npm run start:worker
+```
+
+### Docker compose
+
+We've included a sample [docker-compose.yml](./docker-compose.yml) file which demonstrates how the server, worker, and
+database may be orchestrated with Docker Compose.
+
 ## Plugins
 
 In Vendure, your custom functionality will live in [plugins](https://www.vendure.io/docs/plugins/).
@@ -48,9 +82,7 @@ will be required whenever you make changes to the `customFields` config or defin
 The following npm scripts can be used to generate migrations:
 
 ```
-yarn migration:generate [name]
-# or
-npm run migration:generate [name]
+{{#if useYarn}}yarn{{else}}npm run{{/if}} migration:generate [name]
 ```
 
 The generated migration file will be found in the `./src/migrations/` directory, and should be committed to source control.
@@ -67,15 +99,11 @@ data that you cannot lose.
 You can also run any pending migrations manually, without starting the server by running:
 
 ```
-yarn migration:run
-# or
-npm run migration:run
+{{#if useYarn}}yarn{{else}}npm run{{/if}} migration:run
 ```
 
 You can revert the most recently-applied migration with:
 
 ```
-yarn migration:revert
-# or
-npm run migration:revert
+{{#if useYarn}}yarn{{else}}npm run{{/if}} migration:revert
 ```