@formmy.app/cli 0.2.0 → 0.3.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/README.md +12 -0
- package/index.mjs +46 -3
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -14,3 +14,15 @@ npx @formmy.app/cli forms list
|
|
|
14
14
|
npx @formmy.app/cli forms update <formId> --message "¡Listo! Descarga la guía: https://…"
|
|
15
15
|
npx @formmy.app/cli forms answers <formId> # quién llenó el formulario
|
|
16
16
|
```
|
|
17
|
+
|
|
18
|
+
### Entregar un archivo (lead magnet)
|
|
19
|
+
|
|
20
|
+
El archivo llega **sólo por correo** a quien llena el formulario; el clic en el botón valida su correo.
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
npx @formmy.app/cli forms update <formId> --asset ./guia.pdf \
|
|
24
|
+
--message "Te mandamos la guía a tu correo." \
|
|
25
|
+
--email-subject "Tu guía: {formulario}" --email-body "Hola {nombre}, aquí está tu guía."
|
|
26
|
+
npx @formmy.app/cli forms answers <formId> # [✓ validado · 1 clic · abrió] ← página de origen
|
|
27
|
+
npx @formmy.app/cli forms update <formId> --no-asset
|
|
28
|
+
```
|
package/index.mjs
CHANGED
|
@@ -1,7 +1,31 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import { readFile } from "node:fs/promises";
|
|
3
|
+
import { basename, extname } from "node:path";
|
|
2
4
|
// CLI de Formmy: sin dependencias, pega a /api/v2/sdk con la secret key del dueño
|
|
3
5
|
const API = process.env.FORMMY_API_URL || "https://formmy.app/api/v2/sdk";
|
|
4
6
|
const KEY = process.env.FORMMY_SECRET_KEY;
|
|
7
|
+
const MIME = { ".pdf": "application/pdf", ".zip": "application/zip", ".png": "image/png", ".jpg": "image/jpeg", ".jpeg": "image/jpeg", ".webp": "image/webp" };
|
|
8
|
+
|
|
9
|
+
// --asset acepta una URL o un archivo local; el local se sube a Formmy con una URL prefirmada
|
|
10
|
+
async function resolveAsset(formId, asset) {
|
|
11
|
+
if (/^https?:\/\//.test(asset)) return asset;
|
|
12
|
+
const contentType = MIME[extname(asset).toLowerCase()];
|
|
13
|
+
if (!contentType) {
|
|
14
|
+
console.error(`Tipo no soportado: ${asset} (pdf, zip, png, jpg, webp)`);
|
|
15
|
+
process.exit(1);
|
|
16
|
+
}
|
|
17
|
+
const bytes = await readFile(asset).catch(() => {
|
|
18
|
+
console.error(`No se pudo leer ${asset}`);
|
|
19
|
+
process.exit(1);
|
|
20
|
+
});
|
|
21
|
+
const { uploadUrl, publicUrl } = await call("forms.uploadAsset", "POST", { filename: basename(asset), contentType }, { formId });
|
|
22
|
+
const put = await fetch(uploadUrl, { method: "PUT", headers: { "Content-Type": contentType }, body: bytes });
|
|
23
|
+
if (!put.ok) {
|
|
24
|
+
console.error(`Falló la subida (${put.status})`);
|
|
25
|
+
process.exit(1);
|
|
26
|
+
}
|
|
27
|
+
return publicUrl;
|
|
28
|
+
}
|
|
5
29
|
|
|
6
30
|
const HELP = `Uso:
|
|
7
31
|
formmy forms create --name <nombre> [--type contact|subscription] [--inputs name,email,message]
|
|
@@ -9,10 +33,14 @@ const HELP = `Uso:
|
|
|
9
33
|
formmy forms list [--json]
|
|
10
34
|
formmy forms update <formId> [--name <nombre>] [--inputs name,email] [--theme dark|light]
|
|
11
35
|
[--color #9b9aea] [--message "Gracias"] [--json]
|
|
36
|
+
[--asset ./guia.pdf|https://…] [--no-asset] [--asset-button "Descargar"]
|
|
37
|
+
[--email-subject "Tu guía"] [--email-body "Hola {nombre}, …"]
|
|
12
38
|
formmy forms answers <formId> [--limit 50] [--json]
|
|
13
39
|
|
|
14
40
|
Requiere FORMMY_SECRET_KEY (formmy_sk_live_…, en formmy.app/dashboard/api-keys).
|
|
15
|
-
Inputs válidos: name, email, message, phone, company
|
|
41
|
+
Inputs válidos: name, email, message, phone, company.
|
|
42
|
+
Archivo (lead magnet): se entrega SÓLO por correo; el clic en el botón valida el correo.
|
|
43
|
+
Variables en asunto y texto: {nombre}, {formulario}.`;
|
|
16
44
|
|
|
17
45
|
function parseFlags(args) {
|
|
18
46
|
const flags = {};
|
|
@@ -81,19 +109,34 @@ if (action === "create") {
|
|
|
81
109
|
...(flags.theme && { theme: flags.theme }),
|
|
82
110
|
...(flags.color && { ctaColor: flags.color }),
|
|
83
111
|
...(flags.message && { message: flags.message }),
|
|
112
|
+
...(flags["asset-button"] && { assetButton: flags["asset-button"] }),
|
|
113
|
+
...(flags["email-subject"] && { assetEmailSubject: flags["email-subject"] }),
|
|
114
|
+
...(flags["email-body"] && { assetEmailBody: String(flags["email-body"]).replace(/\\n/g, "\n") }),
|
|
115
|
+
...(flags["no-asset"] && { assetUrl: null }),
|
|
84
116
|
};
|
|
117
|
+
if (flags.asset && flags.asset !== true) body.assetUrl = await resolveAsset(formId, flags.asset);
|
|
85
118
|
if (!Object.keys(body).length) {
|
|
86
119
|
console.error("Nada que cambiar: pasa al menos un flag.\n\n" + HELP);
|
|
87
120
|
process.exit(1);
|
|
88
121
|
}
|
|
89
122
|
const { form } = await call("forms.update", "POST", body, { formId });
|
|
90
123
|
if (flags.json) console.log(JSON.stringify(form, null, 2));
|
|
91
|
-
else
|
|
124
|
+
else {
|
|
125
|
+
const c = form.config;
|
|
126
|
+
console.log(`✓ ${form.name} (${form.id}) actualizado\nMensaje: ${c.message}`);
|
|
127
|
+
if (c.assetUrl) console.log(`Archivo: ${c.assetUrl}\nCorreo: «${c.assetEmailSubject || "Aquí está tu archivo: {formulario}"}» · botón «${c.assetButton || "Descargar"}»`);
|
|
128
|
+
}
|
|
92
129
|
} else {
|
|
93
130
|
const { answers } = await call("forms.answers", "GET", null, { formId, ...(flags.limit && { limit: flags.limit }) });
|
|
94
131
|
if (flags.json) console.log(JSON.stringify(answers, null, 2));
|
|
95
132
|
else if (!answers.length) console.log("Sin respuestas todavía.");
|
|
96
|
-
else
|
|
133
|
+
else
|
|
134
|
+
for (const a of answers) {
|
|
135
|
+
const m = a.meta || {};
|
|
136
|
+
const marks = [a.verifiedAt && "✓ validado", m.clicks && `${m.clicks} clic${m.clicks > 1 ? "s" : ""}`, m.openedAt && "abrió"].filter(Boolean).join(" · ");
|
|
137
|
+
const src = m.source ? ` ← ${m.source.replace(/^https?:\/\//, "")}` : "";
|
|
138
|
+
console.log(`${a.createdAt.slice(0, 16).replace("T", " ")} ${Object.entries(a.data || {}).map(([k, v]) => `${k}=${v}`).join(" ")}${marks ? ` [${marks}]` : ""}${src}`);
|
|
139
|
+
}
|
|
97
140
|
}
|
|
98
141
|
} else {
|
|
99
142
|
const { forms } = await call("forms.list", "GET");
|