@farm.js/cli 0.1.0-beta.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/LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2024 Farm.js Team
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
22
+
package/README.md ADDED
@@ -0,0 +1,11 @@
1
+ # @farm.js/cli
2
+
3
+ CLI for Farm.js framework
4
+
5
+ Farm.js is currently in beta.
6
+
7
+ ```bash
8
+ npm install @farm.js/cli@beta
9
+ ```
10
+
11
+ See the [Farm.js repository](https://github.com/farming-labs/farm.js) for documentation, examples, and support.
package/bin/farm.js ADDED
@@ -0,0 +1,394 @@
1
+ #!/usr/bin/env node
2
+
3
+ // Match Vite's startup path by reusing Node's on-disk compilation cache when
4
+ // the runtime supports it. Older Node releases keep the existing behavior.
5
+ try {
6
+ const nodeModule = require("node:module");
7
+ nodeModule.enableCompileCache?.();
8
+ setTimeout(() => {
9
+ try {
10
+ nodeModule.flushCompileCache?.();
11
+ } catch {}
12
+ }, 10_000).unref();
13
+ } catch {}
14
+
15
+ const { program } = require("commander");
16
+ const { version } = require("../package.json");
17
+
18
+ const banner = `
19
+ _______
20
+ | ___ |__ _ _ __ _ __ ___
21
+ | |_ /| / _\` | '__| '_ \` _ \\
22
+ | _ \\| | (_| | | | | | | | |
23
+ |_| \\_\\_|\\__,_|_| |_| |_| |_|
24
+ `;
25
+
26
+ program.name("farm").description("Farm.js CLI - A modern React meta-framework").version(version);
27
+ program.addHelpText("beforeAll", `${banner}\n`);
28
+
29
+ function collectOption(value, previous) {
30
+ return [...(previous || []), value];
31
+ }
32
+
33
+ program
34
+ .command("dev")
35
+ .description("Start development server")
36
+ .option("-p, --port <port>", "Port to run the server on", "3000")
37
+ .option("-r, --root <root>", "Root directory", process.cwd())
38
+ .option("--cron", "Run configured cron routes in-process during development")
39
+ .action(async (options) => {
40
+ try {
41
+ const { startDevServer } = require("../dist/dev.js");
42
+ const server = await startDevServer(
43
+ {
44
+ root: options.root,
45
+ },
46
+ parseInt(options.port),
47
+ );
48
+ if (options.cron) {
49
+ const { startFarmCronScheduler } = require("../dist/index.js");
50
+ const address = server.httpServer?.address();
51
+ const port = typeof address === "object" && address ? address.port : parseInt(options.port);
52
+ const scheduler = await startFarmCronScheduler({
53
+ root: options.root,
54
+ url: `http://localhost:${port}`,
55
+ });
56
+ server.__farmCronScheduler = scheduler;
57
+ server.httpServer?.once("close", () => scheduler.stop());
58
+ }
59
+ } catch (error) {
60
+ console.error("Failed to start development server:", error);
61
+ process.exit(1);
62
+ }
63
+ });
64
+
65
+ program
66
+ .command("build")
67
+ .description("Build for production")
68
+ .option("-r, --root <root>", "Root directory", process.cwd())
69
+ .option("-p, --preset <preset>", "Nitro preset (node-server, vercel, cloudflare, etc.)")
70
+ .action(async (options) => {
71
+ try {
72
+ const { buildFarm } = require("../dist/build.js");
73
+ await buildFarm({
74
+ root: options.root,
75
+ preset: options.preset,
76
+ });
77
+ } catch (error) {
78
+ console.error("Failed to build:", error);
79
+ process.exit(1);
80
+ }
81
+ });
82
+
83
+ program
84
+ .command("generate")
85
+ .description("Generate route/API types and integration schema artifacts")
86
+ .option("-r, --root <root>", "Root directory", process.cwd())
87
+ .option("-c, --config <config>", "Path to farm config file")
88
+ .option(
89
+ "--orm <orm>",
90
+ "Schema target to generate (prisma, drizzle, postgres, mysql, sqlite, mongodb)",
91
+ )
92
+ .option("--dialect <dialect>", "SQL dialect for Drizzle generation (postgres, mysql, sqlite)")
93
+ .option("-o, --output <output>", "Custom output path")
94
+ .action(async (options) => {
95
+ try {
96
+ const { generateFarmArtifacts } = require("../dist/index.js");
97
+ await generateFarmArtifacts({
98
+ root: options.root,
99
+ configPath: options.config,
100
+ orm: options.orm,
101
+ dialect: options.dialect,
102
+ output: options.output,
103
+ });
104
+ } catch (error) {
105
+ console.error("Failed to generate Farm artifacts:", error);
106
+ process.exit(1);
107
+ }
108
+ });
109
+
110
+ program
111
+ .command("doctor")
112
+ .description("Inspect project configuration and the running Farm runtime")
113
+ .option("-r, --root <root>", "Root directory", process.cwd())
114
+ .option("-c, --config <config>", "Path to farm config file")
115
+ .option("-p, --port <port>", "Port of a running local app")
116
+ .option("--host <host>", "Host of a running local app")
117
+ .option("--url <url>", "Base URL of a running Farm app")
118
+ .option("--offline", "Inspect project files without probing a running app")
119
+ .option("--timeout <ms>", "Live runtime probe timeout in milliseconds", "1200")
120
+ .option("--json", "Print machine-readable JSON")
121
+ .action(async (options) => {
122
+ try {
123
+ const { formatFarmDoctorReport, runFarmDoctor } = require("../dist/index.js");
124
+ const timeoutMs = Number.parseInt(options.timeout, 10);
125
+ if (!Number.isFinite(timeoutMs) || timeoutMs <= 0) {
126
+ throw new Error("--timeout must be a positive number of milliseconds.");
127
+ }
128
+ const report = await runFarmDoctor({
129
+ root: options.root,
130
+ configPath: options.config,
131
+ port: options.port,
132
+ host: options.host,
133
+ url: options.url,
134
+ offline: options.offline,
135
+ timeoutMs,
136
+ });
137
+ console.log(options.json ? JSON.stringify(report, null, 2) : formatFarmDoctorReport(report));
138
+ if (report.health === "error") process.exitCode = 1;
139
+ } catch (error) {
140
+ console.error("Failed to inspect Farm app:", error);
141
+ process.exit(1);
142
+ }
143
+ });
144
+
145
+ program
146
+ .command("preview")
147
+ .description("Create a public URL for a running local Farm app")
148
+ .option("-r, --root <root>", "Root directory", process.cwd())
149
+ .option("-c, --config <config>", "Path to farm config file")
150
+ .option("-p, --port <port>", "Port of the running local app")
151
+ .option("--host <host>", "Host of the running local app", "localhost")
152
+ .option("--url <url>", "Full local URL to expose")
153
+ .option(
154
+ "--gateway <url>",
155
+ "Advanced: override the hosted Farm Preview gateway URL",
156
+ process.env.FARM_PREVIEW_GATEWAY_URL,
157
+ )
158
+ .option(
159
+ "--provider <provider>",
160
+ "Advanced: preview provider to use (farm, local)",
161
+ process.env.FARM_PREVIEW_PROVIDER,
162
+ )
163
+ .option("--name <name>", "Readable preview URL name")
164
+ .option("--dry-run", "Validate target detection and print the preview plan without opening it")
165
+ .option("--no-probe", "Skip local reachability check when --port is provided")
166
+ .action(async (options) => {
167
+ try {
168
+ const { previewFarm } = require("../dist/index.js");
169
+ await previewFarm({
170
+ root: options.root,
171
+ configPath: options.config,
172
+ port: options.port,
173
+ host: options.host,
174
+ url: options.url,
175
+ gatewayUrl: options.gateway,
176
+ name: options.name,
177
+ dryRun: options.dryRun,
178
+ noProbe: options.noProbe,
179
+ provider: options.provider,
180
+ });
181
+ } catch (error) {
182
+ console.error("Failed to create preview:", error);
183
+ process.exit(1);
184
+ }
185
+ });
186
+
187
+ program
188
+ .command("migrate [source]")
189
+ .description("Run one-shot migration commands or migrate from another framework")
190
+ .option("-r, --root <root>", "Root directory", process.cwd())
191
+ .option("-c, --config <config>", "Path to farm config file")
192
+ .option("--command <command>", "Migration command to run; can be repeated", collectOption, [])
193
+ .option("--dry-run", "Print migration commands without running them")
194
+ .option(
195
+ "--write",
196
+ "Apply framework migration changes; framework migrations are dry-run by default",
197
+ )
198
+ .option("--force", "Overwrite existing files during framework migrations")
199
+ .action(async (source, options) => {
200
+ try {
201
+ const { migrateFarm } = require("../dist/index.js");
202
+ await migrateFarm({
203
+ root: options.root,
204
+ configPath: options.config,
205
+ commands: options.command,
206
+ dryRun: options.dryRun,
207
+ source,
208
+ write: options.write,
209
+ force: options.force,
210
+ });
211
+ } catch (error) {
212
+ console.error("Failed to run migrations:", error);
213
+ process.exit(1);
214
+ }
215
+ });
216
+
217
+ const cronCommand = program
218
+ .command("cron")
219
+ .description("Inspect and manually run framework-native cron routes");
220
+
221
+ cronCommand
222
+ .command("list")
223
+ .description("List cron routes configured in farm.config")
224
+ .option("-r, --root <root>", "Root directory", process.cwd())
225
+ .option("-c, --config <config>", "Path to farm config file")
226
+ .option("--json", "Print machine-readable JSON")
227
+ .action(async (options) => {
228
+ try {
229
+ const { formatFarmCronJobs, listFarmCronJobs } = require("../dist/index.js");
230
+ const jobs = await listFarmCronJobs({
231
+ root: options.root,
232
+ configPath: options.config,
233
+ });
234
+ console.log(options.json ? JSON.stringify(jobs, null, 2) : formatFarmCronJobs(jobs));
235
+ } catch (error) {
236
+ console.error("Failed to list cron routes:", error);
237
+ process.exit(1);
238
+ }
239
+ });
240
+
241
+ cronCommand
242
+ .command("run <name>")
243
+ .description("Invoke one configured cron route on a running Farm app")
244
+ .option("-r, --root <root>", "Root directory", process.cwd())
245
+ .option("-c, --config <config>", "Path to farm config file")
246
+ .option("-p, --port <port>", "Port of the running local app", "3000")
247
+ .option("--host <host>", "Host of the running local app", "localhost")
248
+ .option("--url <url>", "Base URL of a running Farm app")
249
+ .option("--secret <secret>", "Bearer secret (defaults to CRON_SECRET)")
250
+ .option("--json", "Print the complete invocation result as JSON")
251
+ .action(async (name, options) => {
252
+ try {
253
+ const { runFarmCronJob } = require("../dist/index.js");
254
+ const result = await runFarmCronJob(name, {
255
+ root: options.root,
256
+ configPath: options.config,
257
+ port: options.port,
258
+ host: options.host,
259
+ url: options.url,
260
+ secret: options.secret,
261
+ });
262
+ if (options.json) {
263
+ console.log(JSON.stringify(result, null, 2));
264
+ } else {
265
+ console.log(
266
+ `Cron ${result.job.name} completed with ${result.status} in ${result.durationMs}ms.`,
267
+ );
268
+ if (result.body !== null && result.body !== undefined && result.body !== "") {
269
+ console.log(
270
+ typeof result.body === "string" ? result.body : JSON.stringify(result.body, null, 2),
271
+ );
272
+ }
273
+ }
274
+ } catch (error) {
275
+ console.error("Failed to run cron route:", error);
276
+ process.exit(1);
277
+ }
278
+ });
279
+
280
+ const addCommand = program.command("add").description("Add Farm.js components to the current app");
281
+
282
+ addCommand
283
+ .command("integration [provider]")
284
+ .alias("integrations")
285
+ .description("Add an official Farm.js integration to the app registry")
286
+ .option("-r, --root <root>", "Root directory", process.cwd())
287
+ .option("-k, --key <key>", "Registry key to use in appIntegrations")
288
+ .option("-f, --file <file>", "Path to the app integrations registry", "src/lib/integrations.ts")
289
+ .option("--force", "Overwrite an existing generated integration component")
290
+ .option("--dry-run", "Show what would be added without writing files")
291
+ .option("--route-file <file>", "Route file path for route-based integrations")
292
+ .option("--ui", "Also install the provider's shadcn-based Farm UI feature pack")
293
+ .option("--skip-package-json", "Do not add @farm.js/integrations to package.json")
294
+ .option("--skip-config", "Do not create or update farm.config")
295
+ .option("-l, --list", "List supported integration providers")
296
+ .action(async (provider, options) => {
297
+ try {
298
+ const {
299
+ addFarmIntegration,
300
+ listFarmIntegrationProviders,
301
+ } = require("../dist/add-integration.js");
302
+
303
+ if (options.list) {
304
+ for (const entry of listFarmIntegrationProviders()) {
305
+ console.log(`${entry.name.padEnd(13)} ${entry.description}`);
306
+ }
307
+ return;
308
+ }
309
+
310
+ if (!provider) {
311
+ console.error("Please pass an integration provider, or use --list.");
312
+ process.exit(1);
313
+ }
314
+
315
+ const result = await addFarmIntegration({
316
+ root: options.root,
317
+ provider,
318
+ key: options.key,
319
+ integrationsFile: options.file,
320
+ routeFile: options.routeFile,
321
+ ui: options.ui,
322
+ dryRun: options.dryRun,
323
+ force: options.force,
324
+ skipPackageJson: options.skipPackageJson,
325
+ skipConfig: options.skipConfig,
326
+ });
327
+
328
+ const verb = options.dryRun ? "Prepared" : "Added";
329
+ if (result.mode === "route") {
330
+ console.log(`${verb} ${result.provider} route at ${result.routePath || result.routeFile}`);
331
+ } else {
332
+ console.log(`${verb} ${result.provider} integration as appIntegrations.${result.key}`);
333
+ }
334
+
335
+ if (result.ui) {
336
+ console.log(`UI feature: ${result.ui.feature}`);
337
+ if (result.ui.components.length) {
338
+ console.log(`Shadcn components: ${result.ui.components.join(", ")}`);
339
+ }
340
+ }
341
+
342
+ if (result.created.length) {
343
+ console.log("Created:");
344
+ for (const file of result.created) {
345
+ console.log(` ${file}`);
346
+ }
347
+ }
348
+
349
+ if (result.updated.length) {
350
+ console.log("Updated:");
351
+ for (const file of result.updated) {
352
+ console.log(` ${file}`);
353
+ }
354
+ }
355
+
356
+ if (result.env.length) {
357
+ console.log("Environment:");
358
+ for (const key of result.env) {
359
+ console.log(` ${key}`);
360
+ }
361
+ }
362
+
363
+ if (result.notes.length) {
364
+ console.log("Notes:");
365
+ for (const note of result.notes) {
366
+ console.log(` ${note}`);
367
+ }
368
+ }
369
+ } catch (error) {
370
+ console.error("Failed to add integration:", error);
371
+ process.exit(1);
372
+ }
373
+ });
374
+
375
+ program
376
+ .command("deploy")
377
+ .description("Deploy to a platform from deploy.target or a platform flag")
378
+ .option("-r, --root <root>", "Root directory", process.cwd())
379
+ .option("--vercel", "Deploy to Vercel")
380
+ .option("--cloudflare", "Deploy to Cloudflare")
381
+ .option("--netlify", "Deploy to Netlify")
382
+ .option("--prod", "Deploy to production (Vercel: uses prebuilt output)")
383
+ .option("--custom", "Use your own credentials (not Farm.js managed)")
384
+ .action(async (options) => {
385
+ try {
386
+ const { deployFarm } = require("../dist/index.js");
387
+ await deployFarm(options);
388
+ } catch (error) {
389
+ console.error("Failed to deploy:", error);
390
+ process.exit(1);
391
+ }
392
+ });
393
+
394
+ program.parse();