From 1326b1cd18d93ef1fd9c0b69d80b1542bfb0613d Mon Sep 17 00:00:00 2001 From: Laurentiu Ciobanu Date: Sun, 5 Sep 2021 15:44:34 +0000 Subject: [PATCH] feat: kubernate init with templates --- .prettierignore | 4 +- package.json | 3 + src/assets/init-templates/basic/.gitignore | 2 + .../init-templates/basic/.kubernaterc.yaml | 3 + .../init-templates/basic/.prettierignore | 2 + src/assets/init-templates/basic/.prettierrc | 6 + .../basic/.vscode/extensions.json | 3 + .../basic/.vscode/settings.json | 5 + src/assets/init-templates/basic/README.md.hbs | 5 + src/assets/init-templates/basic/output/.empty | 0 .../init-templates/basic/package.json.hbs | 21 ++++ .../init-templates/basic/src/hello-world.ts | 68 +++++++++++ src/assets/init-templates/basic/tsconfig.json | 18 +++ .../init-templates/with-resources/.gitignore | 2 + .../with-resources/.kubernaterc.yaml | 12 ++ .../with-resources/.prettierignore | 3 + .../init-templates/with-resources/.prettierrc | 6 + .../with-resources/.vscode/extensions.json | 3 + .../with-resources/.vscode/settings.json | 14 +++ .../with-resources/README.md.hbs | 7 ++ .../with-resources/input/hello.yaml | 14 +++ .../with-resources/output/.empty | 0 .../with-resources/package.json.hbs | 21 ++++ .../with-resources/schemas/objects.json | 75 ++++++++++++ .../with-resources/schemas/resources.json | 8 ++ .../src/generated/declarations.ts | 11 ++ .../with-resources/src/generated/index.ts | 11 ++ .../with-resources/src/hello-world.ts | 79 ++++++++++++ .../src/resources/hello-world-service.ts | 13 ++ .../with-resources/src/resources/index.ts | 6 + .../with-resources/tsconfig.json | 18 +++ src/cli/bin.ts | 4 +- src/cli/commands/init.ts | 115 ++++++++++++++++++ src/scripts/build.ts | 3 + tsconfig.json | 4 +- yarn.lock | 38 +++++- 36 files changed, 602 insertions(+), 5 deletions(-) create mode 100644 src/assets/init-templates/basic/.gitignore create mode 100644 src/assets/init-templates/basic/.kubernaterc.yaml create mode 100644 src/assets/init-templates/basic/.prettierignore create mode 100644 src/assets/init-templates/basic/.prettierrc create mode 100644 src/assets/init-templates/basic/.vscode/extensions.json create mode 100644 src/assets/init-templates/basic/.vscode/settings.json create mode 100644 src/assets/init-templates/basic/README.md.hbs create mode 100644 src/assets/init-templates/basic/output/.empty create mode 100644 src/assets/init-templates/basic/package.json.hbs create mode 100644 src/assets/init-templates/basic/src/hello-world.ts create mode 100644 src/assets/init-templates/basic/tsconfig.json create mode 100644 src/assets/init-templates/with-resources/.gitignore create mode 100644 src/assets/init-templates/with-resources/.kubernaterc.yaml create mode 100644 src/assets/init-templates/with-resources/.prettierignore create mode 100644 src/assets/init-templates/with-resources/.prettierrc create mode 100644 src/assets/init-templates/with-resources/.vscode/extensions.json create mode 100644 src/assets/init-templates/with-resources/.vscode/settings.json create mode 100644 src/assets/init-templates/with-resources/README.md.hbs create mode 100644 src/assets/init-templates/with-resources/input/hello.yaml create mode 100644 src/assets/init-templates/with-resources/output/.empty create mode 100644 src/assets/init-templates/with-resources/package.json.hbs create mode 100644 src/assets/init-templates/with-resources/schemas/objects.json create mode 100644 src/assets/init-templates/with-resources/schemas/resources.json create mode 100644 src/assets/init-templates/with-resources/src/generated/declarations.ts create mode 100644 src/assets/init-templates/with-resources/src/generated/index.ts create mode 100644 src/assets/init-templates/with-resources/src/hello-world.ts create mode 100644 src/assets/init-templates/with-resources/src/resources/hello-world-service.ts create mode 100644 src/assets/init-templates/with-resources/src/resources/index.ts create mode 100644 src/assets/init-templates/with-resources/tsconfig.json create mode 100644 src/cli/commands/init.ts diff --git a/.prettierignore b/.prettierignore index 2338f6f..1c5d2e7 100644 --- a/.prettierignore +++ b/.prettierignore @@ -1,2 +1,4 @@ node_modules -src/__generated__/ \ No newline at end of file +src/__generated__/ +src/assets +dist/ \ No newline at end of file diff --git a/package.json b/package.json index 9c436d4..672a1fc 100644 --- a/package.json +++ b/package.json @@ -26,6 +26,7 @@ "compile": "tsc", "compile:watch": "tsc -w", "build": "tsc && ts-node src/scripts/build.ts '0.0.1' && chmod +x ./dist/cli/bin.js", + "build:assets": "rimraf dist/assets && cp -R src/assets dist/assets", "clean": "rimraf dist", "clean:incremental": "rimraf dist/tsconfig.tsbuildinfo", "format": "prettier --write .", @@ -36,6 +37,7 @@ "cosmiconfig": "^7.0.1", "deepmerge": "^4.2.2", "glob": "^7.1.7", + "handlebars": "^4.7.7", "minimatch": "^3.0.4", "semver": "^7.3.5", "ts-morph": "^11.0.3", @@ -49,6 +51,7 @@ "devDependencies": { "@octokit/rest": "^18.9.1", "@types/deepmerge": "^2.2.0", + "@types/handlebars": "^4.1.0", "@types/minimatch": "^3.0.5", "@types/node": "^16.4.1", "@types/package-json": "^5.0.1", diff --git a/src/assets/init-templates/basic/.gitignore b/src/assets/init-templates/basic/.gitignore new file mode 100644 index 0000000..76add87 --- /dev/null +++ b/src/assets/init-templates/basic/.gitignore @@ -0,0 +1,2 @@ +node_modules +dist \ No newline at end of file diff --git a/src/assets/init-templates/basic/.kubernaterc.yaml b/src/assets/init-templates/basic/.kubernaterc.yaml new file mode 100644 index 0000000..cd847b1 --- /dev/null +++ b/src/assets/init-templates/basic/.kubernaterc.yaml @@ -0,0 +1,3 @@ +targetVersion: "v1" +scripts: + hello-world: ./src/hello-world.ts diff --git a/src/assets/init-templates/basic/.prettierignore b/src/assets/init-templates/basic/.prettierignore new file mode 100644 index 0000000..85e37d5 --- /dev/null +++ b/src/assets/init-templates/basic/.prettierignore @@ -0,0 +1,2 @@ +node_modules +output/ \ No newline at end of file diff --git a/src/assets/init-templates/basic/.prettierrc b/src/assets/init-templates/basic/.prettierrc new file mode 100644 index 0000000..d0c396a --- /dev/null +++ b/src/assets/init-templates/basic/.prettierrc @@ -0,0 +1,6 @@ +{ + "tabWidth": 4, + "singleQuote": false, + "bracketSpacing": false, + "printWidth": 140 +} diff --git a/src/assets/init-templates/basic/.vscode/extensions.json b/src/assets/init-templates/basic/.vscode/extensions.json new file mode 100644 index 0000000..ed6d22a --- /dev/null +++ b/src/assets/init-templates/basic/.vscode/extensions.json @@ -0,0 +1,3 @@ +{ + "recommendations": ["redhat.vscode-yaml", "esbenp.prettier-vscode"] +} diff --git a/src/assets/init-templates/basic/.vscode/settings.json b/src/assets/init-templates/basic/.vscode/settings.json new file mode 100644 index 0000000..2b38dcd --- /dev/null +++ b/src/assets/init-templates/basic/.vscode/settings.json @@ -0,0 +1,5 @@ +{ + "yaml.schemas": { + "node_modules/kubernate/schemas/config.json": ".kubernaterc.yaml" + }, +} diff --git a/src/assets/init-templates/basic/README.md.hbs b/src/assets/init-templates/basic/README.md.hbs new file mode 100644 index 0000000..030f363 --- /dev/null +++ b/src/assets/init-templates/basic/README.md.hbs @@ -0,0 +1,5 @@ +## {{projectName}} + +Run `kubernate hello-world`. + +This example uses Kubernate to create a namespace, a pod template and a deployment. The outputs are written to a single yaml file in `output/hello-world.yaml`. diff --git a/src/assets/init-templates/basic/output/.empty b/src/assets/init-templates/basic/output/.empty new file mode 100644 index 0000000..e69de29 diff --git a/src/assets/init-templates/basic/package.json.hbs b/src/assets/init-templates/basic/package.json.hbs new file mode 100644 index 0000000..d63f566 --- /dev/null +++ b/src/assets/init-templates/basic/package.json.hbs @@ -0,0 +1,21 @@ +{ + "name": "{{projectName}}", + "version": "1.0.0", + "main": "dist/index.js", + "types": "dist/index.d.ts", + "files": [ + "dist/**/*" + ], + "scripts": { + "format": "prettier --write .", + "build": "tsc" + }, + "dependencies": { + "kubernate": "~1.22" + }, + "devDependencies": { + "@types/node": "^16.7.9", + "prettier": "^2.3.2", + "typescript": "^4.3.5" + } +} diff --git a/src/assets/init-templates/basic/src/hello-world.ts b/src/assets/init-templates/basic/src/hello-world.ts new file mode 100644 index 0000000..b5c1f6b --- /dev/null +++ b/src/assets/init-templates/basic/src/hello-world.ts @@ -0,0 +1,68 @@ +import kube, {output} from "kubernate"; + +/** + * Kubernate comes with some pre-built resource helpers for most common resources. + * check imports from "kubernate/resources/*" + */ +import namespace from "kubernate/resources/namespace"; + +import * as path from "path"; + +const outputPath = (fileName: string) => path.join(__dirname, "../output", fileName); + +/** + * This is the main entry point and will be called by Kubernate. + */ +export default async () => { + // carete a new namespace + const ns = namespace("hello-world"); + + // create and reuse this selector + const selector = { + app: "hello-world", + }; + + // create a pod template for out deployment + const pod = kube.core.v1.PodTemplate({ + template: { + metadata: { + namespace: ns, + name: "hello-world", + labels: selector, + }, + spec: { + containers: [ + { + name: "hello-world-http", + image: "kornkitti/express-hello-world:latest", + imagePullPolicy: "Always", + ports: [ + { + containerPort: 8080, + name: "http", + }, + ], + }, + ], + }, + }, + }); + + // create a deployment using our pod template + kube.apps.v1.Deployment({ + metadata: { + namespace: ns, + name: "hello-world", + }, + spec: { + replicas: 1, + selector: { + matchLabels: selector, + }, + template: pod.template!, + }, + }); + + // write the output to disk + await output.bundleToDisk(outputPath("hello-world.yaml")); +}; diff --git a/src/assets/init-templates/basic/tsconfig.json b/src/assets/init-templates/basic/tsconfig.json new file mode 100644 index 0000000..bfe0c7f --- /dev/null +++ b/src/assets/init-templates/basic/tsconfig.json @@ -0,0 +1,18 @@ +{ + "compilerOptions": { + "incremental": true, + "target": "ES2019", + "module": "commonjs", + "declaration": true, + "declarationMap": true, + "sourceMap": true, + "outDir": "./dist", + "strict": true, + "strictFunctionTypes": false, + "moduleResolution": "node", + "esModuleInterop": true, + "skipLibCheck": true, + "forceConsistentCasingInFileNames": true + }, + "include": ["src"] +} diff --git a/src/assets/init-templates/with-resources/.gitignore b/src/assets/init-templates/with-resources/.gitignore new file mode 100644 index 0000000..76add87 --- /dev/null +++ b/src/assets/init-templates/with-resources/.gitignore @@ -0,0 +1,2 @@ +node_modules +dist \ No newline at end of file diff --git a/src/assets/init-templates/with-resources/.kubernaterc.yaml b/src/assets/init-templates/with-resources/.kubernaterc.yaml new file mode 100644 index 0000000..a2e996d --- /dev/null +++ b/src/assets/init-templates/with-resources/.kubernaterc.yaml @@ -0,0 +1,12 @@ +targetVersion: "v1" +scripts: + hello-world: ./src/hello-world.ts +resources: + entry: ./src/resources/index.ts + entryTypeName: Resources # this is optional + exclude: + - output/* + include: input/*.yaml + output: + code: ./src/generated + schemas: ./schemas diff --git a/src/assets/init-templates/with-resources/.prettierignore b/src/assets/init-templates/with-resources/.prettierignore new file mode 100644 index 0000000..f989d40 --- /dev/null +++ b/src/assets/init-templates/with-resources/.prettierignore @@ -0,0 +1,3 @@ +node_modules +output/ +src/generated \ No newline at end of file diff --git a/src/assets/init-templates/with-resources/.prettierrc b/src/assets/init-templates/with-resources/.prettierrc new file mode 100644 index 0000000..d0c396a --- /dev/null +++ b/src/assets/init-templates/with-resources/.prettierrc @@ -0,0 +1,6 @@ +{ + "tabWidth": 4, + "singleQuote": false, + "bracketSpacing": false, + "printWidth": 140 +} diff --git a/src/assets/init-templates/with-resources/.vscode/extensions.json b/src/assets/init-templates/with-resources/.vscode/extensions.json new file mode 100644 index 0000000..ed6d22a --- /dev/null +++ b/src/assets/init-templates/with-resources/.vscode/extensions.json @@ -0,0 +1,3 @@ +{ + "recommendations": ["redhat.vscode-yaml", "esbenp.prettier-vscode"] +} diff --git a/src/assets/init-templates/with-resources/.vscode/settings.json b/src/assets/init-templates/with-resources/.vscode/settings.json new file mode 100644 index 0000000..8576d9c --- /dev/null +++ b/src/assets/init-templates/with-resources/.vscode/settings.json @@ -0,0 +1,14 @@ +{ + "yaml.schemas": { + "node_modules/kubernate/schemas/config.json": ".kubernaterc.yaml", + "schemas/resources.json": "[!output]**/[!.kubernaterc]*.yaml" + }, + "files.exclude": { + "**/.git": true, + "**/.svn": true, + "**/.hg": true, + "**/CVS": true, + "**/.DS_Store": true, + ".kubernate": true + } +} diff --git a/src/assets/init-templates/with-resources/README.md.hbs b/src/assets/init-templates/with-resources/README.md.hbs new file mode 100644 index 0000000..a286346 --- /dev/null +++ b/src/assets/init-templates/with-resources/README.md.hbs @@ -0,0 +1,7 @@ +## {{projectName}} + +Run `kubernate hello-world`. + +This example uses Kubernate to create a namespace, a pod template and a deployment for every demo/v1/HelloWorld you create. The outputs are written to a single yaml file in `output/hello-world.yaml`. + +If you update the spec of the resource, make sure to run `kubernate generate` to update the generated code. diff --git a/src/assets/init-templates/with-resources/input/hello.yaml b/src/assets/init-templates/with-resources/input/hello.yaml new file mode 100644 index 0000000..a29b950 --- /dev/null +++ b/src/assets/init-templates/with-resources/input/hello.yaml @@ -0,0 +1,14 @@ +# you have intellisense here. give it a try :) +apiVersion: demo/v1 +kind: HelloWorld +metadata: + name: hello-test1 +spec: + who: world1 +--- +apiVersion: demo/v1 +kind: HelloWorld +metadata: + name: hello-test2 +spec: + who: world2 diff --git a/src/assets/init-templates/with-resources/output/.empty b/src/assets/init-templates/with-resources/output/.empty new file mode 100644 index 0000000..e69de29 diff --git a/src/assets/init-templates/with-resources/package.json.hbs b/src/assets/init-templates/with-resources/package.json.hbs new file mode 100644 index 0000000..d63f566 --- /dev/null +++ b/src/assets/init-templates/with-resources/package.json.hbs @@ -0,0 +1,21 @@ +{ + "name": "{{projectName}}", + "version": "1.0.0", + "main": "dist/index.js", + "types": "dist/index.d.ts", + "files": [ + "dist/**/*" + ], + "scripts": { + "format": "prettier --write .", + "build": "tsc" + }, + "dependencies": { + "kubernate": "~1.22" + }, + "devDependencies": { + "@types/node": "^16.7.9", + "prettier": "^2.3.2", + "typescript": "^4.3.5" + } +} diff --git a/src/assets/init-templates/with-resources/schemas/objects.json b/src/assets/init-templates/with-resources/schemas/objects.json new file mode 100644 index 0000000..3db004b --- /dev/null +++ b/src/assets/init-templates/with-resources/schemas/objects.json @@ -0,0 +1,75 @@ +{ + "description": "This is how you can declare a resource", + "type": "object", + "properties": { + "apiVersion": { + "type": "string", + "enum": [ + "demo/v1" + ] + }, + "kind": { + "type": "string", + "enum": [ + "HelloWorld" + ] + }, + "metadata": { + "type": "object", + "properties": { + "namespace": { + "type": "string" + }, + "name": { + "type": "string" + }, + "annotations": { + "type": "object", + "additionalProperties": {}, + "propertyOrder": [] + } + }, + "propertyOrder": [ + "namespace", + "name", + "annotations" + ], + "required": [ + "name" + ] + }, + "spec": { + "$ref": "#/definitions/HelloWorldServiceSpec" + } + }, + "propertyOrder": [ + "apiVersion", + "kind", + "metadata", + "spec" + ], + "required": [ + "apiVersion", + "kind", + "metadata", + "spec" + ], + "definitions": { + "HelloWorldServiceSpec": { + "description": "This is the spec of our resource.", + "type": "object", + "properties": { + "who": { + "type": "string" + } + }, + "propertyOrder": [ + "who" + ], + "required": [ + "who" + ] + } + }, + "$schema": "http://json-schema.org/draft-07/schema#" +} \ No newline at end of file diff --git a/src/assets/init-templates/with-resources/schemas/resources.json b/src/assets/init-templates/with-resources/schemas/resources.json new file mode 100644 index 0000000..02e82b0 --- /dev/null +++ b/src/assets/init-templates/with-resources/schemas/resources.json @@ -0,0 +1,8 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "oneOf": [ + { + "$ref": "objects.json" + } + ] +} \ No newline at end of file diff --git a/src/assets/init-templates/with-resources/src/generated/declarations.ts b/src/assets/init-templates/with-resources/src/generated/declarations.ts new file mode 100644 index 0000000..d4e7bc9 --- /dev/null +++ b/src/assets/init-templates/with-resources/src/generated/declarations.ts @@ -0,0 +1,11 @@ + +/** +* This file was generated by kubernate. +* Do not edit this file manually. Any changes will be overwritten. +*/ +import { HelloWorldService } from "../resources/hello-world-service"; + + +export type DeclaredServicesMap = { + "demo/v1/HelloWorld": HelloWorldService +}; diff --git a/src/assets/init-templates/with-resources/src/generated/index.ts b/src/assets/init-templates/with-resources/src/generated/index.ts new file mode 100644 index 0000000..4ea15e6 --- /dev/null +++ b/src/assets/init-templates/with-resources/src/generated/index.ts @@ -0,0 +1,11 @@ + +/** +* This file was generated by kubernate. +* Do not edit this file manually. Any changes will be overwritten. +*/ +import {makeResourcesBrowser} from "kubernate/internal/api"; +import {join as pathJoin} from "path"; +import {DeclaredServicesMap} from "./declarations"; + +const resources = makeResourcesBrowser(pathJoin(__dirname, "../..")); +export default resources; diff --git a/src/assets/init-templates/with-resources/src/hello-world.ts b/src/assets/init-templates/with-resources/src/hello-world.ts new file mode 100644 index 0000000..7757b4b --- /dev/null +++ b/src/assets/init-templates/with-resources/src/hello-world.ts @@ -0,0 +1,79 @@ +import kube, {output} from "kubernate"; + +/** + * Kubernate comes with some pre-built resource helpers for most common resources. + * check imports from "kubernate/resources/*" + */ +import namespace from "kubernate/resources/namespace"; + +import * as path from "path"; +import resources from "./generated"; + +const outputPath = (fileName: string) => path.join(__dirname, "../output", fileName); + +/** + * This is the main entry point and will be called by Kubernate. + */ +export default async () => { + // get all resources of demo/v1/HelloWorld + const helloWorldServices = resources("demo/v1/HelloWorld", "*/*"); + + for (let helloWorldService of helloWorldServices) { + // carete a new namespace + const ns = namespace(`hello-world-${helloWorldService.metadata.name}`); + + // create and reuse this selector + const selector = { + app: `hello-world-${helloWorldService.metadata.name}`, + }; + + // create a pod template for out deployment + const pod = kube.core.v1.PodTemplate({ + template: { + metadata: { + namespace: ns, + name: "hello-world", + labels: selector, + }, + spec: { + containers: [ + { + name: "hello-world-http", + image: "kornkitti/express-hello-world:latest", + imagePullPolicy: "Always", + ports: [ + { + containerPort: 8080, + name: "http", + }, + ], + env: [ + { + name: "WHO", + value: helloWorldService.spec.who, + }, + ], + }, + ], + }, + }, + }); + + // create a deployment using our pod template + kube.apps.v1.Deployment({ + metadata: { + namespace: ns, + name: "hello-world", + }, + spec: { + replicas: 1, + selector: { + matchLabels: selector, + }, + template: pod.template!, + }, + }); + } + // write the output to disk + await output.bundleToDisk(outputPath("hello-world.yaml")); +}; diff --git a/src/assets/init-templates/with-resources/src/resources/hello-world-service.ts b/src/assets/init-templates/with-resources/src/resources/hello-world-service.ts new file mode 100644 index 0000000..d6203f5 --- /dev/null +++ b/src/assets/init-templates/with-resources/src/resources/hello-world-service.ts @@ -0,0 +1,13 @@ +import {Resource} from "kubernate"; + +/** + * This is the spec of our resource. + */ +interface HelloWorldServiceSpec { + who: string; +} + +/** + * This is how you can declare a resource + */ +export interface HelloWorldService extends Resource<"demo/v1", "HelloWorld", HelloWorldServiceSpec> {} diff --git a/src/assets/init-templates/with-resources/src/resources/index.ts b/src/assets/init-templates/with-resources/src/resources/index.ts new file mode 100644 index 0000000..5ab06e5 --- /dev/null +++ b/src/assets/init-templates/with-resources/src/resources/index.ts @@ -0,0 +1,6 @@ +import {HelloWorldService} from "./hello-world-service"; + +/** + * This is a union type (A | B) of all the resources that you want to use. + */ +export type Resources = HelloWorldService; diff --git a/src/assets/init-templates/with-resources/tsconfig.json b/src/assets/init-templates/with-resources/tsconfig.json new file mode 100644 index 0000000..bfe0c7f --- /dev/null +++ b/src/assets/init-templates/with-resources/tsconfig.json @@ -0,0 +1,18 @@ +{ + "compilerOptions": { + "incremental": true, + "target": "ES2019", + "module": "commonjs", + "declaration": true, + "declarationMap": true, + "sourceMap": true, + "outDir": "./dist", + "strict": true, + "strictFunctionTypes": false, + "moduleResolution": "node", + "esModuleInterop": true, + "skipLibCheck": true, + "forceConsistentCasingInFileNames": true + }, + "include": ["src"] +} diff --git a/src/cli/bin.ts b/src/cli/bin.ts index 1a1886a..b7b0fc4 100644 --- a/src/cli/bin.ts +++ b/src/cli/bin.ts @@ -4,8 +4,10 @@ import yargs from "yargs"; import config from "./config"; import {generateCommand} from "./commands/generate"; import {runCommand} from "./commands/run"; +import {initCommand} from "./commands/init"; -const commands = config.root == "not_found" ? [] : [generateCommand, ...Object.keys(config.scripts ?? {}).map((name) => runCommand(name))]; +const commands = + config.root == "not_found" ? [initCommand] : [generateCommand, ...Object.keys(config.scripts ?? {}).map((name) => runCommand(name))]; const args = commands.reduce((arg, command) => command(arg), yargs); diff --git a/src/cli/commands/init.ts b/src/cli/commands/init.ts new file mode 100644 index 0000000..5017278 --- /dev/null +++ b/src/cli/commands/init.ts @@ -0,0 +1,115 @@ +import type Yargs from "yargs"; +import {makeLogger} from "../../log"; +import config, {Config} from "../config"; +import * as path from "path"; +import * as fs from "fs"; +import glob from "glob"; +import handlebars from "handlebars"; +import {exec, cd} from "shelljs"; + +const log = makeLogger("init", { + displayFunctionName: false, +}); + +export const initCommand = (yargs: typeof Yargs) => { + return yargs.command( + "init ", + "start a new project with kubernate", + (args) => + args + .positional("name", {required: true, type: "string", description: "the name of the project"}) + .option("path", { + type: "string", + description: "the path to the project (defaults to the $CWd/name)", + alias: "p", + required: false, + }) + .option("template", { + alias: "t", + description: "the name of the source template", + type: "string", + choices: ["basic", "with-resources"], + default: "basic", + required: true, + }) + .option("package-manager", { + alias: "m", + default: "npm", + description: "the package manager to use", + choices: ["npm", "yarn"], + }), + async (args) => { + const projectName = args.name!; + const projectPath = path.join(process.cwd(), args.path ?? projectName); + const porjectTemplate = args.template ?? "basic"; + const projectPackageManager = args["package-manager"] ?? "npm"; + + log.info(`creating kubernate project ${projectName} at ${projectPath}`); + + if (fs.existsSync(projectPath)) { + if (!fs.statSync(projectPath).isDirectory()) { + log.error(`${projectPath} exists and is not a directory`); + process.exit(1); + } + + const contents = fs.readdirSync(projectPath); + if (contents.length > 0) { + log.error(`directory ${projectPath} already exists and is not empty`); + process.exit(1); + } + } else { + log.debug(`creating directory ${projectPath}`); + fs.mkdirSync(projectPath, {recursive: true}); + } + + const templateDirectory = path.join(__dirname, "../../", "assets", "init-templates", porjectTemplate); + + if (!fs.existsSync(templateDirectory)) { + log.error(`template ${porjectTemplate} does not exist`); + process.exit(1); + } + + const templateFiles = glob.sync("**/*", {cwd: templateDirectory, dot: true, nodir: true}); + + for (let file of templateFiles) { + let fileName = file.replace(".hbs", ""); + if (fileName.includes("{{")) { + fileName = handlebars.compile(file.replace(".hbs", ""))({ + projectName, + projectPath, + porjectTemplate, + projectPackageManager, + }); + } + + const content = file.endsWith(".hbs") + ? handlebars.compile(fs.readFileSync(path.join(templateDirectory, file), "utf8"))({ + projectName, + projectPath, + porjectTemplate, + projectPackageManager, + }) + : fs.readFileSync(path.join(templateDirectory, file), "utf8"); + + const directory = path.dirname(path.join(projectPath, fileName)); + + if (!fs.existsSync(directory)) { + log.debug(`creating directory ${directory}`); + fs.mkdirSync(directory, {recursive: true}); + } + + if (!file.endsWith(".empty")) { + log.debug(`writing file ${file} as ${fileName}`); + fs.writeFileSync(path.join(projectPath, fileName), content); + } + } + + cd(projectPath); + log.info(`$ ${projectPackageManager} install`); + exec(`${projectPackageManager} install`); + + log.info(`project ${projectName} created at ${projectPath}`); + log.info(`now run 'cd ${args.path ?? projectName}'`); + } + ); +}; diff --git a/src/scripts/build.ts b/src/scripts/build.ts index df2853e..1bff2ec 100644 --- a/src/scripts/build.ts +++ b/src/scripts/build.ts @@ -22,6 +22,9 @@ const preparePackage = (input: any) => { writeFileSync(pathJoin(__dirname, "../../dist/package.json"), JSON.stringify(preparePackage(packageJson), null, 4)); cp("README.md", "dist/README.md"); +rimraf("dist/assets", () => { + cp("-R", "src/assets", "dist/assets"); +}); rimraf("dist/scripts", () => {}); rimraf("dist/generator", () => {}); rimraf("dist/**.d.ts.map", () => {}); diff --git a/tsconfig.json b/tsconfig.json index 4df6d6b..9ec3ea3 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -68,5 +68,7 @@ /* Advanced Options */ "skipLibCheck": true /* Skip type checking of declaration files. */, "forceConsistentCasingInFileNames": true /* Disallow inconsistently-cased references to the same file. */ - } + }, + "exclude": ["src/assets"], + "include": ["src"] } diff --git a/yarn.lock b/yarn.lock index 1fd7421..e0630c7 100644 --- a/yarn.lock +++ b/yarn.lock @@ -224,6 +224,13 @@ "@types/minimatch" "*" "@types/node" "*" +"@types/handlebars@^4.1.0": + version "4.1.0" + resolved "https://registry.yarnpkg.com/@types/handlebars/-/handlebars-4.1.0.tgz#3fcce9bf88f85fe73dc932240ab3fb682c624850" + integrity sha512-gq9YweFKNNB1uFK71eRqsd4niVkXrxHugqWFQkeLRJvGjnxsLr16bYtcsG4tOFwmYi0Bax+wCkbf1reUfdl4kA== + dependencies: + handlebars "*" + "@types/http-cache-semantics@*": version "4.0.1" resolved "https://registry.yarnpkg.com/@types/http-cache-semantics/-/http-cache-semantics-4.0.1.tgz#0ea7b61496902b95890dc4c3a116b60cb8dae812" @@ -671,6 +678,18 @@ got@^11.8.2: p-cancelable "^2.0.0" responselike "^2.0.0" +handlebars@*, handlebars@^4.7.7: + version "4.7.7" + resolved "https://registry.yarnpkg.com/handlebars/-/handlebars-4.7.7.tgz#9ce33416aad02dbd6c8fafa8240d5d98004945a1" + integrity sha512-aAcXm5OAfE/8IXkcZvCepKU3VzW1/39Fb5ZuqMtgI/hT8X2YgoMvBY5dLhq/cpOvw7Lk1nK/UF71aLG/ZnVYRA== + dependencies: + minimist "^1.2.5" + neo-async "^2.6.0" + source-map "^0.6.1" + wordwrap "^1.0.0" + optionalDependencies: + uglify-js "^3.1.4" + hard-rejection@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/hard-rejection/-/hard-rejection-2.1.0.tgz#1c6eda5c1685c63942766d79bb40ae773cecd883" @@ -957,7 +976,7 @@ minimist-options@4.1.0: is-plain-obj "^1.1.0" kind-of "^6.0.3" -minimist@^1.2.0: +minimist@^1.2.0, minimist@^1.2.5: version "1.2.5" resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.5.tgz#67d66014b66a6a8aaa0c083c5fd58df4e4e97602" integrity sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw== @@ -967,6 +986,11 @@ mkdirp@^1.0.4: resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-1.0.4.tgz#3eb5ed62622756d79a5f0e2a221dfebad75c2f7e" integrity sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw== +neo-async@^2.6.0: + version "2.6.2" + resolved "https://registry.yarnpkg.com/neo-async/-/neo-async-2.6.2.tgz#b4aafb93e3aeb2d8174ca53cf163ab7d7308305f" + integrity sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw== + node-fetch@^2.6.1: version "2.6.1" resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.1.tgz#045bd323631f76ed2e2b55573394416b639a0052" @@ -1269,7 +1293,7 @@ source-map-support@^0.5.17, source-map-support@^0.5.19: buffer-from "^1.0.0" source-map "^0.6.0" -source-map@^0.6.0: +source-map@^0.6.0, source-map@^0.6.1: version "0.6.1" resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g== @@ -1443,6 +1467,11 @@ typescript@~4.2.3: resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.2.4.tgz#8610b59747de028fda898a8aef0e103f156d0961" integrity sha512-V+evlYHZnQkaz8TRBuxTA92yZBPotr5H+WhQ7bD3hZUndx5tGOa1fuCgeSjxAzM1RiN5IzvadIXTVefuuwZCRg== +uglify-js@^3.1.4: + version "3.14.1" + resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-3.14.1.tgz#e2cb9fe34db9cb4cf7e35d1d26dfea28e09a7d06" + integrity sha512-JhS3hmcVaXlp/xSo3PKY5R0JqKs5M3IV+exdLHW99qKvKivPO4Z8qbej6mte17SOPqAOVMjt/XGgWacnFSzM3g== + universal-user-agent@^6.0.0: version "6.0.0" resolved "https://registry.yarnpkg.com/universal-user-agent/-/universal-user-agent-6.0.0.tgz#3381f8503b251c0d9cd21bc1de939ec9df5480ee" @@ -1456,6 +1485,11 @@ validate-npm-package-license@^3.0.1: spdx-correct "^3.0.0" spdx-expression-parse "^3.0.0" +wordwrap@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-1.0.0.tgz#27584810891456a4171c8d0226441ade90cbcaeb" + integrity sha1-J1hIEIkUVqQXHI0CJkQa3pDLyus= + wrap-ansi@^7.0.0: version "7.0.0" resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43"