@actuallyjamez/elysian 0.2.1 → 0.4.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.
- package/dist/cli/commands/init/detect.d.ts +33 -0
- package/dist/cli/commands/init/detect.js +98 -0
- package/dist/cli/commands/init/index.d.ts +6 -0
- package/dist/cli/commands/init/index.js +6 -0
- package/dist/cli/commands/init/prompts.d.ts +23 -0
- package/dist/cli/commands/init/prompts.js +93 -0
- package/dist/cli/commands/init/scaffold.d.ts +26 -0
- package/dist/cli/commands/init/scaffold.js +196 -0
- package/dist/cli/commands/init/templates.d.ts +23 -0
- package/dist/cli/commands/init/templates.js +103 -0
- package/dist/cli/commands/init/terraform.d.ts +44 -0
- package/dist/cli/commands/init/terraform.js +309 -0
- package/dist/cli/commands/init.d.ts +1 -6
- package/dist/cli/commands/init.js +70 -266
- package/dist/core/config.d.ts +1 -1
- package/dist/core/config.js +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Detection utilities for init wizard
|
|
3
|
+
*/
|
|
4
|
+
export type PackageManager = "bun" | "npm" | "pnpm" | "yarn";
|
|
5
|
+
export interface ProjectInfo {
|
|
6
|
+
/** Whether the directory is empty (only hidden files allowed) */
|
|
7
|
+
isEmpty: boolean;
|
|
8
|
+
/** Detected package manager from lockfile, or null */
|
|
9
|
+
packageManager: PackageManager | null;
|
|
10
|
+
/** Name from package.json, or null */
|
|
11
|
+
packageName: string | null;
|
|
12
|
+
/** Whether elysian.config.ts exists */
|
|
13
|
+
hasElysianConfig: boolean;
|
|
14
|
+
/** Whether terraform/ directory exists */
|
|
15
|
+
hasTerraformDir: boolean;
|
|
16
|
+
/** Existing terraform files */
|
|
17
|
+
terraformFiles: {
|
|
18
|
+
providers: boolean;
|
|
19
|
+
variables: boolean;
|
|
20
|
+
main: boolean;
|
|
21
|
+
outputs: boolean;
|
|
22
|
+
};
|
|
23
|
+
/** Whether src/lambdas/ exists */
|
|
24
|
+
hasLambdasDir: boolean;
|
|
25
|
+
/** Whether there are any .ts files in src/lambdas/ */
|
|
26
|
+
hasLambdaFiles: boolean;
|
|
27
|
+
/** Directory name (for default apiName) */
|
|
28
|
+
directoryName: string;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Gather all project information for the init wizard
|
|
32
|
+
*/
|
|
33
|
+
export declare function detectProject(cwd: string): Promise<ProjectInfo>;
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Detection utilities for init wizard
|
|
3
|
+
*/
|
|
4
|
+
import { existsSync, readdirSync } from "fs";
|
|
5
|
+
import { join, basename } from "path";
|
|
6
|
+
/**
|
|
7
|
+
* Check if a directory is empty (ignoring hidden files like .git)
|
|
8
|
+
*/
|
|
9
|
+
function isDirectoryEmpty(cwd) {
|
|
10
|
+
try {
|
|
11
|
+
const files = readdirSync(cwd);
|
|
12
|
+
// Filter out hidden files (starting with .)
|
|
13
|
+
const visibleFiles = files.filter((f) => !f.startsWith("."));
|
|
14
|
+
return visibleFiles.length === 0;
|
|
15
|
+
}
|
|
16
|
+
catch {
|
|
17
|
+
return true;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Detect package manager from lockfiles
|
|
22
|
+
*/
|
|
23
|
+
function detectPackageManager(cwd) {
|
|
24
|
+
if (existsSync(join(cwd, "bun.lock")) || existsSync(join(cwd, "bun.lockb"))) {
|
|
25
|
+
return "bun";
|
|
26
|
+
}
|
|
27
|
+
if (existsSync(join(cwd, "pnpm-lock.yaml"))) {
|
|
28
|
+
return "pnpm";
|
|
29
|
+
}
|
|
30
|
+
if (existsSync(join(cwd, "yarn.lock"))) {
|
|
31
|
+
return "yarn";
|
|
32
|
+
}
|
|
33
|
+
if (existsSync(join(cwd, "package-lock.json"))) {
|
|
34
|
+
return "npm";
|
|
35
|
+
}
|
|
36
|
+
return null;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Read package name from package.json
|
|
40
|
+
*/
|
|
41
|
+
async function readPackageName(cwd) {
|
|
42
|
+
const packagePath = join(cwd, "package.json");
|
|
43
|
+
if (!existsSync(packagePath)) {
|
|
44
|
+
return null;
|
|
45
|
+
}
|
|
46
|
+
try {
|
|
47
|
+
const content = await Bun.file(packagePath).json();
|
|
48
|
+
return content.name || null;
|
|
49
|
+
}
|
|
50
|
+
catch {
|
|
51
|
+
return null;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Check which terraform files exist
|
|
56
|
+
*/
|
|
57
|
+
function checkTerraformFiles(cwd) {
|
|
58
|
+
const tfDir = join(cwd, "terraform");
|
|
59
|
+
return {
|
|
60
|
+
providers: existsSync(join(tfDir, "providers.tf")),
|
|
61
|
+
variables: existsSync(join(tfDir, "variables.tf")),
|
|
62
|
+
main: existsSync(join(tfDir, "main.tf")),
|
|
63
|
+
outputs: existsSync(join(tfDir, "outputs.tf")),
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Check if there are any .ts files in the lambdas directory
|
|
68
|
+
*/
|
|
69
|
+
function hasLambdaFiles(cwd) {
|
|
70
|
+
const lambdasDir = join(cwd, "src/lambdas");
|
|
71
|
+
if (!existsSync(lambdasDir)) {
|
|
72
|
+
return false;
|
|
73
|
+
}
|
|
74
|
+
try {
|
|
75
|
+
const files = readdirSync(lambdasDir);
|
|
76
|
+
return files.some((f) => f.endsWith(".ts"));
|
|
77
|
+
}
|
|
78
|
+
catch {
|
|
79
|
+
return false;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Gather all project information for the init wizard
|
|
84
|
+
*/
|
|
85
|
+
export async function detectProject(cwd) {
|
|
86
|
+
const packageName = await readPackageName(cwd);
|
|
87
|
+
return {
|
|
88
|
+
isEmpty: isDirectoryEmpty(cwd),
|
|
89
|
+
packageManager: detectPackageManager(cwd),
|
|
90
|
+
packageName,
|
|
91
|
+
hasElysianConfig: existsSync(join(cwd, "elysian.config.ts")),
|
|
92
|
+
hasTerraformDir: existsSync(join(cwd, "terraform")),
|
|
93
|
+
terraformFiles: checkTerraformFiles(cwd),
|
|
94
|
+
hasLambdasDir: existsSync(join(cwd, "src/lambdas")),
|
|
95
|
+
hasLambdaFiles: hasLambdaFiles(cwd),
|
|
96
|
+
directoryName: basename(cwd),
|
|
97
|
+
};
|
|
98
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Init module exports
|
|
3
|
+
*/
|
|
4
|
+
export { detectProject, type ProjectInfo, type PackageManager } from "./detect";
|
|
5
|
+
export { promptTargetDirectory, runFreshProjectWizard, runExistingProjectWizard, type WizardAnswers, } from "./prompts";
|
|
6
|
+
export { scaffoldProject, installDependencies, printResults, type ScaffoldResult, } from "./scaffold";
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Wizard prompts using consola.prompt
|
|
3
|
+
*/
|
|
4
|
+
import type { PackageManager } from "./detect";
|
|
5
|
+
export interface WizardAnswers {
|
|
6
|
+
targetDir: string;
|
|
7
|
+
apiName: string;
|
|
8
|
+
packageManager: PackageManager;
|
|
9
|
+
installDeps: boolean;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Run the initial wizard to get the target directory
|
|
13
|
+
* Returns the target directory path, or null if cancelled
|
|
14
|
+
*/
|
|
15
|
+
export declare function promptTargetDirectory(currentDirName: string): Promise<string | null>;
|
|
16
|
+
/**
|
|
17
|
+
* Run wizard for a fresh (empty) project
|
|
18
|
+
*/
|
|
19
|
+
export declare function runFreshProjectWizard(apiName: string): Promise<Omit<WizardAnswers, "targetDir" | "apiName"> | null>;
|
|
20
|
+
/**
|
|
21
|
+
* Run wizard for an existing project
|
|
22
|
+
*/
|
|
23
|
+
export declare function runExistingProjectWizard(apiName: string, detectedPackageManager: PackageManager | null): Promise<Omit<WizardAnswers, "targetDir" | "apiName"> | null>;
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Wizard prompts using consola.prompt
|
|
3
|
+
*/
|
|
4
|
+
import consola from "consola";
|
|
5
|
+
const CANCEL_SYMBOL = Symbol.for("cancel");
|
|
6
|
+
/**
|
|
7
|
+
* Check if user cancelled the prompt
|
|
8
|
+
*/
|
|
9
|
+
function isCancelled(value) {
|
|
10
|
+
return value === CANCEL_SYMBOL;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Run the initial wizard to get the target directory
|
|
14
|
+
* Returns the target directory path, or null if cancelled
|
|
15
|
+
*/
|
|
16
|
+
export async function promptTargetDirectory(currentDirName) {
|
|
17
|
+
console.log("");
|
|
18
|
+
const targetDir = await consola.prompt("Where would you like to create your project?", {
|
|
19
|
+
type: "text",
|
|
20
|
+
default: ".",
|
|
21
|
+
placeholder: ". (current directory)",
|
|
22
|
+
cancel: "symbol",
|
|
23
|
+
});
|
|
24
|
+
if (isCancelled(targetDir)) {
|
|
25
|
+
return null;
|
|
26
|
+
}
|
|
27
|
+
return targetDir;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Run wizard for a fresh (empty) project
|
|
31
|
+
*/
|
|
32
|
+
export async function runFreshProjectWizard(apiName) {
|
|
33
|
+
consola.info(`Creating new elysian project: ${apiName}\n`);
|
|
34
|
+
// Prompt for package manager
|
|
35
|
+
const packageManager = await consola.prompt("Package manager:", {
|
|
36
|
+
type: "select",
|
|
37
|
+
options: [
|
|
38
|
+
{ value: "bun", label: "bun", hint: "recommended" },
|
|
39
|
+
{ value: "npm", label: "npm" },
|
|
40
|
+
{ value: "pnpm", label: "pnpm" },
|
|
41
|
+
{ value: "yarn", label: "yarn" },
|
|
42
|
+
],
|
|
43
|
+
cancel: "symbol",
|
|
44
|
+
});
|
|
45
|
+
if (isCancelled(packageManager)) {
|
|
46
|
+
return null;
|
|
47
|
+
}
|
|
48
|
+
return {
|
|
49
|
+
packageManager: packageManager,
|
|
50
|
+
installDeps: true, // Always install for fresh projects
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Run wizard for an existing project
|
|
55
|
+
*/
|
|
56
|
+
export async function runExistingProjectWizard(apiName, detectedPackageManager) {
|
|
57
|
+
consola.info(`Adding elysian to: ${apiName}\n`);
|
|
58
|
+
// Use detected package manager or prompt
|
|
59
|
+
let packageManager;
|
|
60
|
+
if (detectedPackageManager) {
|
|
61
|
+
packageManager = detectedPackageManager;
|
|
62
|
+
consola.info(`Detected package manager: ${packageManager}`);
|
|
63
|
+
}
|
|
64
|
+
else {
|
|
65
|
+
const selected = await consola.prompt("Package manager:", {
|
|
66
|
+
type: "select",
|
|
67
|
+
options: [
|
|
68
|
+
{ value: "bun", label: "bun", hint: "recommended" },
|
|
69
|
+
{ value: "npm", label: "npm" },
|
|
70
|
+
{ value: "pnpm", label: "pnpm" },
|
|
71
|
+
{ value: "yarn", label: "yarn" },
|
|
72
|
+
],
|
|
73
|
+
cancel: "symbol",
|
|
74
|
+
});
|
|
75
|
+
if (isCancelled(selected)) {
|
|
76
|
+
return null;
|
|
77
|
+
}
|
|
78
|
+
packageManager = selected;
|
|
79
|
+
}
|
|
80
|
+
// Prompt whether to install dependencies
|
|
81
|
+
const installDeps = await consola.prompt("Install dependencies?", {
|
|
82
|
+
type: "confirm",
|
|
83
|
+
initial: true,
|
|
84
|
+
cancel: "symbol",
|
|
85
|
+
});
|
|
86
|
+
if (isCancelled(installDeps)) {
|
|
87
|
+
return null;
|
|
88
|
+
}
|
|
89
|
+
return {
|
|
90
|
+
packageManager,
|
|
91
|
+
installDeps: installDeps,
|
|
92
|
+
};
|
|
93
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* File scaffolding logic
|
|
3
|
+
*/
|
|
4
|
+
import type { PackageManager, ProjectInfo } from "./detect";
|
|
5
|
+
import type { WizardAnswers } from "./prompts";
|
|
6
|
+
export interface ScaffoldResult {
|
|
7
|
+
created: string[];
|
|
8
|
+
updated: string[];
|
|
9
|
+
skipped: string[];
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Scaffold all project files
|
|
13
|
+
*/
|
|
14
|
+
export declare function scaffoldProject(cwd: string, info: ProjectInfo, answers: WizardAnswers, force: boolean): Promise<ScaffoldResult>;
|
|
15
|
+
/**
|
|
16
|
+
* Install dependencies using the specified package manager
|
|
17
|
+
*/
|
|
18
|
+
export declare function installDependencies(cwd: string, packageManager: PackageManager): Promise<void>;
|
|
19
|
+
/**
|
|
20
|
+
* Get relative path for display
|
|
21
|
+
*/
|
|
22
|
+
export declare function getRelativePath(cwd: string, fullPath: string): string;
|
|
23
|
+
/**
|
|
24
|
+
* Print scaffold results
|
|
25
|
+
*/
|
|
26
|
+
export declare function printResults(cwd: string, result: ScaffoldResult): void;
|
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* File scaffolding logic
|
|
3
|
+
*/
|
|
4
|
+
import { existsSync, mkdirSync } from "fs";
|
|
5
|
+
import { join } from "path";
|
|
6
|
+
import { spawn } from "bun";
|
|
7
|
+
import consola from "consola";
|
|
8
|
+
import { configTemplate, exampleLambdaTemplate, packageJsonTemplate, gitignoreTemplate, tsconfigTemplate, } from "./templates";
|
|
9
|
+
import { templates as tfTemplates, appendProviders, getMissingVariables, appendMain, appendOutputs, } from "./terraform";
|
|
10
|
+
/**
|
|
11
|
+
* Read file content or return empty string if not exists
|
|
12
|
+
*/
|
|
13
|
+
async function readFileOrEmpty(path) {
|
|
14
|
+
if (!existsSync(path)) {
|
|
15
|
+
return "";
|
|
16
|
+
}
|
|
17
|
+
return await Bun.file(path).text();
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Write file and track in result
|
|
21
|
+
*/
|
|
22
|
+
async function writeFile(path, content, result, isUpdate) {
|
|
23
|
+
await Bun.write(path, content);
|
|
24
|
+
if (isUpdate) {
|
|
25
|
+
result.updated.push(path);
|
|
26
|
+
}
|
|
27
|
+
else {
|
|
28
|
+
result.created.push(path);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Scaffold all project files
|
|
33
|
+
*/
|
|
34
|
+
export async function scaffoldProject(cwd, info, answers, force) {
|
|
35
|
+
const result = {
|
|
36
|
+
created: [],
|
|
37
|
+
updated: [],
|
|
38
|
+
skipped: [],
|
|
39
|
+
};
|
|
40
|
+
// Create directories
|
|
41
|
+
const lambdasDir = join(cwd, "src/lambdas");
|
|
42
|
+
const terraformDir = join(cwd, "terraform");
|
|
43
|
+
if (!existsSync(lambdasDir)) {
|
|
44
|
+
mkdirSync(lambdasDir, { recursive: true });
|
|
45
|
+
}
|
|
46
|
+
if (!existsSync(terraformDir)) {
|
|
47
|
+
mkdirSync(terraformDir, { recursive: true });
|
|
48
|
+
}
|
|
49
|
+
// For fresh projects, create package.json, .gitignore, tsconfig.json
|
|
50
|
+
if (info.isEmpty) {
|
|
51
|
+
const packageJsonPath = join(cwd, "package.json");
|
|
52
|
+
await writeFile(packageJsonPath, packageJsonTemplate(answers.apiName), result, false);
|
|
53
|
+
const gitignorePath = join(cwd, ".gitignore");
|
|
54
|
+
if (!existsSync(gitignorePath)) {
|
|
55
|
+
await writeFile(gitignorePath, gitignoreTemplate(), result, false);
|
|
56
|
+
}
|
|
57
|
+
const tsconfigPath = join(cwd, "tsconfig.json");
|
|
58
|
+
if (!existsSync(tsconfigPath)) {
|
|
59
|
+
await writeFile(tsconfigPath, tsconfigTemplate(), result, false);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
// Create elysian.config.ts
|
|
63
|
+
const configPath = join(cwd, "elysian.config.ts");
|
|
64
|
+
if (!existsSync(configPath) || force) {
|
|
65
|
+
await writeFile(configPath, configTemplate(answers.apiName), result, existsSync(configPath));
|
|
66
|
+
}
|
|
67
|
+
else {
|
|
68
|
+
result.skipped.push(configPath);
|
|
69
|
+
}
|
|
70
|
+
// Create example lambda (only if no lambda files exist)
|
|
71
|
+
const exampleLambdaPath = join(lambdasDir, "hello.ts");
|
|
72
|
+
if (!info.hasLambdaFiles) {
|
|
73
|
+
await writeFile(exampleLambdaPath, exampleLambdaTemplate(), result, false);
|
|
74
|
+
}
|
|
75
|
+
// Handle Terraform files
|
|
76
|
+
await scaffoldTerraform(cwd, info, answers.apiName, result);
|
|
77
|
+
return result;
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Scaffold Terraform files with smart appending
|
|
81
|
+
*/
|
|
82
|
+
async function scaffoldTerraform(cwd, info, apiName, result) {
|
|
83
|
+
const tfDir = join(cwd, "terraform");
|
|
84
|
+
// providers.tf
|
|
85
|
+
const providersPath = join(tfDir, "providers.tf");
|
|
86
|
+
if (info.terraformFiles.providers) {
|
|
87
|
+
const existing = await readFileOrEmpty(providersPath);
|
|
88
|
+
const updated = appendProviders(existing);
|
|
89
|
+
if (updated !== existing) {
|
|
90
|
+
await writeFile(providersPath, updated, result, true);
|
|
91
|
+
}
|
|
92
|
+
else {
|
|
93
|
+
result.skipped.push(providersPath);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
else {
|
|
97
|
+
await writeFile(providersPath, tfTemplates.providers, result, false);
|
|
98
|
+
}
|
|
99
|
+
// variables.tf
|
|
100
|
+
const variablesPath = join(tfDir, "variables.tf");
|
|
101
|
+
if (info.terraformFiles.variables) {
|
|
102
|
+
const existing = await readFileOrEmpty(variablesPath);
|
|
103
|
+
const updated = getMissingVariables(existing, apiName);
|
|
104
|
+
if (updated !== existing) {
|
|
105
|
+
await writeFile(variablesPath, updated, result, true);
|
|
106
|
+
}
|
|
107
|
+
else {
|
|
108
|
+
result.skipped.push(variablesPath);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
else {
|
|
112
|
+
await writeFile(variablesPath, tfTemplates.variables(apiName), result, false);
|
|
113
|
+
}
|
|
114
|
+
// main.tf
|
|
115
|
+
const mainPath = join(tfDir, "main.tf");
|
|
116
|
+
if (info.terraformFiles.main) {
|
|
117
|
+
const existing = await readFileOrEmpty(mainPath);
|
|
118
|
+
const updated = appendMain(existing);
|
|
119
|
+
if (updated !== existing) {
|
|
120
|
+
await writeFile(mainPath, updated, result, true);
|
|
121
|
+
}
|
|
122
|
+
else {
|
|
123
|
+
result.skipped.push(mainPath);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
else {
|
|
127
|
+
await writeFile(mainPath, tfTemplates.main, result, false);
|
|
128
|
+
}
|
|
129
|
+
// outputs.tf
|
|
130
|
+
const outputsPath = join(tfDir, "outputs.tf");
|
|
131
|
+
if (info.terraformFiles.outputs) {
|
|
132
|
+
const existing = await readFileOrEmpty(outputsPath);
|
|
133
|
+
const updated = appendOutputs(existing);
|
|
134
|
+
if (updated !== existing) {
|
|
135
|
+
await writeFile(outputsPath, updated, result, true);
|
|
136
|
+
}
|
|
137
|
+
else {
|
|
138
|
+
result.skipped.push(outputsPath);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
else {
|
|
142
|
+
await writeFile(outputsPath, tfTemplates.outputs, result, false);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
/**
|
|
146
|
+
* Install dependencies using the specified package manager
|
|
147
|
+
*/
|
|
148
|
+
export async function installDependencies(cwd, packageManager) {
|
|
149
|
+
const deps = ["elysia", "@actuallyjamez/elysian"];
|
|
150
|
+
const devDeps = ["@types/bun", "typescript"];
|
|
151
|
+
const addCmd = packageManager === "npm" ? "install" : "add";
|
|
152
|
+
const devFlag = packageManager === "npm" ? "--save-dev" : "-D";
|
|
153
|
+
consola.start("Installing dependencies...");
|
|
154
|
+
// Install main dependencies
|
|
155
|
+
const addProc = spawn([packageManager, addCmd, ...deps], {
|
|
156
|
+
cwd,
|
|
157
|
+
stdout: "ignore",
|
|
158
|
+
stderr: "pipe",
|
|
159
|
+
});
|
|
160
|
+
await addProc.exited;
|
|
161
|
+
if (addProc.exitCode !== 0) {
|
|
162
|
+
const stderr = await new Response(addProc.stderr).text();
|
|
163
|
+
throw new Error(`Failed to install dependencies: ${stderr}`);
|
|
164
|
+
}
|
|
165
|
+
// Install dev dependencies
|
|
166
|
+
const devProc = spawn([packageManager, addCmd, devFlag, ...devDeps], {
|
|
167
|
+
cwd,
|
|
168
|
+
stdout: "ignore",
|
|
169
|
+
stderr: "pipe",
|
|
170
|
+
});
|
|
171
|
+
await devProc.exited;
|
|
172
|
+
if (devProc.exitCode !== 0) {
|
|
173
|
+
const stderr = await new Response(devProc.stderr).text();
|
|
174
|
+
throw new Error(`Failed to install dev dependencies: ${stderr}`);
|
|
175
|
+
}
|
|
176
|
+
consola.success("Dependencies installed");
|
|
177
|
+
}
|
|
178
|
+
/**
|
|
179
|
+
* Get relative path for display
|
|
180
|
+
*/
|
|
181
|
+
export function getRelativePath(cwd, fullPath) {
|
|
182
|
+
return fullPath.replace(cwd + "/", "");
|
|
183
|
+
}
|
|
184
|
+
/**
|
|
185
|
+
* Print scaffold results
|
|
186
|
+
*/
|
|
187
|
+
export function printResults(cwd, result) {
|
|
188
|
+
console.log("");
|
|
189
|
+
for (const path of result.created) {
|
|
190
|
+
consola.success(`Created ${getRelativePath(cwd, path)}`);
|
|
191
|
+
}
|
|
192
|
+
for (const path of result.updated) {
|
|
193
|
+
consola.success(`Updated ${getRelativePath(cwd, path)}`);
|
|
194
|
+
}
|
|
195
|
+
// Don't print skipped files - too noisy
|
|
196
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Template strings for generated files
|
|
3
|
+
*/
|
|
4
|
+
/**
|
|
5
|
+
* elysian.config.ts template
|
|
6
|
+
*/
|
|
7
|
+
export declare function configTemplate(apiName: string): string;
|
|
8
|
+
/**
|
|
9
|
+
* Example lambda template
|
|
10
|
+
*/
|
|
11
|
+
export declare function exampleLambdaTemplate(): string;
|
|
12
|
+
/**
|
|
13
|
+
* package.json template for fresh projects
|
|
14
|
+
*/
|
|
15
|
+
export declare function packageJsonTemplate(apiName: string): string;
|
|
16
|
+
/**
|
|
17
|
+
* .gitignore template
|
|
18
|
+
*/
|
|
19
|
+
export declare function gitignoreTemplate(): string;
|
|
20
|
+
/**
|
|
21
|
+
* tsconfig.json template
|
|
22
|
+
*/
|
|
23
|
+
export declare function tsconfigTemplate(): string;
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Template strings for generated files
|
|
3
|
+
*/
|
|
4
|
+
/**
|
|
5
|
+
* elysian.config.ts template
|
|
6
|
+
*/
|
|
7
|
+
export function configTemplate(apiName) {
|
|
8
|
+
return `import { defineConfig } from "@actuallyjamez/elysian";
|
|
9
|
+
|
|
10
|
+
export default defineConfig({
|
|
11
|
+
apiName: "${apiName}",
|
|
12
|
+
openapi: {
|
|
13
|
+
title: "${apiName}",
|
|
14
|
+
version: "1.0.0",
|
|
15
|
+
},
|
|
16
|
+
});
|
|
17
|
+
`;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Example lambda template
|
|
21
|
+
*/
|
|
22
|
+
export function exampleLambdaTemplate() {
|
|
23
|
+
return `import { createLambda, t } from "@actuallyjamez/elysian";
|
|
24
|
+
|
|
25
|
+
export default createLambda()
|
|
26
|
+
.get("/hello", ({ query }) => {
|
|
27
|
+
return \`Hello, \${query.name ?? "World"}!\`;
|
|
28
|
+
}, {
|
|
29
|
+
response: t.String(),
|
|
30
|
+
query: t.Object({
|
|
31
|
+
name: t.Optional(t.String()),
|
|
32
|
+
}),
|
|
33
|
+
detail: {
|
|
34
|
+
summary: "Say hello",
|
|
35
|
+
tags: ["Greeting"],
|
|
36
|
+
},
|
|
37
|
+
});
|
|
38
|
+
`;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* package.json template for fresh projects
|
|
42
|
+
*/
|
|
43
|
+
export function packageJsonTemplate(apiName) {
|
|
44
|
+
return JSON.stringify({
|
|
45
|
+
name: apiName,
|
|
46
|
+
version: "0.1.0",
|
|
47
|
+
type: "module",
|
|
48
|
+
scripts: {
|
|
49
|
+
build: "elysian build",
|
|
50
|
+
dev: "elysian dev",
|
|
51
|
+
},
|
|
52
|
+
}, null, 2);
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* .gitignore template
|
|
56
|
+
*/
|
|
57
|
+
export function gitignoreTemplate() {
|
|
58
|
+
return `# Dependencies
|
|
59
|
+
node_modules/
|
|
60
|
+
|
|
61
|
+
# Build output
|
|
62
|
+
dist/
|
|
63
|
+
|
|
64
|
+
# Environment
|
|
65
|
+
.env
|
|
66
|
+
.env.local
|
|
67
|
+
|
|
68
|
+
# Terraform
|
|
69
|
+
terraform/.terraform/
|
|
70
|
+
terraform/*.tfstate
|
|
71
|
+
terraform/*.tfstate.backup
|
|
72
|
+
terraform/.terraform.lock.hcl
|
|
73
|
+
|
|
74
|
+
# IDE
|
|
75
|
+
.idea/
|
|
76
|
+
.vscode/
|
|
77
|
+
*.swp
|
|
78
|
+
*.swo
|
|
79
|
+
|
|
80
|
+
# OS
|
|
81
|
+
.DS_Store
|
|
82
|
+
Thumbs.db
|
|
83
|
+
`;
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* tsconfig.json template
|
|
87
|
+
*/
|
|
88
|
+
export function tsconfigTemplate() {
|
|
89
|
+
return JSON.stringify({
|
|
90
|
+
compilerOptions: {
|
|
91
|
+
target: "ESNext",
|
|
92
|
+
module: "ESNext",
|
|
93
|
+
moduleResolution: "bundler",
|
|
94
|
+
strict: true,
|
|
95
|
+
esModuleInterop: true,
|
|
96
|
+
skipLibCheck: true,
|
|
97
|
+
noEmit: true,
|
|
98
|
+
types: ["bun-types"],
|
|
99
|
+
},
|
|
100
|
+
include: ["src/**/*", "elysian.config.ts"],
|
|
101
|
+
exclude: ["node_modules", "dist"],
|
|
102
|
+
}, null, 2);
|
|
103
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Smart Terraform file handling - append missing blocks
|
|
3
|
+
*/
|
|
4
|
+
/**
|
|
5
|
+
* Check if content has AWS provider configured
|
|
6
|
+
*/
|
|
7
|
+
export declare function hasAwsProvider(content: string): boolean;
|
|
8
|
+
/**
|
|
9
|
+
* Check if a variable exists in the content
|
|
10
|
+
*/
|
|
11
|
+
export declare function hasVariable(content: string, name: string): boolean;
|
|
12
|
+
/**
|
|
13
|
+
* Check if a resource of a given type exists
|
|
14
|
+
*/
|
|
15
|
+
export declare function hasResource(content: string, type: string): boolean;
|
|
16
|
+
/**
|
|
17
|
+
* Check if an output exists in the content
|
|
18
|
+
*/
|
|
19
|
+
export declare function hasOutput(content: string, name: string): boolean;
|
|
20
|
+
/**
|
|
21
|
+
* Smart append to providers.tf - only add if AWS provider is missing
|
|
22
|
+
*/
|
|
23
|
+
export declare function appendProviders(existing: string): string;
|
|
24
|
+
/**
|
|
25
|
+
* Get missing variables and return the block to append
|
|
26
|
+
*/
|
|
27
|
+
export declare function getMissingVariables(existing: string, apiName: string): string;
|
|
28
|
+
/**
|
|
29
|
+
* Smart append to main.tf - only add if API Gateway resource is missing
|
|
30
|
+
*/
|
|
31
|
+
export declare function appendMain(existing: string): string;
|
|
32
|
+
/**
|
|
33
|
+
* Smart append to outputs.tf - only add if api_endpoint output is missing
|
|
34
|
+
*/
|
|
35
|
+
export declare function appendOutputs(existing: string): string;
|
|
36
|
+
/**
|
|
37
|
+
* Full templates for new files
|
|
38
|
+
*/
|
|
39
|
+
export declare const templates: {
|
|
40
|
+
providers: string;
|
|
41
|
+
variables: (apiName: string) => string;
|
|
42
|
+
main: string;
|
|
43
|
+
outputs: string;
|
|
44
|
+
};
|