@holdyapp/install 0.4.0 → 0.5.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 +8 -3
- package/dist/cli.js +129 -18
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -5,13 +5,16 @@ CLI de instalação do Holdy SDK. Detecta seu framework e wira o SDK em 1 passo.
|
|
|
5
5
|
## Uso
|
|
6
6
|
|
|
7
7
|
```bash
|
|
8
|
-
npx @holdyapp/install
|
|
8
|
+
npx @holdyapp/install
|
|
9
9
|
```
|
|
10
10
|
|
|
11
|
-
|
|
11
|
+
Abre o navegador pra você entrar na Holdy (ou criar a conta), conferir o código e autorizar. A chave chega sozinha no terminal, sem copiar nada. Com `--instrument`, a Holdy também lê seu código e propõe os eventos do produto, usando uma chave secreta que vale 1 hora.
|
|
12
|
+
|
|
13
|
+
Prefere colar a chave?
|
|
12
14
|
|
|
13
15
|
```bash
|
|
14
|
-
npx @holdyapp/install
|
|
16
|
+
npx @holdyapp/install <SUA_API_KEY>
|
|
17
|
+
npx @holdyapp/install --paste-key
|
|
15
18
|
```
|
|
16
19
|
|
|
17
20
|
## Opções
|
|
@@ -19,6 +22,8 @@ npx @holdyapp/install
|
|
|
19
22
|
```
|
|
20
23
|
npx @holdyapp/install <apiKey> [opções]
|
|
21
24
|
|
|
25
|
+
--instrument Lê o código e propõe os eventos do produto
|
|
26
|
+
--paste-key Pede a chave no terminal em vez de abrir o navegador
|
|
22
27
|
--account <id> Account ID padrão pro Holdy.init
|
|
23
28
|
--no-auto-capture Desabilita captura automática de page views
|
|
24
29
|
--version, -v Mostra versão
|
package/dist/cli.js
CHANGED
|
@@ -174,8 +174,11 @@ declare global {
|
|
|
174
174
|
interface Window {
|
|
175
175
|
Holdy?: {
|
|
176
176
|
init: (opts: Record<string, unknown>) => void
|
|
177
|
-
identify: (
|
|
178
|
-
|
|
177
|
+
identify: (userId: string, traits?: Record<string, unknown>) => void
|
|
178
|
+
group: (accountKey: string, traits?: Record<string, unknown>) => void
|
|
179
|
+
track: (event: string, properties?: Record<string, unknown>) => void
|
|
180
|
+
page: (name?: string, properties?: Record<string, unknown>) => void
|
|
181
|
+
reset: () => void
|
|
179
182
|
}
|
|
180
183
|
}
|
|
181
184
|
}
|
|
@@ -235,7 +238,7 @@ function injectNextPages(file, opts) {
|
|
|
235
238
|
if (typeof window !== 'undefined' && !window.__holdyInit) { initHoldy({ apiKey: '${opts.apiKey}', ${opts.accountId ? `accountKey: '${opts.accountId}', ` : ""}autoCapture: ${opts.autoCapture} }); (window as unknown as { __holdyInit?: boolean }).__holdyInit = true }`
|
|
236
239
|
);
|
|
237
240
|
(0, import_node_fs2.writeFileSync)(file, patched);
|
|
238
|
-
return { ok: true, modified: true, file, message: `Patched ${displayPath(file)} \xB7 rode npm install
|
|
241
|
+
return { ok: true, modified: true, file, message: `Patched ${displayPath(file)} \xB7 rode npm install @holdyapp/sdk` };
|
|
239
242
|
}
|
|
240
243
|
function injectViteReact(file, opts) {
|
|
241
244
|
if (!file || !(0, import_node_fs2.existsSync)(file)) {
|
|
@@ -256,7 +259,7 @@ initHoldy({ apiKey: '${opts.apiKey}', ${opts.accountId ? `accountKey: '${opts.ac
|
|
|
256
259
|
|
|
257
260
|
${original}`;
|
|
258
261
|
(0, import_node_fs2.writeFileSync)(file, patched);
|
|
259
|
-
return { ok: true, modified: true, file, message: `Patched ${displayPath(file)} \xB7 rode npm install
|
|
262
|
+
return { ok: true, modified: true, file, message: `Patched ${displayPath(file)} \xB7 rode npm install @holdyapp/sdk` };
|
|
260
263
|
}
|
|
261
264
|
function injectViteVue(file, opts) {
|
|
262
265
|
return injectViteReact(file, opts);
|
|
@@ -278,7 +281,7 @@ export default defineNuxtPlugin(() => {
|
|
|
278
281
|
})
|
|
279
282
|
`;
|
|
280
283
|
(0, import_node_fs2.writeFileSync)(file, content);
|
|
281
|
-
return { ok: true, modified: true, file, message: `Criado ${displayPath(file)} \xB7 rode npm install
|
|
284
|
+
return { ok: true, modified: true, file, message: `Criado ${displayPath(file)} \xB7 rode npm install @holdyapp/sdk` };
|
|
282
285
|
}
|
|
283
286
|
function injectRemix(file, opts) {
|
|
284
287
|
if (!file || !(0, import_node_fs2.existsSync)(file)) {
|
|
@@ -734,8 +737,86 @@ function confirm(question) {
|
|
|
734
737
|
});
|
|
735
738
|
}
|
|
736
739
|
|
|
740
|
+
// src/login.ts
|
|
741
|
+
var import_node_child_process = require("node:child_process");
|
|
742
|
+
var sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
743
|
+
function openBrowser(url) {
|
|
744
|
+
if (process.env.CI || process.env.HOLDY_NO_BROWSER) return false;
|
|
745
|
+
try {
|
|
746
|
+
const child = process.platform === "darwin" ? (0, import_node_child_process.spawn)("open", [url], { stdio: "ignore", detached: true }) : process.platform === "win32" ? (0, import_node_child_process.spawn)("cmd", ["/c", "start", '""', url], { stdio: "ignore", detached: true }) : (0, import_node_child_process.spawn)("xdg-open", [url], { stdio: "ignore", detached: true });
|
|
747
|
+
child.on("error", () => void 0);
|
|
748
|
+
child.unref();
|
|
749
|
+
return true;
|
|
750
|
+
} catch {
|
|
751
|
+
return false;
|
|
752
|
+
}
|
|
753
|
+
}
|
|
754
|
+
async function loginWithBrowser(opts) {
|
|
755
|
+
const { ui } = opts;
|
|
756
|
+
let session;
|
|
757
|
+
try {
|
|
758
|
+
const res = await fetch(`${opts.apiUrl}/cli/sessions`, {
|
|
759
|
+
method: "POST",
|
|
760
|
+
headers: { "Content-Type": "application/json" },
|
|
761
|
+
body: JSON.stringify({ instrument: opts.instrument, client: opts.client })
|
|
762
|
+
});
|
|
763
|
+
if (!res.ok) {
|
|
764
|
+
ui.error(`A Holdy n\xE3o abriu a sess\xE3o de login (HTTP ${res.status}).`);
|
|
765
|
+
return null;
|
|
766
|
+
}
|
|
767
|
+
session = await res.json();
|
|
768
|
+
} catch (err) {
|
|
769
|
+
ui.error(`N\xE3o consegui falar com a Holdy: ${err instanceof Error ? err.message : String(err)}`);
|
|
770
|
+
return null;
|
|
771
|
+
}
|
|
772
|
+
console.log();
|
|
773
|
+
console.log(` Seu c\xF3digo: ${ui.bold(session.user_code)}`);
|
|
774
|
+
const opened = openBrowser(session.verification_url);
|
|
775
|
+
console.log(
|
|
776
|
+
` ${opened ? "Abri o navegador. Se n\xE3o abriu, acesse:" : "Abra no navegador:"} ${ui.cyan(session.verification_url)}`
|
|
777
|
+
);
|
|
778
|
+
console.log(` ${ui.dim("Entre ou crie sua conta, confira o c\xF3digo e clique em Autorizar.")}`);
|
|
779
|
+
console.log();
|
|
780
|
+
const deadline = Date.now() + session.expires_in * 1e3;
|
|
781
|
+
let interval = Math.max(1, session.interval) * 1e3;
|
|
782
|
+
while (Date.now() < deadline) {
|
|
783
|
+
await sleep(interval);
|
|
784
|
+
let res;
|
|
785
|
+
try {
|
|
786
|
+
res = await fetch(`${opts.apiUrl}/cli/sessions/poll`, {
|
|
787
|
+
method: "POST",
|
|
788
|
+
headers: { "Content-Type": "application/json" },
|
|
789
|
+
body: JSON.stringify({ device_code: session.device_code })
|
|
790
|
+
});
|
|
791
|
+
} catch {
|
|
792
|
+
continue;
|
|
793
|
+
}
|
|
794
|
+
if (res.status === 202) continue;
|
|
795
|
+
if (res.status === 429) {
|
|
796
|
+
interval = Math.min(interval * 2, 1e4);
|
|
797
|
+
continue;
|
|
798
|
+
}
|
|
799
|
+
if (res.status === 403) {
|
|
800
|
+
ui.error("Autoriza\xE7\xE3o cancelada no navegador.");
|
|
801
|
+
return null;
|
|
802
|
+
}
|
|
803
|
+
if (res.status === 410) break;
|
|
804
|
+
if (res.ok) {
|
|
805
|
+
const body = await res.json();
|
|
806
|
+
if (!body.public_key) break;
|
|
807
|
+
return {
|
|
808
|
+
publicKey: body.public_key,
|
|
809
|
+
secretKey: body.secret_key ?? null,
|
|
810
|
+
organizationName: body.organization_name ?? null
|
|
811
|
+
};
|
|
812
|
+
}
|
|
813
|
+
}
|
|
814
|
+
ui.error("O c\xF3digo expirou antes da autoriza\xE7\xE3o. Rode o comando de novo.");
|
|
815
|
+
return null;
|
|
816
|
+
}
|
|
817
|
+
|
|
737
818
|
// src/cli.ts
|
|
738
|
-
var VERSION = "0.
|
|
819
|
+
var VERSION = "0.5.0";
|
|
739
820
|
var DEFAULT_API_URL = "https://holdy.chat/api/v1";
|
|
740
821
|
async function main() {
|
|
741
822
|
const argv = process.argv.slice(2);
|
|
@@ -757,14 +838,30 @@ async function main() {
|
|
|
757
838
|
log.warn('N\xE3o consegui identificar o framework. Sigo no modo "vanilla" (script tag).');
|
|
758
839
|
}
|
|
759
840
|
let apiKey = args.apiKey;
|
|
760
|
-
|
|
841
|
+
let instrumentKey = null;
|
|
842
|
+
if (!apiKey && args.pasteKey) {
|
|
761
843
|
apiKey = await promptApiKey();
|
|
844
|
+
} else if (!apiKey) {
|
|
845
|
+
log.step("2", "Entrando na Holdy pelo navegador\u2026");
|
|
846
|
+
const login = await loginWithBrowser({
|
|
847
|
+
apiUrl: args.apiUrl,
|
|
848
|
+
instrument: args.instrument,
|
|
849
|
+
client: { framework: detection.framework, project: projectName(cwd), cli_version: VERSION },
|
|
850
|
+
ui: { ...log, bold, dim, cyan }
|
|
851
|
+
});
|
|
852
|
+
if (!login) {
|
|
853
|
+
log.info(`Prefere colar a chave? ${bold("npx @holdyapp/install --paste-key")}`);
|
|
854
|
+
process.exit(1);
|
|
855
|
+
}
|
|
856
|
+
log.success(login.organizationName ? `Autorizado em ${bold(login.organizationName)}` : "Autorizado");
|
|
857
|
+
apiKey = login.publicKey;
|
|
858
|
+
instrumentKey = login.secretKey;
|
|
762
859
|
}
|
|
763
860
|
if (!apiKey || !apiKey.startsWith("hol_")) {
|
|
764
861
|
log.error("API key inv\xE1lida. Use formato hol_live_\u2026 ou hol_test_\u2026 \xB7 gere em holdy.chat/settings.");
|
|
765
862
|
process.exit(1);
|
|
766
863
|
}
|
|
767
|
-
log.step("
|
|
864
|
+
log.step("3", "Aplicando snippet no(s) arquivo(s)\u2026");
|
|
768
865
|
const result = inject(detection, {
|
|
769
866
|
apiKey,
|
|
770
867
|
accountId: args.accountId,
|
|
@@ -786,19 +883,19 @@ async function main() {
|
|
|
786
883
|
process.exit(2);
|
|
787
884
|
}
|
|
788
885
|
if (detection.usePackage && detection.framework !== "next-app") {
|
|
789
|
-
log.step("
|
|
886
|
+
log.step("4", "Falta instalar o pacote npm:");
|
|
790
887
|
console.log();
|
|
791
|
-
console.log(" npm install
|
|
888
|
+
console.log(" npm install @holdyapp/sdk");
|
|
792
889
|
console.log();
|
|
793
890
|
} else {
|
|
794
|
-
log.step("
|
|
891
|
+
log.step("4", "Sem npm install necess\xE1rio \xB7 script tag via CDN.");
|
|
795
892
|
}
|
|
796
893
|
if (args.instrument) {
|
|
797
894
|
console.log();
|
|
798
|
-
log.step("
|
|
895
|
+
log.step("5", "Instrumentando eventos de produto\u2026");
|
|
799
896
|
const code = await runInstrument({
|
|
800
897
|
cwd,
|
|
801
|
-
apiKey,
|
|
898
|
+
apiKey: instrumentKey ?? apiKey,
|
|
802
899
|
baseUrl: args.apiUrl,
|
|
803
900
|
framework: detection.framework,
|
|
804
901
|
sdkStyle: detection.usePackage ? "npm" : "script",
|
|
@@ -809,10 +906,10 @@ async function main() {
|
|
|
809
906
|
});
|
|
810
907
|
if (code !== 0) process.exit(code);
|
|
811
908
|
console.log();
|
|
812
|
-
log.step("
|
|
909
|
+
log.step("6", "Pronto! Suba o app, use o produto e veja os eventos chegando em holdy.chat.");
|
|
813
910
|
process.exit(0);
|
|
814
911
|
}
|
|
815
|
-
log.step("
|
|
912
|
+
log.step("5", "Pronto! Inicie o app e abra holdy.chat/overview \xB7 o primeiro evento aparece em segundos.");
|
|
816
913
|
process.exit(0);
|
|
817
914
|
}
|
|
818
915
|
function parseArgs(argv) {
|
|
@@ -824,7 +921,8 @@ function parseArgs(argv) {
|
|
|
824
921
|
dryRun: false,
|
|
825
922
|
yes: false,
|
|
826
923
|
maxEvents: 12,
|
|
827
|
-
apiUrl: process.env.HOLDY_API_URL ?? DEFAULT_API_URL
|
|
924
|
+
apiUrl: process.env.HOLDY_API_URL ?? DEFAULT_API_URL,
|
|
925
|
+
pasteKey: false
|
|
828
926
|
};
|
|
829
927
|
for (let i = 0; i < argv.length; i++) {
|
|
830
928
|
const a = argv[i];
|
|
@@ -844,6 +942,8 @@ function parseArgs(argv) {
|
|
|
844
942
|
} else if (a === "--max-events") {
|
|
845
943
|
const parsed = Number(argv[++i]);
|
|
846
944
|
if (Number.isFinite(parsed)) out.maxEvents = Math.min(20, Math.max(3, Math.trunc(parsed)));
|
|
945
|
+
} else if (a === "--paste-key") {
|
|
946
|
+
out.pasteKey = true;
|
|
847
947
|
} else if (a === "--api-url") {
|
|
848
948
|
out.apiUrl = argv[++i] ?? out.apiUrl;
|
|
849
949
|
} else if (!a.startsWith("--") && !out.apiKey) {
|
|
@@ -855,6 +955,14 @@ function parseArgs(argv) {
|
|
|
855
955
|
}
|
|
856
956
|
return out;
|
|
857
957
|
}
|
|
958
|
+
function projectName(cwd) {
|
|
959
|
+
try {
|
|
960
|
+
const pkg = JSON.parse((0, import_node_fs5.readFileSync)((0, import_node_path5.join)(cwd, "package.json"), "utf8"));
|
|
961
|
+
if (pkg.name) return pkg.name;
|
|
962
|
+
} catch {
|
|
963
|
+
}
|
|
964
|
+
return (0, import_node_path5.basename)(cwd);
|
|
965
|
+
}
|
|
858
966
|
function promptApiKey() {
|
|
859
967
|
return new Promise((resolve) => {
|
|
860
968
|
const rl = (0, import_node_readline2.createInterface)({ input: process.stdin, output: process.stdout });
|
|
@@ -870,8 +978,9 @@ function printHelp() {
|
|
|
870
978
|
${bold("@holdyapp/install")} v${VERSION} \xB7 ${isLive ?? "Holdy SDK installer"}
|
|
871
979
|
|
|
872
980
|
${bold("Uso:")}
|
|
981
|
+
npx @holdyapp/install # entra pelo navegador, sem colar chave
|
|
982
|
+
npx @holdyapp/install --instrument # idem, e j\xE1 prop\xF5e os eventos do seu produto
|
|
873
983
|
npx @holdyapp/install <apiKey> [op\xE7\xF5es]
|
|
874
|
-
npx @holdyapp/install # modo interativo
|
|
875
984
|
HOLDY_API_KEY=hol_live_\u2026 npx @holdyapp/install # via env var
|
|
876
985
|
|
|
877
986
|
${bold("Argumentos:")}
|
|
@@ -884,6 +993,7 @@ ${bold("Op\xE7\xF5es:")}
|
|
|
884
993
|
--dry-run S\xF3 lista os eventos propostos \xB7 n\xE3o escreve nada (implica --instrument)
|
|
885
994
|
--max-events <n> Teto de eventos propostos \xB7 3 a 20 \xB7 default 12
|
|
886
995
|
--yes, -y Aplica o diff sem perguntar
|
|
996
|
+
--paste-key Pede a chave no terminal em vez de abrir o navegador
|
|
887
997
|
--api-url <url> Override da API (default https://holdy.chat/api/v1)
|
|
888
998
|
--version, -v Mostra vers\xE3o
|
|
889
999
|
--help, -h Mostra esta mensagem
|
|
@@ -900,7 +1010,8 @@ ${bold("Exemplos:")}
|
|
|
900
1010
|
|
|
901
1011
|
${bold("Sobre --instrument:")}
|
|
902
1012
|
Manda trechos do seu c\xF3digo pra Holdy, que devolve onde inserir cada track().
|
|
903
|
-
|
|
1013
|
+
Pelo navegador, a Holdy entrega uma chave secreta que vale 1 hora s\xF3 pra isso.
|
|
1014
|
+
Com chave colada, exige key SECRETA (n\xE3o a p\xFAblica do browser). Voc\xEA v\xEA o diff antes de aplicar,
|
|
904
1015
|
e nada \xE9 escrito sem confirma\xE7\xE3o. Pageview e rota j\xE1 s\xE3o autom\xE1ticos \u2014 o
|
|
905
1016
|
planner s\xF3 prop\xF5e momentos em que a conta avan\xE7a no produto.
|
|
906
1017
|
`);
|
package/package.json
CHANGED