create-factory 0.0.1
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/CHANGELOG.md +7 -0
- package/bin/cli.mjs +2 -0
- package/dist/index.js +297 -0
- package/package.json +42 -0
package/CHANGELOG.md
ADDED
package/bin/cli.mjs
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1,297 @@
|
|
|
1
|
+
// src/index.ts
|
|
2
|
+
import { readFileSync as readFileSync2 } from "fs";
|
|
3
|
+
import path4 from "path";
|
|
4
|
+
import { fileURLToPath } from "url";
|
|
5
|
+
import { Command } from "commander";
|
|
6
|
+
|
|
7
|
+
// src/analytics.ts
|
|
8
|
+
import { randomUUID } from "crypto";
|
|
9
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
|
|
10
|
+
import os from "os";
|
|
11
|
+
import path from "path";
|
|
12
|
+
import { PostHog } from "posthog-node";
|
|
13
|
+
var ANALYTICS_CONFIG_PATH = path.join(os.homedir(), ".mastra", "analytics.json");
|
|
14
|
+
var POSTHOG_API_KEY = "phc_SBLpZVAB6jmHOct9CABq3PF0Yn5FU3G2FgT4xUr2XrT";
|
|
15
|
+
var POSTHOG_HOST = "https://us.posthog.com";
|
|
16
|
+
function isTelemetryEnabled() {
|
|
17
|
+
const value = process.env.MASTRA_TELEMETRY_DISABLED;
|
|
18
|
+
return !(value && value !== "0" && value.toLowerCase() !== "false");
|
|
19
|
+
}
|
|
20
|
+
function getOrCreateDistinctId() {
|
|
21
|
+
try {
|
|
22
|
+
if (existsSync(ANALYTICS_CONFIG_PATH)) {
|
|
23
|
+
const { distinctId: distinctId2 } = JSON.parse(readFileSync(ANALYTICS_CONFIG_PATH, "utf-8"));
|
|
24
|
+
if (distinctId2) return distinctId2;
|
|
25
|
+
}
|
|
26
|
+
} catch {
|
|
27
|
+
}
|
|
28
|
+
const distinctId = randomUUID();
|
|
29
|
+
try {
|
|
30
|
+
mkdirSync(path.dirname(ANALYTICS_CONFIG_PATH), { recursive: true });
|
|
31
|
+
writeFileSync(ANALYTICS_CONFIG_PATH, JSON.stringify({ distinctId, sessionId: randomUUID() }, null, 2));
|
|
32
|
+
} catch {
|
|
33
|
+
}
|
|
34
|
+
return distinctId;
|
|
35
|
+
}
|
|
36
|
+
var Analytics = class {
|
|
37
|
+
constructor(version) {
|
|
38
|
+
this.version = version;
|
|
39
|
+
if (!isTelemetryEnabled()) return;
|
|
40
|
+
try {
|
|
41
|
+
this.distinctId = getOrCreateDistinctId();
|
|
42
|
+
this.client = new PostHog(POSTHOG_API_KEY, { host: POSTHOG_HOST, flushAt: 1, flushInterval: 100 });
|
|
43
|
+
} catch {
|
|
44
|
+
this.client = void 0;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
version;
|
|
48
|
+
client;
|
|
49
|
+
distinctId = "";
|
|
50
|
+
trackEvent(event, properties = {}) {
|
|
51
|
+
try {
|
|
52
|
+
this.client?.capture({
|
|
53
|
+
distinctId: this.distinctId,
|
|
54
|
+
event,
|
|
55
|
+
properties: { ...properties, cli: "mastra-factory", version: this.version }
|
|
56
|
+
});
|
|
57
|
+
} catch {
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
async shutdown(timeoutMs = 1e3) {
|
|
61
|
+
if (!this.client) return;
|
|
62
|
+
try {
|
|
63
|
+
await Promise.race([this.client.shutdown(), new Promise((resolve) => setTimeout(resolve, timeoutMs))]);
|
|
64
|
+
} catch {
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
// src/create.ts
|
|
70
|
+
import fs2 from "fs";
|
|
71
|
+
import path3 from "path";
|
|
72
|
+
import * as p from "@clack/prompts";
|
|
73
|
+
import color from "picocolors";
|
|
74
|
+
|
|
75
|
+
// src/utils/clone.ts
|
|
76
|
+
import fs from "fs";
|
|
77
|
+
import path2 from "path";
|
|
78
|
+
|
|
79
|
+
// src/utils/exec.ts
|
|
80
|
+
import { execFile, spawn } from "child_process";
|
|
81
|
+
import { promisify } from "util";
|
|
82
|
+
var execFileAsync = promisify(execFile);
|
|
83
|
+
function runInherit(command, args, options) {
|
|
84
|
+
return new Promise((resolve, reject) => {
|
|
85
|
+
const child = spawn(command, args, {
|
|
86
|
+
cwd: options.cwd,
|
|
87
|
+
stdio: "inherit",
|
|
88
|
+
shell: process.platform === "win32"
|
|
89
|
+
});
|
|
90
|
+
let timeout;
|
|
91
|
+
let killTimer;
|
|
92
|
+
let timedOut = false;
|
|
93
|
+
if (options.timeoutMs) {
|
|
94
|
+
timeout = setTimeout(() => {
|
|
95
|
+
timedOut = true;
|
|
96
|
+
if (process.platform === "win32" && child.pid) {
|
|
97
|
+
const killer = spawn("taskkill", ["/pid", String(child.pid), "/T", "/F"], { stdio: "ignore" });
|
|
98
|
+
killer.on("error", () => child.kill());
|
|
99
|
+
killer.on("exit", (code) => {
|
|
100
|
+
if (code !== 0) child.kill();
|
|
101
|
+
});
|
|
102
|
+
} else {
|
|
103
|
+
child.kill("SIGTERM");
|
|
104
|
+
killTimer = setTimeout(() => child.kill("SIGKILL"), 5e3);
|
|
105
|
+
}
|
|
106
|
+
}, options.timeoutMs);
|
|
107
|
+
}
|
|
108
|
+
const clearTimers = () => {
|
|
109
|
+
if (timeout) clearTimeout(timeout);
|
|
110
|
+
if (killTimer) clearTimeout(killTimer);
|
|
111
|
+
};
|
|
112
|
+
child.on("error", (err) => {
|
|
113
|
+
clearTimers();
|
|
114
|
+
reject(err);
|
|
115
|
+
});
|
|
116
|
+
child.on("close", (code) => {
|
|
117
|
+
clearTimers();
|
|
118
|
+
if (timedOut) reject(new Error(`${command} ${args.join(" ")} timed out after ${options.timeoutMs}ms`));
|
|
119
|
+
else if (code === 0) resolve();
|
|
120
|
+
else reject(new Error(`${command} ${args.join(" ")} exited with code ${code}`));
|
|
121
|
+
});
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// src/utils/clone.ts
|
|
126
|
+
var DEFAULT_TEMPLATE_REPO = "https://github.com/mastra-ai/softwarefactory-template";
|
|
127
|
+
async function cloneTemplate(options) {
|
|
128
|
+
const { repoUrl, projectPath, ref, localDir } = options;
|
|
129
|
+
if (fs.existsSync(projectPath)) {
|
|
130
|
+
throw new Error(`Directory ${path2.basename(projectPath)} already exists`);
|
|
131
|
+
}
|
|
132
|
+
if (localDir) {
|
|
133
|
+
fs.cpSync(localDir, projectPath, {
|
|
134
|
+
recursive: true,
|
|
135
|
+
filter: (src) => !src.split(path2.sep).includes("node_modules") && !src.split(path2.sep).includes(".git")
|
|
136
|
+
});
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
139
|
+
const args = ["clone", "--depth", "1"];
|
|
140
|
+
if (ref) args.push("--branch", ref);
|
|
141
|
+
args.push(repoUrl, projectPath);
|
|
142
|
+
try {
|
|
143
|
+
await execFileAsync("git", args);
|
|
144
|
+
} catch (err) {
|
|
145
|
+
throw new Error(
|
|
146
|
+
`Failed to clone template from ${repoUrl}${ref ? `@${ref}` : ""}: ${err instanceof Error ? err.message : String(err)}`
|
|
147
|
+
);
|
|
148
|
+
}
|
|
149
|
+
fs.rmSync(path2.join(projectPath, ".git"), { recursive: true, force: true });
|
|
150
|
+
}
|
|
151
|
+
function renameProject(projectPath, projectName) {
|
|
152
|
+
const pkgPath = path2.join(projectPath, "package.json");
|
|
153
|
+
const pkg2 = JSON.parse(fs.readFileSync(pkgPath, "utf8"));
|
|
154
|
+
pkg2.name = toPackageName(projectName);
|
|
155
|
+
fs.writeFileSync(pkgPath, `${JSON.stringify(pkg2, null, 2)}
|
|
156
|
+
`);
|
|
157
|
+
}
|
|
158
|
+
function toPackageName(name) {
|
|
159
|
+
const cleaned = name.toLowerCase().replace(/[^a-z0-9-_.~]+/g, "-").replace(/^[-_.]+|[-_.]+$/g, "");
|
|
160
|
+
return cleaned || "softwarefactory";
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// src/utils/pm.ts
|
|
164
|
+
function detectPackageManager() {
|
|
165
|
+
const agent = process.env.npm_config_user_agent ?? "";
|
|
166
|
+
if (agent.startsWith("pnpm")) return "pnpm";
|
|
167
|
+
if (agent.startsWith("yarn")) return "yarn";
|
|
168
|
+
if (agent.startsWith("bun")) return "bun";
|
|
169
|
+
return "npm";
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
// src/create.ts
|
|
173
|
+
async function create(args) {
|
|
174
|
+
p.intro(color.inverse(" Mastra Software Factory "));
|
|
175
|
+
let projectName = args.projectName;
|
|
176
|
+
if (!projectName && args.useDefaults) {
|
|
177
|
+
projectName = "my-software-factory";
|
|
178
|
+
}
|
|
179
|
+
if (!projectName) {
|
|
180
|
+
const entered = await p.text({
|
|
181
|
+
message: "What is your project named?",
|
|
182
|
+
initialValue: "my-software-factory",
|
|
183
|
+
validate: (value) => {
|
|
184
|
+
if (!value?.trim()) return "Required";
|
|
185
|
+
if (fs2.existsSync(path3.resolve(value.trim()))) return `Directory ${value.trim()} already exists`;
|
|
186
|
+
return void 0;
|
|
187
|
+
}
|
|
188
|
+
});
|
|
189
|
+
if (p.isCancel(entered)) return cancel2();
|
|
190
|
+
projectName = entered.trim();
|
|
191
|
+
}
|
|
192
|
+
const projectPath = path3.resolve(projectName);
|
|
193
|
+
const packageManager = detectPackageManager();
|
|
194
|
+
args.analytics.trackEvent("sf_create_started", {
|
|
195
|
+
package_manager: packageManager,
|
|
196
|
+
non_interactive: Boolean(args.useDefaults)
|
|
197
|
+
});
|
|
198
|
+
const spinner2 = p.spinner();
|
|
199
|
+
spinner2.start("Downloading the Software Factory template...");
|
|
200
|
+
try {
|
|
201
|
+
await cloneTemplate({
|
|
202
|
+
repoUrl: DEFAULT_TEMPLATE_REPO,
|
|
203
|
+
projectPath,
|
|
204
|
+
ref: args.templateRef,
|
|
205
|
+
localDir: args.templateDir
|
|
206
|
+
});
|
|
207
|
+
renameProject(projectPath, projectName);
|
|
208
|
+
fs2.copyFileSync(path3.join(projectPath, ".env.example"), path3.join(projectPath, ".env"));
|
|
209
|
+
spinner2.stop("Template downloaded.");
|
|
210
|
+
} catch (err) {
|
|
211
|
+
spinner2.stop("Template download failed.");
|
|
212
|
+
throw err;
|
|
213
|
+
}
|
|
214
|
+
const installSpinner = p.spinner();
|
|
215
|
+
installSpinner.start(`Installing dependencies with ${packageManager} (this can take a few minutes)...`);
|
|
216
|
+
try {
|
|
217
|
+
await runInherit(packageManager, ["install"], {
|
|
218
|
+
cwd: projectPath,
|
|
219
|
+
timeoutMs: args.timeout
|
|
220
|
+
});
|
|
221
|
+
installSpinner.stop("Dependencies installed.");
|
|
222
|
+
} catch (err) {
|
|
223
|
+
installSpinner.stop("Dependency install failed.");
|
|
224
|
+
throw new Error(
|
|
225
|
+
`${err instanceof Error ? err.message : String(err)}
|
|
226
|
+
You can retry manually: cd ${projectName} && ${packageManager} install`
|
|
227
|
+
);
|
|
228
|
+
}
|
|
229
|
+
let initGit = true;
|
|
230
|
+
if (!args.useDefaults) {
|
|
231
|
+
initGit = await p.confirm({ message: "Initialize a git repository?", initialValue: true });
|
|
232
|
+
}
|
|
233
|
+
if (!p.isCancel(initGit) && initGit) {
|
|
234
|
+
try {
|
|
235
|
+
await runInherit("git", ["init", "-q"], { cwd: projectPath });
|
|
236
|
+
await runInherit("git", ["add", "-A"], { cwd: projectPath });
|
|
237
|
+
await runInherit("git", ["commit", "-q", "-m", "Initial commit from mastra-factory"], {
|
|
238
|
+
cwd: projectPath
|
|
239
|
+
});
|
|
240
|
+
} catch {
|
|
241
|
+
p.log.warn("git init failed \u2014 you can initialize the repository yourself later.");
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
args.analytics.trackEvent("sf_create_completed", {
|
|
245
|
+
package_manager: packageManager,
|
|
246
|
+
non_interactive: Boolean(args.useDefaults)
|
|
247
|
+
});
|
|
248
|
+
const lines = [
|
|
249
|
+
color.green("Your Software Factory is ready!"),
|
|
250
|
+
"",
|
|
251
|
+
`${color.cyan("cd")} ${projectName}`,
|
|
252
|
+
color.cyan(`${packageManager} run dev`),
|
|
253
|
+
"",
|
|
254
|
+
`Factory UI ${color.underline("http://localhost:5173")}`,
|
|
255
|
+
`Mastra Studio ${color.underline("http://localhost:4111")}`,
|
|
256
|
+
"",
|
|
257
|
+
"Open the Factory UI to finish setup (models, integrations, database)."
|
|
258
|
+
];
|
|
259
|
+
p.note(lines.join("\n"), "Next steps");
|
|
260
|
+
p.outro(`Problems or feedback? ${color.underline("https://github.com/mastra-ai/mastra/issues")}`);
|
|
261
|
+
}
|
|
262
|
+
function cancel2() {
|
|
263
|
+
p.cancel("Cancelled.");
|
|
264
|
+
process.exitCode = 1;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
// src/index.ts
|
|
268
|
+
var pkg = JSON.parse(
|
|
269
|
+
readFileSync2(path4.join(path4.dirname(fileURLToPath(import.meta.url)), "..", "package.json"), "utf8")
|
|
270
|
+
);
|
|
271
|
+
var analytics = new Analytics(pkg.version);
|
|
272
|
+
var program = new Command();
|
|
273
|
+
program.name("mastra-factory").description("Create a Mastra Software Factory project").version(pkg.version, "-v, --version").argument("[project-name]", "Directory name of the project").option("--default", "Non-interactive: default name, init git").option("--template-ref <ref>", "Pin a template repo tag/branch").option("--template-dir <dir>", "Use a local template directory instead of cloning (development)").option("-t, --timeout [ms]", "Timeout for dependency installation in ms").action(async (projectNameArg, args) => {
|
|
274
|
+
let timeout;
|
|
275
|
+
if (args.timeout !== void 0) {
|
|
276
|
+
timeout = args.timeout === true ? 6e4 : Number(args.timeout);
|
|
277
|
+
if (!Number.isInteger(timeout) || timeout <= 0) {
|
|
278
|
+
throw new Error(`--timeout must be a positive integer in milliseconds (got ${String(args.timeout)})`);
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
await create({
|
|
282
|
+
projectName: projectNameArg,
|
|
283
|
+
useDefaults: Boolean(args.default),
|
|
284
|
+
templateRef: args.templateRef ? String(args.templateRef) : void 0,
|
|
285
|
+
templateDir: args.templateDir ? String(args.templateDir) : void 0,
|
|
286
|
+
timeout,
|
|
287
|
+
analytics
|
|
288
|
+
});
|
|
289
|
+
});
|
|
290
|
+
try {
|
|
291
|
+
await program.parseAsync(process.argv);
|
|
292
|
+
} catch (err) {
|
|
293
|
+
console.error(`Error: ${err instanceof Error ? err.message : String(err)}`);
|
|
294
|
+
process.exitCode = 1;
|
|
295
|
+
} finally {
|
|
296
|
+
await analytics.shutdown(1e3);
|
|
297
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "create-factory",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"description": "Create a Mastra Software Factory project. Placeholder package claiming the name; use npx mastra-factory or npm create factory.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"license": "Apache-2.0",
|
|
7
|
+
"bin": {
|
|
8
|
+
"create-factory": "./bin/cli.mjs"
|
|
9
|
+
},
|
|
10
|
+
"files": [
|
|
11
|
+
"bin",
|
|
12
|
+
"dist",
|
|
13
|
+
"CHANGELOG.md"
|
|
14
|
+
],
|
|
15
|
+
"engines": {
|
|
16
|
+
"node": ">=20"
|
|
17
|
+
},
|
|
18
|
+
"repository": {
|
|
19
|
+
"type": "git",
|
|
20
|
+
"url": "git+https://github.com/mastra-ai/mastra.git",
|
|
21
|
+
"directory": "mastracode/mastra-factory"
|
|
22
|
+
},
|
|
23
|
+
"homepage": "https://mastra.ai",
|
|
24
|
+
"bugs": {
|
|
25
|
+
"url": "https://github.com/mastra-ai/mastra/issues"
|
|
26
|
+
},
|
|
27
|
+
"keywords": [
|
|
28
|
+
"mastra",
|
|
29
|
+
"software-factory",
|
|
30
|
+
"create",
|
|
31
|
+
"scaffold",
|
|
32
|
+
"cli",
|
|
33
|
+
"agents",
|
|
34
|
+
"ai"
|
|
35
|
+
],
|
|
36
|
+
"dependencies": {
|
|
37
|
+
"@clack/prompts": "^1.7.0",
|
|
38
|
+
"commander": "^14.0.3",
|
|
39
|
+
"picocolors": "^1.1.1",
|
|
40
|
+
"posthog-node": "^5.37.0"
|
|
41
|
+
}
|
|
42
|
+
}
|