@contfu/cli 0.0.0 → 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/dist/main.js +66 -0
- package/package.json +21 -2
- package/index.js +0 -0
package/dist/main.js
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import{parseArgs as Lt}from"node:util";var K={CLIENT:0,WEB:1,NOTION:20,STRAPI:21,CONTENTFUL:22};var w={NULL:0,STRING:2,STRINGS:4,NUMBER:8,NUMBERS:16,BOOLEAN:32,REF:64,REFS:128,FILE:256,FILES:512,DATE:1024,ENUM:2048,ENUMS:4096};function S(t){return Array.isArray(t)?t[0]:t}function z(t){return Array.isArray(t)?t[1]:void 0}function b(t){return t[0].toUpperCase()+t.slice(1)}function R(t,o){if(o==="lookup")return t.map((n)=>`ContfuCollections["${n}"]`).join(" | ");return t.map(b).join(" | ")}function J(t,o,n="interface",f){switch(t){case w.STRING:return"string";case w.STRINGS:return"string[]";case w.NUMBER:return"number";case w.NUMBERS:return"number[]";case w.BOOLEAN:return"boolean";case w.REF:if(o&&o.length>0)return R(o,n);return"string";case w.REFS:if(o&&o.length>0){let i=R(o,n);return o.length>1?`(${i})[]`:`${i}[]`}return"string[]";case w.FILE:return"{ url: string; alt?: string }";case w.FILES:return"{ url: string; alt?: string }[]";case w.DATE:return"string";case w.ENUM:if(f&&f.length>0)return f.map((i)=>JSON.stringify(i)).join(" | ");return"string";case w.ENUMS:if(f&&f.length>0){let i=f.map((r)=>JSON.stringify(r)).join(" | ");return f.length>1?`(${i})[]`:`${i}[]`}return"string[]";default:return"unknown"}}function a(t,o,n,f,i){let r=[`${f}| {`];for(let[c,g]of Object.entries(t))r.push(`${f} ${c}: ${J(S(g),o?.[c],n,z(g))};`);return r.push(`${f} }${i?";":""}`),r}function v(t){let o=new Set;return t.filter((n)=>{let f=JSON.stringify(Object.entries(n).sort(([i],[r])=>i.localeCompare(r)));if(o.has(f))return!1;return o.add(f),!0})}function G(t){let o=["// Auto-generated by Contfu — do not edit",""];o.push("export type ContfuCollections = {");for(let n of t){o.push(` /** ${n.displayName} */`);let f=n.inflowSchemas?v(n.inflowSchemas):[];if(f.length>=2){o.push(` ${n.name}:`);for(let i=0;i<f.length;i++)o.push(...a(f[i],n.refTargets,"lookup"," ",i===f.length-1))}else{o.push(` ${n.name}: {`);for(let[i,r]of Object.entries(n.schema))o.push(` ${i}: ${J(S(r),n.refTargets?.[i],"lookup",z(r))};`);o.push(" };")}}return o.push("};"),o.push(""),o.join(`
|
|
3
|
+
`)}import{readFileSync as e}from"node:fs";import{join as tt}from"node:path";import{homedir as nt}from"node:os";function ot(){if(process.env.CONTFU_API_KEY)return process.env.CONTFU_API_KEY;try{let t=tt(nt(),".config","contfu","config.json");return JSON.parse(e(t,"utf-8")).apiKey}catch{return}}function it(){return process.env.CONTFU_URL??"https://contfu.com"}function ft(t){let o=t.trim();if(!o)return null;try{let n=JSON.parse(o);if(typeof n.message==="string"&&n.message.trim())return n.message.trim()}catch{}return o}async function C(t,o){let n=ot();if(!n)console.error("No API key configured. Set CONTFU_API_KEY or create ~/.config/contfu/config.json"),process.exit(1);let f=`${it()}${t}`,i=new Headers(o?.headers);i.set("Authorization",`Bearer ${n}`);let r=await fetch(f,{...o,headers:i});if(!r.ok){let c=await r.text(),g=ft(c);if(r.status===403){if(g&&g!=="Insufficient permissions")console.error(g);else console.error("Insufficient permissions. Your API key does not have the required scope for this action.");process.exit(1)}if(r.status===429)console.error("Rate limit exceeded. Please slow down and try again."),process.exit(1);console.error(`Error ${r.status}: ${g??c}`),process.exit(1)}return r}var rt=["connections","collections","flows"];function W(t){return rt.includes(t)}var ct={connections:["name"],collections:["display-name"],flows:["source-id","target-id"]};function X(t,o,n){if(o==="create"){let i=ct[t].filter((r)=>n[r]===void 0);if(i.length>0)console.error(`Missing required flag(s): ${i.map((r)=>`--${r}`).join(", ")}`),process.exit(1)}if(t==="connections"){let i={};if(n.name!==void 0)i.label=n.name;if(n.type!==void 0)i.providerId=n.type;else if(o==="create")i.providerId="notion";if(n.token!==void 0)i.token=n.token;return i}if(t==="flows"){let i={};if(n["source-id"]!==void 0)i.sourceId=Number(n["source-id"]);if(n["target-id"]!==void 0)i.targetId=Number(n["target-id"]);if(n["include-ref"]===!0)i.includeRef=!0;if(n["no-include-ref"]===!0)i.includeRef=!1;return i}let f={};if(n.name!==void 0)f.name=n.name;if(n["display-name"]!==void 0)f.displayName=n["display-name"];if(n["include-ref"]===!0)f.includeRef=!0;if(n["no-include-ref"]===!0)f.includeRef=!1;return f}var gt=Object.fromEntries(Object.entries(w).map(([t,o])=>[o,t.toLowerCase()])),wt=Object.fromEntries(Object.entries(w).map(([t,o])=>[t.toLowerCase(),o]));function H(t){if(t===null||typeof t!=="object")return t;if(Array.isArray(t))return t.map(H);let o={};for(let[n,f]of Object.entries(t))if(n==="schema"&&f!==null&&typeof f==="object"&&!Array.isArray(f))o[n]=Object.fromEntries(Object.entries(f).map(([i,r])=>[i,typeof r==="string"?wt[r]??r:r]));else o[n]=H(f);return o}function U(t){if(t===null||typeof t!=="object")return t;if(Array.isArray(t))return t.map(U);let o={};for(let[n,f]of Object.entries(t))if(n==="schema"&&f!==null&&typeof f==="object"&&!Array.isArray(f))o[n]=Object.fromEntries(Object.entries(f).map(([i,r])=>{let c=S(r),g=gt[c]??c,$=Array.isArray(r)?r[1]:void 0;return[i,$&&$.length>0?`${g}(${$.join("|")})`:g]}));else o[n]=U(f);return o}function x(t){console.log(JSON.stringify(U(t),null,2))}var $t={connections:[{key:"id",header:"ID"},{key:"label",header:"Label"},{key:"providerId",header:"Provider"},{key:"accountId",header:"Account"},{key:"hasCredentials",header:"Credentials",format:(t)=>t?"yes":"no"}],collections:[{key:"id",header:"ID"},{key:"name",header:"Name"},{key:"displayName",header:"Display Name"},{key:"connectionId",header:"Connection"}],flows:[{key:"id",header:"ID"},{key:"sourceId",header:"Source"},{key:"targetId",header:"Target"},{key:"includeRef",header:"Ref",format:(t)=>t?"yes":"no"}]};function Ct(t,o){if(t.length===0){console.log("(none)");return}let n=(i,r)=>i.format?i.format(r[i.key]):String(r[i.key]??""),f=o.map((i)=>Math.max(i.header.length,...t.map((r)=>n(i,r).length)));console.log(o.map((i,r)=>i.header.padEnd(f[r])).join(" ")),console.log(f.map((i)=>"-".repeat(i)).join(" "));for(let i of t)console.log(o.map((r,c)=>n(r,i).padEnd(f[c])).join(" "))}async function Z(t,o){let f=await(await C(`/api/v1/${t}`)).json();if(o==="json")x(f);else Ct(f,$t[t])}async function B(t,o){let f=await(await C(`/api/v1/${t}/${o}`)).json();x(f)}async function Q(t,o,n){let f=o?H(JSON.parse(o)):X(t,"create",n),r=await(await C(`/api/v1/${t}`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(f)})).json();x(r)}async function _(t,o,n,f){let i=n?H(JSON.parse(n)):X(t,"update",f),c=await(await C(`/api/v1/${t}/${o}`,{method:"PATCH",headers:{"Content-Type":"application/json"},body:JSON.stringify(i)})).json();x(c)}async function q(t,o){await C(`/api/v1/${t}/${o}`,{method:"DELETE"}),console.log(`Deleted ${t.slice(0,-1)} ${o}`)}function A(){let t=Object.entries(K),o=t.filter(([,f])=>f<20).map(([f])=>f.toLowerCase()).sort(),n=t.filter(([,f])=>f>=20).map(([f])=>f.toLowerCase()).sort();if(o.length)process.stdout.write(o.join(`
|
|
4
|
+
`)+`
|
|
5
|
+
`);if(o.length&&n.length)process.stdout.write(`
|
|
6
|
+
`);if(n.length)process.stdout.write(n.join(`
|
|
7
|
+
`)+`
|
|
8
|
+
`)}async function Y(t){let n=await(await C(`/api/v1/connections/${t}/types`)).json();if(n.length===0)console.error("No collections connected to this connection"),process.exit(1);process.stdout.write(G(n))}async function F(t){let n=await(await C(`/api/v1/collections/${t}/types`)).json();if(n.length===0)console.error("No types found for this collection"),process.exit(1);process.stdout.write(G(n))}import{parseArgs as h}from"node:util";async function M(t,o){let n=`${t}${o}`,f=await fetch(n);if(!f.ok){let i=await f.text();console.error(`Error ${f.status}: ${i}`),process.exit(1)}return f}function E(t){let o=Object.entries(t).filter((n)=>n[1]!==void 0&&n[1]!=="");if(o.length===0)return"";return"?"+new URLSearchParams(o).toString()}async function D(t){let{values:o}=h({args:t,options:{"client-url":{type:"string",short:"u"},collection:{type:"string"},filter:{type:"string"},sort:{type:"string"},limit:{type:"string",default:"20"},offset:{type:"string",default:"0"},include:{type:"string"},fields:{type:"string"},flat:{type:"boolean",default:!1}},allowPositionals:!0}),n=o["client-url"];if(!n)console.error("Missing required --client-url flag"),process.exit(1);let f=o.collection?`/api/collections/${o.collection}/items`:"/api/items",i=E({filter:o.filter,sort:o.sort,limit:o.limit,offset:o.offset,include:o.include,fields:o.fields,flat:o.flat?"true":void 0}),c=await(await M(n,`${f}${i}`)).json();console.log(JSON.stringify(c,null,2))}async function d(t){let{values:o}=h({args:t,options:{"client-url":{type:"string",short:"u"},collection:{type:"string"},filter:{type:"string"}},allowPositionals:!0}),n=o["client-url"];if(!n)console.error("Missing required --client-url flag"),process.exit(1);let f=o.collection?`/api/collections/${o.collection}/items`:"/api/items",i=E({filter:o.filter,limit:"0"}),c=await(await M(n,`${f}${i}`)).json();console.log(c?.meta?.total??0)}import{createServer as s}from"node:http";import{randomBytes as Nt}from"node:crypto";import{promises as I}from"node:fs";import{join as V}from"node:path";import{homedir as j}from"node:os";import{spawn as pt}from"node:child_process";var T=V(j(),".config","contfu","config.json");function Ot(){return process.env.CONTFU_URL??"https://contfu.com"}async function St(t){let o=process.platform,[n,f]=o==="darwin"?["open",[t]]:o==="win32"?["cmd",["/c","start","",t]]:["xdg-open",[t]];await new Promise((i,r)=>{let c=pt(n,f,{stdio:"ignore",detached:!0});c.on("error",r),c.on("close",(g)=>g===0?i():r(Error(`exit ${g}`)))})}function Ht(){return new Promise((t,o)=>{let n=s();n.listen(0,"127.0.0.1",()=>{let f=n.address();n.close((i)=>{if(i)o(i);else t(f.port)})})})}function xt(){return!!(process.env.SSH_CONNECTION||process.env.SSH_CLIENT||process.env.SSH_TTY)}async function Gt(t){let o=await Ht(),n=Nt(16).toString("hex"),f=`http://localhost:${o}/callback`,i=`${t}/auth/cli?callback=${encodeURIComponent(f)}&state=${encodeURIComponent(n)}`,r=new Promise((c,g)=>{let $=setTimeout(()=>{p.close(),g(Error("Login timed out after 5 minutes"))},300000),p=s((u,N)=>{try{let O=new URL(u.url??"/",`http://localhost:${o}`);if(O.pathname!=="/callback"){N.writeHead(404),N.end("Not found");return}let P=O.searchParams.get("token");if(O.searchParams.get("state")!==n){N.writeHead(400),N.end("State mismatch"),clearTimeout($),p.close(),g(Error("State mismatch — possible CSRF"));return}if(!P){N.writeHead(400),N.end("No token received"),clearTimeout($),p.close(),g(Error("No token in callback"));return}N.writeHead(302,{Location:`${t}/auth/cli/success`}),N.end(),clearTimeout($),p.close(),c(P)}catch(O){N.writeHead(500),N.end("Internal error"),clearTimeout($),p.close(),g(O)}});p.listen(o,"127.0.0.1")});console.log(`Opening browser to ${i}`);try{await St(i)}catch{console.log(`Could not open browser automatically. Please visit:
|
|
9
|
+
${i}`)}return r}async function Ut(t){let o=`${t}/auth/cli?mode=code`;console.log(`Open this URL in your browser:
|
|
10
|
+
|
|
11
|
+
${o}
|
|
12
|
+
`),process.stdout.write("Paste the code from the browser: ");let n=await new Promise((r)=>{process.stdin.resume(),process.stdin.setEncoding("utf-8");let c="";process.stdin.on("data",(g)=>{c+=g;let $=c.split(`
|
|
13
|
+
`)[0].trim();if($)process.stdin.pause(),r($)})}),f=await fetch(`${t}/auth/cli/exchange?code=${encodeURIComponent(n)}`);if(!f.ok)throw Error(`Invalid or expired code (${f.status})`);let{token:i}=await f.json();return i}async function m(t={}){let o=Ot(),f=t.noBrowser||xt()?await Ut(o):await Gt(o);await k({apiKey:f,baseUrl:o}),console.log("Logged in successfully")}async function y(){try{let t=await It();delete t.apiKey,await k(t),console.log("Logged out")}catch{console.log("Logged out (no config found)")}}async function It(){try{let t=await I.readFile(T,"utf-8");return JSON.parse(t)}catch{return{}}}async function k(t){let o=V(j(),".config","contfu");await I.mkdir(o,{recursive:!0}),await I.writeFile(T,JSON.stringify(t,null,2)+`
|
|
14
|
+
`,"utf-8")}async function l(t="table"){let n=await(await C("/api/v1/status")).json();if(t==="json"){console.log(JSON.stringify(n,null,2));return}console.log("contfu status"),console.log("-------------"),console.log(`connections ${n.connections}`),console.log(`collections ${n.collections}`),console.log(`flows ${n.flows}`)}function L(){console.error(`Usage: contfu [--help] <command> [args...]
|
|
15
|
+
|
|
16
|
+
Commands:
|
|
17
|
+
login [--no-browser] Authenticate
|
|
18
|
+
logout Clear stored credentials
|
|
19
|
+
status Show resource summary
|
|
20
|
+
<resource> list List all items
|
|
21
|
+
<resource> get <id> Get item by ID
|
|
22
|
+
<resource> create [options] Create item
|
|
23
|
+
<resource> update <id> [options] Update item
|
|
24
|
+
<resource> delete <id> Delete item
|
|
25
|
+
connections types List valid connection types
|
|
26
|
+
connections types <id> Print TypeScript types for a connection's collections
|
|
27
|
+
collections types <id> Print TypeScript types for a collection
|
|
28
|
+
items query [options] Query items from client app
|
|
29
|
+
items count [options] Count items from client app
|
|
30
|
+
|
|
31
|
+
Resources: connections, collections, flows
|
|
32
|
+
|
|
33
|
+
collections options:
|
|
34
|
+
--display-name <name> Display name (required for create)
|
|
35
|
+
-n, --name <name> Name
|
|
36
|
+
--[no-]include-ref Include ref transmission
|
|
37
|
+
-d, --data <json> Raw JSON body (alternative to above flags)
|
|
38
|
+
|
|
39
|
+
connections options:
|
|
40
|
+
-n, --name <name> Label (required for create)
|
|
41
|
+
-t, --type <provider> Provider ID (default: notion)
|
|
42
|
+
--token <token> API token (for manual token-based connections)
|
|
43
|
+
-d, --data <json> Raw JSON body (alternative to above flags)
|
|
44
|
+
|
|
45
|
+
flows options:
|
|
46
|
+
--source-id <id> Source collection ID (required for create)
|
|
47
|
+
--target-id <id> Target collection ID (required for create)
|
|
48
|
+
--[no-]include-ref Include ref transmission
|
|
49
|
+
-d, --data <json> Raw JSON body (alternative to above flags)
|
|
50
|
+
|
|
51
|
+
items options:
|
|
52
|
+
-u, --client-url <url> Base URL of the client HTTP server (required)
|
|
53
|
+
--collection <name> Filter by collection
|
|
54
|
+
--filter <expr> Filter expression
|
|
55
|
+
--sort <fields> Sort fields, comma-separated (query only)
|
|
56
|
+
--limit <n> Limit results (query only, default 20)
|
|
57
|
+
--offset <n> Offset results (query only, default 0)
|
|
58
|
+
--include <fields> Comma-separated includes (query only)
|
|
59
|
+
--fields <fields> Comma-separated field selection (query only)
|
|
60
|
+
--flat Flatten nested props (query only)
|
|
61
|
+
|
|
62
|
+
list options:
|
|
63
|
+
-f, --format <fmt> Output format: table (default) | json
|
|
64
|
+
|
|
65
|
+
Environment:
|
|
66
|
+
CONTFU_API_KEY API key (overrides stored config)`)}async function Pt(){let{values:t,positionals:o}=Lt({args:process.argv.slice(2),options:{help:{type:"boolean",short:"h"},data:{type:"string",short:"d"},"no-browser":{type:"boolean"},name:{type:"string",short:"n"},type:{type:"string",short:"t"},url:{type:"string"},"display-name":{type:"string"},"source-id":{type:"string"},"target-id":{type:"string"},"collection-id":{type:"string"},"include-ref":{type:"boolean"},"no-include-ref":{type:"boolean"},token:{type:"string"},format:{type:"string",short:"f"}},allowPositionals:!0,strict:!1}),n=o[0];if(t.help)L(),process.exit(0);if(!n)L(),process.exit(1);if(n==="login"){await m({noBrowser:t["no-browser"]});return}if(n==="logout"){await y();return}if(n==="status"){await l(t.format??"table");return}if(n==="items"){let f=o[1],i=process.argv.slice(process.argv.indexOf("items")+2);switch(f){case"query":case void 0:await D(i);return;case"count":await d(i);return;default:console.error(`Unknown items action: ${f}. Use query or count`),process.exit(1)}}if(W(n)){let f=o[1],i=o[2];if(f==="types"){if(n==="connections"){if(!i)A();else await Y(i);return}if(n==="collections"){if(!i)console.error("Missing id"),process.exit(1);await F(i);return}console.error(`'types' is not available for ${n}`),process.exit(1)}let r={name:t.name,type:t.type,url:t.url,"display-name":t["display-name"],"source-id":t["source-id"],"target-id":t["target-id"],"collection-id":t["collection-id"],"include-ref":t["include-ref"],"no-include-ref":t["no-include-ref"],token:t.token};switch(f){case"list":case void 0:await Z(n,t.format??"table");return;case"get":if(!i)console.error("Missing id"),process.exit(1);await B(n,i);return;case"create":await Q(n,t.data,r);return;case"update":case"set":if(!i)console.error("Missing id"),process.exit(1);await _(n,i,t.data,r);return;case"delete":if(!i)console.error("Missing id"),process.exit(1);await q(n,i);return;default:console.error(`Unknown action: ${f}. Use list, get, create, update, or delete`),process.exit(1)}}console.error(`Unknown command: ${n}`),L(),process.exit(1)}Pt();
|
package/package.json
CHANGED
|
@@ -1,6 +1,25 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@contfu/cli",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"repository": {
|
|
5
|
+
"type": "git",
|
|
6
|
+
"url": "git+https://github.com/contfu/contfu.git"
|
|
7
|
+
},
|
|
8
|
+
"bin": {
|
|
9
|
+
"contfu": "dist/main.js"
|
|
10
|
+
},
|
|
11
|
+
"files": [
|
|
12
|
+
"dist"
|
|
13
|
+
],
|
|
4
14
|
"type": "module",
|
|
5
|
-
"
|
|
15
|
+
"scripts": {
|
|
16
|
+
"pack": "bun pm pack",
|
|
17
|
+
"build": "bun build src/main.ts --outdir dist --target node --minify",
|
|
18
|
+
"contfu": "bun src/main.ts",
|
|
19
|
+
"test": "bun test src/",
|
|
20
|
+
"test:e2e": "bun test e2e/"
|
|
21
|
+
},
|
|
22
|
+
"dependencies": {
|
|
23
|
+
"@contfu/core": "0.0.1"
|
|
24
|
+
}
|
|
6
25
|
}
|
package/index.js
DELETED
|
File without changes
|