@xtrn/cli 1.1.0 → 1.1.2
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/cli.js +17 -17
- package/package.json +1 -1
- package/templates/index.ts.template +1 -1
package/dist/cli.js
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import{spawn as
|
|
3
|
-
export { default } from "${
|
|
4
|
-
`;
|
|
2
|
+
import{spawn as Y}from"node:child_process";import*as R from"node:fs";import*as y from"node:path";import{spawn as L}from"node:child_process";import*as d from"node:fs";import*as I from"node:os";import*as g from"node:path";function X(o){let t=o;while(!0){let n=g.join(t,"node_modules");if(d.existsSync(n))return n;let e=g.dirname(t);if(e===t)return null;t=e}}function O(o,t,n){let e=g.dirname(o),r=X(e);if(!r)console.warn("[xtrn] Warning: node_modules not found for symlink");let i=g.join(n,"node_modules");if(r&&!d.existsSync(i))d.symlinkSync(r,i,"junction");let l=`export { XTRNState } from "@xtrn/server";
|
|
3
|
+
export { default } from "${o}";
|
|
4
|
+
`;d.writeFileSync(g.join(n,"_cf_entry.ts"),l);let m=`name = "xtrn-dev"
|
|
5
5
|
main = "_cf_entry.ts"
|
|
6
6
|
compatibility_date = "2026-02-14"
|
|
7
7
|
compatibility_flags = ["nodejs_compat"]
|
|
8
8
|
|
|
9
9
|
[vars]
|
|
10
|
-
${Object.entries(
|
|
10
|
+
${Object.entries(t).map(([c,f])=>`${c} = "${f}"`).join(`
|
|
11
11
|
`)}
|
|
12
12
|
|
|
13
13
|
[[durable_objects.bindings]]
|
|
@@ -17,9 +17,9 @@ class_name = "XTRNState"
|
|
|
17
17
|
[[migrations]]
|
|
18
18
|
tag = "v1"
|
|
19
19
|
new_sqlite_classes = ["XTRNState"]
|
|
20
|
-
`;
|
|
21
|
-
export { default } from "${`./${
|
|
22
|
-
`,
|
|
20
|
+
`;d.writeFileSync(g.join(n,"wrangler.toml"),m)}async function P(o,t){let n=g.dirname(o),e=g.basename(o),r=g.join(t,e);d.copyFileSync(o,r);let i=X(n);if(!i)console.warn("[xtrn] Warning: node_modules not found for symlink");let l=g.join(t,"node_modules");if(i&&!d.existsSync(l))d.symlinkSync(i,l,"junction");let m=`export { XTRNState } from "@xtrn/server";
|
|
21
|
+
export { default } from "${`./${e}`}";
|
|
22
|
+
`,c=g.join(t,"_cf_entry.ts");d.writeFileSync(c,m);let f=`name = "xtrn-dev"
|
|
23
23
|
main = "_cf_entry.ts"
|
|
24
24
|
compatibility_date = "2026-02-14"
|
|
25
25
|
compatibility_flags = ["nodejs_compat"]
|
|
@@ -31,16 +31,16 @@ class_name = "XTRNState"
|
|
|
31
31
|
[[migrations]]
|
|
32
32
|
tag = "v1"
|
|
33
33
|
new_sqlite_classes = ["XTRNState"]
|
|
34
|
-
`;return
|
|
35
|
-
${r}`));return}
|
|
36
|
-
`)){let r=
|
|
37
|
-
${
|
|
38
|
-
`}await
|
|
39
|
-
`}async function
|
|
34
|
+
`;return d.writeFileSync(g.join(t,"wrangler.toml"),f),await Q(t),g.join(t,"_cf_entry.js")}function Q(o){return new Promise((t,n)=>{let e=L("wrangler",["deploy","--dry-run","--outdir","."],{cwd:o,stdio:["ignore","pipe","pipe"]}),r="";e.stdout?.on("data",()=>{}),e.stderr?.on("data",(i)=>{r+=i.toString()}),e.on("error",(i)=>{n(Error(`Failed to spawn wrangler: ${i.message}`))}),e.on("close",(i)=>{if(i!==0){n(Error(`wrangler deploy --dry-run failed (exit ${i}):
|
|
35
|
+
${r}`));return}t()})})}function S(){return d.mkdtempSync(g.join(I.tmpdir(),"xtrn-dev-"))}function v(o){d.rmSync(o,{recursive:!0,force:!0})}import*as _ from"node:fs";import{Miniflare as vt}from"miniflare";function q(o){if(!_.existsSync(o))return{};let t=_.readFileSync(o,"utf-8"),n={};for(let e of t.split(`
|
|
36
|
+
`)){let r=e.trim();if(!r||r.startsWith("#"))continue;let i=r.indexOf("=");if(i===-1)continue;n[r.slice(0,i)]=r.slice(i+1)}return n}var s={reset:"\x1B[0m",bold:"\x1B[1m",dim:"\x1B[2m",cyan:"\x1B[36m",green:"\x1B[32m",yellow:"\x1B[33m",magenta:"\x1B[35m",white:"\x1B[37m",red:"\x1B[31m",gray:"\x1B[90m"};function Z(o){let t="./index.ts",n=1234;for(let e of o)if(e.startsWith("--port="))n=Number.parseInt(e.slice(e.indexOf("=")+1),10);else if(!e.startsWith("--"))t=e;return{entryPoint:y.resolve(t),port:n}}async function K(o){for(let n=0;n<5;n++){try{let e=await fetch(`http://localhost:${o}/details`);if(e.ok)return await e.json()}catch{}await new Promise((e)=>setTimeout(e,200))}throw Error("Failed to fetch /details after server ready")}function k(o,t){let n=`http://localhost:${o}`;if(console.log(""),console.log(` ${s.bold}${s.cyan}${t.name}${s.reset} ${s.dim}${t.version}${s.reset}`),console.log(` ${s.green}➜${s.reset} ${s.bold}${n}${s.reset}`),console.log(""),t.tools.length>0){console.log(` ${s.bold}${s.white}Tools${s.reset}`);for(let e of t.tools){let r=e.tags.length>0?` ${s.dim}${e.tags.map((i)=>`[${i}]`).join(" ")}${s.reset}`:"";console.log(` ${s.cyan}${e.name}${s.reset}${r} ${s.gray}— ${e.description}${s.reset}`)}console.log("")}if(t.config.length>0){console.log(` ${s.bold}${s.white}Config${s.reset}`);for(let e of t.config)console.log(` ${s.yellow}${e.key}${s.reset} ${s.dim}${e.type}${s.reset}`);console.log("")}if(t.requiredEnv.length>0){console.log(` ${s.bold}${s.white}Required Env${s.reset}`);for(let e of t.requiredEnv)console.log(` ${s.yellow}${e}${s.reset}`);console.log("")}if(t.oauth)console.log(` ${s.bold}${s.white}OAuth${s.reset} ${s.magenta}${t.oauth.provider}${s.reset}`),console.log(` ${s.dim}authorize${s.reset} ${t.oauth.authorization_url}`),console.log(` ${s.dim}token${s.reset} ${t.oauth.token_url}`),console.log(` ${s.dim}callback${s.reset} ${t.oauth.callback_url}`),console.log(` ${s.dim}scopes${s.reset} ${t.oauth.scopes.join(", ")}`),console.log("")}function D(o,t){let n=y.join(o,"wrangler.toml"),e=Y("npx",["wrangler","dev","--config",n,"--port",String(t)],{cwd:o,stdio:["ignore","pipe","pipe"]}),r="";e.stderr?.on("data",(a)=>{r+=a.toString()});let i=new Promise((a,m)=>{let c=(f)=>{if(f.toString().includes("Ready on"))e.stdout?.off("data",c),e.stdout?.resume(),a()};e.stdout?.on("data",c),e.on("error",(f)=>m(f)),e.on("close",(f)=>{if(f!==0&&f!==null)m(Error(r.trim()||`wrangler exited with code ${f}`))})}),l=new Promise((a,m)=>{e.on("close",(c,f)=>{if(c!==0&&c!==null&&f===null)m(Error(r.trim()||`wrangler exited with code ${c}`));else a()})});return{proc:e,ready:i,done:l}}async function W(o){let{entryPoint:t,port:n}=Z(o),e=y.dirname(t);if(!R.existsSync(t))console.error(`${s.red}[xtrn dev]${s.reset} Entry point not found: ${t}`),process.exit(1);if(!R.existsSync(y.join(e,"package.json")))console.error(`${s.red}[xtrn dev]${s.reset} No package.json found in ${e}`),process.exit(1);let r=y.join(e,".dev.vars"),i=q(r),l=S();O(t,i,l);let a=null,m=()=>{if(console.log(`
|
|
37
|
+
${s.dim}[xtrn dev] Shutting down...${s.reset}`),a)a.kill(),a=null;v(l),process.exit(0)};process.on("SIGINT",m),process.on("SIGTERM",m);try{console.log(`${s.dim}[xtrn dev] Starting...${s.reset}`);let c=D(l,n);a=c.proc,await c.ready;let f=await K(n);k(n,f),await c.done,v(l),process.exit(0)}catch(c){if(a)a.kill();if(v(l),c instanceof Error)console.error(`${s.red}[xtrn dev]${s.reset} ${c.message}`);process.exit(1)}}import{mkdir as B,readFile as tt,writeFile as et}from"node:fs/promises";import{dirname as nt,join as j}from"node:path";import{fileURLToPath as ot}from"node:url";async function F(o){let t=o[0],n=o[1]??"v1.0.0";if(!t)console.error("Error: <name> argument is required"),console.error("Usage: xtrn new <name> [version]"),process.exit(1);let e=n.startsWith("v")?n:`v${n}`,r=e.replace(/^v/,"");if(!/^\d+\.\d+\.\d+$/.test(r))console.error(`Error: Invalid version "${n}". Expected semver (e.g., v1.0.0)`),process.exit(1);let i=ot(import.meta.url),l=nt(i),a=j(l,"../templates"),m=j(process.cwd(),t);try{await B(m,{recursive:!1})}catch(c){if(c instanceof Error&&"code"in c&&c.code==="EEXIST")console.error(`Error: Directory "${t}" already exists`),process.exit(1);throw c}try{let c=[{src:"index.ts.template",dst:"index.ts"},{src:"package.json.template",dst:"package.json"},{src:"tsconfig.json.template",dst:"tsconfig.json"},{src:"README.md.template",dst:"README.md"}];for(let f of c){let h=j(a,f.src),b=j(m,f.dst),x=await tt(h,"utf-8");if(x=x.replace(/\{\{NAME\}\}/g,t),x=x.replace(/\{\{VERSION\}\}/g,e),x=x.replace(/\{\{VERSION_BARE\}\}/g,r),f.dst==="package.json"){let w=JSON.parse(x);w.dependencies["@xtrn/server"]="^1.0.0",w.devDependencies["@xtrn/cli"]="^1.0.0",w.scripts.submit="xtrn submit",x=`${JSON.stringify(w,null,"\t")}
|
|
38
|
+
`}await et(b,x,"utf-8")}console.log(`✓ Created ${t}/`),console.log(""),console.log("Next steps:"),console.log(` cd ${t}`),console.log(" bun install"),console.log(" bun run dev")}catch(c){try{await B(m,{recursive:!0})}catch{}throw c}}import{spawn as rt}from"node:child_process";import*as u from"node:fs";import{cp as V,mkdir as z,readFile as st,readdir as it,writeFile as at}from"node:fs/promises";import*as A from"node:os";import*as p from"node:path";var N="xtrnai/servers",ct=new Set(["node_modules","bun.lock",".dev.vars","dist","_cf_entry.ts","wrangler.toml",".wrangler",".git",".github"]);function $(o,t,n){return new Promise((e)=>{let r=rt(o,t,{stdio:"pipe",cwd:n?.cwd}),i="",l="";r.stdout.on("data",(a)=>{i+=a.toString()}),r.stderr.on("data",(a)=>{l+=a.toString()}),r.on("close",(a)=>{e({stdout:i.trim(),stderr:l.trim(),code:a??1})}),r.on("error",()=>{e({stdout:"",stderr:`Failed to execute: ${o}`,code:1})})})}async function lt(){if((await $("gh",["--version"])).code!==0)console.error("[xtrn submit] GitHub CLI (gh) is not installed."),console.error(""),console.error("Install it:"),console.error(" macOS: brew install gh"),console.error(" Linux: https://github.com/cli/cli/blob/trunk/docs/install_linux.md"),console.error(" Windows: winget install --id GitHub.cli"),console.error(""),console.error("Then authenticate:"),console.error(" gh auth login"),process.exit(1);if((await $("gh",["auth","status"])).code!==0)console.error("[xtrn submit] GitHub CLI is not authenticated."),console.error(""),console.error("Run:"),console.error(" gh auth login"),process.exit(1)}function mt(o){if(!u.existsSync(p.join(o,"index.ts")))console.error("[xtrn submit] No index.ts found in current directory."),console.error("Run this command from your XTRN server project root."),process.exit(1);if(!u.existsSync(p.join(o,"package.json")))console.error("[xtrn submit] No package.json found in current directory."),console.error("Run this command from your XTRN server project root."),process.exit(1)}async function ft(o){let t=p.join(o,"package.json"),n=JSON.parse(u.readFileSync(t,"utf-8")),e=n.version;if(typeof e!=="string"||!e)throw Error("package.json missing 'version' field");let r=n.name;if(!r)throw Error("package.json missing 'name' field");let i=r.replace(/^@xtrn\//,""),l=`-v${e}`,a=i.endsWith(l)?i.slice(0,-l.length):i;if(!a)throw Error(`Could not parse server name from package name: ${r}`);let m=p.resolve(o,"index.ts"),c=S();try{console.log("[xtrn submit] Validating bundle..."),await P(m,c),console.log("[xtrn submit] Bundle validated successfully.")}finally{v(c)}return console.log(`[xtrn submit] Detected: ${a} v${e}`),{name:a,version:e}}async function U(o,t){await z(t,{recursive:!0});let n=await it(o,{withFileTypes:!0});for(let e of n){if(ct.has(e.name))continue;let r=p.join(o,e.name),i=p.join(t,e.name);if(e.isDirectory())await U(r,i);else await V(r,i)}}function dt(o){let t=JSON.parse(o),n=(e)=>{if(!e||typeof e!=="object")return;let r={};for(let[i,l]of Object.entries(e))if(typeof l==="string"&&(l.startsWith("file:")||l.startsWith("link:")||l.startsWith("workspace:")))r[i]="^1.0.0";else r[i]=l;return r};if(t.dependencies)t.dependencies=n(t.dependencies);if(t.devDependencies)t.devDependencies=n(t.devDependencies);return`${JSON.stringify(t,null,"\t")}
|
|
39
|
+
`}async function gt(o,t){console.log("[xtrn submit] Preparing files..."),await U(o,t);let n=p.join(t,"package.json");if(u.existsSync(n)){let e=await st(n,"utf-8"),r=dt(e);await at(n,r,"utf-8"),console.log("[xtrn submit] Rewrote package.json (local deps → ^1.0.0)")}}async function ut(o,t,n){let e=u.mkdtempSync(p.join(A.tmpdir(),"xtrn-submit-"));try{console.log(`[xtrn submit] Forking ${N}...`);let r=await $("gh",["repo","fork",N,"--clone=true","--default-branch-only"],{cwd:e});if(r.code!==0&&!r.stderr.includes("already exists"))throw Error(`Fork failed: ${r.stderr}`);let l=u.readdirSync(e).find((C)=>u.statSync(p.join(e,C)).isDirectory()&&u.existsSync(p.join(e,C,".git")));if(!l)throw Error(`No cloned repo found in ${e}. Output: ${r.stdout} ${r.stderr}`);let a=p.join(e,l),m=`submit/${o}/v${t}`;console.log(`[xtrn submit] Creating branch ${m}...`);let c=await $("git",["checkout","-b",m],{cwd:a});if(c.code!==0)throw Error(`Branch creation failed: ${c.stderr}`);let f=p.join(a,"servers",o,`v${t}`);await z(f,{recursive:!0}),await V(n,f,{recursive:!0}),console.log("[xtrn submit] Committing...");let h=await $("git",["add","."],{cwd:a});if(h.code!==0)throw Error(`git add failed: ${h.stderr}`);let b=await $("git",["commit","-m",`Add ${o} v${t}`],{cwd:a});if(b.code!==0)throw Error(`git commit failed: ${b.stderr}`);console.log("[xtrn submit] Pushing...");let x=await $("git",["push","--force","origin",m],{cwd:a});if(x.code!==0)throw Error(`git push failed: ${x.stderr}`);let M=(await $("git",["remote","get-url","origin"],{cwd:a})).stdout.match(/[:/]([^/]+)\/[^/]+?(?:\.git)?$/),J=M?`${M[1]}:${m}`:m;console.log("[xtrn submit] Creating pull request...");let T=await $("gh",["pr","create","--repo",N,"--head",J,"--title",`Add ${o} v${t}`,"--body",`Automated submission via \`xtrn submit\` CLI.
|
|
40
40
|
|
|
41
|
-
**Server:** ${
|
|
42
|
-
**Version:** v${
|
|
43
|
-
xtrn v${
|
|
41
|
+
**Server:** ${o}
|
|
42
|
+
**Version:** v${t}`],{cwd:a});if(T.code!==0)throw Error(`PR creation failed: ${T.stderr}`);return T.stdout}finally{u.rmSync(e,{recursive:!0,force:!0})}}async function G(o){let t=process.cwd();await lt(),mt(t);let{name:n,version:e}=await ft(t),r=u.mkdtempSync(p.join(A.tmpdir(),"xtrn-prepared-"));try{await gt(t,r);let i=await ut(n,e,r);console.log(""),console.log("✓ Pull request created!"),console.log(` ${i}`),console.log(""),console.log("Your server will be reviewed and deployed automatically once merged.")}finally{u.rmSync(r,{recursive:!0,force:!0})}}var H="1.0.0",E=`
|
|
43
|
+
xtrn v${H} — XTRN server development toolkit
|
|
44
44
|
|
|
45
45
|
Usage:
|
|
46
46
|
xtrn new <name> <version> Scaffold a new XTRN server project
|
|
@@ -63,4 +63,4 @@ Examples:
|
|
|
63
63
|
xtrn dev ./server.ts Start dev server with custom entry
|
|
64
64
|
xtrn dev --port=3000 Start dev server on port 3000
|
|
65
65
|
xtrn submit Fork xtrn-servers and open PR
|
|
66
|
-
`.trim();async function
|
|
66
|
+
`.trim();async function pt(){let o=process.argv.slice(2),t=o[0];if(!t||t==="--help"||t==="-h")console.log(E),process.exit(0);if(t==="--version"||t==="-v")console.log(`xtrn v${H}`),process.exit(0);if(t==="new"){let n=o.slice(1);if(n.includes("--help")||n.includes("-h"))console.log(E),process.exit(0);await F(n);return}if(t==="dev"){let n=o.slice(1);if(n.includes("--help")||n.includes("-h"))console.log(E),process.exit(0);await W(n);return}if(t==="submit"){let n=o.slice(1);if(n.includes("--help")||n.includes("-h"))console.log(E),process.exit(0);await G(n);return}console.error(`Unknown command: ${t}`),console.error('Run "xtrn --help" for usage.'),process.exit(1)}pt().catch((o)=>{console.error("[xtrn] Fatal:",o),process.exit(1)});
|
package/package.json
CHANGED