render-create 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (82) hide show
  1. package/README.md +207 -0
  2. package/dist/cli.d.ts +6 -0
  3. package/dist/cli.js +45 -0
  4. package/dist/commands/check.d.ts +8 -0
  5. package/dist/commands/check.js +96 -0
  6. package/dist/commands/init.d.ts +12 -0
  7. package/dist/commands/init.js +1201 -0
  8. package/dist/commands/sync.d.ts +8 -0
  9. package/dist/commands/sync.js +126 -0
  10. package/dist/types.d.ts +246 -0
  11. package/dist/types.js +4 -0
  12. package/dist/utils.d.ts +53 -0
  13. package/dist/utils.js +142 -0
  14. package/package.json +65 -0
  15. package/templates/LINTING_SETUP.md +205 -0
  16. package/templates/README_TEMPLATE.md +68 -0
  17. package/templates/STYLE_GUIDE.md +241 -0
  18. package/templates/assets/favicon.png +0 -0
  19. package/templates/assets/favicon.svg +17 -0
  20. package/templates/biome.json +43 -0
  21. package/templates/cursor/rules/drizzle.mdc +165 -0
  22. package/templates/cursor/rules/fastify.mdc +132 -0
  23. package/templates/cursor/rules/general.mdc +112 -0
  24. package/templates/cursor/rules/nextjs.mdc +89 -0
  25. package/templates/cursor/rules/python.mdc +89 -0
  26. package/templates/cursor/rules/react.mdc +200 -0
  27. package/templates/cursor/rules/sqlalchemy.mdc +205 -0
  28. package/templates/cursor/rules/tailwind.mdc +139 -0
  29. package/templates/cursor/rules/typescript.mdc +112 -0
  30. package/templates/cursor/rules/vite.mdc +169 -0
  31. package/templates/cursor/rules/workflows.mdc +349 -0
  32. package/templates/docker-compose.example.yml +55 -0
  33. package/templates/drizzle/db-index.ts +15 -0
  34. package/templates/drizzle/drizzle.config.ts +10 -0
  35. package/templates/drizzle/schema.ts +12 -0
  36. package/templates/env.example +15 -0
  37. package/templates/fastapi/app/__init__.py +1 -0
  38. package/templates/fastapi/app/config.py +12 -0
  39. package/templates/fastapi/app/database.py +16 -0
  40. package/templates/fastapi/app/models.py +13 -0
  41. package/templates/fastapi/main.py +22 -0
  42. package/templates/fastify/index.ts +40 -0
  43. package/templates/github/CODEOWNERS +10 -0
  44. package/templates/github/ISSUE_TEMPLATE/bug_report.md +39 -0
  45. package/templates/github/ISSUE_TEMPLATE/feature_request.md +23 -0
  46. package/templates/github/PULL_REQUEST_TEMPLATE.md +25 -0
  47. package/templates/gitignore/node.gitignore +41 -0
  48. package/templates/gitignore/python.gitignore +49 -0
  49. package/templates/multi-api/README.md +60 -0
  50. package/templates/multi-api/gitignore +28 -0
  51. package/templates/multi-api/node-api/drizzle.config.ts +10 -0
  52. package/templates/multi-api/node-api/package-simple.json +13 -0
  53. package/templates/multi-api/node-api/package.json +16 -0
  54. package/templates/multi-api/node-api/src/db/index.ts +13 -0
  55. package/templates/multi-api/node-api/src/db/schema.ts +9 -0
  56. package/templates/multi-api/node-api/src/index-simple.ts +36 -0
  57. package/templates/multi-api/node-api/src/index.ts +50 -0
  58. package/templates/multi-api/node-api/tsconfig.json +20 -0
  59. package/templates/multi-api/python-api/app/__init__.py +1 -0
  60. package/templates/multi-api/python-api/app/config.py +12 -0
  61. package/templates/multi-api/python-api/app/database.py +16 -0
  62. package/templates/multi-api/python-api/app/models.py +13 -0
  63. package/templates/multi-api/python-api/main-simple.py +25 -0
  64. package/templates/multi-api/python-api/main.py +44 -0
  65. package/templates/multi-api/python-api/requirements-simple.txt +3 -0
  66. package/templates/multi-api/python-api/requirements.txt +8 -0
  67. package/templates/next/globals.css +126 -0
  68. package/templates/next/layout.tsx +34 -0
  69. package/templates/next/next.config.static.ts +10 -0
  70. package/templates/next/page-fullstack.tsx +120 -0
  71. package/templates/next/page.tsx +72 -0
  72. package/templates/presets.json +581 -0
  73. package/templates/ruff.toml +30 -0
  74. package/templates/tsconfig.base.json +17 -0
  75. package/templates/vite/index.css +127 -0
  76. package/templates/vite/vite.config.ts +7 -0
  77. package/templates/worker/py/cron.py +53 -0
  78. package/templates/worker/py/worker.py +95 -0
  79. package/templates/worker/py/workflow.py +73 -0
  80. package/templates/worker/ts/cron.ts +49 -0
  81. package/templates/worker/ts/worker.ts +84 -0
  82. package/templates/worker/ts/workflow.ts +67 -0
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Sync command - Update local rules with the latest package templates
3
+ */
4
+ import type { SyncOptions } from "../types.js";
5
+ /**
6
+ * Main sync command handler
7
+ */
8
+ export declare function sync(options: SyncOptions): Promise<void>;
@@ -0,0 +1,126 @@
1
+ /**
2
+ * Sync command - Update local rules with the latest package templates
3
+ */
4
+ import { existsSync, readdirSync } from "node:fs";
5
+ import { join } from "node:path";
6
+ import chalk from "chalk";
7
+ import inquirer from "inquirer";
8
+ import { compareFiles, copyTemplate, displayDiff, getTargetDir, TEMPLATES_DIR } from "../utils.js";
9
+ /**
10
+ * Get list of synced files in the target project
11
+ */
12
+ function getSyncedFiles(targetDir) {
13
+ const rules = [];
14
+ const configs = [];
15
+ // Check for cursor rules
16
+ const rulesDir = join(targetDir, ".cursor", "rules");
17
+ if (existsSync(rulesDir)) {
18
+ const files = readdirSync(rulesDir);
19
+ for (const file of files) {
20
+ if (file.endsWith(".mdc")) {
21
+ rules.push(file.replace(".mdc", ""));
22
+ }
23
+ }
24
+ }
25
+ // Check for config files
26
+ if (existsSync(join(targetDir, "biome.json"))) {
27
+ configs.push("biome");
28
+ }
29
+ if (existsSync(join(targetDir, "ruff.toml"))) {
30
+ configs.push("ruff");
31
+ }
32
+ if (existsSync(join(targetDir, "tsconfig.base.json"))) {
33
+ configs.push("tsconfig");
34
+ }
35
+ return { rules, configs };
36
+ }
37
+ /**
38
+ * Main sync command handler
39
+ */
40
+ export async function sync(options) {
41
+ const targetDir = getTargetDir();
42
+ const { rules, configs } = getSyncedFiles(targetDir);
43
+ if (rules.length === 0 && configs.length === 0) {
44
+ console.log(chalk.yellow("No cursor-rules files found in this project."));
45
+ console.log(chalk.gray("Run 'npx @render-examples/cursor-rules init' to set up."));
46
+ return;
47
+ }
48
+ console.log(chalk.blue("Checking for updates...\n"));
49
+ const outOfSync = [];
50
+ // Check rule files
51
+ for (const rule of rules) {
52
+ const templatePath = `cursor/rules/${rule}.mdc`;
53
+ const targetPath = join(targetDir, ".cursor", "rules", `${rule}.mdc`);
54
+ // Check if template exists
55
+ if (!existsSync(join(TEMPLATES_DIR, templatePath))) {
56
+ console.log(chalk.yellow(` ${rule}.mdc - custom rule (no template)`));
57
+ continue;
58
+ }
59
+ const result = compareFiles(templatePath, targetPath);
60
+ if (result.status === "in-sync") {
61
+ console.log(chalk.green(` ${rule}.mdc - up to date`));
62
+ }
63
+ else if (result.status === "out-of-sync" && result.diff) {
64
+ console.log(chalk.yellow(` ${rule}.mdc - out of sync`));
65
+ outOfSync.push({ templatePath, targetPath, diff: result.diff });
66
+ }
67
+ }
68
+ // Check config files
69
+ const configMappings = {
70
+ biome: { template: "biome.json", target: "biome.json" },
71
+ ruff: { template: "ruff.toml", target: "ruff.toml" },
72
+ tsconfig: { template: "tsconfig.base.json", target: "tsconfig.base.json" },
73
+ };
74
+ for (const config of configs) {
75
+ const mapping = configMappings[config];
76
+ if (!mapping) {
77
+ continue;
78
+ }
79
+ const targetPath = join(targetDir, mapping.target);
80
+ const result = compareFiles(mapping.template, targetPath);
81
+ if (result.status === "in-sync") {
82
+ console.log(chalk.green(` ${mapping.target} - up to date`));
83
+ }
84
+ else if (result.status === "out-of-sync" && result.diff) {
85
+ console.log(chalk.yellow(` ${mapping.target} - out of sync`));
86
+ outOfSync.push({ templatePath: mapping.template, targetPath, diff: result.diff });
87
+ }
88
+ }
89
+ if (outOfSync.length === 0) {
90
+ console.log(chalk.green("\nAll files are up to date!"));
91
+ return;
92
+ }
93
+ console.log(chalk.yellow(`\n${outOfSync.length} file(s) out of sync.`));
94
+ // Show diffs if not in dry-run mode
95
+ if (options.dryRun) {
96
+ console.log(chalk.gray("\n--dry-run: Showing diffs without applying changes:\n"));
97
+ for (const file of outOfSync) {
98
+ displayDiff(file.diff, file.targetPath);
99
+ }
100
+ return;
101
+ }
102
+ // Confirm before updating
103
+ if (!options.force) {
104
+ for (const file of outOfSync) {
105
+ displayDiff(file.diff, file.targetPath);
106
+ }
107
+ const { confirm } = await inquirer.prompt([
108
+ {
109
+ type: "confirm",
110
+ name: "confirm",
111
+ message: "Apply these changes?",
112
+ default: false,
113
+ },
114
+ ]);
115
+ if (!confirm) {
116
+ console.log(chalk.gray("Sync cancelled."));
117
+ return;
118
+ }
119
+ }
120
+ // Apply updates
121
+ console.log(chalk.blue("\nApplying updates...\n"));
122
+ for (const file of outOfSync) {
123
+ copyTemplate(file.templatePath, file.targetPath);
124
+ }
125
+ console.log(chalk.green("\nSync complete!"));
126
+ }
@@ -0,0 +1,246 @@
1
+ /**
2
+ * Type definitions for cursor-rules CLI
3
+ */
4
+ /** Environment variable from key/value */
5
+ export interface EnvVarKeyValue {
6
+ key: string;
7
+ value?: string;
8
+ generateValue?: boolean;
9
+ sync?: boolean;
10
+ }
11
+ /** Environment variable from database reference */
12
+ export interface EnvVarFromDatabase {
13
+ key: string;
14
+ fromDatabase: {
15
+ name: string;
16
+ property: string;
17
+ };
18
+ }
19
+ /** Environment variable from service reference */
20
+ export interface EnvVarFromService {
21
+ key: string;
22
+ fromService: {
23
+ type: string;
24
+ name: string;
25
+ property?: string;
26
+ envVarKey?: string;
27
+ };
28
+ }
29
+ export type BlueprintEnvVar = EnvVarKeyValue | EnvVarFromDatabase | EnvVarFromService;
30
+ /** Route configuration for static sites */
31
+ export interface BlueprintRoute {
32
+ type: "redirect" | "rewrite";
33
+ source: string;
34
+ destination: string;
35
+ }
36
+ /** Service definition in blueprint */
37
+ export interface BlueprintService {
38
+ type: "web" | "worker" | "pserv" | "cron";
39
+ name?: string;
40
+ runtime: "node" | "python" | "docker" | "static" | "go" | "rust" | "ruby" | "elixir";
41
+ plan?: string;
42
+ rootDir?: string;
43
+ buildCommand?: string;
44
+ startCommand?: string;
45
+ healthCheckPath?: string;
46
+ staticPublishPath?: string;
47
+ envVars?: BlueprintEnvVar[];
48
+ routes?: BlueprintRoute[];
49
+ }
50
+ /** Database definition in blueprint */
51
+ export interface BlueprintDatabase {
52
+ name: string;
53
+ plan?: string;
54
+ postgresMajorVersion?: string;
55
+ }
56
+ /** Key Value (Redis) definition in blueprint */
57
+ export interface BlueprintKeyValue {
58
+ type: "keyvalue";
59
+ name: string;
60
+ plan?: string;
61
+ maxmemoryPolicy?: string;
62
+ ipAllowList?: Array<{
63
+ source: string;
64
+ description?: string;
65
+ }>;
66
+ }
67
+ /** Blueprint configuration for render.yaml */
68
+ export interface BlueprintConfig {
69
+ services?: BlueprintService[];
70
+ databases?: BlueprintDatabase[];
71
+ keyValues?: BlueprintKeyValue[];
72
+ }
73
+ export interface Preset {
74
+ name: string;
75
+ description: string;
76
+ rules: string[];
77
+ configs: string[];
78
+ /** Package manager to use (npm, pnpm, yarn, pip) */
79
+ packageManager?: "npm" | "pnpm" | "yarn" | "pip";
80
+ /** Command to scaffold the project (e.g., npx create-next-app) */
81
+ createCommand?: string;
82
+ /** Dependencies to add after create command */
83
+ postCreateDependencies?: string[];
84
+ /** Dev dependencies to add after create command */
85
+ postCreateDevDependencies?: string[];
86
+ /** Scripts to add to package.json after create */
87
+ postCreateScripts?: Record<string, string>;
88
+ /** Files to copy after create command (target -> template source) */
89
+ postCreateFiles?: Record<string, string>;
90
+ /** Files to delete after create command */
91
+ postCreateDelete?: string[];
92
+ /** For presets without createCommand: npm dependencies */
93
+ dependencies?: string[];
94
+ /** For presets without createCommand: npm dev dependencies */
95
+ devDependencies?: string[];
96
+ /** For presets without createCommand: scripts for package.json */
97
+ scripts?: Record<string, string>;
98
+ /** For presets without createCommand: files to scaffold (target -> template) */
99
+ scaffoldFiles?: Record<string, string>;
100
+ /** Python dependencies for requirements.txt */
101
+ pythonDependencies?: string[];
102
+ /** Render Blueprint configuration */
103
+ blueprint?: BlueprintConfig;
104
+ }
105
+ export interface PresetsConfig {
106
+ presets: Record<string, Preset>;
107
+ components?: ComponentsConfig;
108
+ }
109
+ /** Base component definition */
110
+ export interface BaseComponent {
111
+ name: string;
112
+ description?: string;
113
+ subdir: string;
114
+ rules?: string[];
115
+ configs?: string[];
116
+ }
117
+ /** Blueprint config for frontend deploy types */
118
+ export interface FrontendBlueprintConfig {
119
+ type: "web";
120
+ runtime: "node" | "static";
121
+ buildCommand: string;
122
+ startCommand?: string;
123
+ staticPublishPath?: string;
124
+ healthCheckPath?: string;
125
+ envVars?: BlueprintEnvVar[];
126
+ routes?: BlueprintRoute[];
127
+ }
128
+ /** Frontend component (Next.js, Vite) */
129
+ export interface FrontendComponent extends BaseComponent {
130
+ createCommand: string;
131
+ postCreateDependencies?: string[];
132
+ postCreateDevDependencies?: string[];
133
+ postCreateScripts?: Record<string, string>;
134
+ /** Files to copy for static deploy */
135
+ postCreateFilesStatic?: Record<string, string>;
136
+ /** Files to copy for webservice deploy */
137
+ postCreateFilesWebservice?: Record<string, string>;
138
+ /** Files to copy for both deploy types */
139
+ postCreateFiles?: Record<string, string>;
140
+ postCreateDelete?: string[];
141
+ /** Whether this frontend supports webservice deploy (default: true for Next.js, false for Vite) */
142
+ supportsWebservice?: boolean;
143
+ /** Blueprint config for static deploy */
144
+ blueprintStatic: FrontendBlueprintConfig;
145
+ /** Blueprint config for webservice deploy */
146
+ blueprintWebservice?: FrontendBlueprintConfig;
147
+ }
148
+ /** API component (Fastify, FastAPI) */
149
+ export interface ApiComponent extends BaseComponent {
150
+ runtime: "node" | "python";
151
+ /** For Node.js APIs */
152
+ dependencies?: string[];
153
+ devDependencies?: string[];
154
+ scripts?: Record<string, string>;
155
+ /** For Python APIs */
156
+ pythonDependencies?: string[];
157
+ scaffoldFiles: Record<string, string>;
158
+ blueprint: {
159
+ type: "web";
160
+ runtime: "node" | "python";
161
+ buildCommand: string;
162
+ startCommand: string;
163
+ healthCheckPath: string;
164
+ envVars?: BlueprintEnvVar[];
165
+ };
166
+ }
167
+ /** Worker type for async components */
168
+ export type WorkerType = "worker" | "cron" | "workflow";
169
+ /** Worker/Cron/Workflow component */
170
+ export interface WorkerComponent extends BaseComponent {
171
+ workerType: WorkerType;
172
+ runtime: "node" | "python";
173
+ /** SDK to install (e.g., @renderinc/sdk for workflows) */
174
+ sdk?: string;
175
+ /** For Node.js workers */
176
+ dependencies?: string[];
177
+ devDependencies?: string[];
178
+ scripts?: Record<string, string>;
179
+ /** For Python workers */
180
+ pythonDependencies?: string[];
181
+ scaffoldFiles: Record<string, string>;
182
+ /** Blueprint config - not available for workflows yet */
183
+ blueprint?: {
184
+ type: "worker" | "cron";
185
+ runtime: "node" | "python";
186
+ buildCommand: string;
187
+ startCommand: string;
188
+ schedule?: string;
189
+ envVars?: BlueprintEnvVar[];
190
+ };
191
+ }
192
+ /** Database component (PostgreSQL) */
193
+ export interface DatabaseComponent {
194
+ name: string;
195
+ description?: string;
196
+ blueprint: BlueprintDatabase;
197
+ }
198
+ /** Cache component (Redis/KeyVal) */
199
+ export interface CacheComponent {
200
+ name: string;
201
+ description?: string;
202
+ blueprint: BlueprintKeyValue;
203
+ }
204
+ /** All components configuration */
205
+ export interface ComponentsConfig {
206
+ frontends: Record<string, FrontendComponent>;
207
+ apis: Record<string, ApiComponent>;
208
+ workers: Record<string, WorkerComponent>;
209
+ databases: Record<string, DatabaseComponent>;
210
+ caches: Record<string, CacheComponent>;
211
+ }
212
+ /** User's composable selection */
213
+ /** Deploy type for frontend services */
214
+ export type DeployType = "static" | "webservice";
215
+ export interface ComposableSelection {
216
+ projectName: string;
217
+ frontend: string | null;
218
+ frontendDeployType: DeployType | null;
219
+ apis: string[];
220
+ workers: string[];
221
+ database: string | null;
222
+ cache: string | null;
223
+ extras: string[];
224
+ }
225
+ export interface InitOptions {
226
+ preset?: string;
227
+ yes?: boolean;
228
+ name?: string;
229
+ skipInstall?: boolean;
230
+ }
231
+ export interface SyncOptions {
232
+ force?: boolean;
233
+ dryRun?: boolean;
234
+ }
235
+ export interface CheckOptions {
236
+ ci?: boolean;
237
+ }
238
+ export type FileStatus = "in-sync" | "out-of-sync" | "local-missing" | "template-missing";
239
+ export interface CompareResult {
240
+ status: FileStatus;
241
+ diff: import("diff").Change[] | null;
242
+ }
243
+ export interface FileMapping {
244
+ template: string;
245
+ target: string;
246
+ }
package/dist/types.js ADDED
@@ -0,0 +1,4 @@
1
+ /**
2
+ * Type definitions for cursor-rules CLI
3
+ */
4
+ export {};
@@ -0,0 +1,53 @@
1
+ /**
2
+ * Utility functions for file operations, prompts, and diff display
3
+ */
4
+ import { type Change } from "diff";
5
+ import type { CompareResult, PresetsConfig } from "./types.js";
6
+ /** Path to templates directory */
7
+ export declare const TEMPLATES_DIR: string;
8
+ /** Path to presets.json */
9
+ export declare const PRESETS_PATH: string;
10
+ /**
11
+ * Load presets configuration
12
+ */
13
+ export declare function loadPresets(): PresetsConfig;
14
+ /**
15
+ * Ensure a directory exists, creating it if necessary
16
+ */
17
+ export declare function ensureDir(dirPath: string): void;
18
+ /**
19
+ * Copy a template file to the target project
20
+ */
21
+ export declare function copyTemplate(templatePath: string, targetPath: string): boolean;
22
+ /**
23
+ * Copy a template file with variable substitution
24
+ */
25
+ export declare function copyTemplateWithVars(templatePath: string, targetPath: string, vars?: Record<string, string>): boolean;
26
+ /**
27
+ * Read a file, returning null if it doesn't exist
28
+ */
29
+ export declare function readFileSafe(filePath: string): string | null;
30
+ /**
31
+ * Compare two files and return diff
32
+ */
33
+ export declare function compareFiles(templatePath: string, localPath: string): CompareResult;
34
+ /**
35
+ * Display a diff in the terminal
36
+ */
37
+ export declare function displayDiff(diff: Change[], filePath: string): void;
38
+ /**
39
+ * Get the current working directory (target project)
40
+ */
41
+ export declare function getTargetDir(): string;
42
+ /**
43
+ * Check if we're in a valid project directory
44
+ */
45
+ export declare function isValidProjectDir(): boolean;
46
+ /**
47
+ * Get list of rules for a preset
48
+ */
49
+ export declare function getRulesForPreset(presetId: string): string[];
50
+ /**
51
+ * Get list of configs for a preset
52
+ */
53
+ export declare function getConfigsForPreset(presetId: string): string[];
package/dist/utils.js ADDED
@@ -0,0 +1,142 @@
1
+ /**
2
+ * Utility functions for file operations, prompts, and diff display
3
+ */
4
+ import { copyFileSync, existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
5
+ import { dirname, join, resolve } from "node:path";
6
+ import { fileURLToPath } from "node:url";
7
+ import chalk from "chalk";
8
+ import { diffLines } from "diff";
9
+ const __filename = fileURLToPath(import.meta.url);
10
+ const __dirname = dirname(__filename);
11
+ /** Path to templates directory */
12
+ export const TEMPLATES_DIR = resolve(__dirname, "../templates");
13
+ /** Path to presets.json */
14
+ export const PRESETS_PATH = join(TEMPLATES_DIR, "presets.json");
15
+ /**
16
+ * Load presets configuration
17
+ */
18
+ export function loadPresets() {
19
+ const content = readFileSync(PRESETS_PATH, "utf-8");
20
+ return JSON.parse(content);
21
+ }
22
+ /**
23
+ * Ensure a directory exists, creating it if necessary
24
+ */
25
+ export function ensureDir(dirPath) {
26
+ if (!existsSync(dirPath)) {
27
+ mkdirSync(dirPath, { recursive: true });
28
+ }
29
+ }
30
+ /**
31
+ * Copy a template file to the target project
32
+ */
33
+ export function copyTemplate(templatePath, targetPath) {
34
+ const sourcePath = join(TEMPLATES_DIR, templatePath);
35
+ if (!existsSync(sourcePath)) {
36
+ console.log(chalk.yellow(` Warning: Template not found: ${templatePath}`));
37
+ return false;
38
+ }
39
+ ensureDir(dirname(targetPath));
40
+ copyFileSync(sourcePath, targetPath);
41
+ console.log(chalk.green(` Created ${targetPath}`));
42
+ return true;
43
+ }
44
+ /**
45
+ * Copy a template file with variable substitution
46
+ */
47
+ export function copyTemplateWithVars(templatePath, targetPath, vars = {}) {
48
+ const sourcePath = join(TEMPLATES_DIR, templatePath);
49
+ if (!existsSync(sourcePath)) {
50
+ console.log(chalk.yellow(` Warning: Template not found: ${templatePath}`));
51
+ return false;
52
+ }
53
+ let content = readFileSync(sourcePath, "utf-8");
54
+ // Replace {{VAR_NAME}} patterns
55
+ for (const [key, value] of Object.entries(vars)) {
56
+ content = content.replace(new RegExp(`\\{\\{${key}\\}\\}`, "g"), value);
57
+ }
58
+ ensureDir(dirname(targetPath));
59
+ writeFileSync(targetPath, content);
60
+ console.log(chalk.green(` Created ${targetPath}`));
61
+ return true;
62
+ }
63
+ /**
64
+ * Read a file, returning null if it doesn't exist
65
+ */
66
+ export function readFileSafe(filePath) {
67
+ try {
68
+ return readFileSync(filePath, "utf-8");
69
+ }
70
+ catch {
71
+ return null;
72
+ }
73
+ }
74
+ /**
75
+ * Compare two files and return diff
76
+ */
77
+ export function compareFiles(templatePath, localPath) {
78
+ const templateContent = readFileSafe(join(TEMPLATES_DIR, templatePath));
79
+ const localContent = readFileSafe(localPath);
80
+ if (!templateContent) {
81
+ return { status: "template-missing", diff: null };
82
+ }
83
+ if (!localContent) {
84
+ return { status: "local-missing", diff: null };
85
+ }
86
+ if (templateContent === localContent) {
87
+ return { status: "in-sync", diff: null };
88
+ }
89
+ const diff = diffLines(localContent, templateContent);
90
+ return { status: "out-of-sync", diff };
91
+ }
92
+ /**
93
+ * Display a diff in the terminal
94
+ */
95
+ export function displayDiff(diff, filePath) {
96
+ console.log(chalk.cyan(`\n--- ${filePath}`));
97
+ for (const part of diff) {
98
+ if (part.added) {
99
+ process.stdout.write(chalk.green(part.value));
100
+ }
101
+ else if (part.removed) {
102
+ process.stdout.write(chalk.red(part.value));
103
+ }
104
+ else {
105
+ process.stdout.write(chalk.gray(part.value));
106
+ }
107
+ }
108
+ console.log();
109
+ }
110
+ /**
111
+ * Get the current working directory (target project)
112
+ */
113
+ export function getTargetDir() {
114
+ return process.cwd();
115
+ }
116
+ /**
117
+ * Check if we're in a valid project directory
118
+ */
119
+ export function isValidProjectDir() {
120
+ const cwd = getTargetDir();
121
+ // Check for common project indicators
122
+ return (existsSync(join(cwd, "package.json")) ||
123
+ existsSync(join(cwd, "requirements.txt")) ||
124
+ existsSync(join(cwd, "pyproject.toml")) ||
125
+ existsSync(join(cwd, ".git")));
126
+ }
127
+ /**
128
+ * Get list of rules for a preset
129
+ */
130
+ export function getRulesForPreset(presetId) {
131
+ const presets = loadPresets();
132
+ const preset = presets.presets[presetId];
133
+ return preset?.rules ?? [];
134
+ }
135
+ /**
136
+ * Get list of configs for a preset
137
+ */
138
+ export function getConfigsForPreset(presetId) {
139
+ const presets = loadPresets();
140
+ const preset = presets.presets[presetId];
141
+ return preset?.configs ?? [];
142
+ }
package/package.json ADDED
@@ -0,0 +1,65 @@
1
+ {
2
+ "name": "render-create",
3
+ "version": "0.1.0",
4
+ "description": "CLI to scaffold and deploy applications on Render with best practices, Cursor rules, and Infrastructure as Code",
5
+ "type": "module",
6
+ "bin": {
7
+ "render-create": "dist/cli.js"
8
+ },
9
+ "repository": {
10
+ "type": "git",
11
+ "url": "https://github.com/R4ph-t/render-create-demo.git"
12
+ },
13
+ "homepage": "https://github.com/R4ph-t/render-create-demo#readme",
14
+ "bugs": {
15
+ "url": "https://github.com/R4ph-t/render-create-demo/issues"
16
+ },
17
+ "files": [
18
+ "dist",
19
+ "templates"
20
+ ],
21
+ "scripts": {
22
+ "build": "tsc",
23
+ "dev": "tsc --watch",
24
+ "lint": "biome check .",
25
+ "format": "biome check --write .",
26
+ "test": "vitest run",
27
+ "test:watch": "vitest",
28
+ "prepublishOnly": "npm run build",
29
+ "preversion": "npm run lint && npm run build && npm test",
30
+ "postversion": "git push && git push --tags",
31
+ "release:patch": "npm version patch",
32
+ "release:minor": "npm version minor",
33
+ "release:major": "npm version major"
34
+ },
35
+ "keywords": [
36
+ "render",
37
+ "create",
38
+ "scaffold",
39
+ "deploy",
40
+ "template",
41
+ "cursor",
42
+ "cli",
43
+ "infrastructure-as-code",
44
+ "blueprint"
45
+ ],
46
+ "author": "Render",
47
+ "license": "MIT",
48
+ "dependencies": {
49
+ "chalk": "^5.3.0",
50
+ "commander": "^12.1.0",
51
+ "diff": "^7.0.0",
52
+ "inquirer": "^12.3.0"
53
+ },
54
+ "devDependencies": {
55
+ "@biomejs/biome": "^2.0.0",
56
+ "@types/diff": "^7.0.0",
57
+ "@types/inquirer": "^9.0.7",
58
+ "@types/node": "^22.0.0",
59
+ "typescript": "^5.7.0",
60
+ "vitest": "^4.0.18"
61
+ },
62
+ "engines": {
63
+ "node": ">=18.0.0"
64
+ }
65
+ }