keryx 0.25.1 → 0.25.3
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/initializers/actionts.ts +1 -1
- package/initializers/channels.ts +1 -1
- package/initializers/connections.ts +1 -1
- package/initializers/db.ts +1 -1
- package/initializers/mcp.ts +1 -1
- package/initializers/oauth.ts +1 -1
- package/initializers/observability.ts +1 -1
- package/initializers/process.ts +1 -1
- package/initializers/pubsub.ts +1 -1
- package/initializers/redis.ts +1 -1
- package/initializers/resque.ts +1 -1
- package/initializers/servers.ts +1 -1
- package/initializers/session.ts +1 -1
- package/initializers/signals.ts +1 -1
- package/initializers/swagger.ts +1 -1
- package/package.json +1 -1
- package/util/cli.ts +2 -1
- package/util/componentRegistry.ts +184 -0
- package/util/generate.ts +21 -177
- package/util/scaffold.ts +1 -6
package/initializers/actionts.ts
CHANGED
package/initializers/channels.ts
CHANGED
package/initializers/db.ts
CHANGED
package/initializers/mcp.ts
CHANGED
|
@@ -22,7 +22,7 @@ type McpHandleRequest = (req: Request, ip: string) => Promise<Response>;
|
|
|
22
22
|
|
|
23
23
|
const namespace = "mcp";
|
|
24
24
|
|
|
25
|
-
declare module "
|
|
25
|
+
declare module "keryx" {
|
|
26
26
|
export interface API {
|
|
27
27
|
[namespace]: Awaited<ReturnType<McpInitializer["initialize"]>>;
|
|
28
28
|
}
|
package/initializers/oauth.ts
CHANGED
package/initializers/process.ts
CHANGED
package/initializers/pubsub.ts
CHANGED
package/initializers/redis.ts
CHANGED
package/initializers/resque.ts
CHANGED
package/initializers/servers.ts
CHANGED
package/initializers/session.ts
CHANGED
package/initializers/signals.ts
CHANGED
package/initializers/swagger.ts
CHANGED
package/package.json
CHANGED
package/util/cli.ts
CHANGED
|
@@ -5,7 +5,8 @@ import { Action, api, Connection, RUN_MODE } from "../api";
|
|
|
5
5
|
import { ExitCode } from "./../classes/ExitCode";
|
|
6
6
|
import { TypedError } from "./../classes/TypedError";
|
|
7
7
|
import { config } from "../config";
|
|
8
|
-
import {
|
|
8
|
+
import { getValidTypes } from "./componentRegistry";
|
|
9
|
+
import { generateComponent } from "./generate";
|
|
9
10
|
import { globLoader } from "./glob";
|
|
10
11
|
import {
|
|
11
12
|
interactiveScaffold,
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
import path from "path";
|
|
2
|
+
import { config } from "../config";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* A component that can be produced by `keryx generate`.
|
|
6
|
+
*
|
|
7
|
+
* Built-in components (action, initializer, middleware, channel, ops, plugin)
|
|
8
|
+
* and plugin-contributed generators are both represented as `ComponentDef`s,
|
|
9
|
+
* so `generateComponent()` can handle them through a single code path.
|
|
10
|
+
*/
|
|
11
|
+
export interface ComponentDef {
|
|
12
|
+
/** Generator type name — what the user passes as `keryx generate <type> <name>`. */
|
|
13
|
+
type: string;
|
|
14
|
+
/** Output subdirectory relative to the project root (e.g. `"actions"`). */
|
|
15
|
+
directory: string;
|
|
16
|
+
/** Absolute path to the Mustache template for the component file. */
|
|
17
|
+
templatePath: string;
|
|
18
|
+
/** Absolute path to the Mustache template for the test file. Falls back to the default generate/test.ts.mustache. */
|
|
19
|
+
testTemplatePath?: string;
|
|
20
|
+
/** Suffix appended to the PascalCase class name (e.g. `"Middleware"` → `FooMiddleware`). */
|
|
21
|
+
classSuffix?: string;
|
|
22
|
+
/** When true, a `route` field is added to the Mustache view (used by actions). */
|
|
23
|
+
includeRoute?: boolean;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const generateTemplatesDir = path.join(
|
|
27
|
+
import.meta.dir,
|
|
28
|
+
"..",
|
|
29
|
+
"templates",
|
|
30
|
+
"generate",
|
|
31
|
+
);
|
|
32
|
+
const scaffoldTemplatesDir = path.join(
|
|
33
|
+
import.meta.dir,
|
|
34
|
+
"..",
|
|
35
|
+
"templates",
|
|
36
|
+
"scaffold",
|
|
37
|
+
);
|
|
38
|
+
|
|
39
|
+
const BUILT_IN_DEFS: ComponentDef[] = [
|
|
40
|
+
{
|
|
41
|
+
type: "action",
|
|
42
|
+
directory: "actions",
|
|
43
|
+
templatePath: path.join(generateTemplatesDir, "action.ts.mustache"),
|
|
44
|
+
includeRoute: true,
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
type: "initializer",
|
|
48
|
+
directory: "initializers",
|
|
49
|
+
templatePath: path.join(generateTemplatesDir, "initializer.ts.mustache"),
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
type: "middleware",
|
|
53
|
+
directory: "middleware",
|
|
54
|
+
templatePath: path.join(
|
|
55
|
+
generateTemplatesDir,
|
|
56
|
+
"action-middleware.ts.mustache",
|
|
57
|
+
),
|
|
58
|
+
classSuffix: "Middleware",
|
|
59
|
+
},
|
|
60
|
+
{
|
|
61
|
+
type: "channel",
|
|
62
|
+
directory: "channels",
|
|
63
|
+
templatePath: path.join(generateTemplatesDir, "channel.ts.mustache"),
|
|
64
|
+
classSuffix: "Channel",
|
|
65
|
+
},
|
|
66
|
+
{
|
|
67
|
+
type: "ops",
|
|
68
|
+
directory: "ops",
|
|
69
|
+
templatePath: path.join(generateTemplatesDir, "ops.ts.mustache"),
|
|
70
|
+
},
|
|
71
|
+
{
|
|
72
|
+
type: "plugin",
|
|
73
|
+
directory: "plugins",
|
|
74
|
+
templatePath: path.join(generateTemplatesDir, "plugin.ts.mustache"),
|
|
75
|
+
classSuffix: "Plugin",
|
|
76
|
+
},
|
|
77
|
+
];
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Returns all component definitions, merging built-ins with plugin-contributed generators.
|
|
81
|
+
* Plugin generators with a type matching a built-in are ignored (built-ins win).
|
|
82
|
+
*/
|
|
83
|
+
export function getComponentDefs(): ComponentDef[] {
|
|
84
|
+
const defs: ComponentDef[] = [...BUILT_IN_DEFS];
|
|
85
|
+
const seen = new Set(defs.map((d) => d.type));
|
|
86
|
+
for (const plugin of config.plugins) {
|
|
87
|
+
if (!plugin.generators) continue;
|
|
88
|
+
for (const gen of plugin.generators) {
|
|
89
|
+
if (seen.has(gen.type)) continue;
|
|
90
|
+
defs.push({
|
|
91
|
+
type: gen.type,
|
|
92
|
+
directory: gen.directory,
|
|
93
|
+
templatePath: gen.templatePath,
|
|
94
|
+
testTemplatePath: gen.testTemplatePath,
|
|
95
|
+
});
|
|
96
|
+
seen.add(gen.type);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
return defs;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Look up a component definition by type. Returns undefined when unknown.
|
|
104
|
+
*/
|
|
105
|
+
export function getComponentDef(type: string): ComponentDef | undefined {
|
|
106
|
+
return getComponentDefs().find((d) => d.type === type);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Returns all valid generator type names (built-ins + plugin generators).
|
|
111
|
+
*/
|
|
112
|
+
export function getValidTypes(): string[] {
|
|
113
|
+
return getComponentDefs().map((d) => d.type);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Convert a colon-separated name to PascalCase.
|
|
118
|
+
* e.g. "user:delete" → "UserDelete", "hello" → "Hello".
|
|
119
|
+
*/
|
|
120
|
+
export function toClassName(name: string): string {
|
|
121
|
+
return name
|
|
122
|
+
.split(":")
|
|
123
|
+
.map((segment) => segment.charAt(0).toUpperCase() + segment.slice(1))
|
|
124
|
+
.join("");
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Derive a web route from a colon-separated action name.
|
|
129
|
+
* "hello" → "/hello", "user:delete" → "/user/delete".
|
|
130
|
+
*/
|
|
131
|
+
export function toRoute(name: string): string {
|
|
132
|
+
return "/" + name.replace(/:/g, "/");
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Resolve the component output path relative to the project root.
|
|
137
|
+
* Colon-separated names nest under subdirectories:
|
|
138
|
+
* "user:delete" with directory "actions" → "actions/user/delete.ts".
|
|
139
|
+
*/
|
|
140
|
+
export function resolveComponentPath(def: ComponentDef, name: string): string {
|
|
141
|
+
const segments = name.split(":");
|
|
142
|
+
if (segments.length > 1) {
|
|
143
|
+
const fileName = segments.pop()!;
|
|
144
|
+
return path.join(def.directory, ...segments, `${fileName}.ts`);
|
|
145
|
+
}
|
|
146
|
+
return path.join(def.directory, `${name}.ts`);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Derive the test file path for a component path.
|
|
151
|
+
* "actions/user/delete.ts" → "__tests__/actions/user/delete.test.ts".
|
|
152
|
+
*/
|
|
153
|
+
export function resolveTestPath(componentPath: string): string {
|
|
154
|
+
const parsed = path.parse(componentPath);
|
|
155
|
+
return path.join("__tests__", parsed.dir, `${parsed.name}.test.ts`);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* Build the Mustache view for a component, applying the definition's
|
|
160
|
+
* class-suffix and route conventions.
|
|
161
|
+
*/
|
|
162
|
+
export function buildComponentView(
|
|
163
|
+
def: ComponentDef,
|
|
164
|
+
name: string,
|
|
165
|
+
): Record<string, string> {
|
|
166
|
+
const className = toClassName(name) + (def.classSuffix ?? "");
|
|
167
|
+
const view: Record<string, string> = { name, className };
|
|
168
|
+
if (def.includeRoute) view.route = toRoute(name);
|
|
169
|
+
return view;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Load a template from `packages/keryx/templates/generate/`.
|
|
174
|
+
*/
|
|
175
|
+
export async function loadGenerateTemplate(name: string): Promise<string> {
|
|
176
|
+
return Bun.file(path.join(generateTemplatesDir, name)).text();
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* Load a template from `packages/keryx/templates/scaffold/`.
|
|
181
|
+
*/
|
|
182
|
+
export async function loadScaffoldTemplate(name: string): Promise<string> {
|
|
183
|
+
return Bun.file(path.join(scaffoldTemplatesDir, name)).text();
|
|
184
|
+
}
|
package/util/generate.ts
CHANGED
|
@@ -1,47 +1,16 @@
|
|
|
1
1
|
import fs from "fs";
|
|
2
2
|
import Mustache from "mustache";
|
|
3
3
|
import path from "path";
|
|
4
|
-
import
|
|
5
|
-
|
|
4
|
+
import {
|
|
5
|
+
buildComponentView,
|
|
6
|
+
getComponentDef,
|
|
7
|
+
getValidTypes,
|
|
8
|
+
loadGenerateTemplate,
|
|
9
|
+
resolveComponentPath,
|
|
10
|
+
resolveTestPath,
|
|
11
|
+
} from "./componentRegistry";
|
|
6
12
|
|
|
7
|
-
|
|
8
|
-
"action",
|
|
9
|
-
"initializer",
|
|
10
|
-
"middleware",
|
|
11
|
-
"channel",
|
|
12
|
-
"ops",
|
|
13
|
-
"plugin",
|
|
14
|
-
] as const;
|
|
15
|
-
type GeneratorType = (typeof VALID_TYPES)[number];
|
|
16
|
-
|
|
17
|
-
/**
|
|
18
|
-
* Returns all valid generator types, including built-in types and
|
|
19
|
-
* any custom types registered by plugins.
|
|
20
|
-
*/
|
|
21
|
-
export function getValidTypes(): string[] {
|
|
22
|
-
const types = [...VALID_TYPES] as string[];
|
|
23
|
-
for (const plugin of config.plugins) {
|
|
24
|
-
if (plugin.generators) {
|
|
25
|
-
for (const gen of plugin.generators) {
|
|
26
|
-
if (!types.includes(gen.type)) types.push(gen.type);
|
|
27
|
-
}
|
|
28
|
-
}
|
|
29
|
-
}
|
|
30
|
-
return types;
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
/**
|
|
34
|
-
* Find a plugin generator definition for a given type.
|
|
35
|
-
*/
|
|
36
|
-
function findPluginGenerator(type: string): PluginGenerator | undefined {
|
|
37
|
-
for (const plugin of config.plugins) {
|
|
38
|
-
if (plugin.generators) {
|
|
39
|
-
const gen = plugin.generators.find((g) => g.type === type);
|
|
40
|
-
if (gen) return gen;
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
return undefined;
|
|
44
|
-
}
|
|
13
|
+
export { getValidTypes } from "./componentRegistry";
|
|
45
14
|
|
|
46
15
|
export interface GenerateOptions {
|
|
47
16
|
dryRun?: boolean;
|
|
@@ -49,89 +18,6 @@ export interface GenerateOptions {
|
|
|
49
18
|
noTest?: boolean;
|
|
50
19
|
}
|
|
51
20
|
|
|
52
|
-
const templatesDir = path.join(import.meta.dir, "..", "templates", "generate");
|
|
53
|
-
|
|
54
|
-
async function loadTemplate(name: string): Promise<string> {
|
|
55
|
-
return Bun.file(path.join(templatesDir, name)).text();
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
/**
|
|
59
|
-
* Convert a colon-separated name to PascalCase class name.
|
|
60
|
-
* e.g. "user:delete" → "UserDelete", "hello" → "Hello"
|
|
61
|
-
*/
|
|
62
|
-
function toClassName(name: string): string {
|
|
63
|
-
return name
|
|
64
|
-
.split(":")
|
|
65
|
-
.map((segment) => segment.charAt(0).toUpperCase() + segment.slice(1))
|
|
66
|
-
.join("");
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
/**
|
|
70
|
-
* Determine the directory and filename for a component type + name.
|
|
71
|
-
* Actions with colons get nested: "user:delete" → "actions/user/delete.ts"
|
|
72
|
-
* Others are flat: "cache" → "initializers/cache.ts"
|
|
73
|
-
*/
|
|
74
|
-
function resolveFilePath(type: GeneratorType, name: string): string {
|
|
75
|
-
const dirMap: Record<GeneratorType, string> = {
|
|
76
|
-
action: "actions",
|
|
77
|
-
initializer: "initializers",
|
|
78
|
-
middleware: "middleware",
|
|
79
|
-
channel: "channels",
|
|
80
|
-
ops: "ops",
|
|
81
|
-
plugin: "plugins",
|
|
82
|
-
};
|
|
83
|
-
|
|
84
|
-
const baseDir = dirMap[type];
|
|
85
|
-
const segments = name.split(":");
|
|
86
|
-
|
|
87
|
-
if (segments.length > 1) {
|
|
88
|
-
// "user:delete" → "actions/user/delete.ts"
|
|
89
|
-
const fileName = segments.pop()!;
|
|
90
|
-
return path.join(baseDir, ...segments, `${fileName}.ts`);
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
return path.join(baseDir, `${name}.ts`);
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
/**
|
|
97
|
-
* Determine the directory and filename for a plugin generator type + name.
|
|
98
|
-
*/
|
|
99
|
-
function resolvePluginFilePath(gen: PluginGenerator, name: string): string {
|
|
100
|
-
const segments = name.split(":");
|
|
101
|
-
if (segments.length > 1) {
|
|
102
|
-
const fileName = segments.pop()!;
|
|
103
|
-
return path.join(gen.directory, ...segments, `${fileName}.ts`);
|
|
104
|
-
}
|
|
105
|
-
return path.join(gen.directory, `${name}.ts`);
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
/**
|
|
109
|
-
* Determine the test file path for a plugin generator component.
|
|
110
|
-
*/
|
|
111
|
-
function resolvePluginTestPath(gen: PluginGenerator, name: string): string {
|
|
112
|
-
const componentPath = resolvePluginFilePath(gen, name);
|
|
113
|
-
const parsed = path.parse(componentPath);
|
|
114
|
-
return path.join("__tests__", parsed.dir, `${parsed.name}.test.ts`);
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
/**
|
|
118
|
-
* Determine the test file path for a component.
|
|
119
|
-
* "user:delete" action → "__tests__/actions/user/delete.test.ts"
|
|
120
|
-
*/
|
|
121
|
-
function resolveTestPath(type: GeneratorType, name: string): string {
|
|
122
|
-
const componentPath = resolveFilePath(type, name);
|
|
123
|
-
const parsed = path.parse(componentPath);
|
|
124
|
-
return path.join("__tests__", parsed.dir, `${parsed.name}.test.ts`);
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
/**
|
|
128
|
-
* Derive the web route from an action name.
|
|
129
|
-
* "hello" → "/api/hello", "user:delete" → "/api/user/delete"
|
|
130
|
-
*/
|
|
131
|
-
function toRoute(name: string): string {
|
|
132
|
-
return "/" + name.replace(/:/g, "/");
|
|
133
|
-
}
|
|
134
|
-
|
|
135
21
|
/**
|
|
136
22
|
* Generate a component file (and optionally a test file).
|
|
137
23
|
* @param type The component type to generate
|
|
@@ -146,59 +32,26 @@ export async function generateComponent(
|
|
|
146
32
|
rootDir: string,
|
|
147
33
|
options: GenerateOptions = {},
|
|
148
34
|
): Promise<string[]> {
|
|
149
|
-
const
|
|
150
|
-
if (!
|
|
35
|
+
const def = getComponentDef(type);
|
|
36
|
+
if (!def) {
|
|
151
37
|
throw new Error(
|
|
152
|
-
`Unknown generator type "${type}". Valid types: ${
|
|
38
|
+
`Unknown generator type "${type}". Valid types: ${getValidTypes().join(", ")}`,
|
|
153
39
|
);
|
|
154
40
|
}
|
|
155
41
|
|
|
156
|
-
|
|
157
|
-
const pluginGen = VALID_TYPES.includes(type as GeneratorType)
|
|
158
|
-
? undefined
|
|
159
|
-
: findPluginGenerator(type);
|
|
160
|
-
|
|
161
|
-
const filePath = pluginGen
|
|
162
|
-
? resolvePluginFilePath(pluginGen, name)
|
|
163
|
-
: resolveFilePath(type as GeneratorType, name);
|
|
42
|
+
const filePath = resolveComponentPath(def, name);
|
|
164
43
|
const fullPath = path.join(rootDir, filePath);
|
|
165
44
|
const createdFiles: string[] = [];
|
|
166
45
|
|
|
167
|
-
// Check for conflicts
|
|
168
46
|
if (!options.force && fs.existsSync(fullPath)) {
|
|
169
47
|
throw new Error(
|
|
170
48
|
`File already exists: ${filePath}. Use --force to overwrite.`,
|
|
171
49
|
);
|
|
172
50
|
}
|
|
173
51
|
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
if (type === "channel") className += "Channel";
|
|
178
|
-
if (type === "plugin") className += "Plugin";
|
|
179
|
-
|
|
180
|
-
const view: Record<string, string> = { name, className };
|
|
181
|
-
if (type === "action") {
|
|
182
|
-
view.route = toRoute(name);
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
// Load and render template
|
|
186
|
-
let content: string;
|
|
187
|
-
if (pluginGen) {
|
|
188
|
-
const templateStr = await Bun.file(pluginGen.templatePath).text();
|
|
189
|
-
content = Mustache.render(templateStr, view);
|
|
190
|
-
} else {
|
|
191
|
-
const templateMap: Record<GeneratorType, string> = {
|
|
192
|
-
action: "action.ts.mustache",
|
|
193
|
-
initializer: "initializer.ts.mustache",
|
|
194
|
-
middleware: "action-middleware.ts.mustache",
|
|
195
|
-
channel: "channel.ts.mustache",
|
|
196
|
-
ops: "ops.ts.mustache",
|
|
197
|
-
plugin: "plugin.ts.mustache",
|
|
198
|
-
};
|
|
199
|
-
const template = await loadTemplate(templateMap[type as GeneratorType]);
|
|
200
|
-
content = Mustache.render(template, view);
|
|
201
|
-
}
|
|
52
|
+
const view = buildComponentView(def, name);
|
|
53
|
+
const template = await Bun.file(def.templatePath).text();
|
|
54
|
+
const content = Mustache.render(template, view);
|
|
202
55
|
|
|
203
56
|
if (options.dryRun) {
|
|
204
57
|
console.log(`Would create: ${filePath}`);
|
|
@@ -211,26 +64,17 @@ export async function generateComponent(
|
|
|
211
64
|
createdFiles.push(filePath);
|
|
212
65
|
}
|
|
213
66
|
|
|
214
|
-
// Generate test file
|
|
215
67
|
if (!options.noTest) {
|
|
216
|
-
const testPath =
|
|
217
|
-
? resolvePluginTestPath(pluginGen, name)
|
|
218
|
-
: resolveTestPath(type as GeneratorType, name);
|
|
68
|
+
const testPath = resolveTestPath(filePath);
|
|
219
69
|
const testFullPath = path.join(rootDir, testPath);
|
|
220
70
|
|
|
221
71
|
if (!options.force && fs.existsSync(testFullPath)) {
|
|
222
72
|
// Silently skip test file if it already exists
|
|
223
73
|
} else {
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
).text();
|
|
229
|
-
testContent = Mustache.render(testTemplateStr, view);
|
|
230
|
-
} else {
|
|
231
|
-
const testTemplate = await loadTemplate("test.ts.mustache");
|
|
232
|
-
testContent = Mustache.render(testTemplate, view);
|
|
233
|
-
}
|
|
74
|
+
const testTemplate = def.testTemplatePath
|
|
75
|
+
? await Bun.file(def.testTemplatePath).text()
|
|
76
|
+
: await loadGenerateTemplate("test.ts.mustache");
|
|
77
|
+
const testContent = Mustache.render(testTemplate, view);
|
|
234
78
|
|
|
235
79
|
if (options.dryRun) {
|
|
236
80
|
console.log(`Would create: ${testPath}`);
|
package/util/scaffold.ts
CHANGED
|
@@ -4,18 +4,13 @@ import Mustache from "mustache";
|
|
|
4
4
|
import path from "path";
|
|
5
5
|
import * as readline from "readline";
|
|
6
6
|
import pkg from "../package.json";
|
|
7
|
+
import { loadScaffoldTemplate as loadTemplate } from "./componentRegistry";
|
|
7
8
|
|
|
8
9
|
export interface ScaffoldOptions {
|
|
9
10
|
includeDb: boolean;
|
|
10
11
|
includeExample: boolean;
|
|
11
12
|
}
|
|
12
13
|
|
|
13
|
-
const templatesDir = path.join(import.meta.dir, "..", "templates", "scaffold");
|
|
14
|
-
|
|
15
|
-
async function loadTemplate(name: string): Promise<string> {
|
|
16
|
-
return Bun.file(path.join(templatesDir, name)).text();
|
|
17
|
-
}
|
|
18
|
-
|
|
19
14
|
async function prompt(question: string, defaultValue: string): Promise<string> {
|
|
20
15
|
const rl = readline.createInterface({
|
|
21
16
|
input: process.stdin,
|