From e8393ae6a4d6c005f5565d0cf10be4a2b2dc1f10 Mon Sep 17 00:00:00 2001 From: Vinayak Sarawagi Date: Sun, 5 Jan 2025 21:14:00 +0530 Subject: [PATCH 1/7] feat(cli): add option builder for new project command --- packages/cli/bin/intent.ts | 21 ++- packages/cli/commands/new-project.ts | 126 ++++++++++++++++++ .../lib/configuration/new-project-config.ts | 57 ++++++++ packages/cli/package.json | 2 +- 4 files changed, 204 insertions(+), 2 deletions(-) create mode 100644 packages/cli/commands/new-project.ts create mode 100644 packages/cli/lib/configuration/new-project-config.ts diff --git a/packages/cli/bin/intent.ts b/packages/cli/bin/intent.ts index 5aa9199..3dd3f85 100644 --- a/packages/cli/bin/intent.ts +++ b/packages/cli/bin/intent.ts @@ -1,8 +1,10 @@ #!/usr/bin/env node -import { program } from "commander"; +import { Option, program } from "commander"; import { StartServerCommand } from "../commands/start-server"; import { BuildCommand } from "../commands/build"; +import { NEW_PROJECT_OPTIONS } from "../lib/configuration/new-project-config"; +import { NewProjectCommand } from "../commands/new-project"; program .command("start") @@ -40,4 +42,21 @@ program const buildCommand = new BuildCommand(); buildCommand.handle(str); }); + +const newProjectProgram = program + .command("new") + .description("Command to initiailize a new project") + .argument("name", "Name of the project") + .option("--default", "Uses default configuration"); + +Object.values(NEW_PROJECT_OPTIONS).map((n) => + newProjectProgram.addOption( + new Option(n.option, n.description).choices(n.choices) + ) +); + +newProjectProgram.action(async (name, options) => { + const command = new NewProjectCommand(); + await command.handle(name, options); +}); program.parseAsync(); diff --git a/packages/cli/commands/new-project.ts b/packages/cli/commands/new-project.ts new file mode 100644 index 0000000..2d43327 --- /dev/null +++ b/packages/cli/commands/new-project.ts @@ -0,0 +1,126 @@ +import { join } from "path"; +import { INTENT_LOG_PREFIX } from "../lib/utils/log-helpers"; +import pc from "picocolors"; +import { existsSync } from "fs-extra"; +import * as p from "@clack/prompts"; +import { NEW_PROJECT_OPTIONS } from "../lib/configuration/new-project-config"; + +export class NewProjectCommand { + constructor() {} + + async handle(name: string, options: Record) { + console.log(name, options); + /** + * Check if provided name is available for creating a directory + */ + this.checkIfDirNameIsValidAndAvailable(name); + + /** + * Build Prompt Options + */ + const promptOptions = this.buildPromptOptions(options); + const missingOptions = await p.group( + { ...promptOptions }, + { + onCancel: ({ results }) => { + p.log.error("User cancelled prompt! Exiting now..."); + process.exit(0); + }, + } + ); + + console.log({ ...options, ...missingOptions }); + } + + buildPromptOptions(options: Record) { + console.log(options); + const missingPromptOptions = [] as Record; + if (!("database" in options)) { + const promptConfig = NEW_PROJECT_OPTIONS["database"]; + missingPromptOptions["database"] = () => + p.select({ + message: promptConfig.question, + options: promptConfig.selectOptions, + }); + } + + if (!("cache" in options)) { + const promptConfig = NEW_PROJECT_OPTIONS["cache"]; + missingPromptOptions["cache"] = () => + p.select({ + message: promptConfig.question, + options: promptConfig.selectOptions, + }); + } + + if (!("storage" in options)) { + const promptConfig = NEW_PROJECT_OPTIONS["storage"]; + missingPromptOptions["storage"] = () => + p.select({ + message: promptConfig.question, + options: promptConfig.selectOptions, + }); + } + + if (!("mailer" in options)) { + const promptConfig = NEW_PROJECT_OPTIONS["mailer"]; + missingPromptOptions["mailer"] = () => + p.select({ + message: promptConfig.question, + options: promptConfig.selectOptions, + }); + } + + if (!("queue" in options)) { + const promptConfig = NEW_PROJECT_OPTIONS["queue"]; + missingPromptOptions["queue"] = () => + p.select({ + message: promptConfig.question, + options: promptConfig.selectOptions, + }); + } + + return missingPromptOptions; + } + + checkIfDirNameIsValidAndAvailable(name: string) { + // Check if directory name is valid + const isWindows = process.platform === "win32"; + const invalidCharsRegex = isWindows + ? /[<>:"/\\|?*\x00-\x1F]/g // Windows invalid chars + : /[/\x00]/g; // Unix/Linux invalid chars + + if (!name || name.trim().length === 0) { + console.log(INTENT_LOG_PREFIX, pc.red("Directory name cannot be empty")); + process.exit(1); + } + + if (name === "." || name === "..") { + console.log(INTENT_LOG_PREFIX, pc.red("Invalid directory name")); + process.exit(1); + } + + if (invalidCharsRegex.test(name)) { + console.log( + INTENT_LOG_PREFIX, + pc.red( + `Directory name contains invalid characters${ + isWindows ? ' (< > : " \\ / | ? * are not allowed)' : "" + }` + ) + ); + process.exit(1); + } + + // Check if name is available + const dirPath = join(process.cwd(), name); + console.log(dirPath); + if (existsSync(dirPath)) { + console.log( + INTENT_LOG_PREFIX, + pc.red(`Directory "${name}" already exists`) + ); + process.exit(1); + } + } +} diff --git a/packages/cli/lib/configuration/new-project-config.ts b/packages/cli/lib/configuration/new-project-config.ts new file mode 100644 index 0000000..2297257 --- /dev/null +++ b/packages/cli/lib/configuration/new-project-config.ts @@ -0,0 +1,57 @@ +export const NEW_PROJECT_OPTIONS = { + database: { + option: "-db, --database ", + description: "Default DB for your application", + choices: ["pg", "mysql", "sqlite"], + question: "Choice of Database?", + selectOptions: [ + { value: "pg", label: "Postgres" }, + { value: "mysql", label: "MySQL" }, + { value: "sqlite", label: "SQLite" }, + ], + }, + storage: { + option: "-s, --storage ", + description: "Default Storage Provider", + question: "Which storage provider would you like to use?", + choices: ["s3", "local"], + selectOptions: [ + { value: "s3", label: "Amazon S3" }, + { value: "local", label: "Local Storage" }, + ], + }, + cache: { + option: "-c, --cache ", + description: "Default Cache Provider", + question: "Which cache would you like to integrate?", + choices: ["redis", "inmemory", "dicedb"], + selectOptions: [ + { value: "redis", label: "Redis" }, + { value: "inmemory", label: "In-Memory Cache" }, + { value: "dicedb", label: "DiceDB" }, + ], + }, + mailer: { + option: "-m, --mailer ", + description: "Default Mail Provider", + question: "Which email service would you like to use?", + choices: ["smtp", "mailgun", "resend"], + selectOptions: [ + { value: "smtp", label: "SMTP" }, + { value: "mailgun", label: "Mailgun" }, + { value: "resend", label: "Resend" }, + ], + }, + queue: { + option: "-q, --queue ", + description: "Default Queue for your application", + question: "Which queue system would you like to use?", + choices: ["sync", "sqs", "redis", "db"], + selectOptions: [ + { value: "sync", label: "Synchronous" }, + { value: "sqs", label: "Amazon SQS" }, + { value: "redis", label: "Redis Queue" }, + { value: "db", label: "Database Queue" }, + ], + }, +}; diff --git a/packages/cli/package.json b/packages/cli/package.json index e0ac8df..5908b51 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -27,11 +27,11 @@ "publish:npm": "npm publish --access public" }, "dependencies": { + "@clack/prompts": "^0.9.0", "@swc/cli": "^0.5.2", "@swc/core": "^1.10.0", "chokidar": "^3.5.1", "commander": "^12.1.0", - "enquirer": "^2.4.1", "fs-extra": "^11.2.0", "picocolors": "^1.1.0", "radash": "^12.1.0", From 4162718f5d7c82dfd696fe5dd5e460ffc48c79c1 Mon Sep 17 00:00:00 2001 From: Vinayak Sarawagi Date: Mon, 6 Jan 2025 01:03:39 +0530 Subject: [PATCH 2/7] feat(cli): wip command to setup necessary drivers while initing a new project --- package-lock.json | 311 +++++++++++++++++- packages/cli/commands/new-project.ts | 69 +++- packages/cli/lib/codegen/inject-config.ts | 48 +++ .../actions/download-depedencies.ts | 7 + .../actions/download-from-registry.ts | 30 ++ .../new-project/actions/download-helper.ts | 37 +++ .../lib/new-project/actions/run-command.ts | 6 + packages/cli/lib/new-project/config.ts | 14 + packages/cli/package.json | 4 + 9 files changed, 517 insertions(+), 9 deletions(-) create mode 100644 packages/cli/lib/codegen/inject-config.ts create mode 100644 packages/cli/lib/new-project/actions/download-depedencies.ts create mode 100644 packages/cli/lib/new-project/actions/download-from-registry.ts create mode 100644 packages/cli/lib/new-project/actions/download-helper.ts create mode 100644 packages/cli/lib/new-project/actions/run-command.ts create mode 100644 packages/cli/lib/new-project/config.ts diff --git a/package-lock.json b/package-lock.json index 40b09fd..7f645a6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1487,6 +1487,27 @@ "dev": true, "license": "MIT" }, + "node_modules/@clack/core": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/@clack/core/-/core-0.4.0.tgz", + "integrity": "sha512-YJCYBsyJfNDaTbvDUVSJ3SgSuPrcujarRgkJ5NLjexDZKvaOiVVJvAQYx8lIgG0qRT8ff0fPgqyBCVivanIZ+A==", + "license": "MIT", + "dependencies": { + "picocolors": "^1.0.0", + "sisteransi": "^1.0.5" + } + }, + "node_modules/@clack/prompts": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/@clack/prompts/-/prompts-0.9.0.tgz", + "integrity": "sha512-nGsytiExgUr4FL0pR/LeqxA28nz3E0cW7eLTSh3Iod9TGrbBt8Y7BHbV3mmkNC4G0evdYyQ3ZsbiBkk7ektArA==", + "license": "MIT", + "dependencies": { + "@clack/core": "0.4.0", + "picocolors": "^1.0.0", + "sisteransi": "^1.0.5" + } + }, "node_modules/@colors/colors": { "version": "1.5.0", "license": "MIT", @@ -2667,6 +2688,21 @@ "@jridgewell/sourcemap-codec": "^1.4.14" } }, + "node_modules/@kwsites/file-exists": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@kwsites/file-exists/-/file-exists-1.1.1.tgz", + "integrity": "sha512-m9/5YGR18lIwxSFDwfE3oA7bWuq9kdau6ugN4H2rJeyhFQZcG9AgSHkQtSD15a8WvTgfz9aikZMrKPHvbpqFiw==", + "license": "MIT", + "dependencies": { + "debug": "^4.1.1" + } + }, + "node_modules/@kwsites/promise-deferred": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@kwsites/promise-deferred/-/promise-deferred-1.1.1.tgz", + "integrity": "sha512-GaHYm+c0O9MjZRu0ongGBRbinu8gVAMd2UZjji6jVmqKtZluZnptXGWhz1E8j8D2HJ3f/yMxKAUC0b+57wncIw==", + "license": "MIT" + }, "node_modules/@lerna/create": { "version": "8.1.9", "dev": true, @@ -5440,6 +5476,27 @@ "@types/superagent": "^8.1.0" } }, + "node_modules/@types/tar": { + "version": "6.1.13", + "resolved": "https://registry.npmjs.org/@types/tar/-/tar-6.1.13.tgz", + "integrity": "sha512-IznnlmU5f4WcGTh2ltRu/Ijpmk8wiWXfF0VA4s+HPjHZgvFggk1YaIkbo5krX/zUCzWF8N/l4+W/LNxnvAJ8nw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*", + "minipass": "^4.0.0" + } + }, + "node_modules/@types/tar/node_modules/minipass": { + "version": "4.2.8", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-4.2.8.tgz", + "integrity": "sha512-fNzuVyifolSLFL4NzpF+wEF4qrgqaaKX0haXPQEdQ7NKAN+WecoKMHV09YcuL/DHxrUsYQOK3MiuDf7Ip2OXfQ==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=8" + } + }, "node_modules/@types/through": { "version": "0.0.33", "dev": true, @@ -16963,6 +17020,21 @@ "node": "^16.14.0 || >=18.0.0" } }, + "node_modules/simple-git": { + "version": "3.27.0", + "resolved": "https://registry.npmjs.org/simple-git/-/simple-git-3.27.0.tgz", + "integrity": "sha512-ivHoFS9Yi9GY49ogc6/YAi3Fl9ROnF4VyubNylgCkA+RVqLaKWnDSzXOVzya8csELIaWaYNutsEuAhZrtOjozA==", + "license": "MIT", + "dependencies": { + "@kwsites/file-exists": "^1.1.1", + "@kwsites/promise-deferred": "^1.1.1", + "debug": "^4.3.5" + }, + "funding": { + "type": "github", + "url": "https://github.com/steveukx/git-js?sponsor=1" + } + }, "node_modules/simple-swizzle": { "version": "0.2.2", "license": "MIT", @@ -16976,7 +17048,6 @@ }, "node_modules/sisteransi": { "version": "1.0.5", - "dev": true, "license": "MIT" }, "node_modules/slash": { @@ -18977,15 +19048,18 @@ "version": "0.0.7", "license": "MIT", "dependencies": { + "@clack/prompts": "^0.9.0", "@swc/cli": "^0.5.2", "@swc/core": "^1.10.0", "chokidar": "^3.5.1", "commander": "^12.1.0", - "enquirer": "^2.4.1", "fs-extra": "^11.2.0", + "got": "^14.4.5", "picocolors": "^1.1.0", "radash": "^12.1.0", + "simple-git": "^3.27.0", "tree-kill": "^1.2.2", + "ts-morph": "^25.0.0", "typescript": "^5.6.2" }, "bin": { @@ -18995,6 +19069,7 @@ "@types/fs-extra": "^11.0.4", "@types/jest": "^29.5.13", "@types/node": "^22.7.4", + "@types/tar": "^6.1.13", "ts-jest": "^29.2.5", "ts-loader": "^9.5.1", "ts-node": "^10.9.2" @@ -19003,6 +19078,18 @@ "node": ">= 16.14" } }, + "packages/cli/node_modules/@sindresorhus/is": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-7.0.1.tgz", + "integrity": "sha512-QWLl2P+rsCJeofkDNIT3WFmb6NrRud1SUYW8dIhXK/46XFV8Q/g7Bsvib0Askb0reRLe+WYPeeE+l5cH7SlkuQ==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sindresorhus/is?sponsor=1" + } + }, "packages/cli/node_modules/@swc/cli": { "version": "0.5.2", "resolved": "https://registry.npmjs.org/@swc/cli/-/cli-0.5.2.tgz", @@ -19046,6 +19133,29 @@ "node": ">= 12" } }, + "packages/cli/node_modules/@szmarczak/http-timer": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-5.0.1.tgz", + "integrity": "sha512-+PmQX0PiAYPMeVYe237LJAYvOMYW1j2rH5YROyS3b4CTVJum34HfRvKvAzozHAQG0TnHNdUfY9nCeUyRAs//cw==", + "license": "MIT", + "dependencies": { + "defer-to-connect": "^2.0.1" + }, + "engines": { + "node": ">=14.16" + } + }, + "packages/cli/node_modules/@ts-morph/common": { + "version": "0.26.0", + "resolved": "https://registry.npmjs.org/@ts-morph/common/-/common-0.26.0.tgz", + "integrity": "sha512-/RmKAtctStXqM5nECMQ46duT74Hoig/DBzhWXGHcodlDNrgRbsbwwHqSKFNbca6z9Xt/CUWMeXOsC9QEN1+rqw==", + "license": "MIT", + "dependencies": { + "fast-glob": "^3.3.2", + "minimatch": "^9.0.4", + "path-browserify": "^1.0.1" + } + }, "packages/cli/node_modules/@types/node": { "version": "22.9.0", "dev": true, @@ -19063,6 +19173,33 @@ "balanced-match": "^1.0.0" } }, + "packages/cli/node_modules/cacheable-lookup": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/cacheable-lookup/-/cacheable-lookup-7.0.0.tgz", + "integrity": "sha512-+qJyx4xiKra8mZrcwhjMRMUhD5NR1R8esPkzIYxX96JiecFoxAXFuz/GpR3+ev4PE1WamHip78wV0vcmPQtp8w==", + "license": "MIT", + "engines": { + "node": ">=14.16" + } + }, + "packages/cli/node_modules/cacheable-request": { + "version": "12.0.1", + "resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-12.0.1.tgz", + "integrity": "sha512-Yo9wGIQUaAfIbk+qY0X4cDQgCosecfBe3V9NSyeY4qPC2SAkbCS4Xj79VP8WOzitpJUZKc/wsRCYF5ariDIwkg==", + "license": "MIT", + "dependencies": { + "@types/http-cache-semantics": "^4.0.4", + "get-stream": "^9.0.1", + "http-cache-semantics": "^4.1.1", + "keyv": "^4.5.4", + "mimic-response": "^4.0.0", + "normalize-url": "^8.0.1", + "responselike": "^3.0.0" + }, + "engines": { + "node": ">=18" + } + }, "packages/cli/node_modules/chokidar": { "version": "3.5.1", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.1.tgz", @@ -19084,15 +19221,29 @@ "fsevents": "~2.3.1" } }, - "packages/cli/node_modules/enquirer": { - "version": "2.4.1", + "packages/cli/node_modules/form-data-encoder": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/form-data-encoder/-/form-data-encoder-4.0.2.tgz", + "integrity": "sha512-KQVhvhK8ZkWzxKxOr56CPulAhH3dobtuQ4+hNQ+HekH/Wp5gSOafqRAeTphQUJAIk0GBvHZgJ2ZGRWd5kphMuw==", + "license": "MIT", + "engines": { + "node": ">= 18" + } + }, + "packages/cli/node_modules/get-stream": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-9.0.1.tgz", + "integrity": "sha512-kVCxPF3vQM/N0B1PmoqVUqgHP+EeVjmZSQn+1oCRPxd2P21P2F19lIgbR3HBosbB1PUhOAoctJnfEn2GbN2eZA==", "license": "MIT", "dependencies": { - "ansi-colors": "^4.1.1", - "strip-ansi": "^6.0.1" + "@sec-ant/readable-stream": "^0.4.1", + "is-stream": "^4.0.1" }, "engines": { - "node": ">=8.6" + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "packages/cli/node_modules/glob-parent": { @@ -19107,6 +19258,80 @@ "node": ">= 6" } }, + "packages/cli/node_modules/got": { + "version": "14.4.5", + "resolved": "https://registry.npmjs.org/got/-/got-14.4.5.tgz", + "integrity": "sha512-sq+uET8TnNKRNnjEOPJzMcxeI0irT8BBNmf+GtZcJpmhYsQM1DSKmCROUjPWKsXZ5HzwD5Cf5/RV+QD9BSTxJg==", + "license": "MIT", + "dependencies": { + "@sindresorhus/is": "^7.0.1", + "@szmarczak/http-timer": "^5.0.1", + "cacheable-lookup": "^7.0.0", + "cacheable-request": "^12.0.1", + "decompress-response": "^6.0.0", + "form-data-encoder": "^4.0.2", + "http2-wrapper": "^2.2.1", + "lowercase-keys": "^3.0.0", + "p-cancelable": "^4.0.1", + "responselike": "^3.0.0", + "type-fest": "^4.26.1" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sindresorhus/got?sponsor=1" + } + }, + "packages/cli/node_modules/http2-wrapper": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/http2-wrapper/-/http2-wrapper-2.2.1.tgz", + "integrity": "sha512-V5nVw1PAOgfI3Lmeaj2Exmeg7fenjhRUgz1lPSezy1CuhPYbgQtbQj4jZfEAEMlaL+vupsvhjqCyjzob0yxsmQ==", + "license": "MIT", + "dependencies": { + "quick-lru": "^5.1.1", + "resolve-alpn": "^1.2.0" + }, + "engines": { + "node": ">=10.19.0" + } + }, + "packages/cli/node_modules/is-stream": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-4.0.1.tgz", + "integrity": "sha512-Dnz92NInDqYckGEUJv689RbRiTSEHCQ7wOVeALbkOz999YpqT46yMRIGtSNl2iCL1waAZSx40+h59NV/EwzV/A==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "packages/cli/node_modules/lowercase-keys": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-3.0.0.tgz", + "integrity": "sha512-ozCC6gdQ+glXOQsveKD0YsDy8DSQFjDTz4zyzEHNV5+JP5D62LmfDZ6o1cycFx9ouG940M5dE8C8CTewdj2YWQ==", + "license": "MIT", + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "packages/cli/node_modules/mimic-response": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-4.0.0.tgz", + "integrity": "sha512-e5ISH9xMYU0DzrT+jl8q2ze9D6eWBto+I8CNpe+VI+K2J/F/k3PdkdTdz4wvGVH4NTpo+NRYTVIuMQEMMcsLqg==", + "license": "MIT", + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "packages/cli/node_modules/minimatch": { "version": "9.0.5", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", @@ -19122,6 +19347,39 @@ "url": "https://github.com/sponsors/isaacs" } }, + "packages/cli/node_modules/normalize-url": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-8.0.1.tgz", + "integrity": "sha512-IO9QvjUMWxPQQhs60oOu10CRkWCiZzSUkzbXGGV9pviYl1fXYcvkzQ5jV9z8Y6un8ARoVRl4EtC6v6jNqbaJ/w==", + "license": "MIT", + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "packages/cli/node_modules/p-cancelable": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-4.0.1.tgz", + "integrity": "sha512-wBowNApzd45EIKdO1LaU+LrMBwAcjfPaYtVzV3lmfM3gf8Z4CHZsiIqlM8TZZ8okYvh5A1cP6gTfCRQtwUpaUg==", + "license": "MIT", + "engines": { + "node": ">=14.16" + } + }, + "packages/cli/node_modules/quick-lru": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-5.1.1.tgz", + "integrity": "sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==", + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "packages/cli/node_modules/readdirp": { "version": "3.5.0", "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.5.0.tgz", @@ -19134,6 +19392,21 @@ "node": ">=8.10.0" } }, + "packages/cli/node_modules/responselike": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/responselike/-/responselike-3.0.0.tgz", + "integrity": "sha512-40yHxbNcl2+rzXvZuVkrYohathsSJlMTXKryG5y8uciHv1+xDLHQpgjG64JUO9nrEq2jGLH6IZ8BcZyw3wrweg==", + "license": "MIT", + "dependencies": { + "lowercase-keys": "^3.0.0" + }, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "packages/cli/node_modules/source-map": { "version": "0.7.4", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.4.tgz", @@ -19143,9 +19416,31 @@ "node": ">= 8" } }, + "packages/cli/node_modules/ts-morph": { + "version": "25.0.0", + "resolved": "https://registry.npmjs.org/ts-morph/-/ts-morph-25.0.0.tgz", + "integrity": "sha512-ERPTUVO5qF8cEGJgAejGOsCVlbk8d0SDyiJsucKQT5XgqoZslv0Qml+gnui6Yy6o+uQqw5SestyW2HvlVtT/Sg==", + "license": "MIT", + "dependencies": { + "@ts-morph/common": "~0.26.0", + "code-block-writer": "^13.0.3" + } + }, + "packages/cli/node_modules/type-fest": { + "version": "4.31.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.31.0.tgz", + "integrity": "sha512-yCxltHW07Nkhv/1F6wWBr8kz+5BGMfP+RbRSYFnegVb0qV/UMT0G0ElBloPVerqn4M2ZV80Ir1FtCcYv1cT6vQ==", + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "packages/core": { "name": "@intentjs/core", - "version": "0.1.38", + "version": "0.1.40", "license": "MIT", "dependencies": { "@intentjs/hyper-express": "^0.0.5", diff --git a/packages/cli/commands/new-project.ts b/packages/cli/commands/new-project.ts index 2d43327..a6674cd 100644 --- a/packages/cli/commands/new-project.ts +++ b/packages/cli/commands/new-project.ts @@ -1,9 +1,26 @@ import { join } from "path"; import { INTENT_LOG_PREFIX } from "../lib/utils/log-helpers"; import pc from "picocolors"; -import { existsSync } from "fs-extra"; +import { + emptyDir, + emptyDirSync, + existsSync, + remove, + removeSync, +} from "fs-extra"; import * as p from "@clack/prompts"; import { NEW_PROJECT_OPTIONS } from "../lib/configuration/new-project-config"; +import { downloadRepository } from "../lib/new-project/download-helper"; +import { + NEW_PROJECT_CONFIG, + SAFE_DELETE_FILES, +} from "../lib/new-project/config"; +import { downloadDependenciesUsingNpm } from "../lib/new-project/actions/download-depedencies"; +import { runCommandInsideProject } from "../lib/new-project/actions/run-command"; +import { execSync } from "child_process"; +import { checkIfGitIsInstalled } from "../lib/new-project/actions/download-helper"; +import { DownloadFromRegistry } from "../lib/new-project/actions/download-from-registry"; +import { injectKeyValue } from "../lib/codegen/inject-config"; export class NewProjectCommand { constructor() {} @@ -29,6 +46,56 @@ export class NewProjectCommand { } ); + const config = NEW_PROJECT_CONFIG; + const starterTemplateName = `${config.gitOrg}/${config.repoName}`; + + // await p.tasks([ + // { + // title: "Downloading the starter template", + // task: async (message) => { + // const success = await downloadRepository(starterTemplateName, name); + // !success && process.exit(0); + // return "Starter template cloned! 🎉"; + // }, + // }, + // { + // title: "Cleaning up files", + // task: async (message) => { + // const deleteFilesPromise = []; + // for (const dirOrFileName of SAFE_DELETE_FILES) { + // deleteFilesPromise.push(remove(join(name, dirOrFileName))); + // } + + // await Promise.allSettled(deleteFilesPromise); + // return "Files cleaned up 🗑️"; + // }, + // }, + // { + // title: "Installing via npm", + // task: async (message) => { + // downloadDependenciesUsingNpm(name); + // return "Installed dependencies using npm"; + // }, + // }, + // { + // title: "Setting up the selected configuration", + // task: (message) => {}, + // }, + // ]); + + const finalOptions = { ...options, ...missingOptions }; + + console.log(finalOptions); + + const { queue } = finalOptions; + + if (queue) { + const downloadFromRegistry = new DownloadFromRegistry(); + const configToApply = await downloadFromRegistry.handle(""); + console.log("config to apply ===> ", configToApply); + const filePath = join("config", "queue.ts"); + injectKeyValue(filePath, configToApply); + } console.log({ ...options, ...missingOptions }); } diff --git a/packages/cli/lib/codegen/inject-config.ts b/packages/cli/lib/codegen/inject-config.ts new file mode 100644 index 0000000..b57a197 --- /dev/null +++ b/packages/cli/lib/codegen/inject-config.ts @@ -0,0 +1,48 @@ +import { Project, SyntaxKind } from "ts-morph"; + +export const injectKeyValue = ( + filePath: string, + registryObject: Record +) => { + const project = new Project({ + manipulationSettings: { + useTrailingCommas: true, + }, + }); + const sourceFile = project.addSourceFileAtPath(filePath); + + const returnStatement = sourceFile.getFirstDescendantByKind( + SyntaxKind.ReturnStatement + ); + let objects = returnStatement?.getFirstDescendantByKind( + SyntaxKind.ObjectLiteralExpression + ); + + console.log(registryObject); + const { namespace, dependencies, key: pathArray, value } = registryObject; + + for (let i = 0; i < pathArray.length - 1; i++) { + const key = pathArray[i]; + const property = objects?.getProperty(key); + + if (property) { + objects = property.getFirstChildByKind( + SyntaxKind.ObjectLiteralExpression + ); + } + } + + if (objects) { + console.log(JSON.stringify(value)); + objects.addPropertyAssignment({ + name: pathArray[pathArray.length - 1], + initializer: JSON.stringify(value), + }); + + const updatedText = objects.getText().replace(/,\s*,/g, ","); + objects.replaceWithText(updatedText); + } + + sourceFile.formatText(); + sourceFile.saveSync(); +}; diff --git a/packages/cli/lib/new-project/actions/download-depedencies.ts b/packages/cli/lib/new-project/actions/download-depedencies.ts new file mode 100644 index 0000000..5e338e7 --- /dev/null +++ b/packages/cli/lib/new-project/actions/download-depedencies.ts @@ -0,0 +1,7 @@ +import { execSync } from "child_process"; + +export const downloadDependenciesUsingNpm = async (dirName: string) => { + execSync(`cd ${dirName}`); + execSync(`npm i`, { stdio: "ignore" }); + execSync("cd .."); +}; diff --git a/packages/cli/lib/new-project/actions/download-from-registry.ts b/packages/cli/lib/new-project/actions/download-from-registry.ts new file mode 100644 index 0000000..7e52314 --- /dev/null +++ b/packages/cli/lib/new-project/actions/download-from-registry.ts @@ -0,0 +1,30 @@ +export class DownloadFromRegistry { + async handle(registryUrl: string) { + /** + * Write code to download + */ + + console.log(registryUrl); + const str = `{ + "type": "inject-config", + "dependencies": { + "@aws-sdk/client-s3": "*" + }, + "namespace": "queue", + "key": ["connections", "sqs"], + "value": { + "driver": "sqs", + "listenerType": "poll", + "apiVersion": "2012-11-05", + "credentials": null, + "prefix": "env://SQS_PREFIX", + "queue": "env://SQS_QUEUE", + "suffix": "", + "region": "env://AWS_REGION" + } +} +`; + + return JSON.parse(str); + } +} diff --git a/packages/cli/lib/new-project/actions/download-helper.ts b/packages/cli/lib/new-project/actions/download-helper.ts new file mode 100644 index 0000000..75842e2 --- /dev/null +++ b/packages/cli/lib/new-project/actions/download-helper.ts @@ -0,0 +1,37 @@ +import { execSync } from "child_process"; +import simpleGit from "simple-git"; +import pc from "picocolors"; +import { NEW_PROJECT_CONFIG } from "../config"; + +export const downloadRepository = async ( + starterTemplateName: string, + dirName: string +) => { + const isGitInstalled = checkIfGitIsInstalled(); + + if (isGitInstalled) { + try { + const git = simpleGit(); + await git.clone( + `https://github.com/${starterTemplateName}.git`, + dirName, + ["--branch", NEW_PROJECT_CONFIG.branch] + ); + return 1; + } catch (e) { + console.log(`[ ${pc.bold(pc.red("error"))} ] ${pc.red(e.message)}`); + return 0; + } + } + + return; +}; + +export const checkIfGitIsInstalled = (): boolean => { + try { + execSync("git --version", { stdio: "ignore" }); + return true; + } catch (e) { + return false; + } +}; diff --git a/packages/cli/lib/new-project/actions/run-command.ts b/packages/cli/lib/new-project/actions/run-command.ts new file mode 100644 index 0000000..a6d1937 --- /dev/null +++ b/packages/cli/lib/new-project/actions/run-command.ts @@ -0,0 +1,6 @@ +import { cd, exec } from "shelljs"; + +export const runCommandInsideProject = (dirName: string, command: string) => { + cd(dirName); + exec(command); +}; diff --git a/packages/cli/lib/new-project/config.ts b/packages/cli/lib/new-project/config.ts new file mode 100644 index 0000000..f7a19e2 --- /dev/null +++ b/packages/cli/lib/new-project/config.ts @@ -0,0 +1,14 @@ +export const NEW_PROJECT_CONFIG = { + gitOrg: "intentjs", + repoName: "new-app-starter", + branch: "main", +}; + +export const SAFE_DELETE_FILES = [ + ".git", + ".github", + "CODE_OF_CONDUCT.md", + "LICENSE", + "README.md", + "package-lock.json", +]; diff --git a/packages/cli/package.json b/packages/cli/package.json index 5908b51..6d7ad7c 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -33,15 +33,19 @@ "chokidar": "^3.5.1", "commander": "^12.1.0", "fs-extra": "^11.2.0", + "got": "^14.4.5", "picocolors": "^1.1.0", "radash": "^12.1.0", + "simple-git": "^3.27.0", "tree-kill": "^1.2.2", + "ts-morph": "^25.0.0", "typescript": "^5.6.2" }, "devDependencies": { "@types/fs-extra": "^11.0.4", "@types/jest": "^29.5.13", "@types/node": "^22.7.4", + "@types/tar": "^6.1.13", "ts-jest": "^29.2.5", "ts-loader": "^9.5.1", "ts-node": "^10.9.2" From 89e20cf929ea0a03678d6f33f799b40a06bcb4e3 Mon Sep 17 00:00:00 2001 From: Vinayak Sarawagi Date: Mon, 6 Jan 2025 11:37:04 +0530 Subject: [PATCH 3/7] fix(cli): remove run command function --- packages/cli/lib/new-project/actions/run-command.ts | 6 ------ 1 file changed, 6 deletions(-) delete mode 100644 packages/cli/lib/new-project/actions/run-command.ts diff --git a/packages/cli/lib/new-project/actions/run-command.ts b/packages/cli/lib/new-project/actions/run-command.ts deleted file mode 100644 index a6d1937..0000000 --- a/packages/cli/lib/new-project/actions/run-command.ts +++ /dev/null @@ -1,6 +0,0 @@ -import { cd, exec } from "shelljs"; - -export const runCommandInsideProject = (dirName: string, command: string) => { - cd(dirName); - exec(command); -}; From 67ab30dcebfc5bcc5b0112b896bcbda20af062ed Mon Sep 17 00:00:00 2001 From: Vinayak Sarawagi Date: Sun, 12 Jan 2025 02:16:46 +0530 Subject: [PATCH 4/7] feat(cli): new project configuration codegen --- packages/cli/commands/new-project.ts | 140 +++++++------ packages/cli/lib/codegen/ast/inject-config.ts | 188 ++++++++++++++++++ packages/cli/lib/codegen/download-registry.ts | 7 + packages/cli/lib/codegen/env-manager.ts | 145 ++++++++++++++ packages/cli/lib/codegen/inject-config.ts | 106 ++++++---- .../lib/configuration/new-project-config.ts | 19 +- .../actions/download-depedencies.ts | 21 +- packages/cli/package.json | 1 + 8 files changed, 517 insertions(+), 110 deletions(-) create mode 100644 packages/cli/lib/codegen/ast/inject-config.ts create mode 100644 packages/cli/lib/codegen/download-registry.ts create mode 100644 packages/cli/lib/codegen/env-manager.ts diff --git a/packages/cli/commands/new-project.ts b/packages/cli/commands/new-project.ts index a6674cd..df35bfc 100644 --- a/packages/cli/commands/new-project.ts +++ b/packages/cli/commands/new-project.ts @@ -1,32 +1,22 @@ import { join } from "path"; import { INTENT_LOG_PREFIX } from "../lib/utils/log-helpers"; import pc from "picocolors"; -import { - emptyDir, - emptyDirSync, - existsSync, - remove, - removeSync, -} from "fs-extra"; +import { copyFile, existsSync, remove } from "fs-extra"; import * as p from "@clack/prompts"; import { NEW_PROJECT_OPTIONS } from "../lib/configuration/new-project-config"; -import { downloadRepository } from "../lib/new-project/download-helper"; import { NEW_PROJECT_CONFIG, SAFE_DELETE_FILES, } from "../lib/new-project/config"; +import { InjectConfigCodegen } from "../lib/codegen/inject-config"; +import { cwd } from "process"; +import { downloadRepository } from "../lib/new-project/actions/download-helper"; import { downloadDependenciesUsingNpm } from "../lib/new-project/actions/download-depedencies"; -import { runCommandInsideProject } from "../lib/new-project/actions/run-command"; -import { execSync } from "child_process"; -import { checkIfGitIsInstalled } from "../lib/new-project/actions/download-helper"; -import { DownloadFromRegistry } from "../lib/new-project/actions/download-from-registry"; -import { injectKeyValue } from "../lib/codegen/inject-config"; export class NewProjectCommand { constructor() {} async handle(name: string, options: Record) { - console.log(name, options); /** * Check if provided name is available for creating a directory */ @@ -49,58 +39,86 @@ export class NewProjectCommand { const config = NEW_PROJECT_CONFIG; const starterTemplateName = `${config.gitOrg}/${config.repoName}`; - // await p.tasks([ - // { - // title: "Downloading the starter template", - // task: async (message) => { - // const success = await downloadRepository(starterTemplateName, name); - // !success && process.exit(0); - // return "Starter template cloned! 🎉"; - // }, - // }, - // { - // title: "Cleaning up files", - // task: async (message) => { - // const deleteFilesPromise = []; - // for (const dirOrFileName of SAFE_DELETE_FILES) { - // deleteFilesPromise.push(remove(join(name, dirOrFileName))); - // } - - // await Promise.allSettled(deleteFilesPromise); - // return "Files cleaned up 🗑️"; - // }, - // }, - // { - // title: "Installing via npm", - // task: async (message) => { - // downloadDependenciesUsingNpm(name); - // return "Installed dependencies using npm"; - // }, - // }, - // { - // title: "Setting up the selected configuration", - // task: (message) => {}, - // }, - // ]); + const newProjectDirectory = join(cwd(), name); const finalOptions = { ...options, ...missingOptions }; - - console.log(finalOptions); - - const { queue } = finalOptions; - - if (queue) { - const downloadFromRegistry = new DownloadFromRegistry(); - const configToApply = await downloadFromRegistry.handle(""); - console.log("config to apply ===> ", configToApply); - const filePath = join("config", "queue.ts"); - injectKeyValue(filePath, configToApply); - } - console.log({ ...options, ...missingOptions }); + const injectConfigTask = new InjectConfigCodegen(newProjectDirectory); + await p.tasks([ + { + title: "Downloading the starter template", + task: async (message) => { + const success = await downloadRepository(starterTemplateName, name); + !success && process.exit(0); + return "Starter template cloned! 🎉"; + }, + }, + { + title: "Cleaning up files", + task: async (message) => { + const deleteFilesPromise = []; + for (const dirOrFileName of SAFE_DELETE_FILES) { + deleteFilesPromise.push(remove(join(name, dirOrFileName))); + } + + await Promise.allSettled(deleteFilesPromise); + return "Files cleaned up 🗑️"; + }, + }, + { + title: "Creating .env file", + task: async (message) => { + await copyFile( + join(newProjectDirectory, ".env.example"), + join(newProjectDirectory, ".env") + ); + return "Created .env file"; + }, + }, + { + title: "Installing via npm", + task: async (message) => { + await downloadDependenciesUsingNpm(name); + return "Dependencies installed 🧰"; + }, + }, + { + title: "Setting up the selected configuration", + task: async (message) => { + const { database, cache, storage, mailer, queue } = finalOptions; + /** + * Setup queue configuration + */ + const getProjectSettingConfNamespace = (path: string) => + `new-project-settings/config/${path}.json`; + + const url = `https://raw.githubusercontent.com/intentjs/registry/refs/heads/main/`; + await injectConfigTask.handle( + url + getProjectSettingConfNamespace(`queue/${queue}`) + ); + + await injectConfigTask.handle( + url + getProjectSettingConfNamespace(`db/${database}`) + ); + + await injectConfigTask.handle( + url + getProjectSettingConfNamespace(`storage/${storage}`) + ); + + await injectConfigTask.handle( + url + getProjectSettingConfNamespace(`mailer/${mailer}`) + ); + + await injectConfigTask.handle( + url + getProjectSettingConfNamespace(`cache/${cache}`) + ); + + return `Configuration set!`; + }, + }, + ]); } buildPromptOptions(options: Record) { - console.log(options); const missingPromptOptions = [] as Record; if (!("database" in options)) { const promptConfig = NEW_PROJECT_OPTIONS["database"]; diff --git a/packages/cli/lib/codegen/ast/inject-config.ts b/packages/cli/lib/codegen/ast/inject-config.ts new file mode 100644 index 0000000..31f9f17 --- /dev/null +++ b/packages/cli/lib/codegen/ast/inject-config.ts @@ -0,0 +1,188 @@ +import { join } from "path"; +import { format, resolveConfig, resolveConfigFile } from "prettier"; +import { + Project, + SourceFile, + SyntaxKind, + ObjectLiteralExpression, + Node, +} from "ts-morph"; + +export class InjectConfig { + private project: Project; + private sourceFile: SourceFile; + + constructor( + private projectRoot: string, + filePath: string + ) { + this.project = new Project({ + manipulationSettings: { useTrailingCommas: true }, + }); + this.sourceFile = this.project.addSourceFileAtPath(filePath); + } + + /** + * Injects a key-value pair into the AST at the specified path + */ + public async handle(registryObject: Record): Promise { + const objects = this.traverseToTargetObject(registryObject.key); + + if (!objects) { + console.log("Target object not found"); + return; + } + + this.addImports(registryObject); + this.updateObjectProperties(objects, registryObject); + await this.saveChanges(objects); + } + + addImports(registryObject: Record): void { + const { imports = [] } = registryObject; + if (!imports || !imports.length) return; + + for (const importObj of imports) { + if (importObj.namedImports) { + this.sourceFile.addImportDeclaration({ + namedImports: importObj.namedImports, + moduleSpecifier: importObj.moduleSpecifier, + }); + } + } + } + + /** + * Traverses the AST to find the target object based on the path + */ + private traverseToTargetObject( + pathArray: string[] + ): ObjectLiteralExpression | undefined { + let currentObject = this.getObjectLiteral(); + + if (!currentObject) return undefined; + + // Traverse the path except the last element (which is the key to be set) + for (let i = 0; i < pathArray.length - 1; i++) { + const property = currentObject.getProperty(pathArray[i]) as + | ObjectLiteralExpression + | undefined; + if (!property) return undefined; + + currentObject = property.getFirstChildByKind( + SyntaxKind.ObjectLiteralExpression + ); + + if (!currentObject) return undefined; + } + + return currentObject; + } + + /** + * Updates the properties of the target object + */ + private updateObjectProperties( + objects: ObjectLiteralExpression, + registryObject: Record + ): void { + // Remove existing properties + objects.getProperties().forEach((property) => { + property.remove(); + }); + + // Add new property + const newConfig = objects + .addPropertyAssignment({ + name: registryObject.key[registryObject.key.length - 1], + initializer: "{}", + }) + .getInitializer(); + + if (Node.isObjectLiteralExpression(newConfig)) { + for (const [key, value] of Object.entries(registryObject.value)) { + newConfig.addPropertyAssignment({ + name: key, + initializer: + typeof value === "string" + ? value.includes("env://") + ? value.replaceAll("env://", "process.env.") + : `"${value}"` + : JSON.stringify(value), + }); + } + } + } + + transformEnvValues(obj: Record) { + return Object.fromEntries( + Object.entries(obj).map(([key, value]) => { + if (typeof value === "string" && value.startsWith("env://")) { + return [key, `process.env.${value.replace("env://", "")}`]; + } + return [key, value]; + }) + ); + } + + /** + * Saves the changes to the file + */ + private async saveChanges(objects: ObjectLiteralExpression): Promise { + const updatedText = objects.getText().replace(/,\s*,/g, ","); + objects.replaceWithText(updatedText); + + const prettierConfig = await resolveConfig(this.projectRoot, { + config: join(this.projectRoot, ".prettierrc"), + }); + + const formatted = await format(this.sourceFile.getFullText(), { + parser: "typescript", + ...prettierConfig, + }); + + this.sourceFile.replaceWithText(formatted); + this.sourceFile.saveSync(); + } + + /** + * Gets the root object literal from the source file + */ + private getObjectLiteral(): ObjectLiteralExpression | undefined { + // Check for regular return statement + const returnStatement = this.sourceFile.getFirstDescendantByKind( + SyntaxKind.ReturnStatement + ); + + if (returnStatement) { + return returnStatement.getFirstDescendantByKind( + SyntaxKind.ObjectLiteralExpression + ); + } + + // Check for arrow function + const arrowFunction = this.sourceFile.getFirstDescendantByKind( + SyntaxKind.ArrowFunction + ); + + if (!arrowFunction) { + return undefined; + } + + // Check for parenthesized object literal + const parenthesized = arrowFunction.getFirstDescendantByKind( + SyntaxKind.ParenthesizedExpression + ); + + if (parenthesized) { + return parenthesized.getFirstDescendantByKind( + SyntaxKind.ObjectLiteralExpression + ); + } + + // Check for direct object literal + return arrowFunction.getFirstDescendantByKind( + SyntaxKind.ObjectLiteralExpression + ); + } +} diff --git a/packages/cli/lib/codegen/download-registry.ts b/packages/cli/lib/codegen/download-registry.ts new file mode 100644 index 0000000..ae08065 --- /dev/null +++ b/packages/cli/lib/codegen/download-registry.ts @@ -0,0 +1,7 @@ +export class DownloadFromRegistry { + async handle(registryUrl: string): Promise { + const response = await fetch(registryUrl); + const data = await response.json(); + return data; + } +} diff --git a/packages/cli/lib/codegen/env-manager.ts b/packages/cli/lib/codegen/env-manager.ts new file mode 100644 index 0000000..5181c41 --- /dev/null +++ b/packages/cli/lib/codegen/env-manager.ts @@ -0,0 +1,145 @@ +import { promises as fs } from "fs"; +import { resolve } from "path"; + +interface VariableInfo { + line: string; + lineNumber: number; +} + +interface UpdateResult { + action: "updated" | "appended"; + line: string; +} + +export class EnvError extends Error { + constructor( + message: string, + public readonly code?: string + ) { + super(message); + this.name = "EnvError"; + } +} + +export class EnvManager { + private readonly envPath: string; + + constructor(envPath: string = ".env") { + this.envPath = resolve(envPath); + } + + /** + * Reads and returns the content of the .env file + * Creates the file if it doesn't exist + */ + private async readEnvFile(): Promise { + try { + await fs.access(this.envPath); + return await fs.readFile(this.envPath, "utf8"); + } catch (error) { + if ( + error instanceof Error && + "code" in error && + error.code === "ENOENT" + ) { + // File doesn't exist, create it + await fs.writeFile(this.envPath, ""); + return ""; + } + throw new EnvError( + `Failed to read env file: ${error instanceof Error ? error.message : "Unknown error"}`, + "READ_ERROR" + ); + } + } + + /** + * Finds a variable in the .env file + * @param varName - Name of the variable to find + * @returns Variable info if found, null otherwise + */ + public async findVariable(varName: string): Promise { + try { + const content = await this.readEnvFile(); + const lines = content.split("\n"); + const pattern = new RegExp(`^${this.escapeRegExp(varName)}\\s*=`); + + for (let i = 0; i < lines.length; i++) { + const line = lines[i].trim(); + if (pattern.test(line)) { + return { line, lineNumber: i }; + } + } + return null; + } catch (error) { + throw new EnvError( + `Failed to find variable: ${error instanceof Error ? error.message : "Unknown error"}`, + "FIND_ERROR" + ); + } + } + + /** + * Updates an existing variable or appends a new one + * @param varName - Name of the variable to update + * @param newValue - New value for the variable + * @returns Result of the update operation + */ + public async updateVariable( + varName: string, + newValue: string + ): Promise { + try { + const content = await this.readEnvFile(); + const lines = content.split("\n"); + const varInfo = await this.findVariable(varName); + const newLine = `${varName}=${newValue}`; + + if (varInfo) { + // Update existing variable + lines[varInfo.lineNumber] = newLine; + await fs.writeFile(this.envPath, lines.join("\n")); + return { action: "updated", line: newLine }; + } else { + // Append new variable + const newContent = + content + (content && !content.endsWith("\n") ? "\n" : "") + newLine; + await fs.writeFile(this.envPath, newContent); + return { action: "appended", line: newLine }; + } + } catch (error) { + throw new EnvError( + `Failed to update variable: ${error instanceof Error ? error.message : "Unknown error"}`, + "UPDATE_ERROR" + ); + } + } + + /** + * Gets the value of a variable from the .env file + * @param varName - Name of the variable to get + * @returns Value of the variable if found, null otherwise + */ + public async getVariable(varName: string): Promise { + try { + const varInfo = await this.findVariable(varName); + if (varInfo) { + const [, value] = varInfo.line.split("="); + return value.trim(); + } + return null; + } catch (error) { + throw new EnvError( + `Failed to get variable: ${error instanceof Error ? error.message : "Unknown error"}`, + "GET_ERROR" + ); + } + } + + /** + * Helper method to escape special characters in variable names + */ + private escapeRegExp(string: string): string { + return string.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); + } +} diff --git a/packages/cli/lib/codegen/inject-config.ts b/packages/cli/lib/codegen/inject-config.ts index b57a197..604841f 100644 --- a/packages/cli/lib/codegen/inject-config.ts +++ b/packages/cli/lib/codegen/inject-config.ts @@ -1,48 +1,74 @@ -import { Project, SyntaxKind } from "ts-morph"; - -export const injectKeyValue = ( - filePath: string, - registryObject: Record -) => { - const project = new Project({ - manipulationSettings: { - useTrailingCommas: true, - }, - }); - const sourceFile = project.addSourceFileAtPath(filePath); - - const returnStatement = sourceFile.getFirstDescendantByKind( - SyntaxKind.ReturnStatement - ); - let objects = returnStatement?.getFirstDescendantByKind( - SyntaxKind.ObjectLiteralExpression - ); - - console.log(registryObject); - const { namespace, dependencies, key: pathArray, value } = registryObject; - - for (let i = 0; i < pathArray.length - 1; i++) { - const key = pathArray[i]; - const property = objects?.getProperty(key); - - if (property) { - objects = property.getFirstChildByKind( - SyntaxKind.ObjectLiteralExpression +import { join, normalize } from "path"; +import { DownloadFromRegistry } from "./download-registry"; +import { InjectConfig } from "./ast/inject-config"; +import { downloadPackageUsingNpm } from "../new-project/actions/download-depedencies"; +import { EnvManager } from "./env-manager"; + +export class InjectConfigCodegen { + constructor(private projectDirectory: string) {} + + async handle(registryUrl: string): Promise { + try { + const downloadFromRegistryTask = new DownloadFromRegistry(); + const config = (await downloadFromRegistryTask.handle( + registryUrl + )) as InjectConfigRegistryType; + + const { filename, dependencies, env } = config; + if (!filename) { + console.warn( + "cannot proceed to inject the config without `filename` attribute." + ); + } + const filePath = normalize(join(this.projectDirectory, filename)); + + /** + * Inject key, value in the specified `filename` + */ + const injectConfigTask = new InjectConfig( + this.projectDirectory, + filePath ); + injectConfigTask.handle(config); + + if (dependencies) { + await this.installDependencies(dependencies); + } + + if (env && env.length) { + await this.setEnvironmentVariables(env); + } + } catch (e) { + console.log(e); } } - if (objects) { - console.log(JSON.stringify(value)); - objects.addPropertyAssignment({ - name: pathArray[pathArray.length - 1], - initializer: JSON.stringify(value), - }); + async setEnvironmentVariables(env: [string, string][]): Promise { + const envManager = new EnvManager( + normalize(join(this.projectDirectory, ".env")) + ); + + for (const envRow of env) { + const [variable, value] = envRow; + if (!variable) continue; + await envManager.updateVariable(variable, value); + } + } - const updatedText = objects.getText().replace(/,\s*,/g, ","); - objects.replaceWithText(updatedText); + async installDependencies(dependencies: Record): Promise { + await downloadPackageUsingNpm( + this.projectDirectory, + Object.keys(dependencies) + ); } +} - sourceFile.formatText(); - sourceFile.saveSync(); +export type InjectConfigRegistryType = { + type: "inject-config"; + dependencies: Record | undefined; + namespace: string; + key: string[]; + filename: string; + env: [string, string][]; + value: Record; }; diff --git a/packages/cli/lib/configuration/new-project-config.ts b/packages/cli/lib/configuration/new-project-config.ts index 2297257..f4263f6 100644 --- a/packages/cli/lib/configuration/new-project-config.ts +++ b/packages/cli/lib/configuration/new-project-config.ts @@ -1,11 +1,16 @@ +import picocolors from "picocolors"; + +export const defaultTag = picocolors.gray(`(default)`); + export const NEW_PROJECT_OPTIONS = { database: { option: "-db, --database ", description: "Default DB for your application", choices: ["pg", "mysql", "sqlite"], question: "Choice of Database?", + default: "pg", selectOptions: [ - { value: "pg", label: "Postgres" }, + { value: "pg", label: `Postgres ${defaultTag}` }, { value: "mysql", label: "MySQL" }, { value: "sqlite", label: "SQLite" }, ], @@ -15,9 +20,10 @@ export const NEW_PROJECT_OPTIONS = { description: "Default Storage Provider", question: "Which storage provider would you like to use?", choices: ["s3", "local"], + default: "local", selectOptions: [ + { value: "local", label: `Local Storage ${defaultTag}` }, { value: "s3", label: "Amazon S3" }, - { value: "local", label: "Local Storage" }, ], }, cache: { @@ -25,9 +31,10 @@ export const NEW_PROJECT_OPTIONS = { description: "Default Cache Provider", question: "Which cache would you like to integrate?", choices: ["redis", "inmemory", "dicedb"], + default: "inmemory", selectOptions: [ + { value: "in-memory", label: `In-Memory ${defaultTag}` }, { value: "redis", label: "Redis" }, - { value: "inmemory", label: "In-Memory Cache" }, { value: "dicedb", label: "DiceDB" }, ], }, @@ -36,8 +43,9 @@ export const NEW_PROJECT_OPTIONS = { description: "Default Mail Provider", question: "Which email service would you like to use?", choices: ["smtp", "mailgun", "resend"], + default: "smtp", selectOptions: [ - { value: "smtp", label: "SMTP" }, + { value: "smtp", label: `SMTP ${defaultTag}` }, { value: "mailgun", label: "Mailgun" }, { value: "resend", label: "Resend" }, ], @@ -47,11 +55,12 @@ export const NEW_PROJECT_OPTIONS = { description: "Default Queue for your application", question: "Which queue system would you like to use?", choices: ["sync", "sqs", "redis", "db"], + default: "db", selectOptions: [ + { value: "db", label: `Database Queue ${defaultTag}` }, { value: "sync", label: "Synchronous" }, { value: "sqs", label: "Amazon SQS" }, { value: "redis", label: "Redis Queue" }, - { value: "db", label: "Database Queue" }, ], }, }; diff --git a/packages/cli/lib/new-project/actions/download-depedencies.ts b/packages/cli/lib/new-project/actions/download-depedencies.ts index 5e338e7..14b7249 100644 --- a/packages/cli/lib/new-project/actions/download-depedencies.ts +++ b/packages/cli/lib/new-project/actions/download-depedencies.ts @@ -1,7 +1,20 @@ -import { execSync } from "child_process"; +import { exec, execSync } from "child_process"; +import { promisify } from "node:util"; +const execAsync = promisify(exec); export const downloadDependenciesUsingNpm = async (dirName: string) => { - execSync(`cd ${dirName}`); - execSync(`npm i`, { stdio: "ignore" }); - execSync("cd .."); + await execAsync(`npm install`, { + windowsHide: true, + cwd: dirName, + }); +}; + +export const downloadPackageUsingNpm = async ( + dirName: string, + dependencies: string[] +) => { + await execAsync(`npm install ${dependencies.join(" ")} --save`, { + windowsHide: true, + cwd: dirName, + }); }; diff --git a/packages/cli/package.json b/packages/cli/package.json index 6d7ad7c..703a170 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -35,6 +35,7 @@ "fs-extra": "^11.2.0", "got": "^14.4.5", "picocolors": "^1.1.0", + "prettier": "^3.4.2", "radash": "^12.1.0", "simple-git": "^3.27.0", "tree-kill": "^1.2.2", From a0854cb25f7b664b2f07f53be140b7b9b61ca928 Mon Sep 17 00:00:00 2001 From: Vinayak Sarawagi Date: Sun, 12 Jan 2025 16:59:21 +0530 Subject: [PATCH 5/7] fix(cli): bugs in the new project initialiser command --- package-lock.json | 6 +++-- packages/cli/commands/new-project.ts | 12 +++++++-- packages/cli/lib/codegen/ast/inject-config.ts | 26 ++++++++++++++----- packages/cli/lib/codegen/inject-config.ts | 2 +- 4 files changed, 35 insertions(+), 11 deletions(-) diff --git a/package-lock.json b/package-lock.json index 7f645a6..d782b7a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -15660,8 +15660,9 @@ } }, "node_modules/prettier": { - "version": "3.3.3", - "dev": true, + "version": "3.4.2", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.4.2.tgz", + "integrity": "sha512-e9MewbtFo+Fevyuxn/4rrcDAaq0IYxPGLvObpQjiZBMAzB9IGmzlnG9RZy3FFas+eBMu2vA0CszMeduow5dIuQ==", "license": "MIT", "bin": { "prettier": "bin/prettier.cjs" @@ -19056,6 +19057,7 @@ "fs-extra": "^11.2.0", "got": "^14.4.5", "picocolors": "^1.1.0", + "prettier": "^3.4.2", "radash": "^12.1.0", "simple-git": "^3.27.0", "tree-kill": "^1.2.2", diff --git a/packages/cli/commands/new-project.ts b/packages/cli/commands/new-project.ts index df35bfc..6767a33 100644 --- a/packages/cli/commands/new-project.ts +++ b/packages/cli/commands/new-project.ts @@ -12,6 +12,7 @@ import { InjectConfigCodegen } from "../lib/codegen/inject-config"; import { cwd } from "process"; import { downloadRepository } from "../lib/new-project/actions/download-helper"; import { downloadDependenciesUsingNpm } from "../lib/new-project/actions/download-depedencies"; +import picocolors from "picocolors"; export class NewProjectCommand { constructor() {} @@ -75,7 +76,7 @@ export class NewProjectCommand { }, }, { - title: "Installing via npm", + title: "Installing dependencies via npm", task: async (message) => { await downloadDependenciesUsingNpm(name); return "Dependencies installed 🧰"; @@ -92,22 +93,30 @@ export class NewProjectCommand { `new-project-settings/config/${path}.json`; const url = `https://raw.githubusercontent.com/intentjs/registry/refs/heads/main/`; + + message(`Injecting config for ${picocolors.yellow(queue)} queue`); await injectConfigTask.handle( url + getProjectSettingConfNamespace(`queue/${queue}`) ); + message(`Injecting config for ${picocolors.yellow(database)} db`); await injectConfigTask.handle( url + getProjectSettingConfNamespace(`db/${database}`) ); + message( + `Injecting config for ${picocolors.yellow(storage)} filesystem` + ); await injectConfigTask.handle( url + getProjectSettingConfNamespace(`storage/${storage}`) ); + message(`Injecting config for ${picocolors.yellow(mailer)} mailer`); await injectConfigTask.handle( url + getProjectSettingConfNamespace(`mailer/${mailer}`) ); + message(`Injecting config for ${picocolors.yellow(cache)} cache`); await injectConfigTask.handle( url + getProjectSettingConfNamespace(`cache/${cache}`) ); @@ -199,7 +208,6 @@ export class NewProjectCommand { // Check if name is available const dirPath = join(process.cwd(), name); - console.log(dirPath); if (existsSync(dirPath)) { console.log( INTENT_LOG_PREFIX, diff --git a/packages/cli/lib/codegen/ast/inject-config.ts b/packages/cli/lib/codegen/ast/inject-config.ts index 31f9f17..ec3ed10 100644 --- a/packages/cli/lib/codegen/ast/inject-config.ts +++ b/packages/cli/lib/codegen/ast/inject-config.ts @@ -33,9 +33,11 @@ export class InjectConfig { return; } - this.addImports(registryObject); - this.updateObjectProperties(objects, registryObject); - await this.saveChanges(objects); + const propertyAdded = this.updateObjectProperties(objects, registryObject); + if (propertyAdded) { + this.addImports(registryObject); + await this.saveChanges(objects); + } } addImports(registryObject: Record): void { @@ -85,16 +87,25 @@ export class InjectConfig { private updateObjectProperties( objects: ObjectLiteralExpression, registryObject: Record - ): void { + ): boolean { // Remove existing properties + const initializerName = registryObject.key[registryObject.key.length - 1]; + let propAlreadyExists = false; objects.getProperties().forEach((property) => { - property.remove(); + const propName = property.getSymbol()?.getName(); + if (propName !== initializerName) { + property.remove(); + } else { + propAlreadyExists = true; + } }); + if (propAlreadyExists) return false; + // Add new property const newConfig = objects .addPropertyAssignment({ - name: registryObject.key[registryObject.key.length - 1], + name: initializerName, initializer: "{}", }) .getInitializer(); @@ -111,7 +122,10 @@ export class InjectConfig { : JSON.stringify(value), }); } + return true; } + + return false; } transformEnvValues(obj: Record) { diff --git a/packages/cli/lib/codegen/inject-config.ts b/packages/cli/lib/codegen/inject-config.ts index 604841f..e639971 100644 --- a/packages/cli/lib/codegen/inject-config.ts +++ b/packages/cli/lib/codegen/inject-config.ts @@ -39,7 +39,7 @@ export class InjectConfigCodegen { await this.setEnvironmentVariables(env); } } catch (e) { - console.log(e); + console.log("error ==> ", e); } } From 52540f933cf27ae42061bd598db7168d7e729ace Mon Sep 17 00:00:00 2001 From: Vinayak Sarawagi Date: Sun, 12 Jan 2025 17:06:40 +0530 Subject: [PATCH 6/7] fix(cli): bugs in the new project initialiser command --- packages/cli/commands/new-project.ts | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/packages/cli/commands/new-project.ts b/packages/cli/commands/new-project.ts index 6767a33..a17e997 100644 --- a/packages/cli/commands/new-project.ts +++ b/packages/cli/commands/new-project.ts @@ -27,6 +27,8 @@ export class NewProjectCommand { * Build Prompt Options */ const promptOptions = this.buildPromptOptions(options); + + p.intro("Setting up a new intent project"); const missingOptions = await p.group( { ...promptOptions }, { @@ -125,6 +127,16 @@ export class NewProjectCommand { }, }, ]); + + p.outro("Project setup complete! 🎉"); + + console.log("Building amazing products using Intent!"); + + console.log(); + console.log(`${picocolors.gray("$")} ${picocolors.green(`cd ${name}`)}`); + console.log( + `${picocolors.gray("$")} ${picocolors.cyan("node intent dev")}` + ); } buildPromptOptions(options: Record) { From 451e2a2fefb2dd947b7fc7b17876fab7ef03e3d7 Mon Sep 17 00:00:00 2001 From: Vinayak Sarawagi Date: Sun, 12 Jan 2025 17:08:10 +0530 Subject: [PATCH 7/7] refactor(cli): remove unnecessary packages --- package-lock.json | 233 -------------------------------------- packages/cli/package.json | 2 - 2 files changed, 235 deletions(-) diff --git a/package-lock.json b/package-lock.json index d782b7a..df66d30 100644 --- a/package-lock.json +++ b/package-lock.json @@ -5476,27 +5476,6 @@ "@types/superagent": "^8.1.0" } }, - "node_modules/@types/tar": { - "version": "6.1.13", - "resolved": "https://registry.npmjs.org/@types/tar/-/tar-6.1.13.tgz", - "integrity": "sha512-IznnlmU5f4WcGTh2ltRu/Ijpmk8wiWXfF0VA4s+HPjHZgvFggk1YaIkbo5krX/zUCzWF8N/l4+W/LNxnvAJ8nw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/node": "*", - "minipass": "^4.0.0" - } - }, - "node_modules/@types/tar/node_modules/minipass": { - "version": "4.2.8", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-4.2.8.tgz", - "integrity": "sha512-fNzuVyifolSLFL4NzpF+wEF4qrgqaaKX0haXPQEdQ7NKAN+WecoKMHV09YcuL/DHxrUsYQOK3MiuDf7Ip2OXfQ==", - "dev": true, - "license": "ISC", - "engines": { - "node": ">=8" - } - }, "node_modules/@types/through": { "version": "0.0.33", "dev": true, @@ -19055,7 +19034,6 @@ "chokidar": "^3.5.1", "commander": "^12.1.0", "fs-extra": "^11.2.0", - "got": "^14.4.5", "picocolors": "^1.1.0", "prettier": "^3.4.2", "radash": "^12.1.0", @@ -19071,7 +19049,6 @@ "@types/fs-extra": "^11.0.4", "@types/jest": "^29.5.13", "@types/node": "^22.7.4", - "@types/tar": "^6.1.13", "ts-jest": "^29.2.5", "ts-loader": "^9.5.1", "ts-node": "^10.9.2" @@ -19080,18 +19057,6 @@ "node": ">= 16.14" } }, - "packages/cli/node_modules/@sindresorhus/is": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-7.0.1.tgz", - "integrity": "sha512-QWLl2P+rsCJeofkDNIT3WFmb6NrRud1SUYW8dIhXK/46XFV8Q/g7Bsvib0Askb0reRLe+WYPeeE+l5cH7SlkuQ==", - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sindresorhus/is?sponsor=1" - } - }, "packages/cli/node_modules/@swc/cli": { "version": "0.5.2", "resolved": "https://registry.npmjs.org/@swc/cli/-/cli-0.5.2.tgz", @@ -19135,18 +19100,6 @@ "node": ">= 12" } }, - "packages/cli/node_modules/@szmarczak/http-timer": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-5.0.1.tgz", - "integrity": "sha512-+PmQX0PiAYPMeVYe237LJAYvOMYW1j2rH5YROyS3b4CTVJum34HfRvKvAzozHAQG0TnHNdUfY9nCeUyRAs//cw==", - "license": "MIT", - "dependencies": { - "defer-to-connect": "^2.0.1" - }, - "engines": { - "node": ">=14.16" - } - }, "packages/cli/node_modules/@ts-morph/common": { "version": "0.26.0", "resolved": "https://registry.npmjs.org/@ts-morph/common/-/common-0.26.0.tgz", @@ -19175,33 +19128,6 @@ "balanced-match": "^1.0.0" } }, - "packages/cli/node_modules/cacheable-lookup": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/cacheable-lookup/-/cacheable-lookup-7.0.0.tgz", - "integrity": "sha512-+qJyx4xiKra8mZrcwhjMRMUhD5NR1R8esPkzIYxX96JiecFoxAXFuz/GpR3+ev4PE1WamHip78wV0vcmPQtp8w==", - "license": "MIT", - "engines": { - "node": ">=14.16" - } - }, - "packages/cli/node_modules/cacheable-request": { - "version": "12.0.1", - "resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-12.0.1.tgz", - "integrity": "sha512-Yo9wGIQUaAfIbk+qY0X4cDQgCosecfBe3V9NSyeY4qPC2SAkbCS4Xj79VP8WOzitpJUZKc/wsRCYF5ariDIwkg==", - "license": "MIT", - "dependencies": { - "@types/http-cache-semantics": "^4.0.4", - "get-stream": "^9.0.1", - "http-cache-semantics": "^4.1.1", - "keyv": "^4.5.4", - "mimic-response": "^4.0.0", - "normalize-url": "^8.0.1", - "responselike": "^3.0.0" - }, - "engines": { - "node": ">=18" - } - }, "packages/cli/node_modules/chokidar": { "version": "3.5.1", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.1.tgz", @@ -19223,31 +19149,6 @@ "fsevents": "~2.3.1" } }, - "packages/cli/node_modules/form-data-encoder": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/form-data-encoder/-/form-data-encoder-4.0.2.tgz", - "integrity": "sha512-KQVhvhK8ZkWzxKxOr56CPulAhH3dobtuQ4+hNQ+HekH/Wp5gSOafqRAeTphQUJAIk0GBvHZgJ2ZGRWd5kphMuw==", - "license": "MIT", - "engines": { - "node": ">= 18" - } - }, - "packages/cli/node_modules/get-stream": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-9.0.1.tgz", - "integrity": "sha512-kVCxPF3vQM/N0B1PmoqVUqgHP+EeVjmZSQn+1oCRPxd2P21P2F19lIgbR3HBosbB1PUhOAoctJnfEn2GbN2eZA==", - "license": "MIT", - "dependencies": { - "@sec-ant/readable-stream": "^0.4.1", - "is-stream": "^4.0.1" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "packages/cli/node_modules/glob-parent": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", @@ -19260,80 +19161,6 @@ "node": ">= 6" } }, - "packages/cli/node_modules/got": { - "version": "14.4.5", - "resolved": "https://registry.npmjs.org/got/-/got-14.4.5.tgz", - "integrity": "sha512-sq+uET8TnNKRNnjEOPJzMcxeI0irT8BBNmf+GtZcJpmhYsQM1DSKmCROUjPWKsXZ5HzwD5Cf5/RV+QD9BSTxJg==", - "license": "MIT", - "dependencies": { - "@sindresorhus/is": "^7.0.1", - "@szmarczak/http-timer": "^5.0.1", - "cacheable-lookup": "^7.0.0", - "cacheable-request": "^12.0.1", - "decompress-response": "^6.0.0", - "form-data-encoder": "^4.0.2", - "http2-wrapper": "^2.2.1", - "lowercase-keys": "^3.0.0", - "p-cancelable": "^4.0.1", - "responselike": "^3.0.0", - "type-fest": "^4.26.1" - }, - "engines": { - "node": ">=20" - }, - "funding": { - "url": "https://github.com/sindresorhus/got?sponsor=1" - } - }, - "packages/cli/node_modules/http2-wrapper": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/http2-wrapper/-/http2-wrapper-2.2.1.tgz", - "integrity": "sha512-V5nVw1PAOgfI3Lmeaj2Exmeg7fenjhRUgz1lPSezy1CuhPYbgQtbQj4jZfEAEMlaL+vupsvhjqCyjzob0yxsmQ==", - "license": "MIT", - "dependencies": { - "quick-lru": "^5.1.1", - "resolve-alpn": "^1.2.0" - }, - "engines": { - "node": ">=10.19.0" - } - }, - "packages/cli/node_modules/is-stream": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-4.0.1.tgz", - "integrity": "sha512-Dnz92NInDqYckGEUJv689RbRiTSEHCQ7wOVeALbkOz999YpqT46yMRIGtSNl2iCL1waAZSx40+h59NV/EwzV/A==", - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "packages/cli/node_modules/lowercase-keys": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-3.0.0.tgz", - "integrity": "sha512-ozCC6gdQ+glXOQsveKD0YsDy8DSQFjDTz4zyzEHNV5+JP5D62LmfDZ6o1cycFx9ouG940M5dE8C8CTewdj2YWQ==", - "license": "MIT", - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "packages/cli/node_modules/mimic-response": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-4.0.0.tgz", - "integrity": "sha512-e5ISH9xMYU0DzrT+jl8q2ze9D6eWBto+I8CNpe+VI+K2J/F/k3PdkdTdz4wvGVH4NTpo+NRYTVIuMQEMMcsLqg==", - "license": "MIT", - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "packages/cli/node_modules/minimatch": { "version": "9.0.5", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", @@ -19349,39 +19176,6 @@ "url": "https://github.com/sponsors/isaacs" } }, - "packages/cli/node_modules/normalize-url": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-8.0.1.tgz", - "integrity": "sha512-IO9QvjUMWxPQQhs60oOu10CRkWCiZzSUkzbXGGV9pviYl1fXYcvkzQ5jV9z8Y6un8ARoVRl4EtC6v6jNqbaJ/w==", - "license": "MIT", - "engines": { - "node": ">=14.16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "packages/cli/node_modules/p-cancelable": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-4.0.1.tgz", - "integrity": "sha512-wBowNApzd45EIKdO1LaU+LrMBwAcjfPaYtVzV3lmfM3gf8Z4CHZsiIqlM8TZZ8okYvh5A1cP6gTfCRQtwUpaUg==", - "license": "MIT", - "engines": { - "node": ">=14.16" - } - }, - "packages/cli/node_modules/quick-lru": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-5.1.1.tgz", - "integrity": "sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==", - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "packages/cli/node_modules/readdirp": { "version": "3.5.0", "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.5.0.tgz", @@ -19394,21 +19188,6 @@ "node": ">=8.10.0" } }, - "packages/cli/node_modules/responselike": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/responselike/-/responselike-3.0.0.tgz", - "integrity": "sha512-40yHxbNcl2+rzXvZuVkrYohathsSJlMTXKryG5y8uciHv1+xDLHQpgjG64JUO9nrEq2jGLH6IZ8BcZyw3wrweg==", - "license": "MIT", - "dependencies": { - "lowercase-keys": "^3.0.0" - }, - "engines": { - "node": ">=14.16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "packages/cli/node_modules/source-map": { "version": "0.7.4", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.4.tgz", @@ -19428,18 +19207,6 @@ "code-block-writer": "^13.0.3" } }, - "packages/cli/node_modules/type-fest": { - "version": "4.31.0", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.31.0.tgz", - "integrity": "sha512-yCxltHW07Nkhv/1F6wWBr8kz+5BGMfP+RbRSYFnegVb0qV/UMT0G0ElBloPVerqn4M2ZV80Ir1FtCcYv1cT6vQ==", - "license": "(MIT OR CC0-1.0)", - "engines": { - "node": ">=16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "packages/core": { "name": "@intentjs/core", "version": "0.1.40", diff --git a/packages/cli/package.json b/packages/cli/package.json index 703a170..d861297 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -33,7 +33,6 @@ "chokidar": "^3.5.1", "commander": "^12.1.0", "fs-extra": "^11.2.0", - "got": "^14.4.5", "picocolors": "^1.1.0", "prettier": "^3.4.2", "radash": "^12.1.0", @@ -46,7 +45,6 @@ "@types/fs-extra": "^11.0.4", "@types/jest": "^29.5.13", "@types/node": "^22.7.4", - "@types/tar": "^6.1.13", "ts-jest": "^29.2.5", "ts-loader": "^9.5.1", "ts-node": "^10.9.2"