From d99453862d9b6edb5463cd4bf92a04fc250b7f74 Mon Sep 17 00:00:00 2001 From: Patrick Sullivan Date: Fri, 29 Sep 2023 12:06:47 -0400 Subject: [PATCH] refactor(tools-forecast-codegen): Redesigned Forecast plugin architecture --- .../typescript/server/attachment/project.json | 4 +- .../attachment/{schema.storm => schema.4cast} | 34 +- .../src/context/async-context-scope.ts | 24 + .../typescript/server/utilities/src/exists.ts | 2 +- .../server/utilities/src/file-path-fns.ts | 18 +- .../typescript/shared/logging/package.json | 4 +- .../typescript/shared/logging/project.json | 3 +- .../logging/src/console/console-logger.ts | 35 - nx.json | 3 +- package.json | 2 +- pnpm-lock.yaml | 39 +- project.json | 2 +- tools/forecast/codegen/package.json | 3 + tools/forecast/codegen/project.json | 32 +- tools/forecast/codegen/src/cli/cli-util.ts | 58 +- tools/forecast/codegen/src/cli/config.ts | 2 +- tools/forecast/codegen/src/cli/index.ts | 5 +- .../forecast/codegen/src/cli/plugin-runner.ts | 331 ---- .../codegen/src/cli/plugins-processor.ts | 468 +++++ .../codegen/src/{sdk => }/code-gen.ts | 23 +- .../codegen/src/{sdk => }/constants.ts | 0 .../dmmf-helpers/aggregate-helpers.ts | 0 .../{sdk => }/dmmf-helpers/include-helpers.ts | 0 .../src/{sdk => }/dmmf-helpers/index.ts | 0 .../dmmf-helpers/missing-types-helper.ts | 0 .../{sdk => }/dmmf-helpers/model-helpers.ts | 0 .../dmmf-helpers/modelArgs-helpers.ts | 0 .../{sdk => }/dmmf-helpers/select-helpers.ts | 0 .../src/{sdk => }/dmmf-helpers/types.ts | 0 tools/forecast/codegen/src/index.ts | 3 + .../src/plugins/generators/generator.ts | 75 + .../plugins/generators/template-generator.ts | 50 + .../generators/typescript-generator.ts | 82 + .../codegen/src/plugins/plugin-handler.ts | 28 + .../src/plugins/template-plugin-handler.ts | 141 ++ .../forecast/codegen/src/{sdk => }/prisma.ts | 2 +- tools/forecast/codegen/src/sdk/index.ts | 5 - tools/forecast/codegen/src/sdk/types.ts | 35 - tools/forecast/codegen/src/types.ts | 185 +- .../utils.ts => utils/access-policy-utils.ts} | 0 tools/forecast/codegen/src/utils/index.ts | 3 + .../src/{plugins => utils}/plugin-utils.ts | 0 .../typescript-expression-transformer.ts | 8 +- .../codegen/src/{sdk => utils}/utils.ts | 52 +- tools/forecast/language/project.json | 9 +- .../language/{src => }/res/prism-forecast.js | 0 .../language/{src => }/res/starter.4cast | 0 .../language/{src => }/res/stdlib.forecast | 0 tools/forecast/plugins/access/.eslintrc.json | 28 + tools/forecast/plugins/access/README.md | 144 ++ tools/forecast/plugins/access/package.json | 4 + tools/forecast/plugins/access/project.json | 76 + .../access/src}/expression-writer.ts | 9 +- .../access/src}/index.ts | 2 +- .../access/src}/policy-guard-generator.ts | 14 +- tools/forecast/plugins/access/src/utils.ts | 0 tools/forecast/plugins/access/tsconfig.json | 13 + tools/forecast/plugins/crud/.eslintrc.json | 28 + tools/forecast/plugins/crud/README.md | 145 ++ tools/forecast/plugins/crud/package.json | 8 + tools/forecast/plugins/crud/project.json | 77 + tools/forecast/plugins/crud/src/index.ts | 1694 +++++++++++++++++ tools/forecast/plugins/crud/src/types.ts | 20 + tools/forecast/plugins/crud/tsconfig.json | 20 + tools/forecast/plugins/meta/.eslintrc.json | 28 + tools/forecast/plugins/meta/README.md | 144 ++ tools/forecast/plugins/meta/package.json | 4 + tools/forecast/plugins/meta/project.json | 76 + .../model-meta => plugins/meta/src}/index.ts | 0 tools/forecast/plugins/meta/tsconfig.json | 13 + tools/forecast/plugins/prisma/.eslintrc.json | 28 + tools/forecast/plugins/prisma/README.md | 145 ++ tools/forecast/plugins/prisma/package.json | 9 + tools/forecast/plugins/prisma/project.json | 77 + .../prisma/src}/indent-string.ts | 0 .../prisma => plugins/prisma/src}/index.ts | 0 .../prisma/src}/prisma-builder.ts | 2 +- .../prisma/src/prisma-generator.ts} | 0 .../prisma/src}/storm-code-generator.ts | 0 tools/forecast/plugins/prisma/tsconfig.json | 20 + tsconfig.base.json | 6 + 81 files changed, 3986 insertions(+), 618 deletions(-) rename libs/contact/typescript/server/attachment/{schema.storm => schema.4cast} (86%) create mode 100644 libs/core/typescript/server/application/src/context/async-context-scope.ts delete mode 100644 tools/forecast/codegen/src/cli/plugin-runner.ts create mode 100644 tools/forecast/codegen/src/cli/plugins-processor.ts rename tools/forecast/codegen/src/{sdk => }/code-gen.ts (87%) rename tools/forecast/codegen/src/{sdk => }/constants.ts (100%) rename tools/forecast/codegen/src/{sdk => }/dmmf-helpers/aggregate-helpers.ts (100%) rename tools/forecast/codegen/src/{sdk => }/dmmf-helpers/include-helpers.ts (100%) rename tools/forecast/codegen/src/{sdk => }/dmmf-helpers/index.ts (100%) rename tools/forecast/codegen/src/{sdk => }/dmmf-helpers/missing-types-helper.ts (100%) rename tools/forecast/codegen/src/{sdk => }/dmmf-helpers/model-helpers.ts (100%) rename tools/forecast/codegen/src/{sdk => }/dmmf-helpers/modelArgs-helpers.ts (100%) rename tools/forecast/codegen/src/{sdk => }/dmmf-helpers/select-helpers.ts (100%) rename tools/forecast/codegen/src/{sdk => }/dmmf-helpers/types.ts (100%) create mode 100644 tools/forecast/codegen/src/plugins/generators/generator.ts create mode 100644 tools/forecast/codegen/src/plugins/generators/template-generator.ts create mode 100644 tools/forecast/codegen/src/plugins/generators/typescript-generator.ts create mode 100644 tools/forecast/codegen/src/plugins/plugin-handler.ts create mode 100644 tools/forecast/codegen/src/plugins/template-plugin-handler.ts rename tools/forecast/codegen/src/{sdk => }/prisma.ts (97%) delete mode 100644 tools/forecast/codegen/src/sdk/index.ts delete mode 100644 tools/forecast/codegen/src/sdk/types.ts rename tools/forecast/codegen/src/{plugins/access-policy/utils.ts => utils/access-policy-utils.ts} (100%) rename tools/forecast/codegen/src/{plugins => utils}/plugin-utils.ts (100%) rename tools/forecast/codegen/src/{sdk => utils}/utils.ts (86%) rename tools/forecast/language/{src => }/res/prism-forecast.js (100%) rename tools/forecast/language/{src => }/res/starter.4cast (100%) rename tools/forecast/language/{src => }/res/stdlib.forecast (100%) create mode 100644 tools/forecast/plugins/access/.eslintrc.json create mode 100644 tools/forecast/plugins/access/README.md create mode 100644 tools/forecast/plugins/access/package.json create mode 100644 tools/forecast/plugins/access/project.json rename tools/forecast/{codegen/src/plugins/access-policy => plugins/access/src}/expression-writer.ts (98%) rename tools/forecast/{codegen/src/plugins/access-policy => plugins/access/src}/index.ts (83%) rename tools/forecast/{codegen/src/plugins/access-policy => plugins/access/src}/policy-guard-generator.ts (98%) create mode 100644 tools/forecast/plugins/access/src/utils.ts create mode 100644 tools/forecast/plugins/access/tsconfig.json create mode 100644 tools/forecast/plugins/crud/.eslintrc.json create mode 100644 tools/forecast/plugins/crud/README.md create mode 100644 tools/forecast/plugins/crud/package.json create mode 100644 tools/forecast/plugins/crud/project.json create mode 100644 tools/forecast/plugins/crud/src/index.ts create mode 100644 tools/forecast/plugins/crud/src/types.ts create mode 100644 tools/forecast/plugins/crud/tsconfig.json create mode 100644 tools/forecast/plugins/meta/.eslintrc.json create mode 100644 tools/forecast/plugins/meta/README.md create mode 100644 tools/forecast/plugins/meta/package.json create mode 100644 tools/forecast/plugins/meta/project.json rename tools/forecast/{codegen/src/plugins/model-meta => plugins/meta/src}/index.ts (100%) create mode 100644 tools/forecast/plugins/meta/tsconfig.json create mode 100644 tools/forecast/plugins/prisma/.eslintrc.json create mode 100644 tools/forecast/plugins/prisma/README.md create mode 100644 tools/forecast/plugins/prisma/package.json create mode 100644 tools/forecast/plugins/prisma/project.json rename tools/forecast/{codegen/src/plugins/prisma => plugins/prisma/src}/indent-string.ts (100%) rename tools/forecast/{codegen/src/plugins/prisma => plugins/prisma/src}/index.ts (100%) rename tools/forecast/{codegen/src/plugins/prisma => plugins/prisma/src}/prisma-builder.ts (99%) rename tools/forecast/{codegen/src/plugins/prisma/schema-generator.ts => plugins/prisma/src/prisma-generator.ts} (100%) rename tools/forecast/{codegen/src/plugins/prisma => plugins/prisma/src}/storm-code-generator.ts (100%) create mode 100644 tools/forecast/plugins/prisma/tsconfig.json diff --git a/libs/contact/typescript/server/attachment/project.json b/libs/contact/typescript/server/attachment/project.json index 28fb5fc04..8e0cf6b15 100644 --- a/libs/contact/typescript/server/attachment/project.json +++ b/libs/contact/typescript/server/attachment/project.json @@ -45,10 +45,10 @@ } }, "generate": { - "executor": "@stormstack/tools-executors-typescript:storm-generate", + "executor": "@stormstack/tools-nx-forecast:generate", "options": { "outputPath": "libs/contact/typescript/server/attachment/src/__generated__", - "schema": "schema.storm", + "schema": "schema.4cast", "packageManager": "pnpm", "dependencyCheck": true } diff --git a/libs/contact/typescript/server/attachment/schema.storm b/libs/contact/typescript/server/attachment/schema.4cast similarity index 86% rename from libs/contact/typescript/server/attachment/schema.storm rename to libs/contact/typescript/server/attachment/schema.4cast index 8cfab61f6..48fd07a26 100644 --- a/libs/contact/typescript/server/attachment/schema.storm +++ b/libs/contact/typescript/server/attachment/schema.4cast @@ -9,37 +9,9 @@ generator js { output = "../../../../../../node_modules/@prisma/client/contact-attachment" } -plugin prisma { - provider = "@core/prisma" -} - -plugin meta { - provider = "@core/model-meta" - output = "src/__generated__/model-meta" - compile = false -} - -plugin access { - provider = "@core/access-policy" - output = "src/__generated__/access-policy" - compile = false -} - -plugin zod { - provider = "@plugins/zod" - output = "src/__generated__/zod" - compile = false -} - -plugin drizzle { - provider = "@plugins/drizzle" - output = "src/__generated__/drizzle" - compile = false -} - -plugin graphql { - provider = "@plugins/graphql" - output = "src/__generated__/graphql" +plugin crud { + provider = "@stormstack/tools-forecast-plugins-crud" + output = "src/__generated__/crud" compile = false } diff --git a/libs/core/typescript/server/application/src/context/async-context-scope.ts b/libs/core/typescript/server/application/src/context/async-context-scope.ts new file mode 100644 index 000000000..9e5e718fd --- /dev/null +++ b/libs/core/typescript/server/application/src/context/async-context-scope.ts @@ -0,0 +1,24 @@ +import { MissingContextError } from "@stormstack/core-shared-utilities"; +import { AsyncLocalStorage } from "node:async_hooks"; + +const asyncLocalStorage = new AsyncLocalStorage(); + +export class AsyncScope { + static get() { + const scope = asyncLocalStorage.getStore(); + if (!scope) { + throw new MissingContextError("GlobalContextStore"); + } + + return scope; + } + + constructor(callback: () => void) { + const parentScope = asyncLocalStorage.getStore(); + if (parentScope) { + Object.setPrototypeOf(this, parentScope); + } + + asyncLocalStorage.run(this, callback); + } +} diff --git a/libs/core/typescript/server/utilities/src/exists.ts b/libs/core/typescript/server/utilities/src/exists.ts index 97226c3c6..9c61fa90a 100644 --- a/libs/core/typescript/server/utilities/src/exists.ts +++ b/libs/core/typescript/server/utilities/src/exists.ts @@ -1,4 +1,4 @@ -import { existsSync } from "fs"; +import { existsSync } from "node:fs"; export const exists = (filePath: string): boolean => { return existsSync(filePath); diff --git a/libs/core/typescript/server/utilities/src/file-path-fns.ts b/libs/core/typescript/server/utilities/src/file-path-fns.ts index 01527497d..701293cb1 100644 --- a/libs/core/typescript/server/utilities/src/file-path-fns.ts +++ b/libs/core/typescript/server/utilities/src/file-path-fns.ts @@ -1,6 +1,6 @@ -import Path from "path"; +import Path, { dirname, isAbsolute, join } from "path"; -export const findFileName = (filePath: string): string => { +export function findFileName(filePath: string): string { return ( filePath ?.split( @@ -12,8 +12,16 @@ export const findFileName = (filePath: string): string => { ) ?.pop() ?? "" ); -}; +} -export const findFilePath = (filePath: string): string => { +export function findFilePath(filePath: string): string { return filePath.replace(findFileName(filePath), ""); -}; +} + +export function resolvePath(filePath: string, basePath?: string) { + if (isAbsolute(filePath)) { + return filePath; + } else { + return join(dirname(basePath), filePath); + } +} diff --git a/libs/core/typescript/shared/logging/package.json b/libs/core/typescript/shared/logging/package.json index ca45659bd..692b56c99 100644 --- a/libs/core/typescript/shared/logging/package.json +++ b/libs/core/typescript/shared/logging/package.json @@ -1,9 +1,9 @@ { "name": "@stormstack/core-shared-logging", "version": "0.0.1", + "type": "module", "dependencies": { - "chalk": "4.1.0", - "ora": "^7.0.1" + "chalk": "4.1.0" }, "devDependencies": { "@types/jest": "29.5.3" diff --git a/libs/core/typescript/shared/logging/project.json b/libs/core/typescript/shared/logging/project.json index 8d0c5e25e..ed324d355 100644 --- a/libs/core/typescript/shared/logging/project.json +++ b/libs/core/typescript/shared/logging/project.json @@ -16,7 +16,8 @@ "bundle": true, "metafile": true, "minify": true, - "format": ["esm", "cjs"] + "format": ["esm", "cjs"], + "thirdParty": true } }, "lint": { diff --git a/libs/core/typescript/shared/logging/src/console/console-logger.ts b/libs/core/typescript/shared/logging/src/console/console-logger.ts index 633ed6bfa..4fb3caab8 100644 --- a/libs/core/typescript/shared/logging/src/console/console-logger.ts +++ b/libs/core/typescript/shared/logging/src/console/console-logger.ts @@ -1,7 +1,6 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ /* eslint-disable @typescript-eslint/no-empty-function */ import { Provider } from "@stormstack/core-shared-injection/decorators"; -import ora from "ora"; import { formatLog } from "../format"; import { Logger } from "../logger"; import { @@ -13,7 +12,6 @@ import { printWarning, startGroup } from "./print"; -import { ConsoleSpinner } from "./types"; @Provider(Logger) export class ConsoleLogger extends Logger { @@ -109,39 +107,6 @@ export class ConsoleLogger extends Logger { endGroup(); } - /** - * The function returns a ConsoleSpinner object, which is created using the ora library and takes an - * optional text parameter. - * @param {string} [text] - The `text` parameter is an optional string that represents the text to be - * displayed alongside the spinner. - * @returns an instance of the `ConsoleSpinner` class. - */ - static spinner(text?: string): ConsoleSpinner { - return ora(text); - } - - /** - * The function `spinnerStart` returns a ConsoleSpinner object that starts a spinner animation with an - * optional text. - * @param {string} [text] - The `text` parameter is an optional string that represents the text to be - * displayed alongside the spinner. It is used to provide additional context or information to the user - * while the spinner is running. If no `text` is provided, the spinner will be displayed without any - * accompanying text. - * @returns a ConsoleSpinner object. - */ - static spinnerStart(text?: string): ConsoleSpinner { - return ConsoleLogger.spinner(text).start(); - } - - /** - * The function stops a console spinner and returns the stopped spinner. - * @param {ConsoleSpinner} spinner - The parameter "spinner" is of type "ConsoleSpinner". - * @returns the stopped spinner object. - */ - static spinnerStop(spinner: ConsoleSpinner): ConsoleSpinner { - return spinner.stop(); - } - public constructor(_name = "root") { super(_name); } diff --git a/nx.json b/nx.json index fd4af8430..f743c6d97 100644 --- a/nx.json +++ b/nx.json @@ -48,7 +48,8 @@ "tools/devops/*", "tools/executors/*", "tools/generators/*", - "tools/storm/*", + "tools/nx/*", + "tools/forecast/*", "design-system/*", "libs/core/*", "libs/common/*", diff --git a/package.json b/package.json index e0e0883e7..f5709dc8b 100644 --- a/package.json +++ b/package.json @@ -1,5 +1,5 @@ { - "name": "stormstack", + "name": "@stormstack/root", "namespace": "@stormstack", "version": "0.0.0", "private": true, diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 6b34c5f52..ce2cca939 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -787,6 +787,9 @@ importers: '@envelop/types': specifier: ^4.0.0 version: 4.0.1 + '@stormstack/tools-forecast-plugins-crud': + specifier: workspace:* + version: link:../../../../../tools/forecast/plugins/crud drizzle-orm: specifier: ^0.28.5 version: 0.28.6(@cloudflare/workers-types@4.20230922.0)(@opentelemetry/api@1.6.0) @@ -1170,6 +1173,12 @@ importers: cosmiconfig: specifier: ^8.3.6 version: 8.3.6(typescript@5.2.0-beta) + glob: + specifier: ^10.3.3 + version: 10.3.9 + handlebars: + specifier: ^4.7.8 + version: 4.7.8 langium: specifier: 1.2.1 version: 1.2.1 @@ -1288,6 +1297,8 @@ importers: version: 4.9.5 publishDirectory: dist/tools/forecast/language + tools/forecast/plugins/access: {} + tools/forecast/plugins/core/drizzle: dependencies: '@prisma/generator-helper': @@ -1359,6 +1370,15 @@ importers: specifier: ^19.0.0 version: 19.0.0 + tools/forecast/plugins/crud: + dependencies: + path: + specifier: ^0.12.7 + version: 0.12.7 + ts-morph: + specifier: ^19.0.0 + version: 19.0.0 + tools/forecast/plugins/drizzle: dependencies: '@prisma/generator-helper': @@ -1419,6 +1439,20 @@ importers: tools/forecast/plugins/internal/meta: {} + tools/forecast/plugins/meta: {} + + tools/forecast/plugins/prisma: + dependencies: + '@prisma/generator-helper': + specifier: ^5.2.0 + version: 5.3.1 + path: + specifier: ^0.12.7 + version: 0.12.7 + ts-morph: + specifier: ^19.0.0 + version: 19.0.0 + tools/forecast/plugins/zod: dependencies: '@prisma/generator-helper': @@ -4906,7 +4940,7 @@ packages: '@commitlint/types': 17.4.4 '@types/node': 20.4.7 chalk: 4.1.0 - cosmiconfig: 8.3.6(typescript@4.9.5) + cosmiconfig: 8.3.6(typescript@5.2.0-beta) cosmiconfig-typescript-loader: 4.4.0(@types/node@20.4.7)(cosmiconfig@8.3.6)(ts-node@10.9.1)(typescript@4.9.5) lodash.isplainobject: 4.0.6 lodash.merge: 4.6.2 @@ -20784,7 +20818,7 @@ packages: typescript: '>=4' dependencies: '@types/node': 20.4.7 - cosmiconfig: 8.3.6(typescript@4.9.5) + cosmiconfig: 8.3.6(typescript@5.2.0-beta) ts-node: 10.9.1(@swc/core@1.3.89)(@types/node@20.4.7)(typescript@4.9.5) typescript: 4.9.5 @@ -20842,6 +20876,7 @@ packages: parse-json: 5.2.0 path-type: 4.0.0(patch_hash=t2y4p5c63ifj2lrtth34hk3bda) typescript: 4.9.5 + dev: false /cosmiconfig@8.3.6(typescript@5.2.0-beta): resolution: {integrity: sha512-kcZ6+W5QzcJ3P1Mt+83OUv/oHFqZHIx8DuxG6eZ5RGMERoLqp4BuGjhHLYGK+Kf5XVkQvqBSmAy/nGWN3qDgEA==} diff --git a/project.json b/project.json index 6c82e5ffa..bcffbeca8 100644 --- a/project.json +++ b/project.json @@ -1,5 +1,5 @@ { - "name": "stormstack", + "name": "@stormstack/root", "$schema": "node_modules/nx/schemas/project-schema.json", "targets": { "local-registry": { diff --git a/tools/forecast/codegen/package.json b/tools/forecast/codegen/package.json index 72fa31c8c..0a44702f4 100644 --- a/tools/forecast/codegen/package.json +++ b/tools/forecast/codegen/package.json @@ -7,6 +7,9 @@ "@prisma/client": "^5.2.0", "@vscode/vsce": "^2.20.1", "chalk": "4.1.0", + "cosmiconfig": "^8.3.6", + "glob": "^10.3.3", + "handlebars": "^4.7.8", "langium": "1.2.1", "langium-cli": "1.2.1", "path": "^0.12.7", diff --git a/tools/forecast/codegen/project.json b/tools/forecast/codegen/project.json index 2fa787e21..9a1b1845a 100644 --- a/tools/forecast/codegen/project.json +++ b/tools/forecast/codegen/project.json @@ -9,12 +9,9 @@ "outputs": ["{options.outputPath}"], "options": { "outputPath": "dist/tools/forecast/codegen", - "main": "tools/forecast/codegen/src/cli/index.ts", + "main": "tools/forecast/codegen/src/index.ts", "additionalEntryPoints": [ - "tools/forecast/codegen/src/index.ts", - "tools/forecast/codegen/src/plugins/access-policy/index.ts", - "tools/forecast/codegen/src/plugins/model-meta/index.ts", - "tools/forecast/codegen/src/plugins/prisma/index.ts" + "tools/forecast/codegen/src/cli/index.ts" ], "project": "tools/forecast/codegen/package.json", "tsConfig": "tools/forecast/codegen/tsconfig.json", @@ -46,31 +43,6 @@ "input": "tools/forecast/codegen/build", "glob": "*.*", "output": "build" - }, - { - "input": "tools/forecast/codegen/src/res", - "glob": "*.*", - "output": "res" - }, - { - "input": "tools/forecast/codegen/assets", - "glob": "*.*", - "output": "assets" - }, - { - "input": "tools/forecast/codegen/syntaxes", - "glob": "*.*", - "output": "syntaxes" - }, - { - "input": "tools/forecast/codegen", - "glob": "language-configuration.json", - "output": "." - }, - { - "input": "tools/forecast/codegen/bin", - "glob": ".", - "output": "bin" } ] } diff --git a/tools/forecast/codegen/src/cli/cli-util.ts b/tools/forecast/codegen/src/cli/cli-util.ts index 98a52e35d..5ad865a9f 100644 --- a/tools/forecast/codegen/src/cli/cli-util.ts +++ b/tools/forecast/codegen/src/cli/cli-util.ts @@ -4,10 +4,7 @@ import { PackageManagers } from "@stormstack/core-server-utilities/package-fns"; import { ConsoleLogger } from "@stormstack/core-shared-logging/console"; -import { - ConfigurationError, - ProcessingError -} from "@stormstack/core-shared-utilities"; +import { ConfigurationError } from "@stormstack/core-shared-utilities"; import { isDataSource, isPlugin, @@ -21,7 +18,9 @@ import { createForecastServices, ForecastServices } from "@stormstack/tools-forecast-language/forecast-module"; +import { getLiteral } from "@stormstack/tools-forecast-language/utils"; import chalk from "chalk"; +import { cosmiconfig } from "cosmiconfig"; import fs from "fs"; import getLatestVersion from "get-latest-version"; import { @@ -35,15 +34,13 @@ import { NodeFileSystem } from "langium/node"; import path from "path"; import semver from "semver"; import { URI } from "vscode-uri"; -import { getLiteral, PluginError } from "../sdk"; -import { Context } from "../types"; import { mergeBaseModel, resolveImport, resolveTransitiveImports } from "../utils/ast-utils"; import { getVersion } from "../utils/version-utils"; -import { PluginRunner } from "./plugin-runner"; +import { PluginsProcessor } from "./plugins-processor"; /** * Initializes an existing project for Forecast @@ -98,14 +95,20 @@ export async function initProject( ensurePackage("@prisma/client", false, packageManager, "latest", projectPath); tag = tag ?? getVersion(); - installPackage("forecast", true, packageManager, tag, projectPath); installPackage( + "@stormstack/tools-forecast-language", + true, + packageManager, + tag, + projectPath + ); + /*installPackage( "@stormstack/runtime", false, packageManager, tag, projectPath - ); + );*/ if (sampleModelGenerated) { ConsoleLogger.info(`Sample model generated at: ${chalk.blue(forecastFile)} @@ -143,7 +146,11 @@ export async function loadDocument(fileName: string): Promise { const stdLibFile = URI.file( path.resolve( - path.join(__dirname, "../../../forecast/schema/res", STD_LIB_MODULE_NAME) + path.join( + __dirname, + "../../../../../forecast/language/res", + STD_LIB_MODULE_NAME + ) ) ); ConsoleLogger.info(`Loading standard library file from '${stdLibFile.toString()}' @@ -322,22 +329,17 @@ export async function runPlugins(options: { }) { const model = await loadDocument(options.schema); - const context: Context = { - schema: model, - schemaPath: path.resolve(options.schema), - outDir: options?.outDir ?? path.dirname(options.schema) - }; + const explorer = cosmiconfig("forecast"); + const config = await explorer.search(); - try { - await new PluginRunner().run(context); - } catch (err) { - if (err instanceof PluginError) { - ConsoleLogger.error(chalk.red(`${err.plugin}: ${err.message}`)); - throw new ProcessingError(err.message); - } else { - throw err; + await new PluginsProcessor().process({ + model, + schemaPath: path.resolve(options.schema), + config: { + outDir: options.outDir, + ...config?.config } - } + }); } export async function dumpInfo(projectPath: string) { @@ -352,6 +354,7 @@ export async function dumpInfo(projectPath: string) { ); return; } + const packages = [ "forecast", ...Object.keys(pkgJson.dependencies ?? {}).filter(p => @@ -382,14 +385,13 @@ export async function dumpInfo(projectPath: string) { "Multiple versions of Forecast packages detected. This may cause issues." ); } else if (versions.size > 0) { - const spinner = ConsoleLogger.spinner("Checking npm registry").start(); + ConsoleLogger.info("Checking npm registry"); const latest = await getLatestVersion("forecast"); - if (!latest) { - spinner.fail("unable to check for latest version"); + ConsoleLogger.error("unable to check for latest version"); } else { - spinner.succeed(); + ConsoleLogger.success("Successfully checked npm registry"); const version = [...versions][0]; if (semver.gt(latest, version)) { ConsoleLogger.info( diff --git a/tools/forecast/codegen/src/cli/config.ts b/tools/forecast/codegen/src/cli/config.ts index 58907669d..515cc3d56 100644 --- a/tools/forecast/codegen/src/cli/config.ts +++ b/tools/forecast/codegen/src/cli/config.ts @@ -2,7 +2,7 @@ import { ConfigurationError } from "@stormstack/core-shared-utilities"; import fs from "fs"; import z, { ZodError } from "zod"; import { fromZodError } from "zod-validation-error"; -import { GUARD_FIELD_NAME, TRANSACTION_FIELD_NAME } from "../sdk"; +import { GUARD_FIELD_NAME, TRANSACTION_FIELD_NAME } from "../constants"; const schema = z .object({ diff --git a/tools/forecast/codegen/src/cli/index.ts b/tools/forecast/codegen/src/cli/index.ts index 99e30c0e9..a771dc278 100644 --- a/tools/forecast/codegen/src/cli/index.ts +++ b/tools/forecast/codegen/src/cli/index.ts @@ -13,7 +13,7 @@ import { loadConfig } from "./config"; // required minimal version of Prisma export const requiredPrismaVersion = "4.0.0"; -const DEFAULT_CONFIG_FILE = "forecast.config.json"; +const DEFAULT_CONFIG_FILE = "forecast.json"; export const initAction = async ( projectPath: string, @@ -45,6 +45,7 @@ export const generateAction = async (options: { checkRequiredPackage("prisma", requiredPrismaVersion); checkRequiredPackage("@prisma/client", requiredPrismaVersion); } + return runPlugins(options); }; @@ -87,7 +88,7 @@ export function createProgram() { const schemaOption = new Option( "--schema ", `schema file (with extension ${schemaExtensions})` - ).default("./schema.storm"); + ).default("./schema.4cast"); const configOption = new Option("-c, --config [file]", "config file"); diff --git a/tools/forecast/codegen/src/cli/plugin-runner.ts b/tools/forecast/codegen/src/cli/plugin-runner.ts deleted file mode 100644 index 22f235713..000000000 --- a/tools/forecast/codegen/src/cli/plugin-runner.ts +++ /dev/null @@ -1,331 +0,0 @@ -/* eslint-disable @typescript-eslint/no-explicit-any */ -/* eslint-disable @typescript-eslint/no-var-requires */ -import type { DMMF } from "@prisma/generator-helper"; -import { ConsoleLogger } from "@stormstack/core-shared-logging/console"; -import { isPlugin, Plugin } from "@stormstack/tools-forecast-language/ast"; -import { hasValidationAttributes } from "@stormstack/tools-forecast-language/utils"; -import chalk from "chalk"; -import fs from "fs"; -import path from "path"; -import { ensureDefaultOutputFolder } from "../plugins/plugin-utils"; -import { - getDataModels, - getDMMF, - getLiteral, - getLiteralArray, - PluginError, - PluginFunction, - PluginOptions, - resolvePath -} from "../sdk"; -import type { Context } from "../types"; -import { getVersion } from "../utils/version-utils"; -import { config } from "./config"; - -type PluginInfo = { - name: string; - provider: string; - options: PluginOptions; - run: PluginFunction; - dependencies: string[]; - module: any; -}; - -/** - * Storm plugin runner - */ -export class PluginRunner { - /** - * Runs a series of nested generators - */ - async run(context: Context): Promise { - const version = getVersion(); - ConsoleLogger.info( - chalk.bold(`⌛️ Storm CLI v${version}, running plugins`) - ); - - ConsoleLogger.debug("Ensuring default output folder exists and is empty"); - - ensureDefaultOutputFolder(); - - const plugins: PluginInfo[] = []; - const pluginDecls = context.schema.declarations.filter((d): d is Plugin => - isPlugin(d) - ); - - ConsoleLogger.debug( - `Loading schema from ${chalk.bold(context.schemaPath)}` - ); - - let prismaOutput = resolvePath("./prisma/schema.prisma", { - schemaPath: context.schemaPath, - name: "" - }); - - ConsoleLogger.debug(`Validating ${pluginDecls.length} Plugins from model`); - - for (const pluginDecl of pluginDecls) { - const pluginProvider = this.getPluginProvider(pluginDecl); - if (!pluginProvider) { - ConsoleLogger.error( - `Plugin ${pluginDecl.name} has invalid provider option` - ); - throw new PluginError( - "", - `Plugin ${pluginDecl.name} has invalid provider option` - ); - } - const pluginModulePath = this.getPluginModulePath(pluginProvider); - // eslint-disable-next-line @typescript-eslint/no-explicit-any - let pluginModule: any; - try { - pluginModule = require(pluginModulePath); - } catch (err) { - ConsoleLogger.error( - `Unable to load plugin module ${pluginProvider}: ${pluginModulePath}, ${err}` - ); - throw new PluginError( - "", - `Unable to load plugin module ${pluginProvider}` - ); - } - - if (!pluginModule.default || typeof pluginModule.default !== "function") { - ConsoleLogger.error( - `Plugin provider ${pluginProvider} is missing a default function export` - ); - throw new PluginError( - "", - `Plugin provider ${pluginProvider} is missing a default function export` - ); - } - - const dependencies = this.getPluginDependencies(pluginModule); - const pluginName = this.getPluginName(pluginModule, pluginProvider); - const options: PluginOptions = { - schemaPath: context.schemaPath, - name: pluginName - }; - - ConsoleLogger.debug( - `Preparing to load plugin: -${JSON.stringify(options)}` - ); - - pluginDecl.fields.forEach(f => { - const value = getLiteral(f.value) ?? getLiteralArray(f.value); - if (value === undefined) { - throw new PluginError( - pluginName, - `Invalid option value for ${f.name}` - ); - } - options[f.name] = value; - }); - - const plugin = { - name: pluginName, - provider: pluginProvider, - dependencies, - options, - run: pluginModule.default as PluginFunction, - module: pluginModule - }; - - ConsoleLogger.debug( - `Loading plugin: -${JSON.stringify(plugin)}` - ); - - plugins.push(plugin); - - if ( - pluginProvider === "@core/prisma" && - typeof options.output === "string" - ) { - // record custom prisma output path - prismaOutput = resolvePath(options.output, options); - } - } - - // make sure prerequisites are included - const corePlugins: Array<{ - provider: string; - options?: Record; - }> = [ - { provider: "@core/prisma" }, - { provider: "@core/model-meta" }, - { provider: "@core/access-policy" } - ]; - - if ( - getDataModels(context.schema).some(model => - hasValidationAttributes(model) - ) - ) { - // '@core/zod' plugin is auto-enabled if there're validation rules - corePlugins.push({ - provider: "@plugins/zod", - options: { modelOnly: true } - }); - } - - // core plugins introduced by dependencies - plugins - .flatMap(p => p.dependencies) - .forEach(dep => { - if (dep.startsWith("@core/")) { - const existing = corePlugins.find(p => p.provider === dep); - if (existing) { - // reset options to default - existing.options = undefined; - } else { - // add core dependency - corePlugins.push({ provider: dep }); - } - } - }); - - for (const corePlugin of corePlugins.reverse()) { - const existingIdx = plugins.findIndex( - p => p.provider === corePlugin.provider - ); - if (existingIdx >= 0) { - // shift the plugin to the front - const existing = plugins[existingIdx]; - plugins.splice(existingIdx, 1); - plugins.unshift(existing); - } else { - // synthesize a plugin and insert front - const pluginModule = require(this.getPluginModulePath( - corePlugin.provider - )); - const pluginName = this.getPluginName( - pluginModule, - corePlugin.provider - ); - plugins.unshift({ - name: pluginName, - provider: corePlugin.provider, - dependencies: [], - options: { - schemaPath: context.schemaPath, - name: pluginName, - ...corePlugin.options - }, - run: pluginModule.default, - module: pluginModule - }); - } - } - - // check dependencies - for (const plugin of plugins) { - for (const dep of plugin.dependencies) { - if (!plugins.find(p => p.provider === dep)) { - ConsoleLogger.error( - `Plugin ${plugin.provider} depends on "${dep}" but it's not declared` - ); - throw new PluginError( - plugin.name, - `Plugin ${plugin.provider} depends on "${dep}" but it's not declared` - ); - } - } - } - - const warnings: string[] = []; - - let dmmf: DMMF.Document | undefined = undefined; - for (const { name, provider, run, options } of plugins) { - const start = Date.now(); - await this.runPlugin(name, run, context, options, dmmf, warnings); - ConsoleLogger.log( - `✅ Plugin ${chalk.bold(name)} (${provider}) completed in ${ - Date.now() - start - }ms` - ); - if (provider === "@core/prisma") { - // load prisma DMMF - dmmf = await getDMMF({ - datamodel: fs.readFileSync(prismaOutput, { encoding: "utf-8" }) - }); - } - } - - ConsoleLogger.log( - chalk.green(chalk.bold("\n⚡ All plugins completed successfully!")) - ); - - warnings.forEach(w => ConsoleLogger.warn(chalk.yellow(w))); - - ConsoleLogger.log( - `Don't forget to restart your dev server to let the changes take effect.` - ); - } - - // eslint-disable-next-line @typescript-eslint/no-explicit-any - private getPluginName(pluginModule: any, pluginProvider: string): string { - return typeof pluginModule.name === "string" - ? (pluginModule.name as string) - : pluginProvider; - } - - private getPluginDependencies(pluginModule: any) { - return Array.isArray(pluginModule.dependencies) - ? (pluginModule.dependencies as string[]) - : []; - } - - private getPluginProvider(plugin: Plugin) { - const providerField = plugin.fields.find(f => f.name === "provider"); - return getLiteral(providerField?.value); - } - - private async runPlugin( - name: string, - run: PluginFunction, - context: Context, - options: PluginOptions, - dmmf: DMMF.Document | undefined, - warnings: string[] - ) { - const spinner = ConsoleLogger.spinner( - `Executing plugin ${chalk.bold.cyan(name)}` - ).start(); - - try { - let result = run(context.schema, options, dmmf, config); - if (result instanceof Promise) { - result = await result; - } - if (Array.isArray(result)) { - warnings.push(...result); - } - - spinner.succeed("Plugin completed successfully"); - } catch (err) { - spinner.fail("Plugin failed to complete"); - ConsoleLogger.error(err); - - throw err; - } - } - - private getPluginModulePath(provider: string) { - let pluginModulePath = provider; - if (pluginModulePath.startsWith("@core/")) { - pluginModulePath = pluginModulePath.replace( - /^@core\//, - path.join(__dirname, "../../../forecast/schema/plugins/") - ); - } - if (pluginModulePath.startsWith("@plugins/")) { - pluginModulePath = pluginModulePath.replace( - /^@plugins\//, - path.join(__dirname, "../../../forecast/plugins/") - ); - } - return pluginModulePath; - } -} diff --git a/tools/forecast/codegen/src/cli/plugins-processor.ts b/tools/forecast/codegen/src/cli/plugins-processor.ts new file mode 100644 index 000000000..59ebf87ac --- /dev/null +++ b/tools/forecast/codegen/src/cli/plugins-processor.ts @@ -0,0 +1,468 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ +/* eslint-disable @typescript-eslint/no-var-requires */ +import type { DMMF } from "@prisma/generator-helper"; +import { resolvePath } from "@stormstack/core-server-utilities/file-path-fns"; +import { ConsoleLogger } from "@stormstack/core-shared-logging/console"; +import { ProcessingError } from "@stormstack/core-shared-utilities"; +import { isPlugin, Plugin } from "@stormstack/tools-forecast-language/ast"; +import { getLiteral } from "@stormstack/tools-forecast-language/utils"; +import chalk from "chalk"; +import path from "path"; +import type { + Context, + ForecastConfig, + IGenerator, + IPluginRunner, + PluginModule, + PluginOptions +} from "../types"; +import { ensureDefaultOutputFolder } from "../utils/plugin-utils"; +import { getVersion } from "../utils/version-utils"; +import { config } from "./config"; + +type PluginInfo = { + name: string; + dependencyOf?: string; + provider: string; + options: PluginOptions; + generator: IGenerator; + runner: IPluginRunner; + dependencies: string[]; + module: any; +}; + +/** + * Storm plugin runner + */ +export class PluginsProcessor { + private _plugins: PluginInfo[] = []; + + /** + * Runs a series of nested generators + */ + public async process(context: Context): Promise { + const version = getVersion(); + ConsoleLogger.info( + chalk.bold(`⌛️ Forecast CLI v${version} - Processing Plugins`) + ); + + ConsoleLogger.debug("Ensuring default output folder exists and is empty"); + ensureDefaultOutputFolder(); + + const plugins: PluginInfo[] = []; + const pluginDecls = context.model.declarations.filter((d): d is Plugin => + isPlugin(d) + ); + + ConsoleLogger.debug( + `Loading Forecast schema from ${chalk.bold(context.schemaPath)}` + ); + + let prismaOutput = resolvePath( + "./prisma/schema.prisma", + context.schemaPath + ); + + const warnings: string[] = []; + if (pluginDecls.length > 0) { + ConsoleLogger.debug( + `Loading ${pluginDecls.length} plugins specified in model` + ); + + for (const pluginDecl of pluginDecls) { + const pluginProvider = this.getPluginProvider(pluginDecl); + if (!pluginProvider) { + ConsoleLogger.error( + `Plugin ${pluginDecl.name} has invalid provider option` + ); + throw new ProcessingError( + `Plugin ${pluginDecl.name} has invalid provider option` + ); + } + + const pluginInfoList = this.getPluginInfo( + pluginProvider, + context.config.defaultOptions + ); + + this._plugins = pluginInfoList.reduce( + (ret: PluginInfo[], pluginInfo: PluginInfo) => { + if (!ret.some(p => p.provider === pluginInfo.provider)) { + ret.push(pluginInfo); + } + + return ret; + }, + this._plugins + ); + } + + ConsoleLogger.info( + `Running ${this._plugins} plugins (${ + pluginDecls.length + } specified in model, ${ + this._plugins.length - pluginDecls.length + } dependencies)`, + this._plugins + .map( + p => + `- ${p.provider} - ${p.name}${ + p.dependencyOf ? ` - Dependency of ${p.dependencyOf}` : "" + }` + ) + .join("\r\n") + ); + + /*for (const pluginInfo of this._plugins) { + let pluginModule: PluginModule; + try { + pluginModule = require(pluginModulePath); + } catch (err) { + ConsoleLogger.error( + `Unable to load plugin module ${pluginProvider}: ${pluginModulePath}, ${err}` + ); + throw new ProcessingError( + `Unable to load plugin module ${pluginProvider}` + ); + } + + if (!pluginModule) { + ConsoleLogger.error(`Plugin provider ${pluginProvider} is missing`); + throw new ProcessingError( + `Plugin provider ${pluginProvider} is missing` + ); + } + if (!pluginModule.generator) { + ConsoleLogger.error( + `Plugin provider ${pluginProvider} is missing a "generator" export` + ); + throw new ProcessingError( + `Plugin provider ${pluginProvider} is missing a "generator" export` + ); + } + if (!pluginModule.runner) { + ConsoleLogger.error( + `Plugin provider ${pluginProvider} is missing a "runner" export` + ); + throw new ProcessingError( + `Plugin provider ${pluginProvider} is missing a "runner" export` + ); + } + + const dependencies = this.getPluginDependencies(pluginModule); + const pluginName = this.getPluginName(pluginModule, pluginProvider); + const options: PluginOptions = { + schemaPath: context.schemaPath, + name: pluginName + }; + + ConsoleLogger.debug( + `Preparing to load plugin: +${JSON.stringify(options)}` + ); + + pluginDecl.fields.forEach(f => { + const value = getLiteral(f.value) ?? getLiteralArray(f.value); + if (value === undefined) { + throw new ProcessingError( + `${pluginName} Plugin: Invalid option value for ${f.name}` + ); + } + options[f.name] = value; + }); + + const plugin = { + name: pluginName, + provider: pluginProvider, + dependencies, + options, + run: pluginModule.default as PluginFunction, + module: pluginModule + }; + + ConsoleLogger.debug( + `Loading plugin: +${JSON.stringify(plugin)}` + ); + + plugins.push(plugin); + + if ( + pluginProvider === "@core/prisma" && + typeof options.output === "string" + ) { + // record custom prisma output path + prismaOutput = resolvePath(options.output, options); + } + } + + // make sure prerequisites are included + const corePlugins: Array<{ + provider: string; + options?: Record; + }> = [ + { provider: "@core/prisma" }, + { provider: "@core/model-meta" }, + { provider: "@core/access-policy" } + ]; + + if ( + getDataModels(context.schema).some(model => + hasValidationAttributes(model) + ) + ) { + // '@core/zod' plugin is auto-enabled if there're validation rules + corePlugins.push({ + provider: "@plugins/zod", + options: { modelOnly: true } + }); + } + + // core plugins introduced by dependencies + plugins + .flatMap(p => p.dependencies) + .forEach(dep => { + if (dep.startsWith("@core/")) { + const existing = corePlugins.find(p => p.provider === dep); + if (existing) { + // reset options to default + existing.options = undefined; + } else { + // add core dependency + corePlugins.push({ provider: dep }); + } + } + }); + + for (const corePlugin of corePlugins.reverse()) { + const existingIdx = plugins.findIndex( + p => p.provider === corePlugin.provider + ); + if (existingIdx >= 0) { + // shift the plugin to the front + const existing = plugins[existingIdx]; + plugins.splice(existingIdx, 1); + plugins.unshift(existing); + } else { + // synthesize a plugin and insert front + const pluginModule = require(this.getPluginModulePath( + corePlugin.provider + )); + const pluginName = this.getPluginName( + pluginModule, + corePlugin.provider + ); + plugins.unshift({ + name: pluginName, + provider: corePlugin.provider, + dependencies: [], + options: { + schemaPath: context.schemaPath, + name: pluginName, + ...corePlugin.options + }, + run: pluginModule.default, + module: pluginModule + }); + } + } + + // check dependencies + for (const plugin of plugins) { + for (const dep of plugin.dependencies) { + if (!plugins.find(p => p.provider === dep)) { + ConsoleLogger.error( + `Plugin ${plugin.provider} depends on "${dep}" but it's not declared` + ); + throw new ProcessingError( + `Plugin ${plugin.name}: ${plugin.provider} depends on "${dep}" but it's not declared` + ); + } + } + } + + let dmmf: DMMF.Document | undefined = undefined; + for (const { name, provider, run, options } of plugins) { + const start = Date.now(); + await this.runPlugin(name, run, context, options, dmmf, warnings); + ConsoleLogger.log( + `✅ Plugin ${chalk.bold(name)} (${provider}) completed in ${ + Date.now() - start + }ms` + ); + if (provider === "@core/prisma") { + // load prisma DMMF + dmmf = await getDMMF({ + datamodel: fs.readFileSync(prismaOutput, { encoding: "utf-8" }) + }); + } + }*/ + } else { + ConsoleLogger.warn( + `No plugins specified for this model. Skipping plugin processing (please ensure this is correct).` + ); + } + + ConsoleLogger.log( + chalk.green(chalk.bold("\n⚡ All plugins completed successfully!")) + ); + + warnings.forEach(w => ConsoleLogger.warn(chalk.yellow(w))); + + ConsoleLogger.log( + `Don't forget to restart your dev server to let the changes take effect.` + ); + } + + // eslint-disable-next-line @typescript-eslint/no-explicit-any + private getPluginName(pluginModule: any, pluginProvider: string): string { + return typeof pluginModule.name === "string" + ? (pluginModule.name as string) + : pluginProvider; + } + + private getPluginDependenciesList(pluginModule: any) { + return Array.isArray(pluginModule.dependencies) + ? (pluginModule.dependencies as string[]) + : []; + } + + private getPluginProvider(plugin: Plugin) { + const providerField = plugin.fields.find(f => f.name === "provider"); + return getLiteral(providerField?.value); + } + + private async runPlugin( + name: string, + run: PluginFunction, + context: Context, + options: PluginOptions, + dmmf: DMMF.Document | undefined, + warnings: string[] + ) { + ConsoleLogger.info(`Executing plugin ${chalk.bold.cyan(name)}`); + + try { + const result = Promise.resolve(run(context.model, options, dmmf, config)); + if (Array.isArray(result)) { + warnings.push(...result); + } + + ConsoleLogger.success("Plugin completed successfully"); + } catch (err) { + ConsoleLogger.error("Plugin failed to complete"); + ConsoleLogger.error(err); + + throw err; + } + } + + private getPluginModulePath(provider: string) { + let pluginModulePath = provider; + if (pluginModulePath.startsWith("@core/")) { + pluginModulePath = pluginModulePath.replace( + /^@core\//, + path.join(__dirname, "../../../forecast/schema/plugins/") + ); + } + if (pluginModulePath.startsWith("@plugins/")) { + pluginModulePath = pluginModulePath.replace( + /^@plugins\//, + path.join(__dirname, "../../../forecast/plugins/") + ); + } + return pluginModulePath; + } + + private getPluginModule(pluginProvider: string): PluginModule { + if (!pluginProvider) { + ConsoleLogger.error( + `Plugin ${pluginProvider} has invalid provider option` + ); + throw new ProcessingError( + `Plugin ${pluginProvider} has invalid provider option` + ); + } + + const pluginModulePath = this.getPluginModulePath(pluginProvider); + + let pluginModule: PluginModule; + try { + pluginModule = require(pluginModulePath); + } catch (err) { + ConsoleLogger.error( + `Unable to load plugin module ${pluginProvider}: ${pluginModulePath}, ${err}` + ); + throw new ProcessingError( + `Unable to load plugin module ${pluginProvider}` + ); + } + + if (!pluginModule) { + ConsoleLogger.error(`Plugin provider ${pluginProvider} is missing`); + throw new ProcessingError(`Plugin provider ${pluginProvider} is missing`); + } + if (!pluginModule.generator) { + ConsoleLogger.error( + `Plugin provider ${pluginProvider} is missing a "generator" export` + ); + throw new ProcessingError( + `Plugin provider ${pluginProvider} is missing a "generator" export` + ); + } + if (!pluginModule.runner) { + ConsoleLogger.error( + `Plugin provider ${pluginProvider} is missing a "runner" export` + ); + throw new ProcessingError( + `Plugin provider ${pluginProvider} is missing a "runner" export` + ); + } + + return pluginModule; + } + + private getPluginInfo( + pluginProvider: string, + pluginOptions: ForecastConfig["defaultOptions"] + ): PluginInfo[] { + const module: PluginModule = this.getPluginModule(pluginProvider); + const name = this.getPluginName(module, pluginProvider); + + const dependencies = this.getPluginDependenciesList(module); + const pluginInfo: PluginInfo = { + name, + provider: pluginProvider, + module, + options: { + ...module.options, + ...pluginOptions + }, + generator: module.generator, + runner: module.runner, + dependencies + }; + if (!dependencies) { + return [pluginInfo]; + } + + return [ + ...dependencies.reduce((ret: PluginInfo[], dep: string) => { + if ( + !this._plugins.some(p => p.provider === dep) && + !ret.some(p => p.provider === dep) + ) { + ret.push( + ...{ + ...this.getPluginInfo(dep, pluginInfo.options), + dependencyOf: name + } + ); + } + + return ret; + }, []), + pluginInfo + ]; + } +} diff --git a/tools/forecast/codegen/src/sdk/code-gen.ts b/tools/forecast/codegen/src/code-gen.ts similarity index 87% rename from tools/forecast/codegen/src/sdk/code-gen.ts rename to tools/forecast/codegen/src/code-gen.ts index ee6f94879..5f6009514 100644 --- a/tools/forecast/codegen/src/sdk/code-gen.ts +++ b/tools/forecast/codegen/src/code-gen.ts @@ -1,5 +1,7 @@ import { ConsoleLogger } from "@stormstack/core-shared-logging"; -import prettier, { Options } from "prettier"; +import { ProcessingError } from "@stormstack/core-shared-utilities"; +import prettierConfig from "@stormstack/tools-devops-config/prettier"; +import prettier from "prettier"; import { CompilerOptions, DiagnosticCategory, @@ -8,21 +10,6 @@ import { ScriptTarget, SourceFile } from "ts-morph"; -import { PluginError } from "./types"; - -const prettierConfig: Options = { - trailingComma: "none", - tabWidth: 2, - semi: true, - singleQuote: false, - quoteProps: "preserve", - insertPragma: false, - bracketSameLine: true, - printWidth: 80, - bracketSpacing: true, - arrowParens: "avoid", - endOfLine: "lf" -}; export async function formatFile( sourceFile: SourceFile, @@ -100,7 +87,7 @@ export async function emitProject(project: Project) { project.formatDiagnosticsWithColorAndContext(errors.slice(0, 10)) ); await project.save(); - throw new PluginError("", `Error compiling generated code`); + throw new ProcessingError("Error compiling generated code"); } const result = await project.emit(); @@ -114,7 +101,7 @@ export async function emitProject(project: Project) { project.formatDiagnosticsWithColorAndContext(emitErrors.slice(0, 10)) ); await project.save(); - throw new PluginError("", `Error emitting generated code`); + throw new ProcessingError("Error emitting generated code"); } } diff --git a/tools/forecast/codegen/src/sdk/constants.ts b/tools/forecast/codegen/src/constants.ts similarity index 100% rename from tools/forecast/codegen/src/sdk/constants.ts rename to tools/forecast/codegen/src/constants.ts diff --git a/tools/forecast/codegen/src/sdk/dmmf-helpers/aggregate-helpers.ts b/tools/forecast/codegen/src/dmmf-helpers/aggregate-helpers.ts similarity index 100% rename from tools/forecast/codegen/src/sdk/dmmf-helpers/aggregate-helpers.ts rename to tools/forecast/codegen/src/dmmf-helpers/aggregate-helpers.ts diff --git a/tools/forecast/codegen/src/sdk/dmmf-helpers/include-helpers.ts b/tools/forecast/codegen/src/dmmf-helpers/include-helpers.ts similarity index 100% rename from tools/forecast/codegen/src/sdk/dmmf-helpers/include-helpers.ts rename to tools/forecast/codegen/src/dmmf-helpers/include-helpers.ts diff --git a/tools/forecast/codegen/src/sdk/dmmf-helpers/index.ts b/tools/forecast/codegen/src/dmmf-helpers/index.ts similarity index 100% rename from tools/forecast/codegen/src/sdk/dmmf-helpers/index.ts rename to tools/forecast/codegen/src/dmmf-helpers/index.ts diff --git a/tools/forecast/codegen/src/sdk/dmmf-helpers/missing-types-helper.ts b/tools/forecast/codegen/src/dmmf-helpers/missing-types-helper.ts similarity index 100% rename from tools/forecast/codegen/src/sdk/dmmf-helpers/missing-types-helper.ts rename to tools/forecast/codegen/src/dmmf-helpers/missing-types-helper.ts diff --git a/tools/forecast/codegen/src/sdk/dmmf-helpers/model-helpers.ts b/tools/forecast/codegen/src/dmmf-helpers/model-helpers.ts similarity index 100% rename from tools/forecast/codegen/src/sdk/dmmf-helpers/model-helpers.ts rename to tools/forecast/codegen/src/dmmf-helpers/model-helpers.ts diff --git a/tools/forecast/codegen/src/sdk/dmmf-helpers/modelArgs-helpers.ts b/tools/forecast/codegen/src/dmmf-helpers/modelArgs-helpers.ts similarity index 100% rename from tools/forecast/codegen/src/sdk/dmmf-helpers/modelArgs-helpers.ts rename to tools/forecast/codegen/src/dmmf-helpers/modelArgs-helpers.ts diff --git a/tools/forecast/codegen/src/sdk/dmmf-helpers/select-helpers.ts b/tools/forecast/codegen/src/dmmf-helpers/select-helpers.ts similarity index 100% rename from tools/forecast/codegen/src/sdk/dmmf-helpers/select-helpers.ts rename to tools/forecast/codegen/src/dmmf-helpers/select-helpers.ts diff --git a/tools/forecast/codegen/src/sdk/dmmf-helpers/types.ts b/tools/forecast/codegen/src/dmmf-helpers/types.ts similarity index 100% rename from tools/forecast/codegen/src/sdk/dmmf-helpers/types.ts rename to tools/forecast/codegen/src/dmmf-helpers/types.ts diff --git a/tools/forecast/codegen/src/index.ts b/tools/forecast/codegen/src/index.ts index 9cc2d02e0..aaae8c393 100644 --- a/tools/forecast/codegen/src/index.ts +++ b/tools/forecast/codegen/src/index.ts @@ -1,3 +1,6 @@ export * from "./cli"; +export * from "./code-gen"; +export * from "./constants"; +export * from "./prisma"; export * from "./types"; export * from "./utils"; diff --git a/tools/forecast/codegen/src/plugins/generators/generator.ts b/tools/forecast/codegen/src/plugins/generators/generator.ts new file mode 100644 index 000000000..03cfb6e2f --- /dev/null +++ b/tools/forecast/codegen/src/plugins/generators/generator.ts @@ -0,0 +1,75 @@ +import { BaseUtilityClass } from "@stormstack/core-shared-utilities"; +import { AstNode, Model } from "@stormstack/tools-forecast-language/ast"; +import { getFileHeader } from "../../code-gen"; +import { + Context, + GENERATOR_SYMBOL, + IGenerator, + PluginOptions +} from "../../types"; + +/** + * Forecast base Generator + */ +export abstract class Generator + extends BaseUtilityClass + implements IGenerator +{ + constructor() { + super(GENERATOR_SYMBOL); + } + + public abstract get name(): string; + + public abstract get fileExtension(): string; + + public abstract get commentStart(): string; + + public abstract generate( + options: TOptions, + node: AstNode, + context: Context, + params: any + ): Promise; + + public write( + options: TOptions, + fileContent: string, + fileName: string, + fileExtension?: string + ): Promise { + return this.innerWrite( + options, + ` +${this.getFileHeader()} + +${fileContent} + +${this.getFileFooter()} + `, + fileName, + fileExtension ? fileExtension : this.fileExtension + ); + } + + public abstract save(options: TOptions): Promise; + + public async extendModel(context: Context): Promise { + return context.model; + } + + protected abstract innerWrite( + options: TOptions, + fileContent: string, + fileName: string, + fileExtension: string + ): Promise; + + protected getFileHeader(): string { + return getFileHeader(this.name, this.commentStart); + } + + protected getFileFooter(): string { + return ""; + } +} diff --git a/tools/forecast/codegen/src/plugins/generators/template-generator.ts b/tools/forecast/codegen/src/plugins/generators/template-generator.ts new file mode 100644 index 000000000..2811ba3ed --- /dev/null +++ b/tools/forecast/codegen/src/plugins/generators/template-generator.ts @@ -0,0 +1,50 @@ +import { compile } from "handlebars"; +import { AstNode } from "langium"; +import { CompilerOptions } from "ts-morph"; +import { + Context, + TemplatePluginOptions, + TypescriptPluginOptions +} from "../../types"; +import { TypescriptGenerator } from "./typescript-generator"; + +/** + * Base class for TypeScript generators + */ +export class TemplateGenerator< + TOptions extends TemplatePluginOptions = TypescriptPluginOptions +> extends TypescriptGenerator { + public get name(): string { + return "TemplateGenerator"; + } + + protected templates = new Map(); + + constructor(compilerOptions?: CompilerOptions) { + super(compilerOptions); + } + + public async generate( + options: TOptions, + node: AstNode, + context: Context, + params: { name: string; template: string } + ): Promise { + const template = await this.getTemplate(params.name, params.template); + + return template(node); + } + + protected async getTemplate( + name: string, + template: string + ): Promise { + let compiled = this.templates.get(name); + if (!compiled) { + compiled = compile(template, this.compilerOptions); + this.templates.set(name, compiled); + } + + return compiled; + } +} diff --git a/tools/forecast/codegen/src/plugins/generators/typescript-generator.ts b/tools/forecast/codegen/src/plugins/generators/typescript-generator.ts new file mode 100644 index 000000000..dd20d758f --- /dev/null +++ b/tools/forecast/codegen/src/plugins/generators/typescript-generator.ts @@ -0,0 +1,82 @@ +import { CompilerOptions, Project } from "ts-morph"; +import { + createProject, + emitProject, + formatFile, + saveProject +} from "../../code-gen"; +import { TypescriptPluginOptions } from "../../types"; +import { Generator } from "./generator"; + +/** + * Base class for TypeScript generators + */ +export abstract class TypescriptGenerator< + TOptions extends TypescriptPluginOptions = TypescriptPluginOptions +> extends Generator { + protected project: Project; + + public get fileExtension(): string { + return "ts"; + } + + public get commentStart(): string { + return "//"; + } + + constructor(protected compilerOptions?: CompilerOptions) { + super(); + this.project = createProject(compilerOptions); + } + + public async save(options: TOptions) { + const shouldCompile = options.compile !== false; + if (!shouldCompile || options.preserveTsFiles === true) { + // save ts files + return saveProject(this.project); + } + if (shouldCompile) { + return emitProject(this.project); + } + } + + protected async innerWrite( + options: TOptions, + fileContent: string, + fileName: string, + fileExtension = this.fileExtension + ) { + const extension = fileExtension.startsWith(".") + ? fileExtension + : `.${fileExtension}`; + const file = this.project.createSourceFile( + fileName.endsWith(extension) ? fileName : `${fileName}${extension}` + ); + + file.replaceWithText(writer => { + writer.write(fileContent); + }); + + if (options.prettier !== false) { + await formatFile(file, this.getParserFromExtension(extension)); + } + } + + protected getParserFromExtension(extension: string): string { + switch (extension) { + case ".ts": + case ".tsx": + return "typescript"; + case ".js": + case ".jsx": + return "babel"; + case ".json": + return "json"; + case ".gql": + case ".graphql": + return "graphql"; + default: + return "typescript"; + } + } +} diff --git a/tools/forecast/codegen/src/plugins/plugin-handler.ts b/tools/forecast/codegen/src/plugins/plugin-handler.ts new file mode 100644 index 000000000..2ebe5ee2c --- /dev/null +++ b/tools/forecast/codegen/src/plugins/plugin-handler.ts @@ -0,0 +1,28 @@ +import { + PluginHandler, + type Context, + type IGenerator, + type PluginOptions +} from "../types"; + +/** + * Runs the core functionality of a plugin generator + */ +export const createPluginHandler = + ( + fileName: string = "index" + ): PluginHandler => + async ( + options: TOptions, + context: Context, + generator: IGenerator + ) => { + const fileContent = await generator.generate( + options, + context.model, + context, + {} + ); + + await generator.write(options, fileContent, fileName); + }; diff --git a/tools/forecast/codegen/src/plugins/template-plugin-handler.ts b/tools/forecast/codegen/src/plugins/template-plugin-handler.ts new file mode 100644 index 000000000..6bbb5e9ca --- /dev/null +++ b/tools/forecast/codegen/src/plugins/template-plugin-handler.ts @@ -0,0 +1,141 @@ +import { ProcessingError } from "@stormstack/core-shared-utilities"; +import { AstNode } from "@stormstack/tools-forecast-language/ast"; +import { readFile } from "fs/promises"; +import { glob } from "glob"; +import { + PluginHandler, + TemplatePluginOptions, + TemplatePluginPaths, + type Context +} from "../types"; +import { TypescriptGenerator } from "./generators/typescript-generator"; + +/** + * Runs the core functionality of a plugin generator + */ +export const createTemplatePluginHandler = + ( + templatePaths: TemplatePluginPaths + ): PluginHandler => + async ( + options: TOptions, + context: Context, + generator: TypescriptGenerator + ) => { + const templates = await getTemplates( + templatePaths.templatePath, + options.templatePath + ); + + const generated = await Promise.all( + templates.map(template => + getGenerated(options, context, context.model, generator, template) + ) + ); + + await Promise.all( + generated.map(file => generator.write(options, file.content, file.name)) + ); + + await generator.save(options); + }; + +const getGenerated = async < + TOptions extends TemplatePluginOptions = TemplatePluginOptions +>( + options: TOptions, + context: Context, + node: AstNode, + generator: TypescriptGenerator, + template: { + name: string; + content: string; + } +): Promise<{ name: string; content: string }> => { + const content = await generator.generate(options, context.model, context, { + template + }); + + return { + name: template.name, + content + }; +}; + +const getTemplateNames = async (path: string | string[]): Promise => { + if (Array.isArray(path)) { + return (await Promise.all(path.map(getTemplateNames))).reduce( + (ret: string[], templateNamesListItem: string[]) => { + templateNamesListItem.forEach((templateName: string) => { + if (!ret.includes(templateName)) { + ret.push(templateName); + } + }); + return ret; + }, + [] + ); + } + + return ( + await glob(path, { + ignore: "node_modules/**" + }) + ).reduce((ret: string[], templateName: string) => { + const name = templateName.endsWith(".template") + ? templateName.slice(0, -9) + : templateName; + if (!ret.includes(name)) { + ret.push(name); + } + + return ret; + }, []); +}; + +const getTemplates = async ( + defaultPath: string | string[], + optionsPath?: string | string[] +): Promise> => { + const paths = mergePaths(defaultPath, optionsPath); + + const templateNames = await getTemplateNames(paths); + if (templateNames?.length === 0) { + throw new ProcessingError(`No template files found in ${paths.join(", ")}`); + } + + return Promise.all(templateNames.map(getTemplate)); +}; + +const getTemplate = async ( + templateName: string +): Promise<{ name: string; content: string }> => { + const content = await readFile(templateName, { encoding: "utf8" }); + + return { + name: templateName, + content + }; +}; + +const mergePaths = ( + defaultPath: string | string[], + optionsPath?: string | string[] +): string[] => { + const paths = []; + if (Array.isArray(defaultPath)) { + paths.push(...defaultPath); + } else { + paths.push(defaultPath); + } + + if (optionsPath) { + if (Array.isArray(optionsPath)) { + paths.push(...optionsPath); + } else { + paths.push(optionsPath); + } + } + + return paths; +}; diff --git a/tools/forecast/codegen/src/sdk/prisma.ts b/tools/forecast/codegen/src/prisma.ts similarity index 97% rename from tools/forecast/codegen/src/sdk/prisma.ts rename to tools/forecast/codegen/src/prisma.ts index 9e9c356d6..6a2978ddc 100644 --- a/tools/forecast/codegen/src/sdk/prisma.ts +++ b/tools/forecast/codegen/src/prisma.ts @@ -8,8 +8,8 @@ import { isGeneratorDecl, isPlugin } from "@stormstack/tools-forecast-language/ast"; +import { getLiteral } from "@stormstack/tools-forecast-language/utils"; import { dirname, isAbsolute, posix, relative, resolve, sep } from "path"; -import { getLiteral } from "./utils"; /** * Given a Storm and an import context directory, compute the import spec for the Prisma Client. diff --git a/tools/forecast/codegen/src/sdk/index.ts b/tools/forecast/codegen/src/sdk/index.ts deleted file mode 100644 index d78d6edc6..000000000 --- a/tools/forecast/codegen/src/sdk/index.ts +++ /dev/null @@ -1,5 +0,0 @@ -export * from "./code-gen"; -export * from "./constants"; -export * from "./prisma"; -export * from "./types"; -export * from "./utils"; diff --git a/tools/forecast/codegen/src/sdk/types.ts b/tools/forecast/codegen/src/sdk/types.ts deleted file mode 100644 index 3ef68d0a1..000000000 --- a/tools/forecast/codegen/src/sdk/types.ts +++ /dev/null @@ -1,35 +0,0 @@ -import type { DMMF } from "@prisma/generator-helper"; -import { Model } from "@stormstack/tools-forecast-language/ast"; - -/** - * Plugin configuration option value type - */ -export type OptionValue = string | number | boolean; - -/** - * Plugin configuration options - */ -export type PluginOptions = { - provider?: string; - schemaPath: string; - name: string; -} & Record; - -/** - * Plugin entry point function definition - */ -export type PluginFunction = ( - model: Model, - options: PluginOptions, - dmmf?: DMMF.Document, - config?: Record -) => Promise | string[] | Promise | void; - -/** - * Plugin error - */ -export class PluginError extends Error { - constructor(public plugin: string, message: string) { - super(message); - } -} diff --git a/tools/forecast/codegen/src/types.ts b/tools/forecast/codegen/src/types.ts index f82a03354..187da8868 100644 --- a/tools/forecast/codegen/src/types.ts +++ b/tools/forecast/codegen/src/types.ts @@ -1,19 +1,62 @@ -import type { DMMF } from "@prisma/generator-helper"; -import { Model } from "@stormstack/tools-forecast-language/ast"; +import { AstNode, Model } from "@stormstack/tools-forecast-language/ast"; export interface Context { - schema: Model; - schemaPath: string; - outDir: string; + /** + * The model to be generated + */ + model: Model; + + /** + * The path to the schema file + */ + schemaPath?: string; + + /** + * The options used by forecast during generation + * that is read from the forecast config file + */ + config: ForecastConfig; } -export interface Generator { - get name(): string; - get startMessage(): string; - get successMessage(): string; - generate(context: Context): Promise; +export interface IGenerator { + extendModel(context: Context): Promise; + generate( + options: TOptions, + node: AstNode, + context: Context, + params?: any + ): Promise; + write( + options: TOptions, + fileContent: string, + fileName: string, + fileExtension?: string + ): Promise; } +export const GENERATOR_SYMBOL = Symbol("Generator"); + +export interface IPluginRunner { + run( + options: TOptions, + context: Context, + generator: IGenerator + ): Promise; +} + +export type PluginHandler = ( + options: TOptions, + context: Context, + generator: IGenerator +) => Promise; + +export type PluginExtend = ( + options: TOptions, + context: Context +) => Promise; + +export const PLUGIN_RUNNER_SYMBOL = Symbol("PluginRunner"); + /** * Plugin configuration option value type */ @@ -23,26 +66,116 @@ export type OptionValue = string | number | boolean; * Plugin configuration options */ export type PluginOptions = { - provider?: string; - schemaPath: string; - name: string; + /** + * The name of the provider + */ + provider: string; + + /** + * The output directory + */ + output?: string; } & Record; /** - * Plugin entry point function definition + * TypeScript Plugin configuration options */ -export type PluginFunction = ( - model: Model, - options: PluginOptions, - dmmf?: DMMF.Document, - config?: Record -) => Promise | string[] | Promise | void; +export type TypescriptPluginOptions = PluginOptions & { + /** + * Should the generated TypeScript files be compiled + */ + compile?: boolean; + + /** + * Should the generated TypeScript files be preserved + */ + preserveTsFiles?: boolean; + + /** + * Should prettier be used to format the generated code + * + * @default true + */ + prettier?: boolean; +}; /** - * Plugin error + * Paths to template files for a Template Plugin */ -export class PluginError extends Error { - constructor(public plugin: string, message: string) { - super(message); - } -} +export type TemplatePluginPaths = { + /** + * The path to the template files. This can include a [glob](https://www.npmjs.com/package/glob) pattern. + */ + templatePath: string | string[]; + + /** + * The path to the data model template files. For each data model a file will be generated. + */ + dataModelTemplatePath?: string | string[]; + + /** + * The path to the input template files. For each input a file will be generated. + */ + inputTemplatePath?: string | string[]; + + /** + * The path to the API model template files. For each API model a file will be generated. + */ + apiModelTemplatePath?: string | string[]; + + /** + * The path to the interface template files. For each interface a file will be generated. + */ + interfaceTemplatePath?: string | string[]; + + /** + * The path to the enum template files. For each enum a file will be generated. + */ + enumTemplatePath?: string | string[]; +}; + +/** + * Template Plugin options + */ +export type TemplatePluginOptions = PluginOptions & + Partial; + +/** + * Plugin module structure used in codegen + */ +export type PluginModule = { + /** + * A reference to the plugin generator. + */ + generator: IGenerator; + + /** + * A reference to the plugin runner + */ + handle?: PluginHandler; + + /** + * A list of dependencies that should be installed + */ + dependencies?: string[]; + + /** + * The default options for the plugin + */ + options?: PluginOptions; +}; + +/** + * Forecast configuration options + */ +export type ForecastConfig = { + /** + * The default options for all plugins + */ + defaultOptions?: Omit; + + /** + * The output directory + */ + outDir: string; +} & Record; diff --git a/tools/forecast/codegen/src/plugins/access-policy/utils.ts b/tools/forecast/codegen/src/utils/access-policy-utils.ts similarity index 100% rename from tools/forecast/codegen/src/plugins/access-policy/utils.ts rename to tools/forecast/codegen/src/utils/access-policy-utils.ts diff --git a/tools/forecast/codegen/src/utils/index.ts b/tools/forecast/codegen/src/utils/index.ts index 31d40dff0..ccf8c9905 100644 --- a/tools/forecast/codegen/src/utils/index.ts +++ b/tools/forecast/codegen/src/utils/index.ts @@ -1,3 +1,6 @@ +export * from "./access-policy-utils"; export * from "./ast-utils"; +export * from "./plugin-utils"; export * from "./typescript-expression-transformer"; +export * from "./utils"; export * from "./version-utils"; diff --git a/tools/forecast/codegen/src/plugins/plugin-utils.ts b/tools/forecast/codegen/src/utils/plugin-utils.ts similarity index 100% rename from tools/forecast/codegen/src/plugins/plugin-utils.ts rename to tools/forecast/codegen/src/utils/plugin-utils.ts diff --git a/tools/forecast/codegen/src/utils/typescript-expression-transformer.ts b/tools/forecast/codegen/src/utils/typescript-expression-transformer.ts index 424d3d70d..24907ad51 100644 --- a/tools/forecast/codegen/src/utils/typescript-expression-transformer.ts +++ b/tools/forecast/codegen/src/utils/typescript-expression-transformer.ts @@ -14,9 +14,11 @@ import { UnaryExpr } from "@stormstack/tools-forecast-language/ast"; import { ExpressionContext } from "@stormstack/tools-forecast-language/constants"; -import { isFromStdlib } from "@stormstack/tools-forecast-language/utils"; -import { isFutureExpr } from "../plugins/access-policy/utils"; -import { getLiteral } from "../sdk"; +import { + getLiteral, + isFromStdlib +} from "@stormstack/tools-forecast-language/utils"; +import { isFutureExpr } from "./access-policy-utils"; export class TypeScriptExpressionTransformerError extends Error { constructor(message: string) { diff --git a/tools/forecast/codegen/src/sdk/utils.ts b/tools/forecast/codegen/src/utils/utils.ts similarity index 86% rename from tools/forecast/codegen/src/sdk/utils.ts rename to tools/forecast/codegen/src/utils/utils.ts index 4534dae57..66fcc5346 100644 --- a/tools/forecast/codegen/src/sdk/utils.ts +++ b/tools/forecast/codegen/src/utils/utils.ts @@ -15,7 +15,6 @@ import { Model, Operation, OperationGroup, - Reference, ReferenceExpr, isApiModel, isArrayExpr, @@ -29,8 +28,13 @@ import { isReferenceExpr } from "@stormstack/tools-forecast-language/ast"; import { ExpressionContext } from "@stormstack/tools-forecast-language/constants"; +import { + getLiteral, + getLiteralArray, + resolved +} from "@stormstack/tools-forecast-language/utils"; import { dirname, isAbsolute, join } from "path"; -import { PluginOptions } from "./types"; +import { PluginOptions } from "../types"; /** * Gets data models that are not ignored @@ -78,42 +82,6 @@ export function getInputs(model: Model) { ); } -export function resolved(ref: Reference): T { - if (!ref.ref) { - throw new Error(`Reference not resolved: ${ref.$refText}`); - } - return ref.ref; -} - -// eslint-disable-next-line @typescript-eslint/no-explicit-any -export function getLiteral( - expr: Expression | undefined -): T | undefined { - if (!isLiteralExpr(expr)) { - return getObjectLiteral(expr); - } - return expr.value as T; -} - -export function getArray( - expr: Expression | undefined -): Expression[] | undefined { - return isArrayExpr(expr) ? expr.items : undefined; -} - -export function getLiteralArray< - // eslint-disable-next-line @typescript-eslint/no-explicit-any - T extends string | number | boolean | any = any ->(expr: Expression | undefined): T[] | undefined { - const arr = getArray(expr); - if (!arr) { - return undefined; - } - return arr - .map(item => getLiteral(item)) - .filter((v): v is T => v !== undefined); -} - export function getObjectLiteral( expr: Expression | undefined ): T | undefined { @@ -148,6 +116,7 @@ export function hasAttribute( decl: | DataModel | ApiModel + | Interface | DataModelField | OperationGroup | Operation @@ -320,13 +289,6 @@ export function isForeignKeyField(field: DataModelField) { }); } -export function resolvePath(_path: string, options: PluginOptions) { - if (isAbsolute(_path)) { - return _path; - } else { - return join(dirname(options.schemaPath), _path); - } -} export function requireOption(options: PluginOptions, name: string): T { const value = options[name]; diff --git a/tools/forecast/language/project.json b/tools/forecast/language/project.json index 54c8717f3..a162d44d2 100644 --- a/tools/forecast/language/project.json +++ b/tools/forecast/language/project.json @@ -38,6 +38,11 @@ "glob": "LICENSE", "output": "." }, + { + "input": "tools/forecast/language/res", + "glob": "*.*", + "output": "res" + }, { "input": "assets/favicons/light", "glob": "icon.png", @@ -93,7 +98,7 @@ "output": "." }, { - "input": "tools/forecast/language/src/res", + "input": "tools/forecast/language/res", "glob": "*.*", "output": "res" }, @@ -163,7 +168,7 @@ "defaultConfiguration": "default", "options": { "commands": [ - "pnpm nx run tools-forecast-language:langium:default", + "pnpm nx run tools-forecast-language:langium", "pnpm nx run tools-forecast-language:compile" ] }, diff --git a/tools/forecast/language/src/res/prism-forecast.js b/tools/forecast/language/res/prism-forecast.js similarity index 100% rename from tools/forecast/language/src/res/prism-forecast.js rename to tools/forecast/language/res/prism-forecast.js diff --git a/tools/forecast/language/src/res/starter.4cast b/tools/forecast/language/res/starter.4cast similarity index 100% rename from tools/forecast/language/src/res/starter.4cast rename to tools/forecast/language/res/starter.4cast diff --git a/tools/forecast/language/src/res/stdlib.forecast b/tools/forecast/language/res/stdlib.forecast similarity index 100% rename from tools/forecast/language/src/res/stdlib.forecast rename to tools/forecast/language/res/stdlib.forecast diff --git a/tools/forecast/plugins/access/.eslintrc.json b/tools/forecast/plugins/access/.eslintrc.json new file mode 100644 index 000000000..360232c1f --- /dev/null +++ b/tools/forecast/plugins/access/.eslintrc.json @@ -0,0 +1,28 @@ +{ + "extends": ["../../../../.eslintrc.json"], + "ignorePatterns": ["!**/*"], + "overrides": [ + { + "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], + "parserOptions": { + "project": ["tools/forecast/plugins/access/tsconfig.*?.json"] + }, + "rules": {} + }, + { + "files": ["*.ts", "*.tsx"], + "rules": {} + }, + { + "files": ["*.js", "*.jsx"], + "rules": {} + }, + { + "files": ["*.json"], + "parser": "jsonc-eslint-parser", + "rules": { + "@nx/dependency-checks": "error" + } + } + ] +} diff --git a/tools/forecast/plugins/access/README.md b/tools/forecast/plugins/access/README.md new file mode 100644 index 000000000..c46a16d39 --- /dev/null +++ b/tools/forecast/plugins/access/README.md @@ -0,0 +1,144 @@ + + + + + +
+ + + +
+The Open System is a monorepo containing modern, scalable web application code, additional utility applications/tools, various libraries, and a fully featured, serverless back-end framework. The Open System is built using Nx, a set of extensible dev tools for monorepos, which helps you develop like Google, Facebook, and Microsoft. Building on top of Nx, the Open System provides a set of tools and patterns that help you scale your monorepo to many teams while keeping the codebase maintainable. + +

💻 Visit patsullivan.org to stay up to date with this developer

+ +[![Version](https://img.shields.io/badge/version-0.0.1-10B981.svg?style=for-the-badge&color=10B981)](https://prettier.io/)  +[![Nx](https://img.shields.io/badge/Nx-14.4.2-lightgrey?style=for-the-badge&logo=nx&logoWidth=20&&color=10B981)](http://nx.dev/) [![NextJs](https://img.shields.io/badge/Next.js-13.0.5-lightgrey?style=for-the-badge&logo=nextdotjs&logoWidth=20&color=10B981)](https://nextjs.org/) [![codecov.io](https://img.shields.io/codecov/c/github/commitizen/cz-cli.svg?style=for-the-badge&color=10B981)](https://codecov.io/github/commitizen/cz-cli?branch=master) [![Commitizen friendly](https://img.shields.io/badge/commitizen-friendly-brightgreen.svg?style=for-the-badge&logo=commitlint&color=10B981)](http://commitizen.github.io/cz-cli/) ![Semantic-Release](https://img.shields.io/badge/%20%20%F0%9F%93%A6%F0%9F%9A%80-semantic--release-e10079.svg?style=for-the-badge&color=10B981) [![documented with docusaurus](https://img.shields.io/badge/documented_with-docusaurus-success.svg?style=for-the-badge&logo=readthedocs&color=10B981)](https://docusaurus.io/) + + + + + + + +# ⚡ Storm - Drizzle ORM Plugin + +This library was generated with [Nx](https://nx.dev). + + + + +## Table of Contents + +- [⚡ Storm - Drizzle ORM Plugin](#-storm---drizzle-orm-plugin) + - [Table of Contents](#table-of-contents) + - [Running unit tests](#running-unit-tests) + - [Roadmap](#roadmap) + - [Support](#support) + - [License](#license) + - [Changelog](#changelog) + - [Contributing](#contributing) + - [Contributors](#contributors) + + + +## Running unit tests + +Run `nx test tools-forecast-plugins-drizzle` to execute the unit tests via [Jest](https://jestjs.io). + + + + + + +## Roadmap + +See the [open issues](https://github.com/stormstack/stormstack/issues) for a list of proposed features (and known issues). + +- [Top Feature Requests](https://github.com/stormstack/stormstack/issues?q=label%3Aenhancement+is%3Aopen+sort%3Areactions-%2B1-desc) (Add your votes using the 👍 reaction) +- [Top Bugs](https://github.com/stormstack/stormstack/issues?q=is%3Aissue+is%3Aopen+label%3Abug+sort%3Areactions-%2B1-desc) (Add your votes using the 👍 reaction) +- [Newest Bugs](https://github.com/stormstack/stormstack/issues?q=is%3Aopen+is%3Aissue+label%3Abug) + +## Support + +Reach out to the maintainer at one of the following places: + +- [Contact](https://www.patsullivan.org/contact) +- [GitHub discussions](https://github.com/stormstack/stormstack/discussions) +- + +## License + +This project is licensed under the **BSD-2-Clause license**. Feel free to edit and distribute this template as you like. + +See [LICENSE](LICENSE) for more information. + +## Changelog + +This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). Every release, along with the migration instructions, is documented in the [CHANGELOG](CHANGELOG.md) file + +## Contributing + +First off, thanks for taking the time to contribute! Contributions are what makes the open-source community such an amazing place to learn, inspire, and create. Any contributions you make will benefit everybody else and are **greatly appreciated**. + +Please try to create bug reports that are: + +- _Reproducible._ Include steps to reproduce the problem. +- _Specific._ Include as much detail as possible: which version, what environment, etc. +- _Unique._ Do not duplicate existing opened issues. +- _Scoped to a Single Bug._ One bug per report. + +Please adhere to this project's [code of conduct](.github/CODE_OF_CONDUCT.md). + +You can use [markdownlint-cli](https://github.com/stormstack/stormstack/markdownlint-cli) to check for common markdown style inconsistency. + +## Contributors + +Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/docs/en/emoji-key)): + + + + + + + + + + + + + + + +
Patrick Sullivan
Patrick Sullivan

🎨 💻 🔧 📖 ⚠️
Tyler Benning
Tyler Benning

🎨
+ + + Add your contributions +
+ + + +This project follows the [all-contributors](https://github.com/all-contributors/all-contributors) specification. Contributions of any kind welcome! + +
+
+
+ +
+ + +
+

Fingerprint: 1BD2 7192 7770 2549 F4C9 F238 E6AD C420 DA5C 4C2D

+
+ +

💻 Visit patsullivan.org to stay up to date with this developer

+ + + + + + diff --git a/tools/forecast/plugins/access/package.json b/tools/forecast/plugins/access/package.json new file mode 100644 index 000000000..924000fca --- /dev/null +++ b/tools/forecast/plugins/access/package.json @@ -0,0 +1,4 @@ +{ + "name": "@stormstack/tools-forecast-plugins-access", + "version": "0.0.1" +} diff --git a/tools/forecast/plugins/access/project.json b/tools/forecast/plugins/access/project.json new file mode 100644 index 000000000..e78258227 --- /dev/null +++ b/tools/forecast/plugins/access/project.json @@ -0,0 +1,76 @@ +{ + "name": "tools-forecast-plugins-access", + "$schema": "../../../../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "tools/forecast/plugins/access/src", + "projectType": "library", + "targets": { + "build": { + "executor": "@nx/esbuild:esbuild", + "outputs": ["{options.outputPath}"], + "options": { + "outputPath": "dist/forecast/plugins/access", + "main": "tools/forecast/plugins/access/src/index.ts", + "project": "tools/forecast/plugins/access/package.json", + "tsConfig": "tools/forecast/plugins/access/tsconfig.json", + "platform": "node", + "deleteOutputPath": true, + "bundle": true, + "sourcemap": true, + "minify": true, + "skipTypeCheck": true, + "format": ["esm", "cjs"], + "assets": [ + { + "input": "tools/forecast/plugins/access", + "glob": "README.md", + "output": "." + }, + { + "input": ".", + "glob": "LICENSE", + "output": "." + } + ] + } + }, + "lint": { + "executor": "@nx/linter:eslint", + "options": { + "lintFilePatterns": [ + "tools/forecast/plugins/access/**/*.ts", + "tools/forecast/plugins/access/package.json" + ] + }, + "outputs": ["{options.outputFile}"] + }, + "test": { + "executor": "@nx/jest:jest", + "outputs": ["{workspaceRoot}/coverage/{projectRoot}"], + "options": { + "jestConfig": "tools/forecast/plugins/access/jest.config.ts", + "passWithNoTests": true + }, + "configurations": { + "ci": { + "ci": true, + "codeCoverage": true + } + } + }, + "semantic-release": { + "executor": "@theunderscorer/nx-semantic-release:semantic-release", + "options": { + "github": true, + "npm": false, + "changelog": true, + "tagFormat": "tools-forecast-plugins-access-v${VERSION}" + } + } + }, + "tags": ["scope:tools", "platform:server"], + "implicitDependencies": [ + "core-shared-utilities", + "tools-forecast-language", + "tools-forecast-codegen" + ] +} diff --git a/tools/forecast/codegen/src/plugins/access-policy/expression-writer.ts b/tools/forecast/plugins/access/src/expression-writer.ts similarity index 98% rename from tools/forecast/codegen/src/plugins/access-policy/expression-writer.ts rename to tools/forecast/plugins/access/src/expression-writer.ts index 03f43ecf4..a16b1719b 100644 --- a/tools/forecast/codegen/src/plugins/access-policy/expression-writer.ts +++ b/tools/forecast/plugins/access/src/expression-writer.ts @@ -26,12 +26,15 @@ import { PluginError, getFunctionExpressionContext, getLiteral -} from "../../sdk"; -import { getIdFields, isAuthInvocation } from "../../utils/ast-utils"; +} from "../../../codegen/src/sdk"; +import { + getIdFields, + isAuthInvocation +} from "../../../codegen/src/utils/ast-utils"; import { TypeScriptExpressionTransformer, TypeScriptExpressionTransformerError -} from "../../utils/typescript-expression-transformer"; +} from "../../../codegen/src/utils/typescript-expression-transformer"; import { isFutureExpr } from "./utils"; type ComparisonOperator = "==" | "!=" | ">" | ">=" | "<" | "<="; diff --git a/tools/forecast/codegen/src/plugins/access-policy/index.ts b/tools/forecast/plugins/access/src/index.ts similarity index 83% rename from tools/forecast/codegen/src/plugins/access-policy/index.ts rename to tools/forecast/plugins/access/src/index.ts index c1bc5b892..8eef76ba2 100644 --- a/tools/forecast/codegen/src/plugins/access-policy/index.ts +++ b/tools/forecast/plugins/access/src/index.ts @@ -1,5 +1,5 @@ import { Model } from "@stormstack/tools-forecast-language/ast"; -import { PluginOptions } from "../../sdk"; +import { PluginOptions } from "../../../codegen/src/sdk"; import PolicyGenerator from "./policy-guard-generator"; export const name = "Access Policy"; diff --git a/tools/forecast/codegen/src/plugins/access-policy/policy-guard-generator.ts b/tools/forecast/plugins/access/src/policy-guard-generator.ts similarity index 98% rename from tools/forecast/codegen/src/plugins/access-policy/policy-guard-generator.ts rename to tools/forecast/plugins/access/src/policy-guard-generator.ts index 6d8ade455..6b9f7c736 100644 --- a/tools/forecast/codegen/src/plugins/access-policy/policy-guard-generator.ts +++ b/tools/forecast/plugins/access/src/policy-guard-generator.ts @@ -34,6 +34,10 @@ import { WriterFunction } from "ts-morph"; import { name } from "."; +import { + ALL_OPERATION_KINDS, + getDefaultOutputFolder +} from "../../../codegen/src/plugins/plugin-utils"; import { createProject, emitProject, @@ -47,13 +51,15 @@ import { resolvePath, RUNTIME_PACKAGE, saveProject -} from "../../sdk"; -import { getIdFields, isAuthInvocation } from "../../utils/ast-utils"; +} from "../../../codegen/src/sdk"; +import { + getIdFields, + isAuthInvocation +} from "../../../codegen/src/utils/ast-utils"; import { TypeScriptExpressionTransformer, TypeScriptExpressionTransformerError -} from "../../utils/typescript-expression-transformer"; -import { ALL_OPERATION_KINDS, getDefaultOutputFolder } from "../plugin-utils"; +} from "../../../codegen/src/utils/typescript-expression-transformer"; import { ExpressionWriter, FALSE, TRUE } from "./expression-writer"; import { isFutureExpr } from "./utils"; diff --git a/tools/forecast/plugins/access/src/utils.ts b/tools/forecast/plugins/access/src/utils.ts new file mode 100644 index 000000000..e69de29bb diff --git a/tools/forecast/plugins/access/tsconfig.json b/tools/forecast/plugins/access/tsconfig.json new file mode 100644 index 000000000..82178aee6 --- /dev/null +++ b/tools/forecast/plugins/access/tsconfig.json @@ -0,0 +1,13 @@ +{ + "extends": "../../../../tsconfig.base.json", + "compilerOptions": { + "outDir": "../../../../dist/out-tsc", + "module": "commonjs", + "target": "es2022", + "declaration": true, + "noImplicitAny": false, + "sourceMap": true + }, + "exclude": ["**/*.spec.ts", "**/*.test.ts", "jest.config.ts"], + "include": ["**/*.ts", "**/*.tsx", "**/*.js", "**/*.jsx"] +} diff --git a/tools/forecast/plugins/crud/.eslintrc.json b/tools/forecast/plugins/crud/.eslintrc.json new file mode 100644 index 000000000..7644c7702 --- /dev/null +++ b/tools/forecast/plugins/crud/.eslintrc.json @@ -0,0 +1,28 @@ +{ + "extends": ["../../../../.eslintrc.json"], + "ignorePatterns": ["!**/*"], + "overrides": [ + { + "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], + "parserOptions": { + "project": ["tools/forecast/plugins/crud/tsconfig.*?.json"] + }, + "rules": {} + }, + { + "files": ["*.ts", "*.tsx"], + "rules": {} + }, + { + "files": ["*.js", "*.jsx"], + "rules": {} + }, + { + "files": ["*.json"], + "parser": "jsonc-eslint-parser", + "rules": { + "@nx/dependency-checks": "error" + } + } + ] +} diff --git a/tools/forecast/plugins/crud/README.md b/tools/forecast/plugins/crud/README.md new file mode 100644 index 000000000..4b51c9d95 --- /dev/null +++ b/tools/forecast/plugins/crud/README.md @@ -0,0 +1,145 @@ + + + + + +
+ + + +
+The ⚡StormStack monorepo contains utility applications, tools, and various libraries used to create modern, scalable web applications.With the assistance of StormStack's Forecast modelling, a developer could create and a fully featured, serverless back-end without writing any actual code themselves. +
+StormStack is built using Nx, a set of extensible dev tools for monorepos, which helps you develop like Google, Facebook, and Microsoft. Building on top of Nx, the Open System provides a set of tools and patterns that help you scale your monorepo to many teams while keeping the codebase maintainable. + +

💻 Visit stormcloud.dev to stay up to date with this developer

+ +[![Version](https://img.shields.io/badge/version-0.0.1-1fb2a6.svg?style=for-the-badge&color=1fb2a6)](https://prettier.io/)  +[![Nx](https://img.shields.io/badge/Nx-14.4.2-lightgrey?style=for-the-badge&logo=nx&logoWidth=20&&color=1fb2a6)](http://nx.dev/) [![NextJs](https://img.shields.io/badge/Next.js-13.0.5-lightgrey?style=for-the-badge&logo=nextdotjs&logoWidth=20&color=1fb2a6)](https://nextjs.org/) [![codecov.io](https://img.shields.io/codecov/c/github/commitizen/cz-cli.svg?style=for-the-badge&color=1fb2a6)](https://codecov.io/github/commitizen/cz-cli?branch=master) [![Commitizen friendly](https://img.shields.io/badge/commitizen-friendly-brightgreen.svg?style=for-the-badge&logo=commitlint&color=1fb2a6)](http://commitizen.github.io/cz-cli/) ![Semantic-Release](https://img.shields.io/badge/%20%20%F0%9F%93%A6%F0%9F%9A%80-semantic--release-e10079.svg?style=for-the-badge&color=1fb2a6) [![documented with docusaurus](https://img.shields.io/badge/documented_with-docusaurus-success.svg?style=for-the-badge&logo=readthedocs&color=1fb2a6)](https://docusaurus.io/) + + + + + + + +# ⚡ Forecast Modelling Language - StormStack + +This library was generated with [Nx](https://nx.dev). + + + + +## Table of Contents + +- [⚡ Forecast Modelling Language - StormStack](#-forecast-modelling-language---stormstack) + - [Table of Contents](#table-of-contents) + - [Running unit tests](#running-unit-tests) + - [Roadmap](#roadmap) + - [Support](#support) + - [License](#license) + - [Changelog](#changelog) + - [Contributing](#contributing) + - [Contributors](#contributors) + + + +## Running unit tests + +Run `nx test tools-forecast-language` to execute the unit tests via [Jest](https://jestjs.io). + + + + + + +## Roadmap + +See the [open issues](https://github.com/stormstack/stormstack/issues) for a list of proposed features (and known issues). + +- [Top Feature Requests](https://github.com/stormstack/stormstack/issues?q=label%3Aenhancement+is%3Aopen+sort%3Areactions-%2B1-desc) (Add your votes using the 👍 reaction) +- [Top Bugs](https://github.com/stormstack/stormstack/issues?q=is%3Aissue+is%3Aopen+label%3Abug+sort%3Areactions-%2B1-desc) (Add your votes using the 👍 reaction) +- [Newest Bugs](https://github.com/stormstack/stormstack/issues?q=is%3Aopen+is%3Aissue+label%3Abug) + +## Support + +Reach out to the maintainer at one of the following places: + +- [Contact](https://www.patsullivan.org/contact) +- [GitHub discussions](https://github.com/stormstack/stormstack/discussions) +- + +## License + +This project is licensed under the **BSD-2-Clause license**. Feel free to edit and distribute this template as you like. + +See [LICENSE](LICENSE) for more information. + +## Changelog + +This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). Every release, along with the migration instructions, is documented in the [CHANGELOG](CHANGELOG.md) file + +## Contributing + +First off, thanks for taking the time to contribute! Contributions are what makes the open-source community such an amazing place to learn, inspire, and create. Any contributions you make will benefit everybody else and are **greatly appreciated**. + +Please try to create bug reports that are: + +- _Reproducible._ Include steps to reproduce the problem. +- _Specific._ Include as much detail as possible: which version, what environment, etc. +- _Unique._ Do not duplicate existing opened issues. +- _Scoped to a Single Bug._ One bug per report. + +Please adhere to this project's [code of conduct](.github/CODE_OF_CONDUCT.md). + +You can use [markdownlint-cli](https://github.com/stormstack/stormstack/markdownlint-cli) to check for common markdown style inconsistency. + +## Contributors + +Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/docs/en/emoji-key)): + + + + + + + + + + + + + + + +
Patrick Sullivan
Patrick Sullivan

🎨 💻 🔧 📖 ⚠️
Tyler Benning
Tyler Benning

🎨
+ + Add your contributions +
+ + + +This project follows the [all-contributors](https://github.com/all-contributors/all-contributors) specification. Contributions of any kind welcome! + +
+
+
+ +
+ + +
+

Fingerprint: 1BD2 7192 7770 2549 F4C9 F238 E6AD C420 DA5C 4C2D

+
+ +

💻 Visit patsullivan.org to stay up to date with this developer

+ + + + + + diff --git a/tools/forecast/plugins/crud/package.json b/tools/forecast/plugins/crud/package.json new file mode 100644 index 000000000..c50e10040 --- /dev/null +++ b/tools/forecast/plugins/crud/package.json @@ -0,0 +1,8 @@ +{ + "name": "@stormstack/tools-forecast-plugins-crud", + "version": "0.0.1", + "dependencies": { + "path": "^0.12.7", + "ts-morph": "^19.0.0" + } +} diff --git a/tools/forecast/plugins/crud/project.json b/tools/forecast/plugins/crud/project.json new file mode 100644 index 000000000..9288e8885 --- /dev/null +++ b/tools/forecast/plugins/crud/project.json @@ -0,0 +1,77 @@ +{ + "name": "tools-forecast-plugins-crud", + "$schema": "../../../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "tools/forecast/plugins/crud/src", + "projectType": "library", + "targets": { + "build": { + "executor": "@nx/esbuild:esbuild", + "outputs": ["{options.outputPath}"], + "options": { + "outputPath": "dist/tools/forecast/plugins/crud", + "main": "tools/forecast/plugins/crud/src/index.ts", + "project": "tools/forecast/plugins/crud/package.json", + "tsConfig": "tools/forecast/plugins/crud/tsconfig.json", + "platform": "node", + "deleteOutputPath": true, + "bundle": true, + "sourcemap": true, + "minify": true, + "skipTypeCheck": true, + "format": ["esm", "cjs"], + "assets": [ + { + "input": "tools/forecast/plugins/crud", + "glob": "README.md", + "output": "." + }, + { + "input": ".", + "glob": "LICENSE", + "output": "." + } + ] + } + }, + "lint": { + "executor": "@nx/linter:eslint", + "options": { + "lintFilePatterns": [ + "tools/forecast/plugins/crud/**/*.ts", + "tools/forecast/plugins/crud/generators.json", + "tools/forecast/plugins/crud/package.json" + ] + }, + "outputs": ["{options.outputFile}"] + }, + "test": { + "executor": "@nx/jest:jest", + "outputs": ["{workspaceRoot}/coverage/{projectRoot}"], + "options": { + "jestConfig": "tools/forecast/plugins/crud/jest.config.ts", + "passWithNoTests": true + }, + "configurations": { + "ci": { + "ci": true, + "codeCoverage": true + } + } + }, + "semantic-release": { + "executor": "@theunderscorer/nx-semantic-release:semantic-release", + "options": { + "github": true, + "npm": false, + "changelog": true, + "tagFormat": "tools-forecast-plugins-crud-v${VERSION}" + } + } + }, + "tags": ["scope:tools", "platform:server"], + "implicitDependencies": [ + "core-shared-utilities", + "tools-forecast-language", + "tools-forecast-codegen" + ] +} diff --git a/tools/forecast/plugins/crud/src/index.ts b/tools/forecast/plugins/crud/src/index.ts new file mode 100644 index 000000000..497b13dea --- /dev/null +++ b/tools/forecast/plugins/crud/src/index.ts @@ -0,0 +1,1694 @@ +import { + constantCase, + lowerCaseFirst, + upperCaseFirst +} from "@stormstack/core-shared-utilities/common/string-fns"; +import { + Context, + PluginExtend, + PluginOptions +} from "@stormstack/tools-forecast-codegen/types"; +import { + getApiModels, + getDataModels, + getInputs, + getInterfaces, + getOperationGroups +} from "@stormstack/tools-forecast-codegen/utils"; +import { + ApiModel, + DataModel, + DataModelField, + DataModelFieldType, + Enum, + EnumField, + Input, + Interface, + Model, + Operation, + OperationGroup, + OperationInputParam, + isDataModel, + isEnum +} from "@stormstack/tools-forecast-language/ast"; +import { ENTITY_CLASS_FIELDS, EntityClassFields } from "./types"; + +export const name = "CRUD Operations Enhancer Plugin"; + +export const enhance: PluginExtend = async ( + options: PluginOptions, + context: Context +): Promise => { + const model = context.model; + + const dataModels = getDataModels(model); + let operationGroups = getOperationGroups(model); + const inputs = getInputs(model); + const apiModels = getApiModels(model); + const interfaces = getInterfaces(model); + + const enums: Enum[] = model.declarations.reduce((ret, decl) => { + if (decl.$type === Enum) { + ret.push(decl as Enum); + } + + return ret; + }, []); + + operationGroups = addDefaultOperationGroups( + model, + apiModels, + operationGroups + ); + + addQueries( + model, + dataModels, + operationGroups, + inputs, + apiModels, + interfaces, + enums + ); + + addMutations( + model, + dataModels, + operationGroups, + inputs, + apiModels, + interfaces, + enums + ); + + return model; +}; + +const addDefaultOperationGroups = ( + model: Model, + apiModels: ApiModel[], + operationGroups: OperationGroup[] +): OperationGroup[] => { + const rootContainer = + apiModels.length > 0 ? apiModels[0].$container : undefined; + + // Add operation + let queryOperationGroup: OperationGroup = operationGroups.find( + operationGroup => operationGroup.name === "Query" + ); + if (!queryOperationGroup) { + queryOperationGroup = { + $container: rootContainer, + $type: "OperationGroup", + attributes: [], + comments: [], + name: "Query", + fields: [], + isExtend: true, + superTypes: [], + $resolvedFields: [] + }; + model.declarations.push(queryOperationGroup); + operationGroups.push(queryOperationGroup); + } + /*queryOperationGroup.comments.length === 0 && + queryOperationGroup.comments.push( + "The root query typ which gives access to read operations." + );*/ + + let mutationOperationGroup: OperationGroup = operationGroups.find( + operationGroup => operationGroup.name === "Mutation" + ); + if (!mutationOperationGroup) { + mutationOperationGroup = { + $container: rootContainer, + $type: "OperationGroup", + attributes: [], + comments: [], + name: "Mutation", + fields: [], + isExtend: true, + superTypes: [], + $resolvedFields: [] + }; + model.declarations.push(mutationOperationGroup); + operationGroups.push(mutationOperationGroup); + } + /*mutationOperationGroup.comments.length === 0 && + mutationOperationGroup.comments.push( + "The root mutation type which gives access to write operations." + );*/ + + return operationGroups; +}; + +const addQueries = ( + model: Model, + dataModels: DataModel[], + operationGroups: OperationGroup[], + inputs: Input[], + apiModels: ApiModel[], + interfaces: Interface[], + enums: Enum[] +) => { + const rootContainer = + apiModels.length > 0 ? apiModels[0].$container : undefined; + + const queryOperationGroup: OperationGroup = operationGroups.find( + operationGroup => operationGroup.name === "Query" + ); + + const sortOrderEnum: Enum = { + name: "SortOrder", + $container: rootContainer, + $type: "Enum", + attributes: [], + comments: [], + fields: [] + }; + + const sortOrderAscEnumField: EnumField = { + name: "ASC", + $container: sortOrderEnum, + $type: "EnumField", + attributes: [], + comments: [] + }; + sortOrderEnum.fields.push(sortOrderAscEnumField); + + const sortOrderDescEnumField: EnumField = { + name: "DESC", + $container: sortOrderEnum, + $type: "EnumField", + attributes: [], + comments: [] + }; + sortOrderEnum.fields.push(sortOrderDescEnumField); + + enums.push(sortOrderEnum); + + const stringFilter: Input = { + $container: rootContainer, + $type: "Input", + attributes: [], + comments: [], + fields: [], + name: "StringFilter", + superTypes: [], + $resolvedFields: [] + }; + + const stringFilterEqualsField: DataModelField = { + name: "equals", + "$container": stringFilter, + "$type": "DataModelField", + attributes: [], + comments: [], + type: undefined + }; + stringFilterEqualsField.type = { + $container: stringFilterEqualsField, + $type: "DataModelFieldType", + array: false, + optional: true, + type: "String" + } as DataModelFieldType; + stringFilter.fields.push(stringFilterEqualsField); + + const stringFilterInField: DataModelField = { + name: "in", + "$container": stringFilter, + "$type": "DataModelField", + attributes: [], + comments: [], + type: undefined + }; + stringFilterInField.type = { + $container: stringFilterInField, + $type: "DataModelFieldType", + array: true, + optional: true, + type: "String" + } as DataModelFieldType; + stringFilter.fields.push(stringFilterInField); + + const stringFilterNotInField: DataModelField = { + name: "notIn", + "$container": stringFilter, + "$type": "DataModelField", + attributes: [], + comments: [], + type: undefined + }; + stringFilterNotInField.type = { + $container: stringFilterNotInField, + $type: "DataModelFieldType", + array: true, + optional: true, + type: "String" + } as DataModelFieldType; + stringFilter.fields.push(stringFilterNotInField); + + const stringFilterLtField: DataModelField = { + name: "lt", + "$container": stringFilter, + "$type": "DataModelField", + attributes: [], + comments: [], + type: undefined + }; + stringFilterLtField.type = { + $container: stringFilterLtField, + $type: "DataModelFieldType", + array: false, + optional: true, + type: "String" + } as DataModelFieldType; + stringFilter.fields.push(stringFilterLtField); + + const stringFilterLteField: DataModelField = { + name: "lte", + "$container": stringFilter, + "$type": "DataModelField", + attributes: [], + comments: [], + type: undefined + }; + stringFilterLteField.type = { + $container: stringFilterLteField, + $type: "DataModelFieldType", + array: false, + optional: true, + type: "String" + } as DataModelFieldType; + stringFilter.fields.push(stringFilterLteField); + + const stringFilterGtField: DataModelField = { + name: "gt", + "$container": stringFilter, + "$type": "DataModelField", + attributes: [], + comments: [], + type: undefined + }; + stringFilterGtField.type = { + $container: stringFilterGtField, + $type: "DataModelFieldType", + array: false, + optional: true, + type: "String" + } as DataModelFieldType; + stringFilter.fields.push(stringFilterGtField); + + const stringFilterGteField: DataModelField = { + name: "gte", + "$container": stringFilter, + "$type": "DataModelField", + attributes: [], + comments: [], + type: undefined + }; + stringFilterGteField.type = { + $container: stringFilterGteField, + $type: "DataModelFieldType", + array: false, + optional: true, + type: "String" + } as DataModelFieldType; + stringFilter.fields.push(stringFilterGteField); + + const stringFilterContainsField: DataModelField = { + name: "contains", + "$container": stringFilter, + "$type": "DataModelField", + attributes: [], + comments: [], + type: undefined + }; + stringFilterContainsField.type = { + $container: stringFilterContainsField, + $type: "DataModelFieldType", + array: false, + optional: true, + type: "String" + } as DataModelFieldType; + stringFilter.fields.push(stringFilterContainsField); + + const stringFilterStartsWithField: DataModelField = { + name: "startsWith", + "$container": stringFilter, + "$type": "DataModelField", + attributes: [], + comments: [], + type: undefined + }; + stringFilterStartsWithField.type = { + $container: stringFilterStartsWithField, + $type: "DataModelFieldType", + array: false, + optional: true, + type: "String" + } as DataModelFieldType; + stringFilter.fields.push(stringFilterStartsWithField); + + const stringFilterEndsWithField: DataModelField = { + name: "endsWith", + "$container": stringFilter, + "$type": "DataModelField", + attributes: [], + comments: [], + type: undefined + }; + stringFilterEndsWithField.type = { + $container: stringFilterEndsWithField, + $type: "DataModelFieldType", + array: false, + optional: true, + type: "String" + } as DataModelFieldType; + stringFilter.fields.push(stringFilterEndsWithField); + + const stringFilterNotField: DataModelField = { + name: "not", + "$container": stringFilter, + "$type": "DataModelField", + attributes: [], + comments: [], + type: undefined + }; + stringFilterNotField.type = { + $container: stringFilterNotField, + $type: "DataModelFieldType", + array: false, + optional: true, + type: "String" + } as DataModelFieldType; + stringFilter.fields.push(stringFilterNotField); + + const dateTimeFilter: Input = { + $container: rootContainer, + $type: "Input", + attributes: [], + comments: [], + fields: [], + name: "DateTimeFilter", + superTypes: [], + $resolvedFields: [] + }; + + const dateTimeFilterEqualsField: DataModelField = { + name: "equals", + "$container": dateTimeFilter, + "$type": "DataModelField", + attributes: [], + comments: [], + type: undefined + }; + dateTimeFilterEqualsField.type = { + $container: dateTimeFilterEqualsField, + $type: "DataModelFieldType", + array: false, + optional: true, + type: "DateTime" + } as DataModelFieldType; + dateTimeFilter.fields.push(dateTimeFilterEqualsField); + + const dateTimeFilterInField: DataModelField = { + name: "in", + "$container": dateTimeFilter, + "$type": "DataModelField", + attributes: [], + comments: [], + type: undefined + }; + dateTimeFilterInField.type = { + $container: dateTimeFilterInField, + $type: "DataModelFieldType", + array: true, + optional: true, + type: "DateTime" + } as DataModelFieldType; + dateTimeFilter.fields.push(dateTimeFilterInField); + + const dateTimeFilterNotInField: DataModelField = { + name: "notIn", + "$container": dateTimeFilter, + "$type": "DataModelField", + attributes: [], + comments: [], + type: undefined + }; + dateTimeFilterNotInField.type = { + $container: dateTimeFilterNotInField, + $type: "DataModelFieldType", + array: true, + optional: true, + type: "DateTime" + } as DataModelFieldType; + dateTimeFilter.fields.push(dateTimeFilterNotInField); + + const dateTimeFilterLtField: DataModelField = { + name: "lt", + "$container": dateTimeFilter, + "$type": "DataModelField", + attributes: [], + comments: [], + type: undefined + }; + dateTimeFilterLtField.type = { + $container: dateTimeFilterLtField, + $type: "DataModelFieldType", + array: false, + optional: true, + type: "DateTime" + } as DataModelFieldType; + dateTimeFilter.fields.push(dateTimeFilterLtField); + + const dateTimeFilterLteField: DataModelField = { + name: "lte", + "$container": dateTimeFilter, + "$type": "DataModelField", + attributes: [], + comments: [], + type: undefined + }; + dateTimeFilterLteField.type = { + $container: dateTimeFilterLteField, + $type: "DataModelFieldType", + array: false, + optional: true, + type: "DateTime" + } as DataModelFieldType; + dateTimeFilter.fields.push(dateTimeFilterLteField); + + const dateTimeFilterGtField: DataModelField = { + name: "gt", + "$container": dateTimeFilter, + "$type": "DataModelField", + attributes: [], + comments: [], + type: undefined + }; + dateTimeFilterGtField.type = { + $container: dateTimeFilterGtField, + $type: "DataModelFieldType", + array: false, + optional: true, + type: "DateTime" + } as DataModelFieldType; + dateTimeFilter.fields.push(dateTimeFilterGtField); + + const dateTimeFilterGteField: DataModelField = { + name: "gte", + "$container": dateTimeFilter, + "$type": "DataModelField", + attributes: [], + comments: [], + type: undefined + }; + dateTimeFilterGteField.type = { + $container: dateTimeFilterGteField, + $type: "DataModelFieldType", + array: false, + optional: true, + type: "DateTime" + } as DataModelFieldType; + dateTimeFilter.fields.push(dateTimeFilterGteField); + + const dateTimeFilterNotField: DataModelField = { + name: "not", + "$container": dateTimeFilter, + "$type": "DataModelField", + attributes: [], + comments: [], + type: undefined + }; + dateTimeFilterNotField.type = { + $container: dateTimeFilterNotField, + $type: "DataModelFieldType", + array: false, + optional: true, + type: "DateTime" + } as DataModelFieldType; + dateTimeFilter.fields.push(dateTimeFilterNotField); + + const boolFilter: Input = { + $container: rootContainer, + $type: "Input", + attributes: [], + comments: [], + fields: [], + name: "BoolFilter", + superTypes: [], + $resolvedFields: [] + }; + + const boolFilterEqualsField: DataModelField = { + name: "equals", + "$container": boolFilter, + "$type": "DataModelField", + attributes: [], + comments: [], + type: undefined + }; + boolFilterEqualsField.type = { + $container: boolFilterEqualsField, + $type: "DataModelFieldType", + array: false, + optional: true, + type: "Boolean" + } as DataModelFieldType; + boolFilter.fields.push(boolFilterEqualsField); + + const boolFilterNotField: DataModelField = { + name: "not", + "$container": boolFilter, + "$type": "DataModelField", + attributes: [], + comments: [], + type: undefined + }; + boolFilterNotField.type = { + $container: boolFilterNotField, + $type: "DataModelFieldType", + array: false, + optional: true, + type: "Boolean" + } as DataModelFieldType; + boolFilter.fields.push(boolFilterNotField); + + const pageInfo = { + $container: rootContainer, + $type: "ApiModel", + attributes: [], + comments: [ + "A base type to define the structure of paginated response data" + ], + fields: [], + implements: [], + isExtend: false, + name: "PageInfo", + superTypes: [], + $resolvedFields: [] + } as ApiModel; + + const pageInfoHasNextPageField: DataModelField = { + $container: pageInfo, + $type: "DataModelField", + attributes: [], + comments: ["When paginating forwards, are there more items?"], + name: "hasNextPage", + type: undefined + }; + pageInfoHasNextPageField.type = { + $container: pageInfoHasNextPageField, + $type: "DataModelFieldType", + array: false, + optional: false, + type: "Boolean" + } as DataModelFieldType; + pageInfo.fields.push(pageInfoHasNextPageField); + + const pageInfoHasPreviousPageField: DataModelField = { + $container: pageInfo, + $type: "DataModelField", + attributes: [], + comments: ["When paginating backwards, are there more items?"], + name: "hasPreviousPage", + type: undefined + }; + pageInfoHasPreviousPageField.type = { + $container: pageInfoHasPreviousPageField, + $type: "DataModelFieldType", + array: false, + optional: false, + type: "Boolean" + } as DataModelFieldType; + pageInfo.fields.push(pageInfoHasPreviousPageField); + + const pageInfoStartCursorField: DataModelField = { + $container: pageInfo, + $type: "DataModelField", + attributes: [], + comments: ["When paginating backwards, the cursor to continue."], + name: "startCursor", + type: undefined + }; + pageInfoStartCursorField.type = { + $container: pageInfoStartCursorField, + $type: "DataModelFieldType", + array: false, + optional: false, + type: "String" + } as DataModelFieldType; + pageInfo.fields.push(pageInfoStartCursorField); + + const pageInfoEndCursorField: DataModelField = { + $container: pageInfo, + $type: "DataModelField", + attributes: [], + comments: ["When paginating forwards, the cursor to continue."], + name: "endCursor", + type: undefined + }; + pageInfoEndCursorField.type = { + $container: pageInfoEndCursorField, + $type: "DataModelFieldType", + array: false, + optional: false, + type: "String" + } as DataModelFieldType; + pageInfo.fields.push(pageInfoEndCursorField); + + apiModels.push(pageInfo); + + dataModels.forEach(dataModel => { + const selectObjectSchema: Input = { + $container: rootContainer, + $type: "Input", + attributes: [], + comments: [], + fields: [], + name: `${dataModel.name}SelectObjectSchema`, + superTypes: [], + $resolvedFields: [] + }; + + dataModel.fields.forEach(field => { + const selectField: DataModelField = { + name: field.name, + "$container": selectObjectSchema, + "$type": "DataModelField", + attributes: [], + comments: [], + type: undefined + }; + selectField.type = { + $container: selectField, + $type: "DataModelFieldType", + array: false, + optional: true, + type: "Boolean" + } as DataModelFieldType; + selectObjectSchema.fields.push(selectField); + }); + + const countSelectField: DataModelField = { + name: "_count", + "$container": selectObjectSchema, + "$type": "DataModelField", + attributes: [], + comments: [], + type: undefined + }; + countSelectField.type = { + $container: countSelectField, + $type: "DataModelFieldType", + array: false, + optional: true, + type: "Boolean" + } as DataModelFieldType; + + selectObjectSchema.fields.push(countSelectField); + inputs.push(selectObjectSchema); + + const includeObjectSchema: Input = { + $container: rootContainer, + $type: "Input", + attributes: [], + comments: [], + fields: [], + name: `${dataModel.name}IncludeObjectSchema`, + superTypes: [], + $resolvedFields: [] + }; + + dataModel.fields + .filter(field => isDataModel(field.type.reference?.ref)) + .forEach(field => { + const includeField: DataModelField = { + name: field.name, + "$container": includeObjectSchema, + "$type": "DataModelField", + attributes: [], + comments: [], + type: undefined + }; + includeField.type = { + $container: includeField, + $type: "DataModelFieldType", + array: false, + optional: true, + type: "Boolean" + } as DataModelFieldType; + includeObjectSchema.fields.push(includeField); + }); + + const countIncludeField: DataModelField = { + name: "_count", + "$container": includeObjectSchema, + "$type": "DataModelField", + attributes: [], + comments: [], + type: undefined + }; + countIncludeField.type = { + $container: countIncludeField, + $type: "DataModelFieldType", + array: false, + optional: true, + type: "Boolean" + } as DataModelFieldType; + + includeObjectSchema.fields.push(countIncludeField); + inputs.push(includeObjectSchema); + + const whereManyInputObjectSchema: Input = { + $container: rootContainer, + $type: "Input", + attributes: [], + comments: [], + fields: [], + name: `${dataModel.name}WhereManyInputObjectSchema`, + superTypes: [], + $resolvedFields: [] + }; + + const whereManyInputObjectSchemaAndField: DataModelField = { + name: "AND", + "$container": whereManyInputObjectSchema, + "$type": "DataModelField", + attributes: [], + comments: [], + type: undefined + }; + whereManyInputObjectSchemaAndField.type = { + $container: whereManyInputObjectSchemaAndField, + $type: "DataModelFieldType", + array: true, + optional: true, + reference: { + ref: whereManyInputObjectSchema, + $refText: "whereManyInputObjectSchema" + } + }; + whereManyInputObjectSchema.fields.push(whereManyInputObjectSchemaAndField); + + const whereManyInputObjectSchemaOrField: DataModelField = { + name: "OR", + "$container": whereManyInputObjectSchema, + "$type": "DataModelField", + attributes: [], + comments: [], + type: undefined + }; + whereManyInputObjectSchemaOrField.type = { + $container: whereManyInputObjectSchemaOrField, + $type: "DataModelFieldType", + array: true, + optional: true, + reference: { + ref: whereManyInputObjectSchema, + $refText: "whereManyInputObjectSchema" + } + }; + whereManyInputObjectSchema.fields.push(whereManyInputObjectSchemaOrField); + + const whereManyInputObjectSchemaNotField: DataModelField = { + name: "NOT", + "$container": whereManyInputObjectSchema, + "$type": "DataModelField", + attributes: [], + comments: [], + type: undefined + }; + whereManyInputObjectSchemaNotField.type = { + $container: whereManyInputObjectSchemaNotField, + $type: "DataModelFieldType", + array: true, + optional: true, + reference: { + ref: whereManyInputObjectSchema, + $refText: "whereManyInputObjectSchema" + } + }; + whereManyInputObjectSchema.fields.push(whereManyInputObjectSchemaNotField); + + dataModel.fields.forEach(field => { + if (field.type.type === "DateTime") { + const whereInputObjectSchemaDateTimeField: DataModelField = { + name: field.name, + "$container": whereManyInputObjectSchema, + "$type": "DataModelField", + attributes: [], + comments: [], + type: undefined + }; + whereInputObjectSchemaDateTimeField.type = { + $container: whereInputObjectSchemaDateTimeField, + $type: "DataModelFieldType", + array: false, + optional: true, + reference: { ref: dateTimeFilter, $refText: "dateTimeFilter" } + }; + whereManyInputObjectSchema.fields.push( + whereInputObjectSchemaDateTimeField + ); + } else if (field.type.type === "Boolean") { + const whereInputObjectSchemaBooleanField: DataModelField = { + name: field.name, + "$container": whereManyInputObjectSchema, + "$type": "DataModelField", + attributes: [], + comments: [], + type: undefined + }; + whereInputObjectSchemaBooleanField.type = { + $container: whereInputObjectSchemaBooleanField, + $type: "DataModelFieldType", + array: false, + optional: true, + reference: { ref: boolFilter, $refText: "boolFilter" } + }; + whereManyInputObjectSchema.fields.push( + whereInputObjectSchemaBooleanField + ); + } else { + const whereInputObjectSchemaStringField: DataModelField = { + name: field.name, + "$container": whereManyInputObjectSchema, + "$type": "DataModelField", + attributes: [], + comments: [], + type: undefined + }; + whereInputObjectSchemaStringField.type = { + $container: whereInputObjectSchemaStringField, + $type: "DataModelFieldType", + array: false, + optional: true, + reference: { ref: stringFilter, $refText: "stringFilter" } + }; + whereManyInputObjectSchema.fields.push( + whereInputObjectSchemaStringField + ); + } + }); + + inputs.push(whereManyInputObjectSchema); + + const whereUniqueInputObjectSchema: Input = { + $container: rootContainer, + $type: "Input", + attributes: [], + comments: [], + fields: [], + name: `${dataModel.name}WhereUniqueInputObjectSchema`, + superTypes: [], + $resolvedFields: [] + }; + + const whereUniqueInputObjectSchemaIdField: DataModelField = { + name: "id", + "$container": whereUniqueInputObjectSchema, + "$type": "DataModelField", + attributes: [], + comments: [], + type: undefined + }; + whereUniqueInputObjectSchemaIdField.type = { + $container: whereUniqueInputObjectSchemaIdField, + $type: "DataModelFieldType", + array: false, + optional: true, + type: "String" + }; + whereUniqueInputObjectSchema.fields.push( + whereUniqueInputObjectSchemaIdField + ); + + const whereUniqueInputObjectSchemaAndField: DataModelField = { + name: "AND", + "$container": whereUniqueInputObjectSchema, + "$type": "DataModelField", + attributes: [], + comments: [], + type: undefined + }; + whereUniqueInputObjectSchemaAndField.type = { + $container: whereUniqueInputObjectSchemaAndField, + $type: "DataModelFieldType", + array: true, + optional: true, + reference: { + ref: whereUniqueInputObjectSchema, + $refText: "whereUniqueInputObjectSchema" + } + }; + whereUniqueInputObjectSchema.fields.push( + whereUniqueInputObjectSchemaAndField + ); + + const whereUniqueInputObjectSchemaOrField: DataModelField = { + name: "OR", + "$container": whereUniqueInputObjectSchema, + "$type": "DataModelField", + attributes: [], + comments: [], + type: undefined + }; + whereUniqueInputObjectSchemaOrField.type = { + $container: whereUniqueInputObjectSchemaOrField, + $type: "DataModelFieldType", + array: true, + optional: true, + reference: { + ref: whereUniqueInputObjectSchema, + $refText: "whereUniqueInputObjectSchema" + } + }; + whereUniqueInputObjectSchema.fields.push( + whereUniqueInputObjectSchemaOrField + ); + + const whereUniqueInputObjectSchemaNotField: DataModelField = { + name: "NOT", + "$container": whereUniqueInputObjectSchema, + "$type": "DataModelField", + attributes: [], + comments: [], + type: undefined + }; + whereUniqueInputObjectSchemaNotField.type = { + $container: whereUniqueInputObjectSchemaNotField, + $type: "DataModelFieldType", + array: true, + optional: true, + reference: { + ref: whereUniqueInputObjectSchema, + $refText: "whereUniqueInputObjectSchema" + } + }; + whereUniqueInputObjectSchema.fields.push( + whereUniqueInputObjectSchemaNotField + ); + + dataModel.fields.forEach(field => { + if (field.name !== "id") { + if (field.type.type === "DateTime") { + const whereInputObjectSchemaDateTimeField: DataModelField = { + name: field.name, + "$container": whereUniqueInputObjectSchema, + "$type": "DataModelField", + attributes: [], + comments: [], + type: undefined + }; + whereInputObjectSchemaDateTimeField.type = { + $container: whereInputObjectSchemaDateTimeField, + $type: "DataModelFieldType", + array: false, + optional: true, + reference: { ref: dateTimeFilter, $refText: "dateTimeFilter" } + }; + whereUniqueInputObjectSchema.fields.push( + whereInputObjectSchemaDateTimeField + ); + } else if (field.type.type === "Boolean") { + const whereInputObjectSchemaBooleanField: DataModelField = { + name: field.name, + "$container": whereUniqueInputObjectSchema, + "$type": "DataModelField", + attributes: [], + comments: [], + type: undefined + }; + whereInputObjectSchemaBooleanField.type = { + $container: whereInputObjectSchemaBooleanField, + $type: "DataModelFieldType", + array: false, + optional: true, + reference: { ref: boolFilter, $refText: "boolFilter" } + }; + whereUniqueInputObjectSchema.fields.push( + whereInputObjectSchemaBooleanField + ); + } else { + const whereInputObjectSchemaStringField: DataModelField = { + name: field.name, + "$container": whereUniqueInputObjectSchema, + "$type": "DataModelField", + attributes: [], + comments: [], + type: undefined + }; + whereInputObjectSchemaStringField.type = { + $container: whereInputObjectSchemaStringField, + $type: "DataModelFieldType", + array: false, + optional: true, + reference: { ref: stringFilter, $refText: "stringFilter" } + }; + whereUniqueInputObjectSchema.fields.push( + whereInputObjectSchemaStringField + ); + } + } + }); + + inputs.push(whereUniqueInputObjectSchema); + + const orderByObjectSchema: Input = { + $container: rootContainer, + $type: "Input", + attributes: [], + comments: [], + fields: [], + name: `${dataModel.name}OrderByObjectSchema`, + superTypes: [], + $resolvedFields: [] + }; + + dataModel.fields.forEach(field => { + const orderByObjectSchemaField: DataModelField = { + name: field.name, + "$container": orderByObjectSchema, + "$type": "DataModelField", + attributes: [], + comments: [], + type: undefined + }; + orderByObjectSchemaField.type = { + $container: orderByObjectSchemaField, + $type: "DataModelFieldType", + array: false, + optional: true, + reference: { + ref: sortOrderEnum, + $refText: "sortOrderEnum" + } + }; + orderByObjectSchema.fields.push(orderByObjectSchemaField); + }); + + inputs.push(orderByObjectSchema); + + const modelFieldEnum: Enum = { + name: `${upperCaseFirst(dataModel.name)}Field`, + $container: rootContainer, + $type: "Enum", + attributes: [], + comments: [], + fields: [] + }; + + dataModel.fields.forEach(field => { + const modelFieldEnumField: EnumField = { + name: constantCase(field.name), + $container: modelFieldEnum, + $type: "EnumField", + attributes: [], + comments: [] + }; + modelFieldEnum.fields.push(modelFieldEnumField); + }); + + enums.push(modelFieldEnum); + + // Add "FindUnique" selector input + const findUniqueSelectorInput: Input = { + $container: rootContainer, + $type: "Input", + attributes: [], + comments: [], + fields: [], + name: `${upperCaseFirst(dataModel.name)}SelectorInput`, + superTypes: [], + $resolvedFields: [] + }; + + const findUniqueSelectField: DataModelField = { + name: "select", + "$container": findUniqueSelectorInput, + "$type": "DataModelField", + attributes: [], + comments: [], + type: undefined + }; + findUniqueSelectField.type = { + $container: findUniqueSelectField, + $type: "DataModelFieldType", + array: false, + optional: true, + reference: { + ref: selectObjectSchema, + $refText: "selectObjectSchema" + } + } as DataModelFieldType; + findUniqueSelectorInput.fields.push(findUniqueSelectField); + + const findUniqueIncludeField: DataModelField = { + name: "include", + "$container": findUniqueSelectorInput, + "$type": "DataModelField", + attributes: [], + comments: [], + type: undefined + }; + findUniqueIncludeField.type = { + $container: findUniqueIncludeField, + $type: "DataModelFieldType", + array: false, + optional: true, + reference: { + ref: includeObjectSchema, + $refText: "includeObjectSchema" + } + } as DataModelFieldType; + findUniqueSelectorInput.fields.push(findUniqueIncludeField); + + const findUniqueWhereField: DataModelField = { + name: "where", + "$container": findUniqueSelectorInput, + "$type": "DataModelField", + attributes: [], + comments: [], + type: undefined + }; + findUniqueWhereField.type = { + $container: findUniqueWhereField, + $type: "DataModelFieldType", + array: false, + optional: false, + reference: { + ref: whereUniqueInputObjectSchema, + $refText: "whereUniqueInputObjectSchema" + } + } as DataModelFieldType; + findUniqueSelectorInput.fields.push(findUniqueWhereField); + + inputs.push(findUniqueSelectorInput); + + // Add "FindMany" selector input + const findManySelectorInput: Input = { + $container: rootContainer, + $type: "Input", + attributes: [], + comments: [], + fields: [], + name: `${upperCaseFirst(dataModel.name)}sSelectorInput`, + superTypes: [], + $resolvedFields: [] + }; + + const findManySelectField: DataModelField = { + name: "select", + "$container": findManySelectorInput, + "$type": "DataModelField", + attributes: [], + comments: [], + type: undefined + }; + findManySelectField.type = { + $container: findManySelectField, + $type: "DataModelFieldType", + array: false, + optional: true, + reference: { + ref: selectObjectSchema, + $refText: "selectObjectSchema" + } + } as DataModelFieldType; + findManySelectorInput.fields.push(findManySelectField); + + const findManyIncludeField: DataModelField = { + name: "include", + "$container": findManySelectorInput, + "$type": "DataModelField", + attributes: [], + comments: [], + type: undefined + }; + findManyIncludeField.type = { + $container: findManyIncludeField, + $type: "DataModelFieldType", + array: false, + optional: true, + reference: { + ref: includeObjectSchema, + $refText: "includeObjectSchema" + } + } as DataModelFieldType; + findManySelectorInput.fields.push(findManyIncludeField); + + const findManyWhereField: DataModelField = { + name: "where", + "$container": findManySelectorInput, + "$type": "DataModelField", + attributes: [], + comments: [], + type: undefined + }; + findManyWhereField.type = { + $container: findManyWhereField, + $type: "DataModelFieldType", + array: false, + optional: true, + reference: { + ref: whereManyInputObjectSchema, + $refText: "whereManyInputObjectSchema" + } + } as DataModelFieldType; + findManySelectorInput.fields.push(findManyWhereField); + + const findManyOrderByField: DataModelField = { + name: "orderBy", + "$container": findManySelectorInput, + "$type": "DataModelField", + attributes: [], + comments: [], + type: undefined + }; + findManyOrderByField.type = { + $container: findManyOrderByField, + $type: "DataModelFieldType", + array: true, + optional: true, + reference: { + ref: orderByObjectSchema, + $refText: "orderByObjectSchema" + } + } as DataModelFieldType; + findManySelectorInput.fields.push(findManyOrderByField); + + const findManyCursorField: DataModelField = { + name: "cursor", + "$container": findManySelectorInput, + "$type": "DataModelField", + attributes: [], + comments: [], + type: undefined + }; + findManyCursorField.type = { + $container: findManyCursorField, + $type: "DataModelFieldType", + array: false, + optional: true, + reference: { + ref: whereUniqueInputObjectSchema, + $refText: "whereUniqueInputObjectSchema" + } + } as DataModelFieldType; + findManySelectorInput.fields.push(findManyCursorField); + + const findManyTakeField: DataModelField = { + name: "take", + "$container": findManySelectorInput, + "$type": "DataModelField", + attributes: [], + comments: [], + type: undefined + }; + findManyTakeField.type = { + $container: findManyTakeField, + $type: "DataModelFieldType", + array: false, + optional: true, + type: "Int" + } as DataModelFieldType; + findManySelectorInput.fields.push(findManyTakeField); + + const findManySkipField: DataModelField = { + name: "skip", + "$container": findManySelectorInput, + "$type": "DataModelField", + attributes: [], + comments: [], + type: undefined + }; + findManySkipField.type = { + $container: findManySkipField, + $type: "DataModelFieldType", + array: false, + optional: true, + type: "Int" + } as DataModelFieldType; + findManySelectorInput.fields.push(findManySkipField); + + const findManyDistinctField: DataModelField = { + name: "distinct", + "$container": findManySelectorInput, + "$type": "DataModelField", + attributes: [], + comments: [], + type: undefined + }; + findManyDistinctField.type = { + $container: findManyDistinctField, + $type: "DataModelFieldType", + array: false, + optional: true, + reference: { + ref: modelFieldEnum, + $refText: "modelFieldEnum" + } + } as DataModelFieldType; + findManySelectorInput.fields.push(findManyDistinctField); + + inputs.push(findManySelectorInput); + + const findUniqueOperation: Operation = { + $container: queryOperationGroup, + $type: "Operation", + attributes: [], + comments: [], + params: [], + name: `${lowerCaseFirst(dataModel.name)}`, + resultType: undefined + }; + findUniqueOperation.resultType = { + $container: findUniqueOperation, + $type: "DataModelFieldType", + array: false, + optional: false, + reference: { + ref: dataModel, + $refText: dataModel.name + } + } as DataModelFieldType; + + const findUniqueOperationParam: OperationInputParam = { + $container: findUniqueOperation, + $type: "OperationInputParam", + default: false, + name: "selector", + type: undefined + }; + findUniqueOperationParam.type = { + $container: findUniqueOperationParam, + $type: "DataModelFieldType", + array: false, + optional: false, + reference: { + ref: findUniqueSelectorInput, + $refText: "selectorInput" + } + } as DataModelFieldType; + + findUniqueOperation.params.push(findUniqueOperationParam); + queryOperationGroup.fields.push(findUniqueOperation); + + const edgeObjectSchema = { + $container: rootContainer, + $type: "ApiModel", + attributes: [], + comments: [ + `An object containing the current paginated result data returned from ${dataModel.name} at the current cursor position` + ], + fields: [], + implements: [], + isExtend: false, + name: `${upperCaseFirst(dataModel.name)}Edge`, + superTypes: [], + $resolvedFields: [] + } as ApiModel; + + const edgeNodeObjectSchema: DataModelField = { + $container: edgeObjectSchema, + $type: "DataModelField", + attributes: [], + comments: [], + name: "node", + type: undefined + }; + edgeNodeObjectSchema.type = { + $container: edgeNodeObjectSchema, + $type: "DataModelFieldType", + array: false, + optional: false, + reference: { + ref: dataModel, + $refText: dataModel.name + } + } as DataModelFieldType; + edgeObjectSchema.fields.push(edgeNodeObjectSchema); + + const edgeCursorObjectSchema: DataModelField = { + $container: edgeObjectSchema, + $type: "DataModelField", + attributes: [], + comments: [], + name: "cursor", + type: undefined + }; + edgeCursorObjectSchema.type = { + $container: edgeCursorObjectSchema, + $type: "DataModelFieldType", + array: false, + optional: false, + type: "String" + } as DataModelFieldType; + edgeObjectSchema.fields.push(edgeCursorObjectSchema); + + apiModels.push(edgeObjectSchema); + + const connectionObjectSchema = { + $container: rootContainer, + $type: "ApiModel", + attributes: [], + comments: [ + `An object containing the paginated child model data returned from ${dataModel.name}` + ], + fields: [], + implements: [], + isExtend: false, + name: `${upperCaseFirst(dataModel.name)}Connection`, + superTypes: [], + $resolvedFields: [] + } as ApiModel; + + const connectionEdgesObjectSchema: DataModelField = { + $container: connectionObjectSchema, + $type: "DataModelField", + attributes: [], + comments: [], + name: "edges", + type: undefined + }; + connectionEdgesObjectSchema.type = { + $container: connectionEdgesObjectSchema, + $type: "DataModelFieldType", + array: true, + optional: false, + reference: { + ref: edgeObjectSchema, + $refText: edgeObjectSchema.name + } + } as DataModelFieldType; + connectionObjectSchema.fields.push(connectionEdgesObjectSchema); + + const connectionPageInfoObjectSchema: DataModelField = { + $container: connectionObjectSchema, + $type: "DataModelField", + attributes: [], + comments: [], + name: "pageInfo", + type: undefined + }; + connectionPageInfoObjectSchema.type = { + $container: connectionPageInfoObjectSchema, + $type: "DataModelFieldType", + array: false, + optional: false, + reference: { + ref: pageInfo, + $refText: pageInfo.name + } + } as DataModelFieldType; + connectionObjectSchema.fields.push(connectionPageInfoObjectSchema); + + const connectionTotalCountObjectSchema: DataModelField = { + $container: connectionObjectSchema, + $type: "DataModelField", + attributes: [], + comments: [], + name: "totalCount", + type: undefined + }; + connectionTotalCountObjectSchema.type = { + $container: connectionTotalCountObjectSchema, + $type: "DataModelFieldType", + array: false, + optional: false, + type: "Int" + } as DataModelFieldType; + connectionObjectSchema.fields.push(connectionTotalCountObjectSchema); + + apiModels.push(connectionObjectSchema); + + const findManyOperation: Operation = { + $container: queryOperationGroup, + $type: "Operation", + attributes: [], + comments: [], + params: [], + name: `${lowerCaseFirst(dataModel.name)}s`, + resultType: undefined + }; + findManyOperation.resultType = { + $container: findManyOperation, + $type: "DataModelFieldType", + array: false, + optional: false, + reference: { + ref: connectionObjectSchema, + $refText: connectionObjectSchema.name + } + } as DataModelFieldType; + + const findManyOperationParam: OperationInputParam = { + $container: findManyOperation, + $type: "OperationInputParam", + default: false, + name: "selector", + type: undefined + }; + findManyOperationParam.type = { + $container: findManyOperationParam, + $type: "DataModelFieldType", + array: false, + optional: false, + reference: { + ref: findManySelectorInput, + $refText: "selectorInput" + } + } as DataModelFieldType; + + findManyOperation.params.push(findManyOperationParam); + queryOperationGroup.fields.push(findManyOperation); + }); + + inputs.push(dateTimeFilter); + inputs.push(boolFilter); + inputs.push(stringFilter); +}; + +const addMutations = ( + model: Model, + dataModels: DataModel[], + operationGroups: OperationGroup[], + inputs: Input[], + apiModels: ApiModel[], + interfaces: Interface[], + enums: Enum[] +) => { + const rootContainer = + apiModels.length > 0 ? apiModels[0].$container : undefined; + + const mutationOperationGroup: OperationGroup = operationGroups.find( + operationGroup => operationGroup.name === "Mutation" + ); + + dataModels.forEach(dataModel => { + const createDataModelInput: Input = { + $container: rootContainer, + $type: "Input", + attributes: [], + comments: [], + fields: [], + name: `Create${dataModel.name}Input`, + superTypes: [], + $resolvedFields: [] + }; + + dataModel.fields.forEach(field => { + if (!ENTITY_CLASS_FIELDS.includes(field.name as EntityClassFields)) { + const createDataModelInputDataField: DataModelField = { + name: field.name, + "$container": createDataModelInput, + "$type": "DataModelField", + attributes: [], + comments: [], + type: undefined + }; + if ( + isDataModel(field.type.reference?.ref) || + isEnum(field.type.reference?.ref) + ) { + createDataModelInputDataField.type = { + $container: createDataModelInputDataField, + $type: "DataModelFieldType", + array: field.type.array, + optional: field.type.optional, + reference: { + ref: field.type.reference.ref, + $refText: field.type.reference.ref.name + } + }; + } else { + createDataModelInputDataField.type = { + $container: createDataModelInputDataField, + $type: "DataModelFieldType", + array: field.type.array, + optional: field.type.optional, + type: field.type.type + }; + } + + createDataModelInput.fields.push(createDataModelInputDataField); + } + }); + + inputs.push(createDataModelInput); + + const createDataModelOperation: Operation = { + $container: mutationOperationGroup, + $type: "Operation", + attributes: [], + comments: [], + params: [], + name: `create${upperCaseFirst(dataModel.name)}`, + resultType: undefined + }; + createDataModelOperation.resultType = { + $container: createDataModelOperation, + $type: "DataModelFieldType", + array: false, + optional: false, + reference: { + ref: dataModel, + $refText: dataModel.name + } + } as DataModelFieldType; + + const createDataModelOperationDataParam: OperationInputParam = { + $container: createDataModelOperation, + $type: "OperationInputParam", + default: false, + name: "data", + type: undefined + }; + createDataModelOperationDataParam.type = { + $container: createDataModelOperationDataParam, + $type: "DataModelFieldType", + array: false, + optional: false, + reference: { + ref: createDataModelInput, + $refText: `Create${dataModel.name}DataInput` + } + } as DataModelFieldType; + + createDataModelOperation.params.push(createDataModelOperationDataParam); + mutationOperationGroup.fields.push(createDataModelOperation); + }); + + return { + dataModels, + operationGroups, + inputs, + apiModels, + interfaces, + enums + }; +}; diff --git a/tools/forecast/plugins/crud/src/types.ts b/tools/forecast/plugins/crud/src/types.ts new file mode 100644 index 000000000..c39d68e87 --- /dev/null +++ b/tools/forecast/plugins/crud/src/types.ts @@ -0,0 +1,20 @@ +export type AggregateOperationSupport = { + [model: string]: { + count?: boolean; + min?: boolean; + max?: boolean; + sum?: boolean; + avg?: boolean; + }; +}; + +export const ENTITY_CLASS_FIELDS = [ + "id", + "createdAt", + "createdBy", + "updatedAt", + "updatedBy", + "sequence" +] as const; + +export type EntityClassFields = (typeof ENTITY_CLASS_FIELDS)[number]; diff --git a/tools/forecast/plugins/crud/tsconfig.json b/tools/forecast/plugins/crud/tsconfig.json new file mode 100644 index 000000000..8373de338 --- /dev/null +++ b/tools/forecast/plugins/crud/tsconfig.json @@ -0,0 +1,20 @@ +{ + "extends": "../../../../tsconfig.base.json", + "compilerOptions": { + "outDir": "../../../../dist/out-tsc", + "module": "CommonJS", + "target": "ES6", + "types": ["node"], + "declaration": true, + "strict": false, + "noImplicitAny": false, + "moduleResolution": "node", + "esModuleInterop": true, + "skipLibCheck": true, + "resolveJsonModule": true, + "lib": ["ESNext"], + "sourceMap": true + }, + "exclude": ["**/*.spec.ts", "**/*.test.ts", "jest.config.ts"], + "include": ["**/*.ts", "**/*.tsx", "**/*.js", "**/*.jsx"] +} diff --git a/tools/forecast/plugins/meta/.eslintrc.json b/tools/forecast/plugins/meta/.eslintrc.json new file mode 100644 index 000000000..536c1c01f --- /dev/null +++ b/tools/forecast/plugins/meta/.eslintrc.json @@ -0,0 +1,28 @@ +{ + "extends": ["../../../../.eslintrc.json"], + "ignorePatterns": ["!**/*"], + "overrides": [ + { + "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], + "parserOptions": { + "project": ["tools/forecast/plugins/meta/tsconfig.*?.json"] + }, + "rules": {} + }, + { + "files": ["*.ts", "*.tsx"], + "rules": {} + }, + { + "files": ["*.js", "*.jsx"], + "rules": {} + }, + { + "files": ["*.json"], + "parser": "jsonc-eslint-parser", + "rules": { + "@nx/dependency-checks": "error" + } + } + ] +} diff --git a/tools/forecast/plugins/meta/README.md b/tools/forecast/plugins/meta/README.md new file mode 100644 index 000000000..c46a16d39 --- /dev/null +++ b/tools/forecast/plugins/meta/README.md @@ -0,0 +1,144 @@ + + + + + +
+ + + +
+The Open System is a monorepo containing modern, scalable web application code, additional utility applications/tools, various libraries, and a fully featured, serverless back-end framework. The Open System is built using Nx, a set of extensible dev tools for monorepos, which helps you develop like Google, Facebook, and Microsoft. Building on top of Nx, the Open System provides a set of tools and patterns that help you scale your monorepo to many teams while keeping the codebase maintainable. + +

💻 Visit patsullivan.org to stay up to date with this developer

+ +[![Version](https://img.shields.io/badge/version-0.0.1-10B981.svg?style=for-the-badge&color=10B981)](https://prettier.io/)  +[![Nx](https://img.shields.io/badge/Nx-14.4.2-lightgrey?style=for-the-badge&logo=nx&logoWidth=20&&color=10B981)](http://nx.dev/) [![NextJs](https://img.shields.io/badge/Next.js-13.0.5-lightgrey?style=for-the-badge&logo=nextdotjs&logoWidth=20&color=10B981)](https://nextjs.org/) [![codecov.io](https://img.shields.io/codecov/c/github/commitizen/cz-cli.svg?style=for-the-badge&color=10B981)](https://codecov.io/github/commitizen/cz-cli?branch=master) [![Commitizen friendly](https://img.shields.io/badge/commitizen-friendly-brightgreen.svg?style=for-the-badge&logo=commitlint&color=10B981)](http://commitizen.github.io/cz-cli/) ![Semantic-Release](https://img.shields.io/badge/%20%20%F0%9F%93%A6%F0%9F%9A%80-semantic--release-e10079.svg?style=for-the-badge&color=10B981) [![documented with docusaurus](https://img.shields.io/badge/documented_with-docusaurus-success.svg?style=for-the-badge&logo=readthedocs&color=10B981)](https://docusaurus.io/) + + + + + + + +# ⚡ Storm - Drizzle ORM Plugin + +This library was generated with [Nx](https://nx.dev). + + + + +## Table of Contents + +- [⚡ Storm - Drizzle ORM Plugin](#-storm---drizzle-orm-plugin) + - [Table of Contents](#table-of-contents) + - [Running unit tests](#running-unit-tests) + - [Roadmap](#roadmap) + - [Support](#support) + - [License](#license) + - [Changelog](#changelog) + - [Contributing](#contributing) + - [Contributors](#contributors) + + + +## Running unit tests + +Run `nx test tools-forecast-plugins-drizzle` to execute the unit tests via [Jest](https://jestjs.io). + + + + + + +## Roadmap + +See the [open issues](https://github.com/stormstack/stormstack/issues) for a list of proposed features (and known issues). + +- [Top Feature Requests](https://github.com/stormstack/stormstack/issues?q=label%3Aenhancement+is%3Aopen+sort%3Areactions-%2B1-desc) (Add your votes using the 👍 reaction) +- [Top Bugs](https://github.com/stormstack/stormstack/issues?q=is%3Aissue+is%3Aopen+label%3Abug+sort%3Areactions-%2B1-desc) (Add your votes using the 👍 reaction) +- [Newest Bugs](https://github.com/stormstack/stormstack/issues?q=is%3Aopen+is%3Aissue+label%3Abug) + +## Support + +Reach out to the maintainer at one of the following places: + +- [Contact](https://www.patsullivan.org/contact) +- [GitHub discussions](https://github.com/stormstack/stormstack/discussions) +- + +## License + +This project is licensed under the **BSD-2-Clause license**. Feel free to edit and distribute this template as you like. + +See [LICENSE](LICENSE) for more information. + +## Changelog + +This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). Every release, along with the migration instructions, is documented in the [CHANGELOG](CHANGELOG.md) file + +## Contributing + +First off, thanks for taking the time to contribute! Contributions are what makes the open-source community such an amazing place to learn, inspire, and create. Any contributions you make will benefit everybody else and are **greatly appreciated**. + +Please try to create bug reports that are: + +- _Reproducible._ Include steps to reproduce the problem. +- _Specific._ Include as much detail as possible: which version, what environment, etc. +- _Unique._ Do not duplicate existing opened issues. +- _Scoped to a Single Bug._ One bug per report. + +Please adhere to this project's [code of conduct](.github/CODE_OF_CONDUCT.md). + +You can use [markdownlint-cli](https://github.com/stormstack/stormstack/markdownlint-cli) to check for common markdown style inconsistency. + +## Contributors + +Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/docs/en/emoji-key)): + + + + + + + + + + + + + + + +
Patrick Sullivan
Patrick Sullivan

🎨 💻 🔧 📖 ⚠️
Tyler Benning
Tyler Benning

🎨
+ + + Add your contributions +
+ + + +This project follows the [all-contributors](https://github.com/all-contributors/all-contributors) specification. Contributions of any kind welcome! + +
+
+
+ +
+ + +
+

Fingerprint: 1BD2 7192 7770 2549 F4C9 F238 E6AD C420 DA5C 4C2D

+
+ +

💻 Visit patsullivan.org to stay up to date with this developer

+ + + + + + diff --git a/tools/forecast/plugins/meta/package.json b/tools/forecast/plugins/meta/package.json new file mode 100644 index 000000000..46c1cb4a5 --- /dev/null +++ b/tools/forecast/plugins/meta/package.json @@ -0,0 +1,4 @@ +{ + "name": "@stormstack/tools-forecast-plugins-meta", + "version": "0.0.1" +} diff --git a/tools/forecast/plugins/meta/project.json b/tools/forecast/plugins/meta/project.json new file mode 100644 index 000000000..29245fc05 --- /dev/null +++ b/tools/forecast/plugins/meta/project.json @@ -0,0 +1,76 @@ +{ + "name": "tools-forecast-plugins-meta", + "$schema": "../../../../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "tools/forecast/plugins/meta/src", + "projectType": "library", + "targets": { + "build": { + "executor": "@nx/esbuild:esbuild", + "outputs": ["{options.outputPath}"], + "options": { + "outputPath": "dist/forecast/plugins/meta", + "main": "tools/forecast/plugins/meta/src/index.ts", + "project": "tools/forecast/plugins/meta/package.json", + "tsConfig": "tools/forecast/plugins/meta/tsconfig.json", + "platform": "node", + "deleteOutputPath": true, + "bundle": true, + "sourcemap": true, + "minify": true, + "skipTypeCheck": true, + "format": ["esm", "cjs"], + "assets": [ + { + "input": "tools/forecast/plugins/meta", + "glob": "README.md", + "output": "." + }, + { + "input": ".", + "glob": "LICENSE", + "output": "." + } + ] + } + }, + "lint": { + "executor": "@nx/linter:eslint", + "options": { + "lintFilePatterns": [ + "tools/forecast/plugins/meta/**/*.ts", + "tools/forecast/plugins/meta/package.json" + ] + }, + "outputs": ["{options.outputFile}"] + }, + "test": { + "executor": "@nx/jest:jest", + "outputs": ["{workspaceRoot}/coverage/{projectRoot}"], + "options": { + "jestConfig": "tools/forecast/plugins/meta/jest.config.ts", + "passWithNoTests": true + }, + "configurations": { + "ci": { + "ci": true, + "codeCoverage": true + } + } + }, + "semantic-release": { + "executor": "@theunderscorer/nx-semantic-release:semantic-release", + "options": { + "github": true, + "npm": false, + "changelog": true, + "tagFormat": "tools-forecast-plugins-meta-v${VERSION}" + } + } + }, + "tags": ["scope:tools", "platform:server"], + "implicitDependencies": [ + "core-shared-utilities", + "tools-forecast-language", + "tools-forecast-codegen" + ] +} diff --git a/tools/forecast/codegen/src/plugins/model-meta/index.ts b/tools/forecast/plugins/meta/src/index.ts similarity index 100% rename from tools/forecast/codegen/src/plugins/model-meta/index.ts rename to tools/forecast/plugins/meta/src/index.ts diff --git a/tools/forecast/plugins/meta/tsconfig.json b/tools/forecast/plugins/meta/tsconfig.json new file mode 100644 index 000000000..82178aee6 --- /dev/null +++ b/tools/forecast/plugins/meta/tsconfig.json @@ -0,0 +1,13 @@ +{ + "extends": "../../../../tsconfig.base.json", + "compilerOptions": { + "outDir": "../../../../dist/out-tsc", + "module": "commonjs", + "target": "es2022", + "declaration": true, + "noImplicitAny": false, + "sourceMap": true + }, + "exclude": ["**/*.spec.ts", "**/*.test.ts", "jest.config.ts"], + "include": ["**/*.ts", "**/*.tsx", "**/*.js", "**/*.jsx"] +} diff --git a/tools/forecast/plugins/prisma/.eslintrc.json b/tools/forecast/plugins/prisma/.eslintrc.json new file mode 100644 index 000000000..c2ff268ce --- /dev/null +++ b/tools/forecast/plugins/prisma/.eslintrc.json @@ -0,0 +1,28 @@ +{ + "extends": ["../../../../.eslintrc.json"], + "ignorePatterns": ["!**/*"], + "overrides": [ + { + "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], + "parserOptions": { + "project": ["tools/forecast/plugins/prisma/tsconfig.*?.json"] + }, + "rules": {} + }, + { + "files": ["*.ts", "*.tsx"], + "rules": {} + }, + { + "files": ["*.js", "*.jsx"], + "rules": {} + }, + { + "files": ["*.json"], + "parser": "jsonc-eslint-parser", + "rules": { + "@nx/dependency-checks": "error" + } + } + ] +} diff --git a/tools/forecast/plugins/prisma/README.md b/tools/forecast/plugins/prisma/README.md new file mode 100644 index 000000000..4b51c9d95 --- /dev/null +++ b/tools/forecast/plugins/prisma/README.md @@ -0,0 +1,145 @@ + + + + + +
+ + + +
+The ⚡StormStack monorepo contains utility applications, tools, and various libraries used to create modern, scalable web applications.With the assistance of StormStack's Forecast modelling, a developer could create and a fully featured, serverless back-end without writing any actual code themselves. +
+StormStack is built using Nx, a set of extensible dev tools for monorepos, which helps you develop like Google, Facebook, and Microsoft. Building on top of Nx, the Open System provides a set of tools and patterns that help you scale your monorepo to many teams while keeping the codebase maintainable. + +

💻 Visit stormcloud.dev to stay up to date with this developer

+ +[![Version](https://img.shields.io/badge/version-0.0.1-1fb2a6.svg?style=for-the-badge&color=1fb2a6)](https://prettier.io/)  +[![Nx](https://img.shields.io/badge/Nx-14.4.2-lightgrey?style=for-the-badge&logo=nx&logoWidth=20&&color=1fb2a6)](http://nx.dev/) [![NextJs](https://img.shields.io/badge/Next.js-13.0.5-lightgrey?style=for-the-badge&logo=nextdotjs&logoWidth=20&color=1fb2a6)](https://nextjs.org/) [![codecov.io](https://img.shields.io/codecov/c/github/commitizen/cz-cli.svg?style=for-the-badge&color=1fb2a6)](https://codecov.io/github/commitizen/cz-cli?branch=master) [![Commitizen friendly](https://img.shields.io/badge/commitizen-friendly-brightgreen.svg?style=for-the-badge&logo=commitlint&color=1fb2a6)](http://commitizen.github.io/cz-cli/) ![Semantic-Release](https://img.shields.io/badge/%20%20%F0%9F%93%A6%F0%9F%9A%80-semantic--release-e10079.svg?style=for-the-badge&color=1fb2a6) [![documented with docusaurus](https://img.shields.io/badge/documented_with-docusaurus-success.svg?style=for-the-badge&logo=readthedocs&color=1fb2a6)](https://docusaurus.io/) + + + + + + + +# ⚡ Forecast Modelling Language - StormStack + +This library was generated with [Nx](https://nx.dev). + + + + +## Table of Contents + +- [⚡ Forecast Modelling Language - StormStack](#-forecast-modelling-language---stormstack) + - [Table of Contents](#table-of-contents) + - [Running unit tests](#running-unit-tests) + - [Roadmap](#roadmap) + - [Support](#support) + - [License](#license) + - [Changelog](#changelog) + - [Contributing](#contributing) + - [Contributors](#contributors) + + + +## Running unit tests + +Run `nx test tools-forecast-language` to execute the unit tests via [Jest](https://jestjs.io). + + + + + + +## Roadmap + +See the [open issues](https://github.com/stormstack/stormstack/issues) for a list of proposed features (and known issues). + +- [Top Feature Requests](https://github.com/stormstack/stormstack/issues?q=label%3Aenhancement+is%3Aopen+sort%3Areactions-%2B1-desc) (Add your votes using the 👍 reaction) +- [Top Bugs](https://github.com/stormstack/stormstack/issues?q=is%3Aissue+is%3Aopen+label%3Abug+sort%3Areactions-%2B1-desc) (Add your votes using the 👍 reaction) +- [Newest Bugs](https://github.com/stormstack/stormstack/issues?q=is%3Aopen+is%3Aissue+label%3Abug) + +## Support + +Reach out to the maintainer at one of the following places: + +- [Contact](https://www.patsullivan.org/contact) +- [GitHub discussions](https://github.com/stormstack/stormstack/discussions) +- + +## License + +This project is licensed under the **BSD-2-Clause license**. Feel free to edit and distribute this template as you like. + +See [LICENSE](LICENSE) for more information. + +## Changelog + +This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). Every release, along with the migration instructions, is documented in the [CHANGELOG](CHANGELOG.md) file + +## Contributing + +First off, thanks for taking the time to contribute! Contributions are what makes the open-source community such an amazing place to learn, inspire, and create. Any contributions you make will benefit everybody else and are **greatly appreciated**. + +Please try to create bug reports that are: + +- _Reproducible._ Include steps to reproduce the problem. +- _Specific._ Include as much detail as possible: which version, what environment, etc. +- _Unique._ Do not duplicate existing opened issues. +- _Scoped to a Single Bug._ One bug per report. + +Please adhere to this project's [code of conduct](.github/CODE_OF_CONDUCT.md). + +You can use [markdownlint-cli](https://github.com/stormstack/stormstack/markdownlint-cli) to check for common markdown style inconsistency. + +## Contributors + +Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/docs/en/emoji-key)): + + + + + + + + + + + + + + + +
Patrick Sullivan
Patrick Sullivan

🎨 💻 🔧 📖 ⚠️
Tyler Benning
Tyler Benning

🎨
+ + Add your contributions +
+ + + +This project follows the [all-contributors](https://github.com/all-contributors/all-contributors) specification. Contributions of any kind welcome! + +
+
+
+ +
+ + +
+

Fingerprint: 1BD2 7192 7770 2549 F4C9 F238 E6AD C420 DA5C 4C2D

+
+ +

💻 Visit patsullivan.org to stay up to date with this developer

+ + + + + + diff --git a/tools/forecast/plugins/prisma/package.json b/tools/forecast/plugins/prisma/package.json new file mode 100644 index 000000000..259d1279f --- /dev/null +++ b/tools/forecast/plugins/prisma/package.json @@ -0,0 +1,9 @@ +{ + "name": "@stormstack/tools-forecast-plugins-prisma", + "version": "0.0.1", + "dependencies": { + "@prisma/generator-helper": "^5.2.0", + "path": "^0.12.7", + "ts-morph": "^19.0.0" + } +} diff --git a/tools/forecast/plugins/prisma/project.json b/tools/forecast/plugins/prisma/project.json new file mode 100644 index 000000000..6f53b0797 --- /dev/null +++ b/tools/forecast/plugins/prisma/project.json @@ -0,0 +1,77 @@ +{ + "name": "tools-forecast-plugins-prisma", + "$schema": "../../../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "tools/forecast/plugins/prisma/src", + "projectType": "library", + "targets": { + "build": { + "executor": "@nx/esbuild:esbuild", + "outputs": ["{options.outputPath}"], + "options": { + "outputPath": "dist/tools/forecast/plugins/prisma", + "main": "tools/forecast/plugins/prisma/src/index.ts", + "project": "tools/forecast/plugins/prisma/package.json", + "tsConfig": "tools/forecast/plugins/prisma/tsconfig.json", + "platform": "node", + "deleteOutputPath": true, + "bundle": true, + "sourcemap": true, + "minify": true, + "skipTypeCheck": true, + "format": ["esm", "cjs"], + "assets": [ + { + "input": "tools/forecast/plugins/prisma", + "glob": "README.md", + "output": "." + }, + { + "input": ".", + "glob": "LICENSE", + "output": "." + } + ] + } + }, + "lint": { + "executor": "@nx/linter:eslint", + "options": { + "lintFilePatterns": [ + "tools/forecast/plugins/prisma/**/*.ts", + "tools/forecast/plugins/prisma/generators.json", + "tools/forecast/plugins/prisma/package.json" + ] + }, + "outputs": ["{options.outputFile}"] + }, + "test": { + "executor": "@nx/jest:jest", + "outputs": ["{workspaceRoot}/coverage/{projectRoot}"], + "options": { + "jestConfig": "tools/forecast/plugins/prisma/jest.config.ts", + "passWithNoTests": true + }, + "configurations": { + "ci": { + "ci": true, + "codeCoverage": true + } + } + }, + "semantic-release": { + "executor": "@theunderscorer/nx-semantic-release:semantic-release", + "options": { + "github": true, + "npm": false, + "changelog": true, + "tagFormat": "tools-forecast-plugins-prisma-v${VERSION}" + } + } + }, + "tags": ["scope:tools", "platform:server"], + "implicitDependencies": [ + "core-shared-utilities", + "tools-forecast-language", + "tools-forecast-codegen" + ] +} diff --git a/tools/forecast/codegen/src/plugins/prisma/indent-string.ts b/tools/forecast/plugins/prisma/src/indent-string.ts similarity index 100% rename from tools/forecast/codegen/src/plugins/prisma/indent-string.ts rename to tools/forecast/plugins/prisma/src/indent-string.ts diff --git a/tools/forecast/codegen/src/plugins/prisma/index.ts b/tools/forecast/plugins/prisma/src/index.ts similarity index 100% rename from tools/forecast/codegen/src/plugins/prisma/index.ts rename to tools/forecast/plugins/prisma/src/index.ts diff --git a/tools/forecast/codegen/src/plugins/prisma/prisma-builder.ts b/tools/forecast/plugins/prisma/src/prisma-builder.ts similarity index 99% rename from tools/forecast/codegen/src/plugins/prisma/prisma-builder.ts rename to tools/forecast/plugins/prisma/src/prisma-builder.ts index 00c623221..eb3f97385 100644 --- a/tools/forecast/codegen/src/plugins/prisma/prisma-builder.ts +++ b/tools/forecast/plugins/prisma/src/prisma-builder.ts @@ -1,4 +1,4 @@ -import { AUXILIARY_FIELDS } from "../../sdk"; +import { AUXILIARY_FIELDS } from "@stormstack/tools-forecast-codegen"; import indentString from "./indent-string"; /** diff --git a/tools/forecast/codegen/src/plugins/prisma/schema-generator.ts b/tools/forecast/plugins/prisma/src/prisma-generator.ts similarity index 100% rename from tools/forecast/codegen/src/plugins/prisma/schema-generator.ts rename to tools/forecast/plugins/prisma/src/prisma-generator.ts diff --git a/tools/forecast/codegen/src/plugins/prisma/storm-code-generator.ts b/tools/forecast/plugins/prisma/src/storm-code-generator.ts similarity index 100% rename from tools/forecast/codegen/src/plugins/prisma/storm-code-generator.ts rename to tools/forecast/plugins/prisma/src/storm-code-generator.ts diff --git a/tools/forecast/plugins/prisma/tsconfig.json b/tools/forecast/plugins/prisma/tsconfig.json new file mode 100644 index 000000000..8373de338 --- /dev/null +++ b/tools/forecast/plugins/prisma/tsconfig.json @@ -0,0 +1,20 @@ +{ + "extends": "../../../../tsconfig.base.json", + "compilerOptions": { + "outDir": "../../../../dist/out-tsc", + "module": "CommonJS", + "target": "ES6", + "types": ["node"], + "declaration": true, + "strict": false, + "noImplicitAny": false, + "moduleResolution": "node", + "esModuleInterop": true, + "skipLibCheck": true, + "resolveJsonModule": true, + "lib": ["ESNext"], + "sourceMap": true + }, + "exclude": ["**/*.spec.ts", "**/*.test.ts", "jest.config.ts"], + "include": ["**/*.ts", "**/*.tsx", "**/*.js", "**/*.jsx"] +} diff --git a/tsconfig.base.json b/tsconfig.base.json index c1ef2bc8b..8e5f1b8b1 100644 --- a/tsconfig.base.json +++ b/tsconfig.base.json @@ -260,6 +260,12 @@ "@stormstack/tools-forecast-language/*": [ "tools/forecast/language/src/*" ], + "@stormstack/tools-forecast-plugins-crud": [ + "tools/forecast/plugins/crud/src/index.ts" + ], + "@stormstack/tools-forecast-plugins-prisma": [ + "tools/forecast/plugins/prisma/src/index.ts" + ], "@stormstack/tools-forecast-plugins-drizzle": [ "tools/forecast/plugins/drizzle/src/index.ts" ],