@roxyon/deploy-core 0.1.1 → 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/dist/index.cjs CHANGED
@@ -35,17 +35,20 @@ __export(index_exports, {
35
35
  PROJECT_FILE: () => PROJECT_FILE,
36
36
  buildIgnore: () => buildIgnore,
37
37
  buildProjectConfig: () => buildProjectConfig,
38
+ buildWpConfig: () => buildWpConfig,
38
39
  clearCredentials: () => clearCredentials,
39
40
  credentialsDir: () => credentialsDir,
40
41
  credentialsPath: () => credentialsPath,
41
42
  deployProject: () => deployProject,
42
43
  detectRuntime: () => detectRuntime,
43
44
  endpointsFromEnv: () => endpointsFromEnv,
45
+ installWordPress: () => installWordPress,
44
46
  listFiles: () => listFiles,
45
47
  loadCredentials: () => loadCredentials,
46
48
  loadProjectConfig: () => loadProjectConfig,
47
49
  measure: () => measure,
48
50
  packDirectory: () => packDirectory,
51
+ packFiles: () => packFiles,
49
52
  projectConfigPath: () => projectConfigPath,
50
53
  runShellCommand: () => runShellCommand,
51
54
  saveCredentials: () => saveCredentials,
@@ -56,6 +59,7 @@ module.exports = __toCommonJS(index_exports);
56
59
 
57
60
  // src/archive.ts
58
61
  var import_promises = require("fs/promises");
62
+ var import_node_os = require("os");
59
63
  var import_node_path = require("path");
60
64
  var import_ignore = __toESM(require("ignore"), 1);
61
65
  var import_tar = require("tar");
@@ -136,6 +140,44 @@ async function packDirectory(root, opts = {}) {
136
140
  }
137
141
  return { buffer: Buffer.concat(chunks), files, bytes };
138
142
  }
143
+ function safeEntryPath(p) {
144
+ const norm = p.replace(/\\/g, "/").replace(/^\.\//, "");
145
+ if (!norm || norm.startsWith("/") || (0, import_node_path.isAbsolute)(norm) || norm.includes("\0")) return null;
146
+ if (norm.split("/").some((seg) => seg === ".." || seg === "." || seg === "")) return null;
147
+ return norm;
148
+ }
149
+ async function packFiles(entries, opts = {}) {
150
+ if (entries.length === 0) throw new Error("No files to deploy.");
151
+ const seen = /* @__PURE__ */ new Set();
152
+ const clean = [];
153
+ let total = 0;
154
+ for (const e of entries) {
155
+ const rel = safeEntryPath(e.path);
156
+ if (!rel) throw new Error(`Unsafe file path: ${JSON.stringify(e.path)}`);
157
+ if (seen.has(rel)) throw new Error(`Duplicate file path: ${rel}`);
158
+ seen.add(rel);
159
+ const buf = Buffer.from(e.content, e.encoding === "base64" ? "base64" : "utf8");
160
+ total += buf.length;
161
+ clean.push({ path: rel, buf });
162
+ }
163
+ const max = opts.maxBytes ?? 512 * 1024 * 1024;
164
+ if (total > max) {
165
+ throw new Error(
166
+ `Content is ${(total / 1024 / 1024).toFixed(1)} MB, which exceeds the ${(max / 1024 / 1024).toFixed(1)} MB limit.`
167
+ );
168
+ }
169
+ const dir = await (0, import_promises.mkdtemp)((0, import_node_path.join)((0, import_node_os.tmpdir)(), "roxyon-content-"));
170
+ try {
171
+ for (const { path: rel, buf } of clean) {
172
+ const abs = (0, import_node_path.join)(dir, rel);
173
+ await (0, import_promises.mkdir)((0, import_node_path.dirname)(abs), { recursive: true });
174
+ await (0, import_promises.writeFile)(abs, buf);
175
+ }
176
+ return await packDirectory(dir, opts);
177
+ } finally {
178
+ await (0, import_promises.rm)(dir, { recursive: true, force: true });
179
+ }
180
+ }
139
181
  async function measure(root, files) {
140
182
  let total = 0;
141
183
  for (const f of files) {
@@ -457,12 +499,57 @@ async function watch(roxyon, appId, opts) {
457
499
  };
458
500
  }
459
501
 
460
- // src/credentials.ts
502
+ // src/wordpress.ts
461
503
  var import_promises5 = require("fs/promises");
462
- var import_node_os = require("os");
504
+ var import_node_stream = require("stream");
505
+ var import_tar2 = require("tar");
506
+ var WORDPRESS_TARBALL_URL = "https://wordpress.org/latest.tar.gz";
507
+ var WORDPRESS_SALTS_URL = "https://api.wordpress.org/secret-key/1.1/salt/";
508
+ var PLACEHOLDER = "put your unique phrase here";
509
+ async function installWordPress(opts) {
510
+ const fetchImpl = opts.fetchImpl ?? globalThis.fetch;
511
+ await (0, import_promises5.mkdir)(opts.dir, { recursive: true });
512
+ const coreRes = await fetchImpl(WORDPRESS_TARBALL_URL);
513
+ if (!coreRes.ok) {
514
+ throw new Error(`Could not download WordPress core (HTTP ${coreRes.status}).`);
515
+ }
516
+ const coreBuf = Buffer.from(await coreRes.arrayBuffer());
517
+ await new Promise((resolvePromise, reject) => {
518
+ const writer = (0, import_tar2.extract)({ cwd: opts.dir, strip: 1 });
519
+ import_node_stream.Readable.from(coreBuf).pipe(writer).on("finish", () => resolvePromise()).on("error", reject);
520
+ });
521
+ const saltsRes = await fetchImpl(WORDPRESS_SALTS_URL);
522
+ const salts = saltsRes.ok ? (await saltsRes.text()).trim() : "";
523
+ const samplePath = `${opts.dir}/wp-config-sample.php`;
524
+ const sample = await (0, import_promises5.readFile)(samplePath, "utf8");
525
+ const config = buildWpConfig(sample, opts.db, opts.tablePrefix, salts);
526
+ await (0, import_promises5.writeFile)(`${opts.dir}/wp-config.php`, config, "utf8");
527
+ return { dir: opts.dir };
528
+ }
529
+ function buildWpConfig(sample, db, tablePrefix = "wp_", salts = "") {
530
+ let out = sample.replace(/(['"])database_name_here\1/, `$1${escapePhp(db.name)}$1`).replace(/(['"])username_here\1/, `$1${escapePhp(db.username)}$1`).replace(/(['"])password_here\1/, `$1${escapePhp(db.password)}$1`).replace(/(define\(\s*['"]DB_HOST['"]\s*,\s*)(['"])localhost\2/, `$1$2${escapePhp(db.host)}$2`).replace(/(\$table_prefix\s*=\s*)(['"])wp_\2/, `$1$2${escapePhp(tablePrefix)}$2`);
531
+ if (salts) {
532
+ const lines = out.split("\n");
533
+ const start = lines.findIndex((l) => l.includes(PLACEHOLDER));
534
+ if (start !== -1) {
535
+ let end = start;
536
+ while (end < lines.length && lines[end].includes(PLACEHOLDER)) end++;
537
+ lines.splice(start, end - start, salts);
538
+ out = lines.join("\n");
539
+ }
540
+ }
541
+ return out;
542
+ }
543
+ function escapePhp(s) {
544
+ return s.replace(/\\/g, "\\\\").replace(/'/g, "\\'");
545
+ }
546
+
547
+ // src/credentials.ts
548
+ var import_promises6 = require("fs/promises");
549
+ var import_node_os2 = require("os");
463
550
  var import_node_path5 = require("path");
464
551
  function credentialsDir() {
465
- return (0, import_node_path5.join)((0, import_node_os.homedir)(), ".roxyon");
552
+ return (0, import_node_path5.join)((0, import_node_os2.homedir)(), ".roxyon");
466
553
  }
467
554
  function credentialsPath() {
468
555
  return (0, import_node_path5.join)(credentialsDir(), "config.json");
@@ -478,21 +565,21 @@ function tokenFromEnv() {
478
565
  }
479
566
  async function loadCredentials() {
480
567
  try {
481
- return JSON.parse(await (0, import_promises5.readFile)(credentialsPath(), "utf8"));
568
+ return JSON.parse(await (0, import_promises6.readFile)(credentialsPath(), "utf8"));
482
569
  } catch {
483
570
  return void 0;
484
571
  }
485
572
  }
486
573
  async function saveCredentials(creds) {
487
574
  const path = credentialsPath();
488
- await (0, import_promises5.mkdir)(credentialsDir(), { recursive: true, mode: 448 });
489
- await (0, import_promises5.writeFile)(path, `${JSON.stringify(creds, null, 2)}
575
+ await (0, import_promises6.mkdir)(credentialsDir(), { recursive: true, mode: 448 });
576
+ await (0, import_promises6.writeFile)(path, `${JSON.stringify(creds, null, 2)}
490
577
  `, { mode: 384 });
491
- await (0, import_promises5.chmod)(path, 384).catch(() => void 0);
578
+ await (0, import_promises6.chmod)(path, 384).catch(() => void 0);
492
579
  return path;
493
580
  }
494
581
  async function clearCredentials() {
495
- await (0, import_promises5.rm)(credentialsPath(), { force: true });
582
+ await (0, import_promises6.rm)(credentialsPath(), { force: true });
496
583
  }
497
584
  // Annotate the CommonJS export names for ESM import in node:
498
585
  0 && (module.exports = {
@@ -501,17 +588,20 @@ async function clearCredentials() {
501
588
  PROJECT_FILE,
502
589
  buildIgnore,
503
590
  buildProjectConfig,
591
+ buildWpConfig,
504
592
  clearCredentials,
505
593
  credentialsDir,
506
594
  credentialsPath,
507
595
  deployProject,
508
596
  detectRuntime,
509
597
  endpointsFromEnv,
598
+ installWordPress,
510
599
  listFiles,
511
600
  loadCredentials,
512
601
  loadProjectConfig,
513
602
  measure,
514
603
  packDirectory,
604
+ packFiles,
515
605
  projectConfigPath,
516
606
  runShellCommand,
517
607
  saveCredentials,
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts","../src/archive.ts","../src/detect.ts","../src/project.ts","../src/deploy.ts","../src/run-command.ts","../src/credentials.ts"],"sourcesContent":["export {\n ALWAYS_IGNORE,\n buildIgnore,\n listFiles,\n measure,\n packDirectory,\n type PackResult,\n} from './archive.js';\nexport { type Detection, detectRuntime } from './detect.js';\nexport {\n buildProjectConfig,\n type BuildConfigInput,\n type DeployKind,\n loadProjectConfig,\n PROJECT_FILE,\n type ProjectConfig,\n projectConfigPath,\n saveProjectConfig,\n} from './project.js';\nexport {\n deployProject,\n DeployError,\n type DeployOutcome,\n type DeployParams,\n type DeployReporter,\n} from './deploy.js';\nexport { runShellCommand } from './run-command.js';\nexport {\n type Credentials,\n credentialsDir,\n credentialsPath,\n clearCredentials,\n type Endpoints,\n endpointsFromEnv,\n loadCredentials,\n saveCredentials,\n tokenFromEnv,\n} from './credentials.js';\n","import { readFile, readdir, stat } from 'node:fs/promises';\nimport { join, relative, sep } from 'node:path';\nimport ignore, { type Ignore } from 'ignore';\nimport { create as tarCreate } from 'tar';\n\n/** Always excluded, regardless of ignore files — build artefacts and VCS/OS cruft. */\nexport const ALWAYS_IGNORE = [\n 'node_modules',\n '.git',\n '.next',\n '.nuxt',\n '.venv',\n '__pycache__',\n '.DS_Store',\n '.roxyon',\n 'dist/.cache',\n];\n\nasync function readIgnoreFile(root: string, name: string): Promise<string[]> {\n try {\n return (await readFile(join(root, name), 'utf8')).split(/\\r?\\n/);\n } catch {\n return [];\n }\n}\n\n/** Build the ignore matcher: `.roxyonignore` if present, else `.gitignore`, plus the always-list. */\nexport async function buildIgnore(root: string): Promise<Ignore> {\n const ig = ignore().add(ALWAYS_IGNORE);\n const roxyon = await readIgnoreFile(root, '.roxyonignore');\n if (roxyon.length) ig.add(roxyon);\n else ig.add(await readIgnoreFile(root, '.gitignore'));\n return ig;\n}\n\n/** Recursively list files under `root` that survive the ignore matcher (POSIX-relative paths). */\nexport async function listFiles(root: string, ig: Ignore): Promise<string[]> {\n const files: string[] = [];\n\n async function walk(dir: string): Promise<void> {\n const entries = await readdir(dir, { withFileTypes: true });\n for (const entry of entries) {\n const abs = join(dir, entry.name);\n const rel = relative(root, abs).split(sep).join('/');\n if (!rel || ig.ignores(rel) || ig.ignores(`${rel}/`)) continue;\n if (entry.isDirectory()) {\n await walk(abs);\n } else if (entry.isFile() || entry.isSymbolicLink()) {\n files.push(rel);\n }\n }\n }\n\n await walk(root);\n return files.sort();\n}\n\nexport interface PackResult {\n buffer: Buffer;\n files: string[];\n bytes: number;\n}\n\n/**\n * Create a deterministic gzipped tarball of `root` (minus ignored paths).\n * `prefix`, when given, is prepended to every entry path inside the archive.\n */\nexport async function packDirectory(\n root: string,\n opts: { prefix?: string; maxBytes?: number } = {},\n): Promise<PackResult> {\n const ig = await buildIgnore(root);\n const files = await listFiles(root, ig);\n if (files.length === 0) {\n throw new Error(\n `Nothing to deploy from ${root} — every file is ignored or the folder is empty.`,\n );\n }\n\n const stream = tarCreate(\n {\n gzip: true,\n cwd: root,\n portable: true,\n mtime: new Date(0), // stable -> byte-identical archive for an unchanged tree\n prefix: opts.prefix,\n follow: false,\n },\n files,\n );\n\n const chunks: Buffer[] = [];\n let bytes = 0;\n const max = opts.maxBytes ?? 512 * 1024 * 1024;\n for await (const chunk of stream as AsyncIterable<Buffer>) {\n bytes += chunk.length;\n if (bytes > max) {\n throw new Error(\n `Deploy archive exceeds ${(max / 1024 / 1024) | 0} MB. Add large files to .roxyonignore.`,\n );\n }\n chunks.push(chunk);\n }\n\n return { buffer: Buffer.concat(chunks), files, bytes };\n}\n\n/** Total on-disk size of a set of files. */\nexport async function measure(root: string, files: string[]): Promise<number> {\n let total = 0;\n for (const f of files) {\n try {\n total += (await stat(join(root, f))).size;\n } catch {\n /* ignore */\n }\n }\n return total;\n}\n","import { readFile, readdir } from 'node:fs/promises';\nimport { join } from 'node:path';\nimport type { RuntimeName } from '@roxyon/api-client';\n\nexport interface Detection {\n runtime: RuntimeName;\n /** why we think so — shown to the user, who can override */\n reason: string;\n /** best-guess start command for a server runtime */\n start?: string;\n}\n\nasync function exists(root: string, name: string): Promise<boolean> {\n try {\n await readFile(join(root, name));\n return true;\n } catch {\n return false;\n }\n}\n\nasync function readJson(root: string, name: string): Promise<Record<string, unknown> | undefined> {\n try {\n return JSON.parse(await readFile(join(root, name), 'utf8')) as Record<string, unknown>;\n } catch {\n return undefined;\n }\n}\n\n/** Best-effort project-type detection. Deterministic; the caller confirms. */\nexport async function detectRuntime(root: string): Promise<Detection> {\n const pkg = await readJson(root, 'package.json');\n const hasConfigJson = await exists(root, 'config.json');\n let hasViews = false;\n try {\n hasViews = (await readdir(join(root, 'src', 'views'))).length >= 0;\n } catch {\n hasViews = false;\n }\n\n if (hasConfigJson && hasViews) {\n const deps = {\n ...(pkg?.dependencies as object),\n ...(pkg?.devDependencies as object),\n } as Record<string, string>;\n if (!('next' in deps) && !('nuxt' in deps) && !('express' in deps) && !('fastify' in deps)) {\n return { runtime: 'lumen', reason: 'config.json + src/views/ and no server framework' };\n }\n }\n\n if (pkg) {\n const scripts = (pkg.scripts as Record<string, string>) ?? {};\n const start = scripts.start ? 'npm run start' : scripts.serve ? 'npm run serve' : 'node .';\n const deps = { ...(pkg.dependencies as object), ...(pkg.devDependencies as object) } as Record<\n string,\n string\n >;\n if ('next' in deps)\n return { runtime: 'node', reason: 'Next.js in package.json', start: 'npm run start' };\n if ('nuxt' in deps)\n return { runtime: 'node', reason: 'Nuxt in package.json', start: 'npm run start' };\n return { runtime: 'node', reason: 'package.json present', start };\n }\n\n if ((await exists(root, 'requirements.txt')) || (await exists(root, 'pyproject.toml'))) {\n return {\n runtime: 'python',\n reason: 'requirements.txt / pyproject.toml',\n start: 'gunicorn app:app',\n };\n }\n\n if ((await exists(root, 'composer.json')) || (await exists(root, 'server.php'))) {\n return { runtime: 'php', reason: 'composer.json / server.php', start: 'php8.4 server.php' };\n }\n\n return { runtime: 'lumen', reason: 'no framework markers — treating as a static site' };\n}\n","import { mkdir, readFile, writeFile } from 'node:fs/promises';\nimport { basename, dirname, resolve } from 'node:path';\nimport { type DeployKind, LUMEN_BUILD, RUNTIMES, type RuntimeName } from '@roxyon/api-client';\n\nexport type { DeployKind };\n\nexport interface ProjectConfig {\n name: string;\n /** `Applications.objectId`, filled after the first `app`-kind deploy. */\n application?: string;\n /** Route host — `Domains.Name`. */\n host: string;\n /** Sub-path under the host's docroot / source tree. `\"\"` = root. */\n folder: string;\n runtime: RuntimeName;\n runtimeVersion?: string;\n preset?: string;\n kind: DeployKind;\n /** Local build command (LumenJS / static). Empty for server runtimes. */\n build?: string;\n /** Directory the build writes to (relative to project root). */\n outDir?: string;\n /** Web-process start command (app kind only). */\n start?: string;\n /** Serve the app on the host instead of the host's own files. */\n public?: boolean;\n}\n\nexport const PROJECT_FILE = 'roxyon.json';\n\nexport function projectConfigPath(cwd: string = process.cwd()): string {\n return resolve(cwd, PROJECT_FILE);\n}\n\nexport async function loadProjectConfig(\n cwd: string = process.cwd(),\n): Promise<ProjectConfig | undefined> {\n try {\n return JSON.parse(await readFile(projectConfigPath(cwd), 'utf8')) as ProjectConfig;\n } catch {\n return undefined;\n }\n}\n\nexport async function saveProjectConfig(\n config: ProjectConfig,\n cwd: string = process.cwd(),\n): Promise<string> {\n const path = projectConfigPath(cwd);\n await mkdir(dirname(path), { recursive: true });\n await writeFile(path, `${JSON.stringify(config, null, 2)}\\n`);\n return path;\n}\n\nexport interface BuildConfigInput {\n cwd: string;\n runtime: RuntimeName;\n host: string;\n folder?: string;\n name?: string;\n start?: string;\n}\n\n/**\n * Produce a `ProjectConfig` from a resolved runtime + host. Shared by the CLI's\n * `roxyon init` and the MCP `roxyon_init` tool so both write the same shape.\n */\nexport function buildProjectConfig(input: BuildConfigInput): ProjectConfig {\n const folder = String(input.folder ?? '').replace(/^\\/+|\\/+$/g, '');\n const config: ProjectConfig = {\n name: input.name ?? basename(input.cwd),\n host: input.host,\n folder,\n runtime: input.runtime,\n kind: input.runtime === 'lumen' ? 'static' : 'app',\n };\n\n if (input.runtime === 'lumen') {\n config.build = LUMEN_BUILD.command;\n config.outDir = LUMEN_BUILD.outDir;\n } else {\n const spec = RUNTIMES[input.runtime];\n config.runtimeVersion = spec.defaultVersion;\n config.preset = spec.presets[0]?.[0];\n config.start = input.start ?? spec.command;\n config.public = true;\n config.build = '';\n }\n return config;\n}\n","import { access } from 'node:fs/promises';\nimport { resolve } from 'node:path';\nimport {\n type AccountApp,\n RUNTIMES,\n type Roxyon,\n RoxyonApiError,\n isAuthError,\n} from '@roxyon/api-client';\nimport { packDirectory } from './archive.js';\nimport { type ProjectConfig, loadProjectConfig, saveProjectConfig } from './project.js';\nimport { runShellCommand } from './run-command.js';\n\nexport interface DeployReporter {\n /** A high-level phase change (\"Building\", \"Uploading\", \"Live\"). */\n step?: (msg: string) => void;\n /** A single build- or runtime-log line. */\n log?: (line: string) => void;\n}\n\nexport interface DeployParams {\n cwd: string;\n /** An authenticated client. */\n roxyon: Roxyon;\n /** Subscription objectId / Name to prefer when the account has several. */\n preferredSubscription?: string;\n /** Run the configured build command (default true). */\n build?: boolean;\n /** For app deploys: poll until running/failed (default true). */\n follow?: boolean;\n pollMs?: number;\n timeoutMs?: number;\n reporter?: DeployReporter;\n /** Override the build-command runner (default: spawn a shell). */\n runCommand?: (command: string, cwd: string, reporter?: DeployReporter) => Promise<number>;\n}\n\nexport type DeployOutcome =\n | { kind: 'static'; ok: true; url: string; files: number; config: ProjectConfig }\n | {\n kind: 'app';\n ok: true;\n application: string;\n status: string;\n revision: number;\n url?: string;\n config: ProjectConfig;\n logs: string[];\n }\n | {\n kind: 'app';\n ok: false;\n application: string;\n status: string;\n error: string;\n config: ProjectConfig;\n logs: string[];\n };\n\nexport class DeployError extends Error {\n readonly code:\n | 'no-config'\n | 'auth'\n | 'bad-host'\n | 'build-failed'\n | 'no-build-output'\n | 'upload-failed'\n | 'create-failed';\n constructor(message: string, code: DeployError['code']) {\n super(message);\n this.name = 'DeployError';\n this.code = code;\n }\n}\n\nconst sleep = (ms: number) => new Promise((r) => setTimeout(r, ms));\n\n/**\n * Build (if configured), archive, upload, and — for app runtimes — poll until\n * the deploy settles. Self-contained: one `/account/context` call validates the\n * host (works with a session token or a `roxp_` PAT), and the console creates\n * the application on the first deploy — the new id is written back to\n * `roxyon.json`.\n */\nexport async function deployProject(params: DeployParams): Promise<DeployOutcome> {\n const { cwd, roxyon, reporter } = params;\n const runCommand = params.runCommand ?? runShellCommand;\n\n const config = await loadProjectConfig(cwd);\n if (!config) {\n throw new DeployError('No roxyon.json in this directory — run init first.', 'no-config');\n }\n\n // One call — works with a session token or a PAT.\n const ctx = await roxyon.account.context().catch((e) => {\n if (isAuthError(e)) throw new DeployError('Session expired — sign in again.', 'auth');\n throw e;\n });\n const domain = ctx.domains.find((d) => d.name === config.host);\n if (!domain) {\n throw new DeployError(\n `Host \"${config.host}\" is not on this account (have: ${\n ctx.domains.map((d) => d.name).join(', ') || 'none'\n }).`,\n 'bad-host',\n );\n }\n\n // ---- build ----\n if (config.build && params.build !== false) {\n reporter?.step?.(`Build: ${config.build}`);\n const code = await runCommand(config.build, cwd, reporter);\n if (code !== 0) throw new DeployError(`Build command exited ${code}.`, 'build-failed');\n }\n\n // ---- pack ----\n const artifactDir = config.kind === 'static' && config.outDir ? resolve(cwd, config.outDir) : cwd;\n if (config.kind === 'static' && config.outDir) {\n await access(artifactDir).catch(() => {\n throw new DeployError(\n `Build output \"${config.outDir}\" not found — did the build run?`,\n 'no-build-output',\n );\n });\n }\n reporter?.step?.(`Packing ${artifactDir === cwd ? '.' : config.outDir}`);\n const pack = await packDirectory(artifactDir);\n reporter?.log?.(`${pack.files.length} files · ${(pack.bytes / 1024).toFixed(0)} KB`);\n\n // ---- static ----\n if (config.kind === 'static') {\n reporter?.step?.(`Uploading to ${config.host}${config.folder ? `/${config.folder}` : ''}`);\n let res: Awaited<ReturnType<typeof roxyon.sites.deploy>>;\n try {\n res = await roxyon.sites.deploy(config.host, config.folder, pack.buffer);\n } catch (err) {\n throw new DeployError(\n err instanceof RoxyonApiError ? err.message : String(err),\n 'upload-failed',\n );\n }\n const url = `https://${res.host}${res.path && res.path !== '/' ? res.path : ''}`;\n reporter?.step?.(`Live — ${url}`);\n return { kind: 'static', ok: true, url, files: res.files ?? pack.files.length, config };\n }\n\n // ---- app ----\n // The console's /applications/deploy creates the app rows on the first deploy\n // (no `application` id yet) from the host + runtime params — one trusted call,\n // so a PAT-only CI job needs no BaaS write access.\n const runtime = config.runtime === 'lumen' ? 'node' : config.runtime;\n const spec = RUNTIMES[runtime];\n const target = config.application ?? {\n host: config.host,\n folder: config.folder,\n runtime,\n runtimeVersion: config.runtimeVersion ?? spec.defaultVersion,\n preset: config.preset ?? spec.presets[0]?.[0] ?? runtime,\n command: config.start ?? spec.command,\n public: config.public ?? true,\n };\n\n reporter?.step?.(\n config.application ? 'Uploading source' : 'Creating the application & uploading',\n );\n let revision = 0;\n let appId: string;\n try {\n const res = await roxyon.applications.uploadSource(target, pack.buffer);\n revision = res.configRevision ?? 0;\n appId = res.application;\n if (res.created && appId && appId !== config.application) {\n config.application = appId;\n await saveProjectConfig(config, cwd);\n reporter?.log?.(`application ${appId} created`);\n }\n } catch (err) {\n throw new DeployError(\n err instanceof RoxyonApiError ? err.message : String(err),\n config.application ? 'upload-failed' : 'create-failed',\n );\n }\n\n const url = config.public\n ? `https://${config.host}${config.folder ? `/${config.folder}` : ''}`\n : undefined;\n\n if (params.follow === false) {\n reporter?.step?.(`Build queued (revision ${revision})`);\n return {\n kind: 'app',\n ok: true,\n application: appId,\n status: 'building',\n revision,\n url,\n config,\n logs: [],\n };\n }\n\n return watch(roxyon, appId, {\n reporter,\n url,\n config,\n pollMs: params.pollMs ?? 3000,\n timeoutMs: params.timeoutMs ?? 10 * 60 * 1000,\n });\n}\n\nasync function watch(\n roxyon: Roxyon,\n appId: string,\n opts: {\n reporter?: DeployReporter;\n url?: string;\n config: ProjectConfig;\n pollMs: number;\n timeoutMs: number;\n },\n): Promise<DeployOutcome> {\n const started = Date.now();\n const seen = new Set<string>();\n const collected: string[] = [];\n let lastStatus = '';\n\n opts.reporter?.step?.('Building');\n while (true) {\n await sleep(opts.pollMs);\n\n let app: AccountApp | undefined;\n try {\n app = await roxyon.account.getApp(appId);\n } catch {\n /* transient */\n }\n if (!app) {\n if (Date.now() - started > opts.timeoutMs) break;\n continue;\n }\n\n const cfg = app.configRevision;\n const applied = app.appliedRevision;\n const status = app.status;\n if (status !== lastStatus) {\n opts.reporter?.step?.(`${status || 'pending'} (${applied}/${cfg})`);\n lastStatus = status;\n }\n\n try {\n for (const line of await roxyon.applications.logs(appId, 40)) {\n if (line && !seen.has(line)) {\n seen.add(line);\n collected.push(line);\n opts.reporter?.log?.(line);\n }\n }\n } catch {\n /* transient */\n }\n\n if (cfg <= applied && status === 'running') {\n opts.reporter?.step?.(`Live${opts.url ? ` — ${opts.url}` : ''}`);\n return {\n kind: 'app',\n ok: true,\n application: appId,\n status: 'running',\n revision: applied,\n url: opts.url,\n config: opts.config,\n logs: collected,\n };\n }\n if (status === 'failed') {\n return {\n kind: 'app',\n ok: false,\n application: appId,\n status: 'failed',\n error: (app.lastError || 'Deploy failed.').trim(),\n config: opts.config,\n logs: collected,\n };\n }\n if (Date.now() - started > opts.timeoutMs) break;\n }\n\n return {\n kind: 'app',\n ok: false,\n application: appId,\n status: 'timeout',\n error: 'Still building after the timeout — check `roxyon logs`.',\n config: opts.config,\n logs: collected,\n };\n}\n","import { spawn } from 'node:child_process';\nimport type { DeployReporter } from './deploy.js';\n\n/**\n * Default build-command runner: spawn a shell, stream output to the reporter.\n * Callers in a non-local context (a hosted server) can pass their own.\n */\nexport function runShellCommand(\n command: string,\n cwd: string,\n reporter?: DeployReporter,\n): Promise<number> {\n return new Promise((resolvePromise) => {\n const child = spawn(command, { cwd, shell: true, stdio: ['ignore', 'pipe', 'pipe'] });\n const pipe = (buf: Buffer) => {\n for (const line of buf.toString('utf8').split('\\n')) {\n if (line.trim()) reporter?.log?.(line);\n }\n };\n child.stdout?.on('data', pipe);\n child.stderr?.on('data', pipe);\n child.on('close', (code) => resolvePromise(code ?? 0));\n child.on('error', () => resolvePromise(127));\n });\n}\n","import { chmod, mkdir, readFile, rm, writeFile } from 'node:fs/promises';\nimport { homedir } from 'node:os';\nimport { join } from 'node:path';\n\n/**\n * Stored login for Roxyon tooling — `~/.roxyon/config.json`, mode 0600. Written\n * by `roxyon login`; read by the CLI and the MCP server alike.\n */\nexport interface Credentials {\n sessionToken: string;\n refreshToken?: string;\n userId: string;\n email?: string;\n /** Preferred subscription (objectId / Name) when the account has several. */\n subscription?: string;\n savedAt: string;\n}\n\nexport interface Endpoints {\n apiUrl?: string;\n consoleUrl?: string;\n}\n\nexport function credentialsDir(): string {\n return join(homedir(), '.roxyon');\n}\nexport function credentialsPath(): string {\n return join(credentialsDir(), 'config.json');\n}\n\n/** Endpoint overrides from the environment (`ROXYON_API_URL`, `ROXYON_CONSOLE_URL`). */\nexport function endpointsFromEnv(): Endpoints {\n return {\n apiUrl: process.env.ROXYON_API_URL,\n consoleUrl: process.env.ROXYON_CONSOLE_URL,\n };\n}\n\n/** A token supplied out-of-band (CI, or an MCP host env). Skips the stored login. */\nexport function tokenFromEnv(): string | undefined {\n return process.env.ROXYON_TOKEN || undefined;\n}\n\nexport async function loadCredentials(): Promise<Credentials | undefined> {\n try {\n return JSON.parse(await readFile(credentialsPath(), 'utf8')) as Credentials;\n } catch {\n return undefined;\n }\n}\n\nexport async function saveCredentials(creds: Credentials): Promise<string> {\n const path = credentialsPath();\n await mkdir(credentialsDir(), { recursive: true, mode: 0o700 });\n await writeFile(path, `${JSON.stringify(creds, null, 2)}\\n`, { mode: 0o600 });\n await chmod(path, 0o600).catch(() => undefined);\n return path;\n}\n\nexport async function clearCredentials(): Promise<void> {\n await rm(credentialsPath(), { force: true });\n}\n"],"mappings":"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","names":["ignore","tarCreate","import_promises","import_node_path","import_promises","import_node_path","import_promises","import_node_path","import_api_client","url","import_promises","import_node_path"]}
1
+ {"version":3,"sources":["../src/index.ts","../src/archive.ts","../src/detect.ts","../src/project.ts","../src/deploy.ts","../src/run-command.ts","../src/wordpress.ts","../src/credentials.ts"],"sourcesContent":["export {\n ALWAYS_IGNORE,\n buildIgnore,\n type FileEntry,\n listFiles,\n measure,\n packDirectory,\n packFiles,\n type PackResult,\n} from './archive.js';\nexport { type Detection, detectRuntime } from './detect.js';\nexport {\n buildProjectConfig,\n type BuildConfigInput,\n type DeployKind,\n loadProjectConfig,\n PROJECT_FILE,\n type ProjectConfig,\n projectConfigPath,\n saveProjectConfig,\n} from './project.js';\nexport {\n deployProject,\n DeployError,\n type DeployOutcome,\n type DeployParams,\n type DeployReporter,\n} from './deploy.js';\nexport { runShellCommand } from './run-command.js';\nexport {\n buildWpConfig,\n installWordPress,\n type InstallWordPressOptions,\n type InstallWordPressResult,\n type WordPressDbCredentials,\n} from './wordpress.js';\nexport {\n type Credentials,\n credentialsDir,\n credentialsPath,\n clearCredentials,\n type Endpoints,\n endpointsFromEnv,\n loadCredentials,\n saveCredentials,\n tokenFromEnv,\n} from './credentials.js';\n","import { mkdir, mkdtemp, readFile, readdir, rm, stat, writeFile } from 'node:fs/promises';\nimport { tmpdir } from 'node:os';\nimport { dirname, isAbsolute, join, relative, sep } from 'node:path';\nimport ignore, { type Ignore } from 'ignore';\nimport { create as tarCreate } from 'tar';\n\n/** Always excluded, regardless of ignore files — build artefacts and VCS/OS cruft. */\nexport const ALWAYS_IGNORE = [\n 'node_modules',\n '.git',\n '.next',\n '.nuxt',\n '.venv',\n '__pycache__',\n '.DS_Store',\n '.roxyon',\n 'dist/.cache',\n];\n\nasync function readIgnoreFile(root: string, name: string): Promise<string[]> {\n try {\n return (await readFile(join(root, name), 'utf8')).split(/\\r?\\n/);\n } catch {\n return [];\n }\n}\n\n/** Build the ignore matcher: `.roxyonignore` if present, else `.gitignore`, plus the always-list. */\nexport async function buildIgnore(root: string): Promise<Ignore> {\n const ig = ignore().add(ALWAYS_IGNORE);\n const roxyon = await readIgnoreFile(root, '.roxyonignore');\n if (roxyon.length) ig.add(roxyon);\n else ig.add(await readIgnoreFile(root, '.gitignore'));\n return ig;\n}\n\n/** Recursively list files under `root` that survive the ignore matcher (POSIX-relative paths). */\nexport async function listFiles(root: string, ig: Ignore): Promise<string[]> {\n const files: string[] = [];\n\n async function walk(dir: string): Promise<void> {\n const entries = await readdir(dir, { withFileTypes: true });\n for (const entry of entries) {\n const abs = join(dir, entry.name);\n const rel = relative(root, abs).split(sep).join('/');\n if (!rel || ig.ignores(rel) || ig.ignores(`${rel}/`)) continue;\n if (entry.isDirectory()) {\n await walk(abs);\n } else if (entry.isFile() || entry.isSymbolicLink()) {\n files.push(rel);\n }\n }\n }\n\n await walk(root);\n return files.sort();\n}\n\nexport interface PackResult {\n buffer: Buffer;\n files: string[];\n bytes: number;\n}\n\n/**\n * Create a deterministic gzipped tarball of `root` (minus ignored paths).\n * `prefix`, when given, is prepended to every entry path inside the archive.\n */\nexport async function packDirectory(\n root: string,\n opts: { prefix?: string; maxBytes?: number } = {},\n): Promise<PackResult> {\n const ig = await buildIgnore(root);\n const files = await listFiles(root, ig);\n if (files.length === 0) {\n throw new Error(\n `Nothing to deploy from ${root} — every file is ignored or the folder is empty.`,\n );\n }\n\n const stream = tarCreate(\n {\n gzip: true,\n cwd: root,\n portable: true,\n mtime: new Date(0), // stable -> byte-identical archive for an unchanged tree\n prefix: opts.prefix,\n follow: false,\n },\n files,\n );\n\n const chunks: Buffer[] = [];\n let bytes = 0;\n const max = opts.maxBytes ?? 512 * 1024 * 1024;\n for await (const chunk of stream as AsyncIterable<Buffer>) {\n bytes += chunk.length;\n if (bytes > max) {\n throw new Error(\n `Deploy archive exceeds ${(max / 1024 / 1024) | 0} MB. Add large files to .roxyonignore.`,\n );\n }\n chunks.push(chunk);\n }\n\n return { buffer: Buffer.concat(chunks), files, bytes };\n}\n\nexport interface FileEntry {\n /** POSIX-style relative path inside the site, e.g. `index.html`, `about/index.html`. */\n path: string;\n /** File body — UTF-8 text, or base64 when `encoding: 'base64'`. */\n content: string;\n encoding?: 'utf8' | 'base64';\n}\n\n/** A path is safe if it is relative, has no `..`/`.` segment, and no NUL. */\nfunction safeEntryPath(p: string): string | null {\n const norm = p.replace(/\\\\/g, '/').replace(/^\\.\\//, '');\n if (!norm || norm.startsWith('/') || isAbsolute(norm) || norm.includes('\\0')) return null;\n if (norm.split('/').some((seg) => seg === '..' || seg === '.' || seg === '')) return null;\n return norm;\n}\n\n/**\n * Pack an in-memory list of `{ path, content }` into the same deterministic\n * gzip tarball {@link packDirectory} produces — materialise to a temp dir, then\n * reuse that path so the archive format and determinism are identical. Rejects\n * absolute paths and `..` traversal; `maxBytes` caps the total *decoded* content.\n */\nexport async function packFiles(\n entries: FileEntry[],\n opts: { prefix?: string; maxBytes?: number } = {},\n): Promise<PackResult> {\n if (entries.length === 0) throw new Error('No files to deploy.');\n\n const seen = new Set<string>();\n const clean: { path: string; buf: Buffer }[] = [];\n let total = 0;\n for (const e of entries) {\n const rel = safeEntryPath(e.path);\n if (!rel) throw new Error(`Unsafe file path: ${JSON.stringify(e.path)}`);\n if (seen.has(rel)) throw new Error(`Duplicate file path: ${rel}`);\n seen.add(rel);\n const buf = Buffer.from(e.content, e.encoding === 'base64' ? 'base64' : 'utf8');\n total += buf.length;\n clean.push({ path: rel, buf });\n }\n const max = opts.maxBytes ?? 512 * 1024 * 1024;\n if (total > max) {\n throw new Error(\n `Content is ${(total / 1024 / 1024).toFixed(1)} MB, which exceeds the ${(max / 1024 / 1024).toFixed(1)} MB limit.`,\n );\n }\n\n const dir = await mkdtemp(join(tmpdir(), 'roxyon-content-'));\n try {\n for (const { path: rel, buf } of clean) {\n const abs = join(dir, rel);\n await mkdir(dirname(abs), { recursive: true });\n await writeFile(abs, buf);\n }\n return await packDirectory(dir, opts);\n } finally {\n await rm(dir, { recursive: true, force: true });\n }\n}\n\n/** Total on-disk size of a set of files. */\nexport async function measure(root: string, files: string[]): Promise<number> {\n let total = 0;\n for (const f of files) {\n try {\n total += (await stat(join(root, f))).size;\n } catch {\n /* ignore */\n }\n }\n return total;\n}\n","import { readFile, readdir } from 'node:fs/promises';\nimport { join } from 'node:path';\nimport type { RuntimeName } from '@roxyon/api-client';\n\nexport interface Detection {\n runtime: RuntimeName;\n /** why we think so — shown to the user, who can override */\n reason: string;\n /** best-guess start command for a server runtime */\n start?: string;\n}\n\nasync function exists(root: string, name: string): Promise<boolean> {\n try {\n await readFile(join(root, name));\n return true;\n } catch {\n return false;\n }\n}\n\nasync function readJson(root: string, name: string): Promise<Record<string, unknown> | undefined> {\n try {\n return JSON.parse(await readFile(join(root, name), 'utf8')) as Record<string, unknown>;\n } catch {\n return undefined;\n }\n}\n\n/** Best-effort project-type detection. Deterministic; the caller confirms. */\nexport async function detectRuntime(root: string): Promise<Detection> {\n const pkg = await readJson(root, 'package.json');\n const hasConfigJson = await exists(root, 'config.json');\n let hasViews = false;\n try {\n hasViews = (await readdir(join(root, 'src', 'views'))).length >= 0;\n } catch {\n hasViews = false;\n }\n\n if (hasConfigJson && hasViews) {\n const deps = {\n ...(pkg?.dependencies as object),\n ...(pkg?.devDependencies as object),\n } as Record<string, string>;\n if (!('next' in deps) && !('nuxt' in deps) && !('express' in deps) && !('fastify' in deps)) {\n return { runtime: 'lumen', reason: 'config.json + src/views/ and no server framework' };\n }\n }\n\n if (pkg) {\n const scripts = (pkg.scripts as Record<string, string>) ?? {};\n const start = scripts.start ? 'npm run start' : scripts.serve ? 'npm run serve' : 'node .';\n const deps = { ...(pkg.dependencies as object), ...(pkg.devDependencies as object) } as Record<\n string,\n string\n >;\n if ('next' in deps)\n return { runtime: 'node', reason: 'Next.js in package.json', start: 'npm run start' };\n if ('nuxt' in deps)\n return { runtime: 'node', reason: 'Nuxt in package.json', start: 'npm run start' };\n return { runtime: 'node', reason: 'package.json present', start };\n }\n\n if ((await exists(root, 'requirements.txt')) || (await exists(root, 'pyproject.toml'))) {\n return {\n runtime: 'python',\n reason: 'requirements.txt / pyproject.toml',\n start: 'gunicorn app:app',\n };\n }\n\n if ((await exists(root, 'composer.json')) || (await exists(root, 'server.php'))) {\n return { runtime: 'php', reason: 'composer.json / server.php', start: 'php8.4 server.php' };\n }\n\n return { runtime: 'lumen', reason: 'no framework markers — treating as a static site' };\n}\n","import { mkdir, readFile, writeFile } from 'node:fs/promises';\nimport { basename, dirname, resolve } from 'node:path';\nimport { type DeployKind, LUMEN_BUILD, RUNTIMES, type RuntimeName } from '@roxyon/api-client';\n\nexport type { DeployKind };\n\nexport interface ProjectConfig {\n name: string;\n /** `Applications.objectId`, filled after the first `app`-kind deploy. */\n application?: string;\n /** Route host — `Domains.Name`. */\n host: string;\n /** Sub-path under the host's docroot / source tree. `\"\"` = root. */\n folder: string;\n runtime: RuntimeName;\n runtimeVersion?: string;\n preset?: string;\n kind: DeployKind;\n /** Local build command (LumenJS / static). Empty for server runtimes. */\n build?: string;\n /** Directory the build writes to (relative to project root). */\n outDir?: string;\n /** Web-process start command (app kind only). */\n start?: string;\n /** Serve the app on the host instead of the host's own files. */\n public?: boolean;\n}\n\nexport const PROJECT_FILE = 'roxyon.json';\n\nexport function projectConfigPath(cwd: string = process.cwd()): string {\n return resolve(cwd, PROJECT_FILE);\n}\n\nexport async function loadProjectConfig(\n cwd: string = process.cwd(),\n): Promise<ProjectConfig | undefined> {\n try {\n return JSON.parse(await readFile(projectConfigPath(cwd), 'utf8')) as ProjectConfig;\n } catch {\n return undefined;\n }\n}\n\nexport async function saveProjectConfig(\n config: ProjectConfig,\n cwd: string = process.cwd(),\n): Promise<string> {\n const path = projectConfigPath(cwd);\n await mkdir(dirname(path), { recursive: true });\n await writeFile(path, `${JSON.stringify(config, null, 2)}\\n`);\n return path;\n}\n\nexport interface BuildConfigInput {\n cwd: string;\n runtime: RuntimeName;\n host: string;\n folder?: string;\n name?: string;\n start?: string;\n}\n\n/**\n * Produce a `ProjectConfig` from a resolved runtime + host. Shared by the CLI's\n * `roxyon init` and the MCP `roxyon_init` tool so both write the same shape.\n */\nexport function buildProjectConfig(input: BuildConfigInput): ProjectConfig {\n const folder = String(input.folder ?? '').replace(/^\\/+|\\/+$/g, '');\n const config: ProjectConfig = {\n name: input.name ?? basename(input.cwd),\n host: input.host,\n folder,\n runtime: input.runtime,\n kind: input.runtime === 'lumen' ? 'static' : 'app',\n };\n\n if (input.runtime === 'lumen') {\n config.build = LUMEN_BUILD.command;\n config.outDir = LUMEN_BUILD.outDir;\n } else {\n const spec = RUNTIMES[input.runtime];\n config.runtimeVersion = spec.defaultVersion;\n config.preset = spec.presets[0]?.[0];\n config.start = input.start ?? spec.command;\n config.public = true;\n config.build = '';\n }\n return config;\n}\n","import { access } from 'node:fs/promises';\nimport { resolve } from 'node:path';\nimport {\n type AccountApp,\n RUNTIMES,\n type Roxyon,\n RoxyonApiError,\n isAuthError,\n} from '@roxyon/api-client';\nimport { packDirectory } from './archive.js';\nimport { type ProjectConfig, loadProjectConfig, saveProjectConfig } from './project.js';\nimport { runShellCommand } from './run-command.js';\n\nexport interface DeployReporter {\n /** A high-level phase change (\"Building\", \"Uploading\", \"Live\"). */\n step?: (msg: string) => void;\n /** A single build- or runtime-log line. */\n log?: (line: string) => void;\n}\n\nexport interface DeployParams {\n cwd: string;\n /** An authenticated client. */\n roxyon: Roxyon;\n /** Subscription objectId / Name to prefer when the account has several. */\n preferredSubscription?: string;\n /** Run the configured build command (default true). */\n build?: boolean;\n /** For app deploys: poll until running/failed (default true). */\n follow?: boolean;\n pollMs?: number;\n timeoutMs?: number;\n reporter?: DeployReporter;\n /** Override the build-command runner (default: spawn a shell). */\n runCommand?: (command: string, cwd: string, reporter?: DeployReporter) => Promise<number>;\n}\n\nexport type DeployOutcome =\n | { kind: 'static'; ok: true; url: string; files: number; config: ProjectConfig }\n | {\n kind: 'app';\n ok: true;\n application: string;\n status: string;\n revision: number;\n url?: string;\n config: ProjectConfig;\n logs: string[];\n }\n | {\n kind: 'app';\n ok: false;\n application: string;\n status: string;\n error: string;\n config: ProjectConfig;\n logs: string[];\n };\n\nexport class DeployError extends Error {\n readonly code:\n | 'no-config'\n | 'auth'\n | 'bad-host'\n | 'build-failed'\n | 'no-build-output'\n | 'upload-failed'\n | 'create-failed';\n constructor(message: string, code: DeployError['code']) {\n super(message);\n this.name = 'DeployError';\n this.code = code;\n }\n}\n\nconst sleep = (ms: number) => new Promise((r) => setTimeout(r, ms));\n\n/**\n * Build (if configured), archive, upload, and — for app runtimes — poll until\n * the deploy settles. Self-contained: one `/account/context` call validates the\n * host (works with a session token or a `roxp_` PAT), and the console creates\n * the application on the first deploy — the new id is written back to\n * `roxyon.json`.\n */\nexport async function deployProject(params: DeployParams): Promise<DeployOutcome> {\n const { cwd, roxyon, reporter } = params;\n const runCommand = params.runCommand ?? runShellCommand;\n\n const config = await loadProjectConfig(cwd);\n if (!config) {\n throw new DeployError('No roxyon.json in this directory — run init first.', 'no-config');\n }\n\n // One call — works with a session token or a PAT.\n const ctx = await roxyon.account.context().catch((e) => {\n if (isAuthError(e)) throw new DeployError('Session expired — sign in again.', 'auth');\n throw e;\n });\n const domain = ctx.domains.find((d) => d.name === config.host);\n if (!domain) {\n throw new DeployError(\n `Host \"${config.host}\" is not on this account (have: ${\n ctx.domains.map((d) => d.name).join(', ') || 'none'\n }).`,\n 'bad-host',\n );\n }\n\n // ---- build ----\n if (config.build && params.build !== false) {\n reporter?.step?.(`Build: ${config.build}`);\n const code = await runCommand(config.build, cwd, reporter);\n if (code !== 0) throw new DeployError(`Build command exited ${code}.`, 'build-failed');\n }\n\n // ---- pack ----\n const artifactDir = config.kind === 'static' && config.outDir ? resolve(cwd, config.outDir) : cwd;\n if (config.kind === 'static' && config.outDir) {\n await access(artifactDir).catch(() => {\n throw new DeployError(\n `Build output \"${config.outDir}\" not found — did the build run?`,\n 'no-build-output',\n );\n });\n }\n reporter?.step?.(`Packing ${artifactDir === cwd ? '.' : config.outDir}`);\n const pack = await packDirectory(artifactDir);\n reporter?.log?.(`${pack.files.length} files · ${(pack.bytes / 1024).toFixed(0)} KB`);\n\n // ---- static ----\n if (config.kind === 'static') {\n reporter?.step?.(`Uploading to ${config.host}${config.folder ? `/${config.folder}` : ''}`);\n let res: Awaited<ReturnType<typeof roxyon.sites.deploy>>;\n try {\n res = await roxyon.sites.deploy(config.host, config.folder, pack.buffer);\n } catch (err) {\n throw new DeployError(\n err instanceof RoxyonApiError ? err.message : String(err),\n 'upload-failed',\n );\n }\n const url = `https://${res.host}${res.path && res.path !== '/' ? res.path : ''}`;\n reporter?.step?.(`Live — ${url}`);\n return { kind: 'static', ok: true, url, files: res.files ?? pack.files.length, config };\n }\n\n // ---- app ----\n // The console's /applications/deploy creates the app rows on the first deploy\n // (no `application` id yet) from the host + runtime params — one trusted call,\n // so a PAT-only CI job needs no BaaS write access.\n const runtime = config.runtime === 'lumen' ? 'node' : config.runtime;\n const spec = RUNTIMES[runtime];\n const target = config.application ?? {\n host: config.host,\n folder: config.folder,\n runtime,\n runtimeVersion: config.runtimeVersion ?? spec.defaultVersion,\n preset: config.preset ?? spec.presets[0]?.[0] ?? runtime,\n command: config.start ?? spec.command,\n public: config.public ?? true,\n };\n\n reporter?.step?.(\n config.application ? 'Uploading source' : 'Creating the application & uploading',\n );\n let revision = 0;\n let appId: string;\n try {\n const res = await roxyon.applications.uploadSource(target, pack.buffer);\n revision = res.configRevision ?? 0;\n appId = res.application;\n if (res.created && appId && appId !== config.application) {\n config.application = appId;\n await saveProjectConfig(config, cwd);\n reporter?.log?.(`application ${appId} created`);\n }\n } catch (err) {\n throw new DeployError(\n err instanceof RoxyonApiError ? err.message : String(err),\n config.application ? 'upload-failed' : 'create-failed',\n );\n }\n\n const url = config.public\n ? `https://${config.host}${config.folder ? `/${config.folder}` : ''}`\n : undefined;\n\n if (params.follow === false) {\n reporter?.step?.(`Build queued (revision ${revision})`);\n return {\n kind: 'app',\n ok: true,\n application: appId,\n status: 'building',\n revision,\n url,\n config,\n logs: [],\n };\n }\n\n return watch(roxyon, appId, {\n reporter,\n url,\n config,\n pollMs: params.pollMs ?? 3000,\n timeoutMs: params.timeoutMs ?? 10 * 60 * 1000,\n });\n}\n\nasync function watch(\n roxyon: Roxyon,\n appId: string,\n opts: {\n reporter?: DeployReporter;\n url?: string;\n config: ProjectConfig;\n pollMs: number;\n timeoutMs: number;\n },\n): Promise<DeployOutcome> {\n const started = Date.now();\n const seen = new Set<string>();\n const collected: string[] = [];\n let lastStatus = '';\n\n opts.reporter?.step?.('Building');\n while (true) {\n await sleep(opts.pollMs);\n\n let app: AccountApp | undefined;\n try {\n app = await roxyon.account.getApp(appId);\n } catch {\n /* transient */\n }\n if (!app) {\n if (Date.now() - started > opts.timeoutMs) break;\n continue;\n }\n\n const cfg = app.configRevision;\n const applied = app.appliedRevision;\n const status = app.status;\n if (status !== lastStatus) {\n opts.reporter?.step?.(`${status || 'pending'} (${applied}/${cfg})`);\n lastStatus = status;\n }\n\n try {\n for (const line of await roxyon.applications.logs(appId, 40)) {\n if (line && !seen.has(line)) {\n seen.add(line);\n collected.push(line);\n opts.reporter?.log?.(line);\n }\n }\n } catch {\n /* transient */\n }\n\n if (cfg <= applied && status === 'running') {\n opts.reporter?.step?.(`Live${opts.url ? ` — ${opts.url}` : ''}`);\n return {\n kind: 'app',\n ok: true,\n application: appId,\n status: 'running',\n revision: applied,\n url: opts.url,\n config: opts.config,\n logs: collected,\n };\n }\n if (status === 'failed') {\n return {\n kind: 'app',\n ok: false,\n application: appId,\n status: 'failed',\n error: (app.lastError || 'Deploy failed.').trim(),\n config: opts.config,\n logs: collected,\n };\n }\n if (Date.now() - started > opts.timeoutMs) break;\n }\n\n return {\n kind: 'app',\n ok: false,\n application: appId,\n status: 'timeout',\n error: 'Still building after the timeout — check `roxyon logs`.',\n config: opts.config,\n logs: collected,\n };\n}\n","import { spawn } from 'node:child_process';\nimport type { DeployReporter } from './deploy.js';\n\n/**\n * Default build-command runner: spawn a shell, stream output to the reporter.\n * Callers in a non-local context (a hosted server) can pass their own.\n */\nexport function runShellCommand(\n command: string,\n cwd: string,\n reporter?: DeployReporter,\n): Promise<number> {\n return new Promise((resolvePromise) => {\n const child = spawn(command, { cwd, shell: true, stdio: ['ignore', 'pipe', 'pipe'] });\n const pipe = (buf: Buffer) => {\n for (const line of buf.toString('utf8').split('\\n')) {\n if (line.trim()) reporter?.log?.(line);\n }\n };\n child.stdout?.on('data', pipe);\n child.stderr?.on('data', pipe);\n child.on('close', (code) => resolvePromise(code ?? 0));\n child.on('error', () => resolvePromise(127));\n });\n}\n","import { mkdir, readFile, writeFile } from 'node:fs/promises';\nimport { Readable } from 'node:stream';\nimport { extract } from 'tar';\n\nconst WORDPRESS_TARBALL_URL = 'https://wordpress.org/latest.tar.gz';\nconst WORDPRESS_SALTS_URL = 'https://api.wordpress.org/secret-key/1.1/salt/';\nconst PLACEHOLDER = 'put your unique phrase here';\n\nexport interface WordPressDbCredentials {\n name: string;\n username: string;\n password: string;\n /** `host:port`, e.g. `10.0.0.2:6033` — WordPress's `DB_HOST` accepts this form directly. */\n host: string;\n}\n\nexport interface InstallWordPressOptions {\n /** Destination directory — created if missing. Must be empty or not yet exist. */\n dir: string;\n db: WordPressDbCredentials;\n /** Default `wp_`. */\n tablePrefix?: string;\n /** Override for tests — must resolve like the global `fetch`. */\n fetchImpl?: typeof fetch;\n}\n\nexport interface InstallWordPressResult {\n dir: string;\n}\n\n/**\n * Download WordPress core into `dir` and write a working `wp-config.php`\n * against the given database. Does not touch the network beyond\n * wordpress.org/api.wordpress.org, and does not deploy anywhere — pack `dir`\n * with {@link packDirectory} and upload with `roxyon.sites.deploy()` (the same\n * tar path `roxyon deploy` uses for static/app content; WordPress core is\n * thousands of files, well past the small JSON `roxyon_deploy_content` tool's\n * budget).\n */\nexport async function installWordPress(\n opts: InstallWordPressOptions,\n): Promise<InstallWordPressResult> {\n const fetchImpl = opts.fetchImpl ?? globalThis.fetch;\n await mkdir(opts.dir, { recursive: true });\n\n const coreRes = await fetchImpl(WORDPRESS_TARBALL_URL);\n if (!coreRes.ok) {\n throw new Error(`Could not download WordPress core (HTTP ${coreRes.status}).`);\n }\n const coreBuf = Buffer.from(await coreRes.arrayBuffer());\n\n // The official tarball has one top-level \"wordpress/\" directory — strip it\n // so core files land directly in `dir`.\n await new Promise<void>((resolvePromise, reject) => {\n const writer = extract({ cwd: opts.dir, strip: 1 });\n Readable.from(coreBuf)\n .pipe(writer)\n .on('finish', () => resolvePromise())\n .on('error', reject);\n });\n\n const saltsRes = await fetchImpl(WORDPRESS_SALTS_URL);\n const salts = saltsRes.ok ? (await saltsRes.text()).trim() : '';\n\n const samplePath = `${opts.dir}/wp-config-sample.php`;\n const sample = await readFile(samplePath, 'utf8');\n const config = buildWpConfig(sample, opts.db, opts.tablePrefix, salts);\n await writeFile(`${opts.dir}/wp-config.php`, config, 'utf8');\n\n return { dir: opts.dir };\n}\n\n/** Pure string transform — exported separately so it's testable without any network. */\nexport function buildWpConfig(\n sample: string,\n db: WordPressDbCredentials,\n tablePrefix = 'wp_',\n salts = '',\n): string {\n let out = sample\n .replace(/(['\"])database_name_here\\1/, `$1${escapePhp(db.name)}$1`)\n .replace(/(['\"])username_here\\1/, `$1${escapePhp(db.username)}$1`)\n .replace(/(['\"])password_here\\1/, `$1${escapePhp(db.password)}$1`)\n .replace(/(define\\(\\s*['\"]DB_HOST['\"]\\s*,\\s*)(['\"])localhost\\2/, `$1$2${escapePhp(db.host)}$2`)\n .replace(/(\\$table_prefix\\s*=\\s*)(['\"])wp_\\2/, `$1$2${escapePhp(tablePrefix)}$2`);\n\n if (salts) {\n const lines = out.split('\\n');\n const start = lines.findIndex((l) => l.includes(PLACEHOLDER));\n if (start !== -1) {\n let end = start;\n while (end < lines.length && lines[end]!.includes(PLACEHOLDER)) end++;\n lines.splice(start, end - start, salts);\n out = lines.join('\\n');\n }\n }\n\n return out;\n}\n\nfunction escapePhp(s: string): string {\n return s.replace(/\\\\/g, '\\\\\\\\').replace(/'/g, \"\\\\'\");\n}\n","import { chmod, mkdir, readFile, rm, writeFile } from 'node:fs/promises';\nimport { homedir } from 'node:os';\nimport { join } from 'node:path';\n\n/**\n * Stored login for Roxyon tooling — `~/.roxyon/config.json`, mode 0600. Written\n * by `roxyon login`; read by the CLI and the MCP server alike.\n */\nexport interface Credentials {\n sessionToken: string;\n refreshToken?: string;\n userId: string;\n email?: string;\n /** Preferred subscription (objectId / Name) when the account has several. */\n subscription?: string;\n savedAt: string;\n}\n\nexport interface Endpoints {\n apiUrl?: string;\n consoleUrl?: string;\n}\n\nexport function credentialsDir(): string {\n return join(homedir(), '.roxyon');\n}\nexport function credentialsPath(): string {\n return join(credentialsDir(), 'config.json');\n}\n\n/** Endpoint overrides from the environment (`ROXYON_API_URL`, `ROXYON_CONSOLE_URL`). */\nexport function endpointsFromEnv(): Endpoints {\n return {\n apiUrl: process.env.ROXYON_API_URL,\n consoleUrl: process.env.ROXYON_CONSOLE_URL,\n };\n}\n\n/** A token supplied out-of-band (CI, or an MCP host env). Skips the stored login. */\nexport function tokenFromEnv(): string | undefined {\n return process.env.ROXYON_TOKEN || undefined;\n}\n\nexport async function loadCredentials(): Promise<Credentials | undefined> {\n try {\n return JSON.parse(await readFile(credentialsPath(), 'utf8')) as Credentials;\n } catch {\n return undefined;\n }\n}\n\nexport async function saveCredentials(creds: Credentials): Promise<string> {\n const path = credentialsPath();\n await mkdir(credentialsDir(), { recursive: true, mode: 0o700 });\n await writeFile(path, `${JSON.stringify(creds, null, 2)}\\n`, { mode: 0o600 });\n await chmod(path, 0o600).catch(() => undefined);\n return path;\n}\n\nexport async function clearCredentials(): Promise<void> {\n await rm(credentialsPath(), { force: true });\n}\n"],"mappings":"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","names":["ignore","tarCreate","import_promises","import_node_path","import_promises","import_node_path","import_promises","import_node_path","import_api_client","url","import_promises","import_tar","import_promises","import_node_os","import_node_path"]}
package/dist/index.d.cts CHANGED
@@ -21,6 +21,23 @@ declare function packDirectory(root: string, opts?: {
21
21
  prefix?: string;
22
22
  maxBytes?: number;
23
23
  }): Promise<PackResult>;
24
+ interface FileEntry {
25
+ /** POSIX-style relative path inside the site, e.g. `index.html`, `about/index.html`. */
26
+ path: string;
27
+ /** File body — UTF-8 text, or base64 when `encoding: 'base64'`. */
28
+ content: string;
29
+ encoding?: 'utf8' | 'base64';
30
+ }
31
+ /**
32
+ * Pack an in-memory list of `{ path, content }` into the same deterministic
33
+ * gzip tarball {@link packDirectory} produces — materialise to a temp dir, then
34
+ * reuse that path so the archive format and determinism are identical. Rejects
35
+ * absolute paths and `..` traversal; `maxBytes` caps the total *decoded* content.
36
+ */
37
+ declare function packFiles(entries: FileEntry[], opts?: {
38
+ prefix?: string;
39
+ maxBytes?: number;
40
+ }): Promise<PackResult>;
24
41
  /** Total on-disk size of a set of files. */
25
42
  declare function measure(root: string, files: string[]): Promise<number>;
26
43
 
@@ -138,6 +155,38 @@ declare function deployProject(params: DeployParams): Promise<DeployOutcome>;
138
155
  */
139
156
  declare function runShellCommand(command: string, cwd: string, reporter?: DeployReporter): Promise<number>;
140
157
 
158
+ interface WordPressDbCredentials {
159
+ name: string;
160
+ username: string;
161
+ password: string;
162
+ /** `host:port`, e.g. `10.0.0.2:6033` — WordPress's `DB_HOST` accepts this form directly. */
163
+ host: string;
164
+ }
165
+ interface InstallWordPressOptions {
166
+ /** Destination directory — created if missing. Must be empty or not yet exist. */
167
+ dir: string;
168
+ db: WordPressDbCredentials;
169
+ /** Default `wp_`. */
170
+ tablePrefix?: string;
171
+ /** Override for tests — must resolve like the global `fetch`. */
172
+ fetchImpl?: typeof fetch;
173
+ }
174
+ interface InstallWordPressResult {
175
+ dir: string;
176
+ }
177
+ /**
178
+ * Download WordPress core into `dir` and write a working `wp-config.php`
179
+ * against the given database. Does not touch the network beyond
180
+ * wordpress.org/api.wordpress.org, and does not deploy anywhere — pack `dir`
181
+ * with {@link packDirectory} and upload with `roxyon.sites.deploy()` (the same
182
+ * tar path `roxyon deploy` uses for static/app content; WordPress core is
183
+ * thousands of files, well past the small JSON `roxyon_deploy_content` tool's
184
+ * budget).
185
+ */
186
+ declare function installWordPress(opts: InstallWordPressOptions): Promise<InstallWordPressResult>;
187
+ /** Pure string transform — exported separately so it's testable without any network. */
188
+ declare function buildWpConfig(sample: string, db: WordPressDbCredentials, tablePrefix?: string, salts?: string): string;
189
+
141
190
  /**
142
191
  * Stored login for Roxyon tooling — `~/.roxyon/config.json`, mode 0600. Written
143
192
  * by `roxyon login`; read by the CLI and the MCP server alike.
@@ -165,4 +214,4 @@ declare function loadCredentials(): Promise<Credentials | undefined>;
165
214
  declare function saveCredentials(creds: Credentials): Promise<string>;
166
215
  declare function clearCredentials(): Promise<void>;
167
216
 
168
- export { ALWAYS_IGNORE, type BuildConfigInput, type Credentials, DeployError, type DeployOutcome, type DeployParams, type DeployReporter, type Detection, type Endpoints, PROJECT_FILE, type PackResult, type ProjectConfig, buildIgnore, buildProjectConfig, clearCredentials, credentialsDir, credentialsPath, deployProject, detectRuntime, endpointsFromEnv, listFiles, loadCredentials, loadProjectConfig, measure, packDirectory, projectConfigPath, runShellCommand, saveCredentials, saveProjectConfig, tokenFromEnv };
217
+ export { ALWAYS_IGNORE, type BuildConfigInput, type Credentials, DeployError, type DeployOutcome, type DeployParams, type DeployReporter, type Detection, type Endpoints, type FileEntry, type InstallWordPressOptions, type InstallWordPressResult, PROJECT_FILE, type PackResult, type ProjectConfig, type WordPressDbCredentials, buildIgnore, buildProjectConfig, buildWpConfig, clearCredentials, credentialsDir, credentialsPath, deployProject, detectRuntime, endpointsFromEnv, installWordPress, listFiles, loadCredentials, loadProjectConfig, measure, packDirectory, packFiles, projectConfigPath, runShellCommand, saveCredentials, saveProjectConfig, tokenFromEnv };
package/dist/index.d.ts CHANGED
@@ -21,6 +21,23 @@ declare function packDirectory(root: string, opts?: {
21
21
  prefix?: string;
22
22
  maxBytes?: number;
23
23
  }): Promise<PackResult>;
24
+ interface FileEntry {
25
+ /** POSIX-style relative path inside the site, e.g. `index.html`, `about/index.html`. */
26
+ path: string;
27
+ /** File body — UTF-8 text, or base64 when `encoding: 'base64'`. */
28
+ content: string;
29
+ encoding?: 'utf8' | 'base64';
30
+ }
31
+ /**
32
+ * Pack an in-memory list of `{ path, content }` into the same deterministic
33
+ * gzip tarball {@link packDirectory} produces — materialise to a temp dir, then
34
+ * reuse that path so the archive format and determinism are identical. Rejects
35
+ * absolute paths and `..` traversal; `maxBytes` caps the total *decoded* content.
36
+ */
37
+ declare function packFiles(entries: FileEntry[], opts?: {
38
+ prefix?: string;
39
+ maxBytes?: number;
40
+ }): Promise<PackResult>;
24
41
  /** Total on-disk size of a set of files. */
25
42
  declare function measure(root: string, files: string[]): Promise<number>;
26
43
 
@@ -138,6 +155,38 @@ declare function deployProject(params: DeployParams): Promise<DeployOutcome>;
138
155
  */
139
156
  declare function runShellCommand(command: string, cwd: string, reporter?: DeployReporter): Promise<number>;
140
157
 
158
+ interface WordPressDbCredentials {
159
+ name: string;
160
+ username: string;
161
+ password: string;
162
+ /** `host:port`, e.g. `10.0.0.2:6033` — WordPress's `DB_HOST` accepts this form directly. */
163
+ host: string;
164
+ }
165
+ interface InstallWordPressOptions {
166
+ /** Destination directory — created if missing. Must be empty or not yet exist. */
167
+ dir: string;
168
+ db: WordPressDbCredentials;
169
+ /** Default `wp_`. */
170
+ tablePrefix?: string;
171
+ /** Override for tests — must resolve like the global `fetch`. */
172
+ fetchImpl?: typeof fetch;
173
+ }
174
+ interface InstallWordPressResult {
175
+ dir: string;
176
+ }
177
+ /**
178
+ * Download WordPress core into `dir` and write a working `wp-config.php`
179
+ * against the given database. Does not touch the network beyond
180
+ * wordpress.org/api.wordpress.org, and does not deploy anywhere — pack `dir`
181
+ * with {@link packDirectory} and upload with `roxyon.sites.deploy()` (the same
182
+ * tar path `roxyon deploy` uses for static/app content; WordPress core is
183
+ * thousands of files, well past the small JSON `roxyon_deploy_content` tool's
184
+ * budget).
185
+ */
186
+ declare function installWordPress(opts: InstallWordPressOptions): Promise<InstallWordPressResult>;
187
+ /** Pure string transform — exported separately so it's testable without any network. */
188
+ declare function buildWpConfig(sample: string, db: WordPressDbCredentials, tablePrefix?: string, salts?: string): string;
189
+
141
190
  /**
142
191
  * Stored login for Roxyon tooling — `~/.roxyon/config.json`, mode 0600. Written
143
192
  * by `roxyon login`; read by the CLI and the MCP server alike.
@@ -165,4 +214,4 @@ declare function loadCredentials(): Promise<Credentials | undefined>;
165
214
  declare function saveCredentials(creds: Credentials): Promise<string>;
166
215
  declare function clearCredentials(): Promise<void>;
167
216
 
168
- export { ALWAYS_IGNORE, type BuildConfigInput, type Credentials, DeployError, type DeployOutcome, type DeployParams, type DeployReporter, type Detection, type Endpoints, PROJECT_FILE, type PackResult, type ProjectConfig, buildIgnore, buildProjectConfig, clearCredentials, credentialsDir, credentialsPath, deployProject, detectRuntime, endpointsFromEnv, listFiles, loadCredentials, loadProjectConfig, measure, packDirectory, projectConfigPath, runShellCommand, saveCredentials, saveProjectConfig, tokenFromEnv };
217
+ export { ALWAYS_IGNORE, type BuildConfigInput, type Credentials, DeployError, type DeployOutcome, type DeployParams, type DeployReporter, type Detection, type Endpoints, type FileEntry, type InstallWordPressOptions, type InstallWordPressResult, PROJECT_FILE, type PackResult, type ProjectConfig, type WordPressDbCredentials, buildIgnore, buildProjectConfig, buildWpConfig, clearCredentials, credentialsDir, credentialsPath, deployProject, detectRuntime, endpointsFromEnv, installWordPress, listFiles, loadCredentials, loadProjectConfig, measure, packDirectory, packFiles, projectConfigPath, runShellCommand, saveCredentials, saveProjectConfig, tokenFromEnv };
package/dist/index.js CHANGED
@@ -1,6 +1,7 @@
1
1
  // src/archive.ts
2
- import { readFile, readdir, stat } from "fs/promises";
3
- import { join, relative, sep } from "path";
2
+ import { mkdir, mkdtemp, readFile, readdir, rm, stat, writeFile } from "fs/promises";
3
+ import { tmpdir } from "os";
4
+ import { dirname, isAbsolute, join, relative, sep } from "path";
4
5
  import ignore from "ignore";
5
6
  import { create as tarCreate } from "tar";
6
7
  var ALWAYS_IGNORE = [
@@ -80,6 +81,44 @@ async function packDirectory(root, opts = {}) {
80
81
  }
81
82
  return { buffer: Buffer.concat(chunks), files, bytes };
82
83
  }
84
+ function safeEntryPath(p) {
85
+ const norm = p.replace(/\\/g, "/").replace(/^\.\//, "");
86
+ if (!norm || norm.startsWith("/") || isAbsolute(norm) || norm.includes("\0")) return null;
87
+ if (norm.split("/").some((seg) => seg === ".." || seg === "." || seg === "")) return null;
88
+ return norm;
89
+ }
90
+ async function packFiles(entries, opts = {}) {
91
+ if (entries.length === 0) throw new Error("No files to deploy.");
92
+ const seen = /* @__PURE__ */ new Set();
93
+ const clean = [];
94
+ let total = 0;
95
+ for (const e of entries) {
96
+ const rel = safeEntryPath(e.path);
97
+ if (!rel) throw new Error(`Unsafe file path: ${JSON.stringify(e.path)}`);
98
+ if (seen.has(rel)) throw new Error(`Duplicate file path: ${rel}`);
99
+ seen.add(rel);
100
+ const buf = Buffer.from(e.content, e.encoding === "base64" ? "base64" : "utf8");
101
+ total += buf.length;
102
+ clean.push({ path: rel, buf });
103
+ }
104
+ const max = opts.maxBytes ?? 512 * 1024 * 1024;
105
+ if (total > max) {
106
+ throw new Error(
107
+ `Content is ${(total / 1024 / 1024).toFixed(1)} MB, which exceeds the ${(max / 1024 / 1024).toFixed(1)} MB limit.`
108
+ );
109
+ }
110
+ const dir = await mkdtemp(join(tmpdir(), "roxyon-content-"));
111
+ try {
112
+ for (const { path: rel, buf } of clean) {
113
+ const abs = join(dir, rel);
114
+ await mkdir(dirname(abs), { recursive: true });
115
+ await writeFile(abs, buf);
116
+ }
117
+ return await packDirectory(dir, opts);
118
+ } finally {
119
+ await rm(dir, { recursive: true, force: true });
120
+ }
121
+ }
83
122
  async function measure(root, files) {
84
123
  let total = 0;
85
124
  for (const f of files) {
@@ -151,8 +190,8 @@ async function detectRuntime(root) {
151
190
  }
152
191
 
153
192
  // src/project.ts
154
- import { mkdir, readFile as readFile3, writeFile } from "fs/promises";
155
- import { basename, dirname, resolve } from "path";
193
+ import { mkdir as mkdir2, readFile as readFile3, writeFile as writeFile2 } from "fs/promises";
194
+ import { basename, dirname as dirname2, resolve } from "path";
156
195
  import { LUMEN_BUILD, RUNTIMES } from "@roxyon/api-client";
157
196
  var PROJECT_FILE = "roxyon.json";
158
197
  function projectConfigPath(cwd = process.cwd()) {
@@ -167,8 +206,8 @@ async function loadProjectConfig(cwd = process.cwd()) {
167
206
  }
168
207
  async function saveProjectConfig(config, cwd = process.cwd()) {
169
208
  const path = projectConfigPath(cwd);
170
- await mkdir(dirname(path), { recursive: true });
171
- await writeFile(path, `${JSON.stringify(config, null, 2)}
209
+ await mkdir2(dirname2(path), { recursive: true });
210
+ await writeFile2(path, `${JSON.stringify(config, null, 2)}
172
211
  `);
173
212
  return path;
174
213
  }
@@ -405,8 +444,53 @@ async function watch(roxyon, appId, opts) {
405
444
  };
406
445
  }
407
446
 
447
+ // src/wordpress.ts
448
+ import { mkdir as mkdir3, readFile as readFile4, writeFile as writeFile3 } from "fs/promises";
449
+ import { Readable } from "stream";
450
+ import { extract } from "tar";
451
+ var WORDPRESS_TARBALL_URL = "https://wordpress.org/latest.tar.gz";
452
+ var WORDPRESS_SALTS_URL = "https://api.wordpress.org/secret-key/1.1/salt/";
453
+ var PLACEHOLDER = "put your unique phrase here";
454
+ async function installWordPress(opts) {
455
+ const fetchImpl = opts.fetchImpl ?? globalThis.fetch;
456
+ await mkdir3(opts.dir, { recursive: true });
457
+ const coreRes = await fetchImpl(WORDPRESS_TARBALL_URL);
458
+ if (!coreRes.ok) {
459
+ throw new Error(`Could not download WordPress core (HTTP ${coreRes.status}).`);
460
+ }
461
+ const coreBuf = Buffer.from(await coreRes.arrayBuffer());
462
+ await new Promise((resolvePromise, reject) => {
463
+ const writer = extract({ cwd: opts.dir, strip: 1 });
464
+ Readable.from(coreBuf).pipe(writer).on("finish", () => resolvePromise()).on("error", reject);
465
+ });
466
+ const saltsRes = await fetchImpl(WORDPRESS_SALTS_URL);
467
+ const salts = saltsRes.ok ? (await saltsRes.text()).trim() : "";
468
+ const samplePath = `${opts.dir}/wp-config-sample.php`;
469
+ const sample = await readFile4(samplePath, "utf8");
470
+ const config = buildWpConfig(sample, opts.db, opts.tablePrefix, salts);
471
+ await writeFile3(`${opts.dir}/wp-config.php`, config, "utf8");
472
+ return { dir: opts.dir };
473
+ }
474
+ function buildWpConfig(sample, db, tablePrefix = "wp_", salts = "") {
475
+ let out = sample.replace(/(['"])database_name_here\1/, `$1${escapePhp(db.name)}$1`).replace(/(['"])username_here\1/, `$1${escapePhp(db.username)}$1`).replace(/(['"])password_here\1/, `$1${escapePhp(db.password)}$1`).replace(/(define\(\s*['"]DB_HOST['"]\s*,\s*)(['"])localhost\2/, `$1$2${escapePhp(db.host)}$2`).replace(/(\$table_prefix\s*=\s*)(['"])wp_\2/, `$1$2${escapePhp(tablePrefix)}$2`);
476
+ if (salts) {
477
+ const lines = out.split("\n");
478
+ const start = lines.findIndex((l) => l.includes(PLACEHOLDER));
479
+ if (start !== -1) {
480
+ let end = start;
481
+ while (end < lines.length && lines[end].includes(PLACEHOLDER)) end++;
482
+ lines.splice(start, end - start, salts);
483
+ out = lines.join("\n");
484
+ }
485
+ }
486
+ return out;
487
+ }
488
+ function escapePhp(s) {
489
+ return s.replace(/\\/g, "\\\\").replace(/'/g, "\\'");
490
+ }
491
+
408
492
  // src/credentials.ts
409
- import { chmod, mkdir as mkdir2, readFile as readFile4, rm, writeFile as writeFile2 } from "fs/promises";
493
+ import { chmod, mkdir as mkdir4, readFile as readFile5, rm as rm2, writeFile as writeFile4 } from "fs/promises";
410
494
  import { homedir } from "os";
411
495
  import { join as join3 } from "path";
412
496
  function credentialsDir() {
@@ -426,21 +510,21 @@ function tokenFromEnv() {
426
510
  }
427
511
  async function loadCredentials() {
428
512
  try {
429
- return JSON.parse(await readFile4(credentialsPath(), "utf8"));
513
+ return JSON.parse(await readFile5(credentialsPath(), "utf8"));
430
514
  } catch {
431
515
  return void 0;
432
516
  }
433
517
  }
434
518
  async function saveCredentials(creds) {
435
519
  const path = credentialsPath();
436
- await mkdir2(credentialsDir(), { recursive: true, mode: 448 });
437
- await writeFile2(path, `${JSON.stringify(creds, null, 2)}
520
+ await mkdir4(credentialsDir(), { recursive: true, mode: 448 });
521
+ await writeFile4(path, `${JSON.stringify(creds, null, 2)}
438
522
  `, { mode: 384 });
439
523
  await chmod(path, 384).catch(() => void 0);
440
524
  return path;
441
525
  }
442
526
  async function clearCredentials() {
443
- await rm(credentialsPath(), { force: true });
527
+ await rm2(credentialsPath(), { force: true });
444
528
  }
445
529
  export {
446
530
  ALWAYS_IGNORE,
@@ -448,17 +532,20 @@ export {
448
532
  PROJECT_FILE,
449
533
  buildIgnore,
450
534
  buildProjectConfig,
535
+ buildWpConfig,
451
536
  clearCredentials,
452
537
  credentialsDir,
453
538
  credentialsPath,
454
539
  deployProject,
455
540
  detectRuntime,
456
541
  endpointsFromEnv,
542
+ installWordPress,
457
543
  listFiles,
458
544
  loadCredentials,
459
545
  loadProjectConfig,
460
546
  measure,
461
547
  packDirectory,
548
+ packFiles,
462
549
  projectConfigPath,
463
550
  runShellCommand,
464
551
  saveCredentials,
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/archive.ts","../src/detect.ts","../src/project.ts","../src/deploy.ts","../src/run-command.ts","../src/credentials.ts"],"sourcesContent":["import { readFile, readdir, stat } from 'node:fs/promises';\nimport { join, relative, sep } from 'node:path';\nimport ignore, { type Ignore } from 'ignore';\nimport { create as tarCreate } from 'tar';\n\n/** Always excluded, regardless of ignore files — build artefacts and VCS/OS cruft. */\nexport const ALWAYS_IGNORE = [\n 'node_modules',\n '.git',\n '.next',\n '.nuxt',\n '.venv',\n '__pycache__',\n '.DS_Store',\n '.roxyon',\n 'dist/.cache',\n];\n\nasync function readIgnoreFile(root: string, name: string): Promise<string[]> {\n try {\n return (await readFile(join(root, name), 'utf8')).split(/\\r?\\n/);\n } catch {\n return [];\n }\n}\n\n/** Build the ignore matcher: `.roxyonignore` if present, else `.gitignore`, plus the always-list. */\nexport async function buildIgnore(root: string): Promise<Ignore> {\n const ig = ignore().add(ALWAYS_IGNORE);\n const roxyon = await readIgnoreFile(root, '.roxyonignore');\n if (roxyon.length) ig.add(roxyon);\n else ig.add(await readIgnoreFile(root, '.gitignore'));\n return ig;\n}\n\n/** Recursively list files under `root` that survive the ignore matcher (POSIX-relative paths). */\nexport async function listFiles(root: string, ig: Ignore): Promise<string[]> {\n const files: string[] = [];\n\n async function walk(dir: string): Promise<void> {\n const entries = await readdir(dir, { withFileTypes: true });\n for (const entry of entries) {\n const abs = join(dir, entry.name);\n const rel = relative(root, abs).split(sep).join('/');\n if (!rel || ig.ignores(rel) || ig.ignores(`${rel}/`)) continue;\n if (entry.isDirectory()) {\n await walk(abs);\n } else if (entry.isFile() || entry.isSymbolicLink()) {\n files.push(rel);\n }\n }\n }\n\n await walk(root);\n return files.sort();\n}\n\nexport interface PackResult {\n buffer: Buffer;\n files: string[];\n bytes: number;\n}\n\n/**\n * Create a deterministic gzipped tarball of `root` (minus ignored paths).\n * `prefix`, when given, is prepended to every entry path inside the archive.\n */\nexport async function packDirectory(\n root: string,\n opts: { prefix?: string; maxBytes?: number } = {},\n): Promise<PackResult> {\n const ig = await buildIgnore(root);\n const files = await listFiles(root, ig);\n if (files.length === 0) {\n throw new Error(\n `Nothing to deploy from ${root} — every file is ignored or the folder is empty.`,\n );\n }\n\n const stream = tarCreate(\n {\n gzip: true,\n cwd: root,\n portable: true,\n mtime: new Date(0), // stable -> byte-identical archive for an unchanged tree\n prefix: opts.prefix,\n follow: false,\n },\n files,\n );\n\n const chunks: Buffer[] = [];\n let bytes = 0;\n const max = opts.maxBytes ?? 512 * 1024 * 1024;\n for await (const chunk of stream as AsyncIterable<Buffer>) {\n bytes += chunk.length;\n if (bytes > max) {\n throw new Error(\n `Deploy archive exceeds ${(max / 1024 / 1024) | 0} MB. Add large files to .roxyonignore.`,\n );\n }\n chunks.push(chunk);\n }\n\n return { buffer: Buffer.concat(chunks), files, bytes };\n}\n\n/** Total on-disk size of a set of files. */\nexport async function measure(root: string, files: string[]): Promise<number> {\n let total = 0;\n for (const f of files) {\n try {\n total += (await stat(join(root, f))).size;\n } catch {\n /* ignore */\n }\n }\n return total;\n}\n","import { readFile, readdir } from 'node:fs/promises';\nimport { join } from 'node:path';\nimport type { RuntimeName } from '@roxyon/api-client';\n\nexport interface Detection {\n runtime: RuntimeName;\n /** why we think so — shown to the user, who can override */\n reason: string;\n /** best-guess start command for a server runtime */\n start?: string;\n}\n\nasync function exists(root: string, name: string): Promise<boolean> {\n try {\n await readFile(join(root, name));\n return true;\n } catch {\n return false;\n }\n}\n\nasync function readJson(root: string, name: string): Promise<Record<string, unknown> | undefined> {\n try {\n return JSON.parse(await readFile(join(root, name), 'utf8')) as Record<string, unknown>;\n } catch {\n return undefined;\n }\n}\n\n/** Best-effort project-type detection. Deterministic; the caller confirms. */\nexport async function detectRuntime(root: string): Promise<Detection> {\n const pkg = await readJson(root, 'package.json');\n const hasConfigJson = await exists(root, 'config.json');\n let hasViews = false;\n try {\n hasViews = (await readdir(join(root, 'src', 'views'))).length >= 0;\n } catch {\n hasViews = false;\n }\n\n if (hasConfigJson && hasViews) {\n const deps = {\n ...(pkg?.dependencies as object),\n ...(pkg?.devDependencies as object),\n } as Record<string, string>;\n if (!('next' in deps) && !('nuxt' in deps) && !('express' in deps) && !('fastify' in deps)) {\n return { runtime: 'lumen', reason: 'config.json + src/views/ and no server framework' };\n }\n }\n\n if (pkg) {\n const scripts = (pkg.scripts as Record<string, string>) ?? {};\n const start = scripts.start ? 'npm run start' : scripts.serve ? 'npm run serve' : 'node .';\n const deps = { ...(pkg.dependencies as object), ...(pkg.devDependencies as object) } as Record<\n string,\n string\n >;\n if ('next' in deps)\n return { runtime: 'node', reason: 'Next.js in package.json', start: 'npm run start' };\n if ('nuxt' in deps)\n return { runtime: 'node', reason: 'Nuxt in package.json', start: 'npm run start' };\n return { runtime: 'node', reason: 'package.json present', start };\n }\n\n if ((await exists(root, 'requirements.txt')) || (await exists(root, 'pyproject.toml'))) {\n return {\n runtime: 'python',\n reason: 'requirements.txt / pyproject.toml',\n start: 'gunicorn app:app',\n };\n }\n\n if ((await exists(root, 'composer.json')) || (await exists(root, 'server.php'))) {\n return { runtime: 'php', reason: 'composer.json / server.php', start: 'php8.4 server.php' };\n }\n\n return { runtime: 'lumen', reason: 'no framework markers — treating as a static site' };\n}\n","import { mkdir, readFile, writeFile } from 'node:fs/promises';\nimport { basename, dirname, resolve } from 'node:path';\nimport { type DeployKind, LUMEN_BUILD, RUNTIMES, type RuntimeName } from '@roxyon/api-client';\n\nexport type { DeployKind };\n\nexport interface ProjectConfig {\n name: string;\n /** `Applications.objectId`, filled after the first `app`-kind deploy. */\n application?: string;\n /** Route host — `Domains.Name`. */\n host: string;\n /** Sub-path under the host's docroot / source tree. `\"\"` = root. */\n folder: string;\n runtime: RuntimeName;\n runtimeVersion?: string;\n preset?: string;\n kind: DeployKind;\n /** Local build command (LumenJS / static). Empty for server runtimes. */\n build?: string;\n /** Directory the build writes to (relative to project root). */\n outDir?: string;\n /** Web-process start command (app kind only). */\n start?: string;\n /** Serve the app on the host instead of the host's own files. */\n public?: boolean;\n}\n\nexport const PROJECT_FILE = 'roxyon.json';\n\nexport function projectConfigPath(cwd: string = process.cwd()): string {\n return resolve(cwd, PROJECT_FILE);\n}\n\nexport async function loadProjectConfig(\n cwd: string = process.cwd(),\n): Promise<ProjectConfig | undefined> {\n try {\n return JSON.parse(await readFile(projectConfigPath(cwd), 'utf8')) as ProjectConfig;\n } catch {\n return undefined;\n }\n}\n\nexport async function saveProjectConfig(\n config: ProjectConfig,\n cwd: string = process.cwd(),\n): Promise<string> {\n const path = projectConfigPath(cwd);\n await mkdir(dirname(path), { recursive: true });\n await writeFile(path, `${JSON.stringify(config, null, 2)}\\n`);\n return path;\n}\n\nexport interface BuildConfigInput {\n cwd: string;\n runtime: RuntimeName;\n host: string;\n folder?: string;\n name?: string;\n start?: string;\n}\n\n/**\n * Produce a `ProjectConfig` from a resolved runtime + host. Shared by the CLI's\n * `roxyon init` and the MCP `roxyon_init` tool so both write the same shape.\n */\nexport function buildProjectConfig(input: BuildConfigInput): ProjectConfig {\n const folder = String(input.folder ?? '').replace(/^\\/+|\\/+$/g, '');\n const config: ProjectConfig = {\n name: input.name ?? basename(input.cwd),\n host: input.host,\n folder,\n runtime: input.runtime,\n kind: input.runtime === 'lumen' ? 'static' : 'app',\n };\n\n if (input.runtime === 'lumen') {\n config.build = LUMEN_BUILD.command;\n config.outDir = LUMEN_BUILD.outDir;\n } else {\n const spec = RUNTIMES[input.runtime];\n config.runtimeVersion = spec.defaultVersion;\n config.preset = spec.presets[0]?.[0];\n config.start = input.start ?? spec.command;\n config.public = true;\n config.build = '';\n }\n return config;\n}\n","import { access } from 'node:fs/promises';\nimport { resolve } from 'node:path';\nimport {\n type AccountApp,\n RUNTIMES,\n type Roxyon,\n RoxyonApiError,\n isAuthError,\n} from '@roxyon/api-client';\nimport { packDirectory } from './archive.js';\nimport { type ProjectConfig, loadProjectConfig, saveProjectConfig } from './project.js';\nimport { runShellCommand } from './run-command.js';\n\nexport interface DeployReporter {\n /** A high-level phase change (\"Building\", \"Uploading\", \"Live\"). */\n step?: (msg: string) => void;\n /** A single build- or runtime-log line. */\n log?: (line: string) => void;\n}\n\nexport interface DeployParams {\n cwd: string;\n /** An authenticated client. */\n roxyon: Roxyon;\n /** Subscription objectId / Name to prefer when the account has several. */\n preferredSubscription?: string;\n /** Run the configured build command (default true). */\n build?: boolean;\n /** For app deploys: poll until running/failed (default true). */\n follow?: boolean;\n pollMs?: number;\n timeoutMs?: number;\n reporter?: DeployReporter;\n /** Override the build-command runner (default: spawn a shell). */\n runCommand?: (command: string, cwd: string, reporter?: DeployReporter) => Promise<number>;\n}\n\nexport type DeployOutcome =\n | { kind: 'static'; ok: true; url: string; files: number; config: ProjectConfig }\n | {\n kind: 'app';\n ok: true;\n application: string;\n status: string;\n revision: number;\n url?: string;\n config: ProjectConfig;\n logs: string[];\n }\n | {\n kind: 'app';\n ok: false;\n application: string;\n status: string;\n error: string;\n config: ProjectConfig;\n logs: string[];\n };\n\nexport class DeployError extends Error {\n readonly code:\n | 'no-config'\n | 'auth'\n | 'bad-host'\n | 'build-failed'\n | 'no-build-output'\n | 'upload-failed'\n | 'create-failed';\n constructor(message: string, code: DeployError['code']) {\n super(message);\n this.name = 'DeployError';\n this.code = code;\n }\n}\n\nconst sleep = (ms: number) => new Promise((r) => setTimeout(r, ms));\n\n/**\n * Build (if configured), archive, upload, and — for app runtimes — poll until\n * the deploy settles. Self-contained: one `/account/context` call validates the\n * host (works with a session token or a `roxp_` PAT), and the console creates\n * the application on the first deploy — the new id is written back to\n * `roxyon.json`.\n */\nexport async function deployProject(params: DeployParams): Promise<DeployOutcome> {\n const { cwd, roxyon, reporter } = params;\n const runCommand = params.runCommand ?? runShellCommand;\n\n const config = await loadProjectConfig(cwd);\n if (!config) {\n throw new DeployError('No roxyon.json in this directory — run init first.', 'no-config');\n }\n\n // One call — works with a session token or a PAT.\n const ctx = await roxyon.account.context().catch((e) => {\n if (isAuthError(e)) throw new DeployError('Session expired — sign in again.', 'auth');\n throw e;\n });\n const domain = ctx.domains.find((d) => d.name === config.host);\n if (!domain) {\n throw new DeployError(\n `Host \"${config.host}\" is not on this account (have: ${\n ctx.domains.map((d) => d.name).join(', ') || 'none'\n }).`,\n 'bad-host',\n );\n }\n\n // ---- build ----\n if (config.build && params.build !== false) {\n reporter?.step?.(`Build: ${config.build}`);\n const code = await runCommand(config.build, cwd, reporter);\n if (code !== 0) throw new DeployError(`Build command exited ${code}.`, 'build-failed');\n }\n\n // ---- pack ----\n const artifactDir = config.kind === 'static' && config.outDir ? resolve(cwd, config.outDir) : cwd;\n if (config.kind === 'static' && config.outDir) {\n await access(artifactDir).catch(() => {\n throw new DeployError(\n `Build output \"${config.outDir}\" not found — did the build run?`,\n 'no-build-output',\n );\n });\n }\n reporter?.step?.(`Packing ${artifactDir === cwd ? '.' : config.outDir}`);\n const pack = await packDirectory(artifactDir);\n reporter?.log?.(`${pack.files.length} files · ${(pack.bytes / 1024).toFixed(0)} KB`);\n\n // ---- static ----\n if (config.kind === 'static') {\n reporter?.step?.(`Uploading to ${config.host}${config.folder ? `/${config.folder}` : ''}`);\n let res: Awaited<ReturnType<typeof roxyon.sites.deploy>>;\n try {\n res = await roxyon.sites.deploy(config.host, config.folder, pack.buffer);\n } catch (err) {\n throw new DeployError(\n err instanceof RoxyonApiError ? err.message : String(err),\n 'upload-failed',\n );\n }\n const url = `https://${res.host}${res.path && res.path !== '/' ? res.path : ''}`;\n reporter?.step?.(`Live — ${url}`);\n return { kind: 'static', ok: true, url, files: res.files ?? pack.files.length, config };\n }\n\n // ---- app ----\n // The console's /applications/deploy creates the app rows on the first deploy\n // (no `application` id yet) from the host + runtime params — one trusted call,\n // so a PAT-only CI job needs no BaaS write access.\n const runtime = config.runtime === 'lumen' ? 'node' : config.runtime;\n const spec = RUNTIMES[runtime];\n const target = config.application ?? {\n host: config.host,\n folder: config.folder,\n runtime,\n runtimeVersion: config.runtimeVersion ?? spec.defaultVersion,\n preset: config.preset ?? spec.presets[0]?.[0] ?? runtime,\n command: config.start ?? spec.command,\n public: config.public ?? true,\n };\n\n reporter?.step?.(\n config.application ? 'Uploading source' : 'Creating the application & uploading',\n );\n let revision = 0;\n let appId: string;\n try {\n const res = await roxyon.applications.uploadSource(target, pack.buffer);\n revision = res.configRevision ?? 0;\n appId = res.application;\n if (res.created && appId && appId !== config.application) {\n config.application = appId;\n await saveProjectConfig(config, cwd);\n reporter?.log?.(`application ${appId} created`);\n }\n } catch (err) {\n throw new DeployError(\n err instanceof RoxyonApiError ? err.message : String(err),\n config.application ? 'upload-failed' : 'create-failed',\n );\n }\n\n const url = config.public\n ? `https://${config.host}${config.folder ? `/${config.folder}` : ''}`\n : undefined;\n\n if (params.follow === false) {\n reporter?.step?.(`Build queued (revision ${revision})`);\n return {\n kind: 'app',\n ok: true,\n application: appId,\n status: 'building',\n revision,\n url,\n config,\n logs: [],\n };\n }\n\n return watch(roxyon, appId, {\n reporter,\n url,\n config,\n pollMs: params.pollMs ?? 3000,\n timeoutMs: params.timeoutMs ?? 10 * 60 * 1000,\n });\n}\n\nasync function watch(\n roxyon: Roxyon,\n appId: string,\n opts: {\n reporter?: DeployReporter;\n url?: string;\n config: ProjectConfig;\n pollMs: number;\n timeoutMs: number;\n },\n): Promise<DeployOutcome> {\n const started = Date.now();\n const seen = new Set<string>();\n const collected: string[] = [];\n let lastStatus = '';\n\n opts.reporter?.step?.('Building');\n while (true) {\n await sleep(opts.pollMs);\n\n let app: AccountApp | undefined;\n try {\n app = await roxyon.account.getApp(appId);\n } catch {\n /* transient */\n }\n if (!app) {\n if (Date.now() - started > opts.timeoutMs) break;\n continue;\n }\n\n const cfg = app.configRevision;\n const applied = app.appliedRevision;\n const status = app.status;\n if (status !== lastStatus) {\n opts.reporter?.step?.(`${status || 'pending'} (${applied}/${cfg})`);\n lastStatus = status;\n }\n\n try {\n for (const line of await roxyon.applications.logs(appId, 40)) {\n if (line && !seen.has(line)) {\n seen.add(line);\n collected.push(line);\n opts.reporter?.log?.(line);\n }\n }\n } catch {\n /* transient */\n }\n\n if (cfg <= applied && status === 'running') {\n opts.reporter?.step?.(`Live${opts.url ? ` — ${opts.url}` : ''}`);\n return {\n kind: 'app',\n ok: true,\n application: appId,\n status: 'running',\n revision: applied,\n url: opts.url,\n config: opts.config,\n logs: collected,\n };\n }\n if (status === 'failed') {\n return {\n kind: 'app',\n ok: false,\n application: appId,\n status: 'failed',\n error: (app.lastError || 'Deploy failed.').trim(),\n config: opts.config,\n logs: collected,\n };\n }\n if (Date.now() - started > opts.timeoutMs) break;\n }\n\n return {\n kind: 'app',\n ok: false,\n application: appId,\n status: 'timeout',\n error: 'Still building after the timeout — check `roxyon logs`.',\n config: opts.config,\n logs: collected,\n };\n}\n","import { spawn } from 'node:child_process';\nimport type { DeployReporter } from './deploy.js';\n\n/**\n * Default build-command runner: spawn a shell, stream output to the reporter.\n * Callers in a non-local context (a hosted server) can pass their own.\n */\nexport function runShellCommand(\n command: string,\n cwd: string,\n reporter?: DeployReporter,\n): Promise<number> {\n return new Promise((resolvePromise) => {\n const child = spawn(command, { cwd, shell: true, stdio: ['ignore', 'pipe', 'pipe'] });\n const pipe = (buf: Buffer) => {\n for (const line of buf.toString('utf8').split('\\n')) {\n if (line.trim()) reporter?.log?.(line);\n }\n };\n child.stdout?.on('data', pipe);\n child.stderr?.on('data', pipe);\n child.on('close', (code) => resolvePromise(code ?? 0));\n child.on('error', () => resolvePromise(127));\n });\n}\n","import { chmod, mkdir, readFile, rm, writeFile } from 'node:fs/promises';\nimport { homedir } from 'node:os';\nimport { join } from 'node:path';\n\n/**\n * Stored login for Roxyon tooling — `~/.roxyon/config.json`, mode 0600. Written\n * by `roxyon login`; read by the CLI and the MCP server alike.\n */\nexport interface Credentials {\n sessionToken: string;\n refreshToken?: string;\n userId: string;\n email?: string;\n /** Preferred subscription (objectId / Name) when the account has several. */\n subscription?: string;\n savedAt: string;\n}\n\nexport interface Endpoints {\n apiUrl?: string;\n consoleUrl?: string;\n}\n\nexport function credentialsDir(): string {\n return join(homedir(), '.roxyon');\n}\nexport function credentialsPath(): string {\n return join(credentialsDir(), 'config.json');\n}\n\n/** Endpoint overrides from the environment (`ROXYON_API_URL`, `ROXYON_CONSOLE_URL`). */\nexport function endpointsFromEnv(): Endpoints {\n return {\n apiUrl: process.env.ROXYON_API_URL,\n consoleUrl: process.env.ROXYON_CONSOLE_URL,\n };\n}\n\n/** A token supplied out-of-band (CI, or an MCP host env). Skips the stored login. */\nexport function tokenFromEnv(): string | undefined {\n return process.env.ROXYON_TOKEN || undefined;\n}\n\nexport async function loadCredentials(): Promise<Credentials | undefined> {\n try {\n return JSON.parse(await readFile(credentialsPath(), 'utf8')) as Credentials;\n } catch {\n return undefined;\n }\n}\n\nexport async function saveCredentials(creds: Credentials): Promise<string> {\n const path = credentialsPath();\n await mkdir(credentialsDir(), { recursive: true, mode: 0o700 });\n await writeFile(path, `${JSON.stringify(creds, null, 2)}\\n`, { mode: 0o600 });\n await chmod(path, 0o600).catch(() => undefined);\n return path;\n}\n\nexport async function clearCredentials(): Promise<void> {\n await rm(credentialsPath(), { force: true });\n}\n"],"mappings":"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","names":["readFile","readdir","join","readFile","resolve","RUNTIMES","resolve","url","RUNTIMES","mkdir","readFile","writeFile","join"]}
1
+ {"version":3,"sources":["../src/archive.ts","../src/detect.ts","../src/project.ts","../src/deploy.ts","../src/run-command.ts","../src/wordpress.ts","../src/credentials.ts"],"sourcesContent":["import { mkdir, mkdtemp, readFile, readdir, rm, stat, writeFile } from 'node:fs/promises';\nimport { tmpdir } from 'node:os';\nimport { dirname, isAbsolute, join, relative, sep } from 'node:path';\nimport ignore, { type Ignore } from 'ignore';\nimport { create as tarCreate } from 'tar';\n\n/** Always excluded, regardless of ignore files — build artefacts and VCS/OS cruft. */\nexport const ALWAYS_IGNORE = [\n 'node_modules',\n '.git',\n '.next',\n '.nuxt',\n '.venv',\n '__pycache__',\n '.DS_Store',\n '.roxyon',\n 'dist/.cache',\n];\n\nasync function readIgnoreFile(root: string, name: string): Promise<string[]> {\n try {\n return (await readFile(join(root, name), 'utf8')).split(/\\r?\\n/);\n } catch {\n return [];\n }\n}\n\n/** Build the ignore matcher: `.roxyonignore` if present, else `.gitignore`, plus the always-list. */\nexport async function buildIgnore(root: string): Promise<Ignore> {\n const ig = ignore().add(ALWAYS_IGNORE);\n const roxyon = await readIgnoreFile(root, '.roxyonignore');\n if (roxyon.length) ig.add(roxyon);\n else ig.add(await readIgnoreFile(root, '.gitignore'));\n return ig;\n}\n\n/** Recursively list files under `root` that survive the ignore matcher (POSIX-relative paths). */\nexport async function listFiles(root: string, ig: Ignore): Promise<string[]> {\n const files: string[] = [];\n\n async function walk(dir: string): Promise<void> {\n const entries = await readdir(dir, { withFileTypes: true });\n for (const entry of entries) {\n const abs = join(dir, entry.name);\n const rel = relative(root, abs).split(sep).join('/');\n if (!rel || ig.ignores(rel) || ig.ignores(`${rel}/`)) continue;\n if (entry.isDirectory()) {\n await walk(abs);\n } else if (entry.isFile() || entry.isSymbolicLink()) {\n files.push(rel);\n }\n }\n }\n\n await walk(root);\n return files.sort();\n}\n\nexport interface PackResult {\n buffer: Buffer;\n files: string[];\n bytes: number;\n}\n\n/**\n * Create a deterministic gzipped tarball of `root` (minus ignored paths).\n * `prefix`, when given, is prepended to every entry path inside the archive.\n */\nexport async function packDirectory(\n root: string,\n opts: { prefix?: string; maxBytes?: number } = {},\n): Promise<PackResult> {\n const ig = await buildIgnore(root);\n const files = await listFiles(root, ig);\n if (files.length === 0) {\n throw new Error(\n `Nothing to deploy from ${root} — every file is ignored or the folder is empty.`,\n );\n }\n\n const stream = tarCreate(\n {\n gzip: true,\n cwd: root,\n portable: true,\n mtime: new Date(0), // stable -> byte-identical archive for an unchanged tree\n prefix: opts.prefix,\n follow: false,\n },\n files,\n );\n\n const chunks: Buffer[] = [];\n let bytes = 0;\n const max = opts.maxBytes ?? 512 * 1024 * 1024;\n for await (const chunk of stream as AsyncIterable<Buffer>) {\n bytes += chunk.length;\n if (bytes > max) {\n throw new Error(\n `Deploy archive exceeds ${(max / 1024 / 1024) | 0} MB. Add large files to .roxyonignore.`,\n );\n }\n chunks.push(chunk);\n }\n\n return { buffer: Buffer.concat(chunks), files, bytes };\n}\n\nexport interface FileEntry {\n /** POSIX-style relative path inside the site, e.g. `index.html`, `about/index.html`. */\n path: string;\n /** File body — UTF-8 text, or base64 when `encoding: 'base64'`. */\n content: string;\n encoding?: 'utf8' | 'base64';\n}\n\n/** A path is safe if it is relative, has no `..`/`.` segment, and no NUL. */\nfunction safeEntryPath(p: string): string | null {\n const norm = p.replace(/\\\\/g, '/').replace(/^\\.\\//, '');\n if (!norm || norm.startsWith('/') || isAbsolute(norm) || norm.includes('\\0')) return null;\n if (norm.split('/').some((seg) => seg === '..' || seg === '.' || seg === '')) return null;\n return norm;\n}\n\n/**\n * Pack an in-memory list of `{ path, content }` into the same deterministic\n * gzip tarball {@link packDirectory} produces — materialise to a temp dir, then\n * reuse that path so the archive format and determinism are identical. Rejects\n * absolute paths and `..` traversal; `maxBytes` caps the total *decoded* content.\n */\nexport async function packFiles(\n entries: FileEntry[],\n opts: { prefix?: string; maxBytes?: number } = {},\n): Promise<PackResult> {\n if (entries.length === 0) throw new Error('No files to deploy.');\n\n const seen = new Set<string>();\n const clean: { path: string; buf: Buffer }[] = [];\n let total = 0;\n for (const e of entries) {\n const rel = safeEntryPath(e.path);\n if (!rel) throw new Error(`Unsafe file path: ${JSON.stringify(e.path)}`);\n if (seen.has(rel)) throw new Error(`Duplicate file path: ${rel}`);\n seen.add(rel);\n const buf = Buffer.from(e.content, e.encoding === 'base64' ? 'base64' : 'utf8');\n total += buf.length;\n clean.push({ path: rel, buf });\n }\n const max = opts.maxBytes ?? 512 * 1024 * 1024;\n if (total > max) {\n throw new Error(\n `Content is ${(total / 1024 / 1024).toFixed(1)} MB, which exceeds the ${(max / 1024 / 1024).toFixed(1)} MB limit.`,\n );\n }\n\n const dir = await mkdtemp(join(tmpdir(), 'roxyon-content-'));\n try {\n for (const { path: rel, buf } of clean) {\n const abs = join(dir, rel);\n await mkdir(dirname(abs), { recursive: true });\n await writeFile(abs, buf);\n }\n return await packDirectory(dir, opts);\n } finally {\n await rm(dir, { recursive: true, force: true });\n }\n}\n\n/** Total on-disk size of a set of files. */\nexport async function measure(root: string, files: string[]): Promise<number> {\n let total = 0;\n for (const f of files) {\n try {\n total += (await stat(join(root, f))).size;\n } catch {\n /* ignore */\n }\n }\n return total;\n}\n","import { readFile, readdir } from 'node:fs/promises';\nimport { join } from 'node:path';\nimport type { RuntimeName } from '@roxyon/api-client';\n\nexport interface Detection {\n runtime: RuntimeName;\n /** why we think so — shown to the user, who can override */\n reason: string;\n /** best-guess start command for a server runtime */\n start?: string;\n}\n\nasync function exists(root: string, name: string): Promise<boolean> {\n try {\n await readFile(join(root, name));\n return true;\n } catch {\n return false;\n }\n}\n\nasync function readJson(root: string, name: string): Promise<Record<string, unknown> | undefined> {\n try {\n return JSON.parse(await readFile(join(root, name), 'utf8')) as Record<string, unknown>;\n } catch {\n return undefined;\n }\n}\n\n/** Best-effort project-type detection. Deterministic; the caller confirms. */\nexport async function detectRuntime(root: string): Promise<Detection> {\n const pkg = await readJson(root, 'package.json');\n const hasConfigJson = await exists(root, 'config.json');\n let hasViews = false;\n try {\n hasViews = (await readdir(join(root, 'src', 'views'))).length >= 0;\n } catch {\n hasViews = false;\n }\n\n if (hasConfigJson && hasViews) {\n const deps = {\n ...(pkg?.dependencies as object),\n ...(pkg?.devDependencies as object),\n } as Record<string, string>;\n if (!('next' in deps) && !('nuxt' in deps) && !('express' in deps) && !('fastify' in deps)) {\n return { runtime: 'lumen', reason: 'config.json + src/views/ and no server framework' };\n }\n }\n\n if (pkg) {\n const scripts = (pkg.scripts as Record<string, string>) ?? {};\n const start = scripts.start ? 'npm run start' : scripts.serve ? 'npm run serve' : 'node .';\n const deps = { ...(pkg.dependencies as object), ...(pkg.devDependencies as object) } as Record<\n string,\n string\n >;\n if ('next' in deps)\n return { runtime: 'node', reason: 'Next.js in package.json', start: 'npm run start' };\n if ('nuxt' in deps)\n return { runtime: 'node', reason: 'Nuxt in package.json', start: 'npm run start' };\n return { runtime: 'node', reason: 'package.json present', start };\n }\n\n if ((await exists(root, 'requirements.txt')) || (await exists(root, 'pyproject.toml'))) {\n return {\n runtime: 'python',\n reason: 'requirements.txt / pyproject.toml',\n start: 'gunicorn app:app',\n };\n }\n\n if ((await exists(root, 'composer.json')) || (await exists(root, 'server.php'))) {\n return { runtime: 'php', reason: 'composer.json / server.php', start: 'php8.4 server.php' };\n }\n\n return { runtime: 'lumen', reason: 'no framework markers — treating as a static site' };\n}\n","import { mkdir, readFile, writeFile } from 'node:fs/promises';\nimport { basename, dirname, resolve } from 'node:path';\nimport { type DeployKind, LUMEN_BUILD, RUNTIMES, type RuntimeName } from '@roxyon/api-client';\n\nexport type { DeployKind };\n\nexport interface ProjectConfig {\n name: string;\n /** `Applications.objectId`, filled after the first `app`-kind deploy. */\n application?: string;\n /** Route host — `Domains.Name`. */\n host: string;\n /** Sub-path under the host's docroot / source tree. `\"\"` = root. */\n folder: string;\n runtime: RuntimeName;\n runtimeVersion?: string;\n preset?: string;\n kind: DeployKind;\n /** Local build command (LumenJS / static). Empty for server runtimes. */\n build?: string;\n /** Directory the build writes to (relative to project root). */\n outDir?: string;\n /** Web-process start command (app kind only). */\n start?: string;\n /** Serve the app on the host instead of the host's own files. */\n public?: boolean;\n}\n\nexport const PROJECT_FILE = 'roxyon.json';\n\nexport function projectConfigPath(cwd: string = process.cwd()): string {\n return resolve(cwd, PROJECT_FILE);\n}\n\nexport async function loadProjectConfig(\n cwd: string = process.cwd(),\n): Promise<ProjectConfig | undefined> {\n try {\n return JSON.parse(await readFile(projectConfigPath(cwd), 'utf8')) as ProjectConfig;\n } catch {\n return undefined;\n }\n}\n\nexport async function saveProjectConfig(\n config: ProjectConfig,\n cwd: string = process.cwd(),\n): Promise<string> {\n const path = projectConfigPath(cwd);\n await mkdir(dirname(path), { recursive: true });\n await writeFile(path, `${JSON.stringify(config, null, 2)}\\n`);\n return path;\n}\n\nexport interface BuildConfigInput {\n cwd: string;\n runtime: RuntimeName;\n host: string;\n folder?: string;\n name?: string;\n start?: string;\n}\n\n/**\n * Produce a `ProjectConfig` from a resolved runtime + host. Shared by the CLI's\n * `roxyon init` and the MCP `roxyon_init` tool so both write the same shape.\n */\nexport function buildProjectConfig(input: BuildConfigInput): ProjectConfig {\n const folder = String(input.folder ?? '').replace(/^\\/+|\\/+$/g, '');\n const config: ProjectConfig = {\n name: input.name ?? basename(input.cwd),\n host: input.host,\n folder,\n runtime: input.runtime,\n kind: input.runtime === 'lumen' ? 'static' : 'app',\n };\n\n if (input.runtime === 'lumen') {\n config.build = LUMEN_BUILD.command;\n config.outDir = LUMEN_BUILD.outDir;\n } else {\n const spec = RUNTIMES[input.runtime];\n config.runtimeVersion = spec.defaultVersion;\n config.preset = spec.presets[0]?.[0];\n config.start = input.start ?? spec.command;\n config.public = true;\n config.build = '';\n }\n return config;\n}\n","import { access } from 'node:fs/promises';\nimport { resolve } from 'node:path';\nimport {\n type AccountApp,\n RUNTIMES,\n type Roxyon,\n RoxyonApiError,\n isAuthError,\n} from '@roxyon/api-client';\nimport { packDirectory } from './archive.js';\nimport { type ProjectConfig, loadProjectConfig, saveProjectConfig } from './project.js';\nimport { runShellCommand } from './run-command.js';\n\nexport interface DeployReporter {\n /** A high-level phase change (\"Building\", \"Uploading\", \"Live\"). */\n step?: (msg: string) => void;\n /** A single build- or runtime-log line. */\n log?: (line: string) => void;\n}\n\nexport interface DeployParams {\n cwd: string;\n /** An authenticated client. */\n roxyon: Roxyon;\n /** Subscription objectId / Name to prefer when the account has several. */\n preferredSubscription?: string;\n /** Run the configured build command (default true). */\n build?: boolean;\n /** For app deploys: poll until running/failed (default true). */\n follow?: boolean;\n pollMs?: number;\n timeoutMs?: number;\n reporter?: DeployReporter;\n /** Override the build-command runner (default: spawn a shell). */\n runCommand?: (command: string, cwd: string, reporter?: DeployReporter) => Promise<number>;\n}\n\nexport type DeployOutcome =\n | { kind: 'static'; ok: true; url: string; files: number; config: ProjectConfig }\n | {\n kind: 'app';\n ok: true;\n application: string;\n status: string;\n revision: number;\n url?: string;\n config: ProjectConfig;\n logs: string[];\n }\n | {\n kind: 'app';\n ok: false;\n application: string;\n status: string;\n error: string;\n config: ProjectConfig;\n logs: string[];\n };\n\nexport class DeployError extends Error {\n readonly code:\n | 'no-config'\n | 'auth'\n | 'bad-host'\n | 'build-failed'\n | 'no-build-output'\n | 'upload-failed'\n | 'create-failed';\n constructor(message: string, code: DeployError['code']) {\n super(message);\n this.name = 'DeployError';\n this.code = code;\n }\n}\n\nconst sleep = (ms: number) => new Promise((r) => setTimeout(r, ms));\n\n/**\n * Build (if configured), archive, upload, and — for app runtimes — poll until\n * the deploy settles. Self-contained: one `/account/context` call validates the\n * host (works with a session token or a `roxp_` PAT), and the console creates\n * the application on the first deploy — the new id is written back to\n * `roxyon.json`.\n */\nexport async function deployProject(params: DeployParams): Promise<DeployOutcome> {\n const { cwd, roxyon, reporter } = params;\n const runCommand = params.runCommand ?? runShellCommand;\n\n const config = await loadProjectConfig(cwd);\n if (!config) {\n throw new DeployError('No roxyon.json in this directory — run init first.', 'no-config');\n }\n\n // One call — works with a session token or a PAT.\n const ctx = await roxyon.account.context().catch((e) => {\n if (isAuthError(e)) throw new DeployError('Session expired — sign in again.', 'auth');\n throw e;\n });\n const domain = ctx.domains.find((d) => d.name === config.host);\n if (!domain) {\n throw new DeployError(\n `Host \"${config.host}\" is not on this account (have: ${\n ctx.domains.map((d) => d.name).join(', ') || 'none'\n }).`,\n 'bad-host',\n );\n }\n\n // ---- build ----\n if (config.build && params.build !== false) {\n reporter?.step?.(`Build: ${config.build}`);\n const code = await runCommand(config.build, cwd, reporter);\n if (code !== 0) throw new DeployError(`Build command exited ${code}.`, 'build-failed');\n }\n\n // ---- pack ----\n const artifactDir = config.kind === 'static' && config.outDir ? resolve(cwd, config.outDir) : cwd;\n if (config.kind === 'static' && config.outDir) {\n await access(artifactDir).catch(() => {\n throw new DeployError(\n `Build output \"${config.outDir}\" not found — did the build run?`,\n 'no-build-output',\n );\n });\n }\n reporter?.step?.(`Packing ${artifactDir === cwd ? '.' : config.outDir}`);\n const pack = await packDirectory(artifactDir);\n reporter?.log?.(`${pack.files.length} files · ${(pack.bytes / 1024).toFixed(0)} KB`);\n\n // ---- static ----\n if (config.kind === 'static') {\n reporter?.step?.(`Uploading to ${config.host}${config.folder ? `/${config.folder}` : ''}`);\n let res: Awaited<ReturnType<typeof roxyon.sites.deploy>>;\n try {\n res = await roxyon.sites.deploy(config.host, config.folder, pack.buffer);\n } catch (err) {\n throw new DeployError(\n err instanceof RoxyonApiError ? err.message : String(err),\n 'upload-failed',\n );\n }\n const url = `https://${res.host}${res.path && res.path !== '/' ? res.path : ''}`;\n reporter?.step?.(`Live — ${url}`);\n return { kind: 'static', ok: true, url, files: res.files ?? pack.files.length, config };\n }\n\n // ---- app ----\n // The console's /applications/deploy creates the app rows on the first deploy\n // (no `application` id yet) from the host + runtime params — one trusted call,\n // so a PAT-only CI job needs no BaaS write access.\n const runtime = config.runtime === 'lumen' ? 'node' : config.runtime;\n const spec = RUNTIMES[runtime];\n const target = config.application ?? {\n host: config.host,\n folder: config.folder,\n runtime,\n runtimeVersion: config.runtimeVersion ?? spec.defaultVersion,\n preset: config.preset ?? spec.presets[0]?.[0] ?? runtime,\n command: config.start ?? spec.command,\n public: config.public ?? true,\n };\n\n reporter?.step?.(\n config.application ? 'Uploading source' : 'Creating the application & uploading',\n );\n let revision = 0;\n let appId: string;\n try {\n const res = await roxyon.applications.uploadSource(target, pack.buffer);\n revision = res.configRevision ?? 0;\n appId = res.application;\n if (res.created && appId && appId !== config.application) {\n config.application = appId;\n await saveProjectConfig(config, cwd);\n reporter?.log?.(`application ${appId} created`);\n }\n } catch (err) {\n throw new DeployError(\n err instanceof RoxyonApiError ? err.message : String(err),\n config.application ? 'upload-failed' : 'create-failed',\n );\n }\n\n const url = config.public\n ? `https://${config.host}${config.folder ? `/${config.folder}` : ''}`\n : undefined;\n\n if (params.follow === false) {\n reporter?.step?.(`Build queued (revision ${revision})`);\n return {\n kind: 'app',\n ok: true,\n application: appId,\n status: 'building',\n revision,\n url,\n config,\n logs: [],\n };\n }\n\n return watch(roxyon, appId, {\n reporter,\n url,\n config,\n pollMs: params.pollMs ?? 3000,\n timeoutMs: params.timeoutMs ?? 10 * 60 * 1000,\n });\n}\n\nasync function watch(\n roxyon: Roxyon,\n appId: string,\n opts: {\n reporter?: DeployReporter;\n url?: string;\n config: ProjectConfig;\n pollMs: number;\n timeoutMs: number;\n },\n): Promise<DeployOutcome> {\n const started = Date.now();\n const seen = new Set<string>();\n const collected: string[] = [];\n let lastStatus = '';\n\n opts.reporter?.step?.('Building');\n while (true) {\n await sleep(opts.pollMs);\n\n let app: AccountApp | undefined;\n try {\n app = await roxyon.account.getApp(appId);\n } catch {\n /* transient */\n }\n if (!app) {\n if (Date.now() - started > opts.timeoutMs) break;\n continue;\n }\n\n const cfg = app.configRevision;\n const applied = app.appliedRevision;\n const status = app.status;\n if (status !== lastStatus) {\n opts.reporter?.step?.(`${status || 'pending'} (${applied}/${cfg})`);\n lastStatus = status;\n }\n\n try {\n for (const line of await roxyon.applications.logs(appId, 40)) {\n if (line && !seen.has(line)) {\n seen.add(line);\n collected.push(line);\n opts.reporter?.log?.(line);\n }\n }\n } catch {\n /* transient */\n }\n\n if (cfg <= applied && status === 'running') {\n opts.reporter?.step?.(`Live${opts.url ? ` — ${opts.url}` : ''}`);\n return {\n kind: 'app',\n ok: true,\n application: appId,\n status: 'running',\n revision: applied,\n url: opts.url,\n config: opts.config,\n logs: collected,\n };\n }\n if (status === 'failed') {\n return {\n kind: 'app',\n ok: false,\n application: appId,\n status: 'failed',\n error: (app.lastError || 'Deploy failed.').trim(),\n config: opts.config,\n logs: collected,\n };\n }\n if (Date.now() - started > opts.timeoutMs) break;\n }\n\n return {\n kind: 'app',\n ok: false,\n application: appId,\n status: 'timeout',\n error: 'Still building after the timeout — check `roxyon logs`.',\n config: opts.config,\n logs: collected,\n };\n}\n","import { spawn } from 'node:child_process';\nimport type { DeployReporter } from './deploy.js';\n\n/**\n * Default build-command runner: spawn a shell, stream output to the reporter.\n * Callers in a non-local context (a hosted server) can pass their own.\n */\nexport function runShellCommand(\n command: string,\n cwd: string,\n reporter?: DeployReporter,\n): Promise<number> {\n return new Promise((resolvePromise) => {\n const child = spawn(command, { cwd, shell: true, stdio: ['ignore', 'pipe', 'pipe'] });\n const pipe = (buf: Buffer) => {\n for (const line of buf.toString('utf8').split('\\n')) {\n if (line.trim()) reporter?.log?.(line);\n }\n };\n child.stdout?.on('data', pipe);\n child.stderr?.on('data', pipe);\n child.on('close', (code) => resolvePromise(code ?? 0));\n child.on('error', () => resolvePromise(127));\n });\n}\n","import { mkdir, readFile, writeFile } from 'node:fs/promises';\nimport { Readable } from 'node:stream';\nimport { extract } from 'tar';\n\nconst WORDPRESS_TARBALL_URL = 'https://wordpress.org/latest.tar.gz';\nconst WORDPRESS_SALTS_URL = 'https://api.wordpress.org/secret-key/1.1/salt/';\nconst PLACEHOLDER = 'put your unique phrase here';\n\nexport interface WordPressDbCredentials {\n name: string;\n username: string;\n password: string;\n /** `host:port`, e.g. `10.0.0.2:6033` — WordPress's `DB_HOST` accepts this form directly. */\n host: string;\n}\n\nexport interface InstallWordPressOptions {\n /** Destination directory — created if missing. Must be empty or not yet exist. */\n dir: string;\n db: WordPressDbCredentials;\n /** Default `wp_`. */\n tablePrefix?: string;\n /** Override for tests — must resolve like the global `fetch`. */\n fetchImpl?: typeof fetch;\n}\n\nexport interface InstallWordPressResult {\n dir: string;\n}\n\n/**\n * Download WordPress core into `dir` and write a working `wp-config.php`\n * against the given database. Does not touch the network beyond\n * wordpress.org/api.wordpress.org, and does not deploy anywhere — pack `dir`\n * with {@link packDirectory} and upload with `roxyon.sites.deploy()` (the same\n * tar path `roxyon deploy` uses for static/app content; WordPress core is\n * thousands of files, well past the small JSON `roxyon_deploy_content` tool's\n * budget).\n */\nexport async function installWordPress(\n opts: InstallWordPressOptions,\n): Promise<InstallWordPressResult> {\n const fetchImpl = opts.fetchImpl ?? globalThis.fetch;\n await mkdir(opts.dir, { recursive: true });\n\n const coreRes = await fetchImpl(WORDPRESS_TARBALL_URL);\n if (!coreRes.ok) {\n throw new Error(`Could not download WordPress core (HTTP ${coreRes.status}).`);\n }\n const coreBuf = Buffer.from(await coreRes.arrayBuffer());\n\n // The official tarball has one top-level \"wordpress/\" directory — strip it\n // so core files land directly in `dir`.\n await new Promise<void>((resolvePromise, reject) => {\n const writer = extract({ cwd: opts.dir, strip: 1 });\n Readable.from(coreBuf)\n .pipe(writer)\n .on('finish', () => resolvePromise())\n .on('error', reject);\n });\n\n const saltsRes = await fetchImpl(WORDPRESS_SALTS_URL);\n const salts = saltsRes.ok ? (await saltsRes.text()).trim() : '';\n\n const samplePath = `${opts.dir}/wp-config-sample.php`;\n const sample = await readFile(samplePath, 'utf8');\n const config = buildWpConfig(sample, opts.db, opts.tablePrefix, salts);\n await writeFile(`${opts.dir}/wp-config.php`, config, 'utf8');\n\n return { dir: opts.dir };\n}\n\n/** Pure string transform — exported separately so it's testable without any network. */\nexport function buildWpConfig(\n sample: string,\n db: WordPressDbCredentials,\n tablePrefix = 'wp_',\n salts = '',\n): string {\n let out = sample\n .replace(/(['\"])database_name_here\\1/, `$1${escapePhp(db.name)}$1`)\n .replace(/(['\"])username_here\\1/, `$1${escapePhp(db.username)}$1`)\n .replace(/(['\"])password_here\\1/, `$1${escapePhp(db.password)}$1`)\n .replace(/(define\\(\\s*['\"]DB_HOST['\"]\\s*,\\s*)(['\"])localhost\\2/, `$1$2${escapePhp(db.host)}$2`)\n .replace(/(\\$table_prefix\\s*=\\s*)(['\"])wp_\\2/, `$1$2${escapePhp(tablePrefix)}$2`);\n\n if (salts) {\n const lines = out.split('\\n');\n const start = lines.findIndex((l) => l.includes(PLACEHOLDER));\n if (start !== -1) {\n let end = start;\n while (end < lines.length && lines[end]!.includes(PLACEHOLDER)) end++;\n lines.splice(start, end - start, salts);\n out = lines.join('\\n');\n }\n }\n\n return out;\n}\n\nfunction escapePhp(s: string): string {\n return s.replace(/\\\\/g, '\\\\\\\\').replace(/'/g, \"\\\\'\");\n}\n","import { chmod, mkdir, readFile, rm, writeFile } from 'node:fs/promises';\nimport { homedir } from 'node:os';\nimport { join } from 'node:path';\n\n/**\n * Stored login for Roxyon tooling — `~/.roxyon/config.json`, mode 0600. Written\n * by `roxyon login`; read by the CLI and the MCP server alike.\n */\nexport interface Credentials {\n sessionToken: string;\n refreshToken?: string;\n userId: string;\n email?: string;\n /** Preferred subscription (objectId / Name) when the account has several. */\n subscription?: string;\n savedAt: string;\n}\n\nexport interface Endpoints {\n apiUrl?: string;\n consoleUrl?: string;\n}\n\nexport function credentialsDir(): string {\n return join(homedir(), '.roxyon');\n}\nexport function credentialsPath(): string {\n return join(credentialsDir(), 'config.json');\n}\n\n/** Endpoint overrides from the environment (`ROXYON_API_URL`, `ROXYON_CONSOLE_URL`). */\nexport function endpointsFromEnv(): Endpoints {\n return {\n apiUrl: process.env.ROXYON_API_URL,\n consoleUrl: process.env.ROXYON_CONSOLE_URL,\n };\n}\n\n/** A token supplied out-of-band (CI, or an MCP host env). Skips the stored login. */\nexport function tokenFromEnv(): string | undefined {\n return process.env.ROXYON_TOKEN || undefined;\n}\n\nexport async function loadCredentials(): Promise<Credentials | undefined> {\n try {\n return JSON.parse(await readFile(credentialsPath(), 'utf8')) as Credentials;\n } catch {\n return undefined;\n }\n}\n\nexport async function saveCredentials(creds: Credentials): Promise<string> {\n const path = credentialsPath();\n await mkdir(credentialsDir(), { recursive: true, mode: 0o700 });\n await writeFile(path, `${JSON.stringify(creds, null, 2)}\\n`, { mode: 0o600 });\n await chmod(path, 0o600).catch(() => undefined);\n return path;\n}\n\nexport async function clearCredentials(): Promise<void> {\n await rm(credentialsPath(), { force: true });\n}\n"],"mappings":";AAAA,SAAS,OAAO,SAAS,UAAU,SAAS,IAAI,MAAM,iBAAiB;AACvE,SAAS,cAAc;AACvB,SAAS,SAAS,YAAY,MAAM,UAAU,WAAW;AACzD,OAAO,YAA6B;AACpC,SAAS,UAAU,iBAAiB;AAG7B,IAAM,gBAAgB;AAAA,EAC3B;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;AAEA,eAAe,eAAe,MAAc,MAAiC;AAC3E,MAAI;AACF,YAAQ,MAAM,SAAS,KAAK,MAAM,IAAI,GAAG,MAAM,GAAG,MAAM,OAAO;AAAA,EACjE,QAAQ;AACN,WAAO,CAAC;AAAA,EACV;AACF;AAGA,eAAsB,YAAY,MAA+B;AAC/D,QAAM,KAAK,OAAO,EAAE,IAAI,aAAa;AACrC,QAAM,SAAS,MAAM,eAAe,MAAM,eAAe;AACzD,MAAI,OAAO,OAAQ,IAAG,IAAI,MAAM;AAAA,MAC3B,IAAG,IAAI,MAAM,eAAe,MAAM,YAAY,CAAC;AACpD,SAAO;AACT;AAGA,eAAsB,UAAU,MAAc,IAA+B;AAC3E,QAAM,QAAkB,CAAC;AAEzB,iBAAe,KAAK,KAA4B;AAC9C,UAAM,UAAU,MAAM,QAAQ,KAAK,EAAE,eAAe,KAAK,CAAC;AAC1D,eAAW,SAAS,SAAS;AAC3B,YAAM,MAAM,KAAK,KAAK,MAAM,IAAI;AAChC,YAAM,MAAM,SAAS,MAAM,GAAG,EAAE,MAAM,GAAG,EAAE,KAAK,GAAG;AACnD,UAAI,CAAC,OAAO,GAAG,QAAQ,GAAG,KAAK,GAAG,QAAQ,GAAG,GAAG,GAAG,EAAG;AACtD,UAAI,MAAM,YAAY,GAAG;AACvB,cAAM,KAAK,GAAG;AAAA,MAChB,WAAW,MAAM,OAAO,KAAK,MAAM,eAAe,GAAG;AACnD,cAAM,KAAK,GAAG;AAAA,MAChB;AAAA,IACF;AAAA,EACF;AAEA,QAAM,KAAK,IAAI;AACf,SAAO,MAAM,KAAK;AACpB;AAYA,eAAsB,cACpB,MACA,OAA+C,CAAC,GAC3B;AACrB,QAAM,KAAK,MAAM,YAAY,IAAI;AACjC,QAAM,QAAQ,MAAM,UAAU,MAAM,EAAE;AACtC,MAAI,MAAM,WAAW,GAAG;AACtB,UAAM,IAAI;AAAA,MACR,0BAA0B,IAAI;AAAA,IAChC;AAAA,EACF;AAEA,QAAM,SAAS;AAAA,IACb;AAAA,MACE,MAAM;AAAA,MACN,KAAK;AAAA,MACL,UAAU;AAAA,MACV,OAAO,oBAAI,KAAK,CAAC;AAAA;AAAA,MACjB,QAAQ,KAAK;AAAA,MACb,QAAQ;AAAA,IACV;AAAA,IACA;AAAA,EACF;AAEA,QAAM,SAAmB,CAAC;AAC1B,MAAI,QAAQ;AACZ,QAAM,MAAM,KAAK,YAAY,MAAM,OAAO;AAC1C,mBAAiB,SAAS,QAAiC;AACzD,aAAS,MAAM;AACf,QAAI,QAAQ,KAAK;AACf,YAAM,IAAI;AAAA,QACR,0BAA2B,MAAM,OAAO,OAAQ,CAAC;AAAA,MACnD;AAAA,IACF;AACA,WAAO,KAAK,KAAK;AAAA,EACnB;AAEA,SAAO,EAAE,QAAQ,OAAO,OAAO,MAAM,GAAG,OAAO,MAAM;AACvD;AAWA,SAAS,cAAc,GAA0B;AAC/C,QAAM,OAAO,EAAE,QAAQ,OAAO,GAAG,EAAE,QAAQ,SAAS,EAAE;AACtD,MAAI,CAAC,QAAQ,KAAK,WAAW,GAAG,KAAK,WAAW,IAAI,KAAK,KAAK,SAAS,IAAI,EAAG,QAAO;AACrF,MAAI,KAAK,MAAM,GAAG,EAAE,KAAK,CAAC,QAAQ,QAAQ,QAAQ,QAAQ,OAAO,QAAQ,EAAE,EAAG,QAAO;AACrF,SAAO;AACT;AAQA,eAAsB,UACpB,SACA,OAA+C,CAAC,GAC3B;AACrB,MAAI,QAAQ,WAAW,EAAG,OAAM,IAAI,MAAM,qBAAqB;AAE/D,QAAM,OAAO,oBAAI,IAAY;AAC7B,QAAM,QAAyC,CAAC;AAChD,MAAI,QAAQ;AACZ,aAAW,KAAK,SAAS;AACvB,UAAM,MAAM,cAAc,EAAE,IAAI;AAChC,QAAI,CAAC,IAAK,OAAM,IAAI,MAAM,qBAAqB,KAAK,UAAU,EAAE,IAAI,CAAC,EAAE;AACvE,QAAI,KAAK,IAAI,GAAG,EAAG,OAAM,IAAI,MAAM,wBAAwB,GAAG,EAAE;AAChE,SAAK,IAAI,GAAG;AACZ,UAAM,MAAM,OAAO,KAAK,EAAE,SAAS,EAAE,aAAa,WAAW,WAAW,MAAM;AAC9E,aAAS,IAAI;AACb,UAAM,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;AAAA,EAC/B;AACA,QAAM,MAAM,KAAK,YAAY,MAAM,OAAO;AAC1C,MAAI,QAAQ,KAAK;AACf,UAAM,IAAI;AAAA,MACR,eAAe,QAAQ,OAAO,MAAM,QAAQ,CAAC,CAAC,2BAA2B,MAAM,OAAO,MAAM,QAAQ,CAAC,CAAC;AAAA,IACxG;AAAA,EACF;AAEA,QAAM,MAAM,MAAM,QAAQ,KAAK,OAAO,GAAG,iBAAiB,CAAC;AAC3D,MAAI;AACF,eAAW,EAAE,MAAM,KAAK,IAAI,KAAK,OAAO;AACtC,YAAM,MAAM,KAAK,KAAK,GAAG;AACzB,YAAM,MAAM,QAAQ,GAAG,GAAG,EAAE,WAAW,KAAK,CAAC;AAC7C,YAAM,UAAU,KAAK,GAAG;AAAA,IAC1B;AACA,WAAO,MAAM,cAAc,KAAK,IAAI;AAAA,EACtC,UAAE;AACA,UAAM,GAAG,KAAK,EAAE,WAAW,MAAM,OAAO,KAAK,CAAC;AAAA,EAChD;AACF;AAGA,eAAsB,QAAQ,MAAc,OAAkC;AAC5E,MAAI,QAAQ;AACZ,aAAW,KAAK,OAAO;AACrB,QAAI;AACF,gBAAU,MAAM,KAAK,KAAK,MAAM,CAAC,CAAC,GAAG;AAAA,IACvC,QAAQ;AAAA,IAER;AAAA,EACF;AACA,SAAO;AACT;;;ACnLA,SAAS,YAAAA,WAAU,WAAAC,gBAAe;AAClC,SAAS,QAAAC,aAAY;AAWrB,eAAe,OAAO,MAAc,MAAgC;AAClE,MAAI;AACF,UAAMF,UAASE,MAAK,MAAM,IAAI,CAAC;AAC/B,WAAO;AAAA,EACT,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAEA,eAAe,SAAS,MAAc,MAA4D;AAChG,MAAI;AACF,WAAO,KAAK,MAAM,MAAMF,UAASE,MAAK,MAAM,IAAI,GAAG,MAAM,CAAC;AAAA,EAC5D,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAGA,eAAsB,cAAc,MAAkC;AACpE,QAAM,MAAM,MAAM,SAAS,MAAM,cAAc;AAC/C,QAAM,gBAAgB,MAAM,OAAO,MAAM,aAAa;AACtD,MAAI,WAAW;AACf,MAAI;AACF,gBAAY,MAAMD,SAAQC,MAAK,MAAM,OAAO,OAAO,CAAC,GAAG,UAAU;AAAA,EACnE,QAAQ;AACN,eAAW;AAAA,EACb;AAEA,MAAI,iBAAiB,UAAU;AAC7B,UAAM,OAAO;AAAA,MACX,GAAI,KAAK;AAAA,MACT,GAAI,KAAK;AAAA,IACX;AACA,QAAI,EAAE,UAAU,SAAS,EAAE,UAAU,SAAS,EAAE,aAAa,SAAS,EAAE,aAAa,OAAO;AAC1F,aAAO,EAAE,SAAS,SAAS,QAAQ,mDAAmD;AAAA,IACxF;AAAA,EACF;AAEA,MAAI,KAAK;AACP,UAAM,UAAW,IAAI,WAAsC,CAAC;AAC5D,UAAM,QAAQ,QAAQ,QAAQ,kBAAkB,QAAQ,QAAQ,kBAAkB;AAClF,UAAM,OAAO,EAAE,GAAI,IAAI,cAAyB,GAAI,IAAI,gBAA2B;AAInF,QAAI,UAAU;AACZ,aAAO,EAAE,SAAS,QAAQ,QAAQ,2BAA2B,OAAO,gBAAgB;AACtF,QAAI,UAAU;AACZ,aAAO,EAAE,SAAS,QAAQ,QAAQ,wBAAwB,OAAO,gBAAgB;AACnF,WAAO,EAAE,SAAS,QAAQ,QAAQ,wBAAwB,MAAM;AAAA,EAClE;AAEA,MAAK,MAAM,OAAO,MAAM,kBAAkB,KAAO,MAAM,OAAO,MAAM,gBAAgB,GAAI;AACtF,WAAO;AAAA,MACL,SAAS;AAAA,MACT,QAAQ;AAAA,MACR,OAAO;AAAA,IACT;AAAA,EACF;AAEA,MAAK,MAAM,OAAO,MAAM,eAAe,KAAO,MAAM,OAAO,MAAM,YAAY,GAAI;AAC/E,WAAO,EAAE,SAAS,OAAO,QAAQ,8BAA8B,OAAO,oBAAoB;AAAA,EAC5F;AAEA,SAAO,EAAE,SAAS,SAAS,QAAQ,wDAAmD;AACxF;;;AC7EA,SAAS,SAAAC,QAAO,YAAAC,WAAU,aAAAC,kBAAiB;AAC3C,SAAS,UAAU,WAAAC,UAAS,eAAe;AAC3C,SAA0B,aAAa,gBAAkC;AA0BlE,IAAM,eAAe;AAErB,SAAS,kBAAkB,MAAc,QAAQ,IAAI,GAAW;AACrE,SAAO,QAAQ,KAAK,YAAY;AAClC;AAEA,eAAsB,kBACpB,MAAc,QAAQ,IAAI,GACU;AACpC,MAAI;AACF,WAAO,KAAK,MAAM,MAAMF,UAAS,kBAAkB,GAAG,GAAG,MAAM,CAAC;AAAA,EAClE,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAEA,eAAsB,kBACpB,QACA,MAAc,QAAQ,IAAI,GACT;AACjB,QAAM,OAAO,kBAAkB,GAAG;AAClC,QAAMD,OAAMG,SAAQ,IAAI,GAAG,EAAE,WAAW,KAAK,CAAC;AAC9C,QAAMD,WAAU,MAAM,GAAG,KAAK,UAAU,QAAQ,MAAM,CAAC,CAAC;AAAA,CAAI;AAC5D,SAAO;AACT;AAeO,SAAS,mBAAmB,OAAwC;AACzE,QAAM,SAAS,OAAO,MAAM,UAAU,EAAE,EAAE,QAAQ,cAAc,EAAE;AAClE,QAAM,SAAwB;AAAA,IAC5B,MAAM,MAAM,QAAQ,SAAS,MAAM,GAAG;AAAA,IACtC,MAAM,MAAM;AAAA,IACZ;AAAA,IACA,SAAS,MAAM;AAAA,IACf,MAAM,MAAM,YAAY,UAAU,WAAW;AAAA,EAC/C;AAEA,MAAI,MAAM,YAAY,SAAS;AAC7B,WAAO,QAAQ,YAAY;AAC3B,WAAO,SAAS,YAAY;AAAA,EAC9B,OAAO;AACL,UAAM,OAAO,SAAS,MAAM,OAAO;AACnC,WAAO,iBAAiB,KAAK;AAC7B,WAAO,SAAS,KAAK,QAAQ,CAAC,IAAI,CAAC;AACnC,WAAO,QAAQ,MAAM,SAAS,KAAK;AACnC,WAAO,SAAS;AAChB,WAAO,QAAQ;AAAA,EACjB;AACA,SAAO;AACT;;;ACzFA,SAAS,cAAc;AACvB,SAAS,WAAAE,gBAAe;AACxB;AAAA,EAEE,YAAAC;AAAA,EAEA;AAAA,EACA;AAAA,OACK;;;ACRP,SAAS,aAAa;AAOf,SAAS,gBACd,SACA,KACA,UACiB;AACjB,SAAO,IAAI,QAAQ,CAAC,mBAAmB;AACrC,UAAM,QAAQ,MAAM,SAAS,EAAE,KAAK,OAAO,MAAM,OAAO,CAAC,UAAU,QAAQ,MAAM,EAAE,CAAC;AACpF,UAAM,OAAO,CAAC,QAAgB;AAC5B,iBAAW,QAAQ,IAAI,SAAS,MAAM,EAAE,MAAM,IAAI,GAAG;AACnD,YAAI,KAAK,KAAK,EAAG,WAAU,MAAM,IAAI;AAAA,MACvC;AAAA,IACF;AACA,UAAM,QAAQ,GAAG,QAAQ,IAAI;AAC7B,UAAM,QAAQ,GAAG,QAAQ,IAAI;AAC7B,UAAM,GAAG,SAAS,CAAC,SAAS,eAAe,QAAQ,CAAC,CAAC;AACrD,UAAM,GAAG,SAAS,MAAM,eAAe,GAAG,CAAC;AAAA,EAC7C,CAAC;AACH;;;ADmCO,IAAM,cAAN,cAA0B,MAAM;AAAA,EAC5B;AAAA,EAQT,YAAY,SAAiB,MAA2B;AACtD,UAAM,OAAO;AACb,SAAK,OAAO;AACZ,SAAK,OAAO;AAAA,EACd;AACF;AAEA,IAAM,QAAQ,CAAC,OAAe,IAAI,QAAQ,CAAC,MAAM,WAAW,GAAG,EAAE,CAAC;AASlE,eAAsB,cAAc,QAA8C;AAChF,QAAM,EAAE,KAAK,QAAQ,SAAS,IAAI;AAClC,QAAM,aAAa,OAAO,cAAc;AAExC,QAAM,SAAS,MAAM,kBAAkB,GAAG;AAC1C,MAAI,CAAC,QAAQ;AACX,UAAM,IAAI,YAAY,2DAAsD,WAAW;AAAA,EACzF;AAGA,QAAM,MAAM,MAAM,OAAO,QAAQ,QAAQ,EAAE,MAAM,CAAC,MAAM;AACtD,QAAI,YAAY,CAAC,EAAG,OAAM,IAAI,YAAY,yCAAoC,MAAM;AACpF,UAAM;AAAA,EACR,CAAC;AACD,QAAM,SAAS,IAAI,QAAQ,KAAK,CAAC,MAAM,EAAE,SAAS,OAAO,IAAI;AAC7D,MAAI,CAAC,QAAQ;AACX,UAAM,IAAI;AAAA,MACR,SAAS,OAAO,IAAI,mCAClB,IAAI,QAAQ,IAAI,CAAC,MAAM,EAAE,IAAI,EAAE,KAAK,IAAI,KAAK,MAC/C;AAAA,MACA;AAAA,IACF;AAAA,EACF;AAGA,MAAI,OAAO,SAAS,OAAO,UAAU,OAAO;AAC1C,cAAU,OAAO,UAAU,OAAO,KAAK,EAAE;AACzC,UAAM,OAAO,MAAM,WAAW,OAAO,OAAO,KAAK,QAAQ;AACzD,QAAI,SAAS,EAAG,OAAM,IAAI,YAAY,wBAAwB,IAAI,KAAK,cAAc;AAAA,EACvF;AAGA,QAAM,cAAc,OAAO,SAAS,YAAY,OAAO,SAASC,SAAQ,KAAK,OAAO,MAAM,IAAI;AAC9F,MAAI,OAAO,SAAS,YAAY,OAAO,QAAQ;AAC7C,UAAM,OAAO,WAAW,EAAE,MAAM,MAAM;AACpC,YAAM,IAAI;AAAA,QACR,iBAAiB,OAAO,MAAM;AAAA,QAC9B;AAAA,MACF;AAAA,IACF,CAAC;AAAA,EACH;AACA,YAAU,OAAO,WAAW,gBAAgB,MAAM,MAAM,OAAO,MAAM,EAAE;AACvE,QAAM,OAAO,MAAM,cAAc,WAAW;AAC5C,YAAU,MAAM,GAAG,KAAK,MAAM,MAAM,gBAAa,KAAK,QAAQ,MAAM,QAAQ,CAAC,CAAC,KAAK;AAGnF,MAAI,OAAO,SAAS,UAAU;AAC5B,cAAU,OAAO,gBAAgB,OAAO,IAAI,GAAG,OAAO,SAAS,IAAI,OAAO,MAAM,KAAK,EAAE,EAAE;AACzF,QAAI;AACJ,QAAI;AACF,YAAM,MAAM,OAAO,MAAM,OAAO,OAAO,MAAM,OAAO,QAAQ,KAAK,MAAM;AAAA,IACzE,SAAS,KAAK;AACZ,YAAM,IAAI;AAAA,QACR,eAAe,iBAAiB,IAAI,UAAU,OAAO,GAAG;AAAA,QACxD;AAAA,MACF;AAAA,IACF;AACA,UAAMC,OAAM,WAAW,IAAI,IAAI,GAAG,IAAI,QAAQ,IAAI,SAAS,MAAM,IAAI,OAAO,EAAE;AAC9E,cAAU,OAAO,eAAUA,IAAG,EAAE;AAChC,WAAO,EAAE,MAAM,UAAU,IAAI,MAAM,KAAAA,MAAK,OAAO,IAAI,SAAS,KAAK,MAAM,QAAQ,OAAO;AAAA,EACxF;AAMA,QAAM,UAAU,OAAO,YAAY,UAAU,SAAS,OAAO;AAC7D,QAAM,OAAOC,UAAS,OAAO;AAC7B,QAAM,SAAS,OAAO,eAAe;AAAA,IACnC,MAAM,OAAO;AAAA,IACb,QAAQ,OAAO;AAAA,IACf;AAAA,IACA,gBAAgB,OAAO,kBAAkB,KAAK;AAAA,IAC9C,QAAQ,OAAO,UAAU,KAAK,QAAQ,CAAC,IAAI,CAAC,KAAK;AAAA,IACjD,SAAS,OAAO,SAAS,KAAK;AAAA,IAC9B,QAAQ,OAAO,UAAU;AAAA,EAC3B;AAEA,YAAU;AAAA,IACR,OAAO,cAAc,qBAAqB;AAAA,EAC5C;AACA,MAAI,WAAW;AACf,MAAI;AACJ,MAAI;AACF,UAAM,MAAM,MAAM,OAAO,aAAa,aAAa,QAAQ,KAAK,MAAM;AACtE,eAAW,IAAI,kBAAkB;AACjC,YAAQ,IAAI;AACZ,QAAI,IAAI,WAAW,SAAS,UAAU,OAAO,aAAa;AACxD,aAAO,cAAc;AACrB,YAAM,kBAAkB,QAAQ,GAAG;AACnC,gBAAU,MAAM,eAAe,KAAK,UAAU;AAAA,IAChD;AAAA,EACF,SAAS,KAAK;AACZ,UAAM,IAAI;AAAA,MACR,eAAe,iBAAiB,IAAI,UAAU,OAAO,GAAG;AAAA,MACxD,OAAO,cAAc,kBAAkB;AAAA,IACzC;AAAA,EACF;AAEA,QAAM,MAAM,OAAO,SACf,WAAW,OAAO,IAAI,GAAG,OAAO,SAAS,IAAI,OAAO,MAAM,KAAK,EAAE,KACjE;AAEJ,MAAI,OAAO,WAAW,OAAO;AAC3B,cAAU,OAAO,0BAA0B,QAAQ,GAAG;AACtD,WAAO;AAAA,MACL,MAAM;AAAA,MACN,IAAI;AAAA,MACJ,aAAa;AAAA,MACb,QAAQ;AAAA,MACR;AAAA,MACA;AAAA,MACA;AAAA,MACA,MAAM,CAAC;AAAA,IACT;AAAA,EACF;AAEA,SAAO,MAAM,QAAQ,OAAO;AAAA,IAC1B;AAAA,IACA;AAAA,IACA;AAAA,IACA,QAAQ,OAAO,UAAU;AAAA,IACzB,WAAW,OAAO,aAAa,KAAK,KAAK;AAAA,EAC3C,CAAC;AACH;AAEA,eAAe,MACb,QACA,OACA,MAOwB;AACxB,QAAM,UAAU,KAAK,IAAI;AACzB,QAAM,OAAO,oBAAI,IAAY;AAC7B,QAAM,YAAsB,CAAC;AAC7B,MAAI,aAAa;AAEjB,OAAK,UAAU,OAAO,UAAU;AAChC,SAAO,MAAM;AACX,UAAM,MAAM,KAAK,MAAM;AAEvB,QAAI;AACJ,QAAI;AACF,YAAM,MAAM,OAAO,QAAQ,OAAO,KAAK;AAAA,IACzC,QAAQ;AAAA,IAER;AACA,QAAI,CAAC,KAAK;AACR,UAAI,KAAK,IAAI,IAAI,UAAU,KAAK,UAAW;AAC3C;AAAA,IACF;AAEA,UAAM,MAAM,IAAI;AAChB,UAAM,UAAU,IAAI;AACpB,UAAM,SAAS,IAAI;AACnB,QAAI,WAAW,YAAY;AACzB,WAAK,UAAU,OAAO,GAAG,UAAU,SAAS,KAAK,OAAO,IAAI,GAAG,GAAG;AAClE,mBAAa;AAAA,IACf;AAEA,QAAI;AACF,iBAAW,QAAQ,MAAM,OAAO,aAAa,KAAK,OAAO,EAAE,GAAG;AAC5D,YAAI,QAAQ,CAAC,KAAK,IAAI,IAAI,GAAG;AAC3B,eAAK,IAAI,IAAI;AACb,oBAAU,KAAK,IAAI;AACnB,eAAK,UAAU,MAAM,IAAI;AAAA,QAC3B;AAAA,MACF;AAAA,IACF,QAAQ;AAAA,IAER;AAEA,QAAI,OAAO,WAAW,WAAW,WAAW;AAC1C,WAAK,UAAU,OAAO,OAAO,KAAK,MAAM,WAAM,KAAK,GAAG,KAAK,EAAE,EAAE;AAC/D,aAAO;AAAA,QACL,MAAM;AAAA,QACN,IAAI;AAAA,QACJ,aAAa;AAAA,QACb,QAAQ;AAAA,QACR,UAAU;AAAA,QACV,KAAK,KAAK;AAAA,QACV,QAAQ,KAAK;AAAA,QACb,MAAM;AAAA,MACR;AAAA,IACF;AACA,QAAI,WAAW,UAAU;AACvB,aAAO;AAAA,QACL,MAAM;AAAA,QACN,IAAI;AAAA,QACJ,aAAa;AAAA,QACb,QAAQ;AAAA,QACR,QAAQ,IAAI,aAAa,kBAAkB,KAAK;AAAA,QAChD,QAAQ,KAAK;AAAA,QACb,MAAM;AAAA,MACR;AAAA,IACF;AACA,QAAI,KAAK,IAAI,IAAI,UAAU,KAAK,UAAW;AAAA,EAC7C;AAEA,SAAO;AAAA,IACL,MAAM;AAAA,IACN,IAAI;AAAA,IACJ,aAAa;AAAA,IACb,QAAQ;AAAA,IACR,OAAO;AAAA,IACP,QAAQ,KAAK;AAAA,IACb,MAAM;AAAA,EACR;AACF;;;AEzSA,SAAS,SAAAC,QAAO,YAAAC,WAAU,aAAAC,kBAAiB;AAC3C,SAAS,gBAAgB;AACzB,SAAS,eAAe;AAExB,IAAM,wBAAwB;AAC9B,IAAM,sBAAsB;AAC5B,IAAM,cAAc;AAiCpB,eAAsB,iBACpB,MACiC;AACjC,QAAM,YAAY,KAAK,aAAa,WAAW;AAC/C,QAAMF,OAAM,KAAK,KAAK,EAAE,WAAW,KAAK,CAAC;AAEzC,QAAM,UAAU,MAAM,UAAU,qBAAqB;AACrD,MAAI,CAAC,QAAQ,IAAI;AACf,UAAM,IAAI,MAAM,2CAA2C,QAAQ,MAAM,IAAI;AAAA,EAC/E;AACA,QAAM,UAAU,OAAO,KAAK,MAAM,QAAQ,YAAY,CAAC;AAIvD,QAAM,IAAI,QAAc,CAAC,gBAAgB,WAAW;AAClD,UAAM,SAAS,QAAQ,EAAE,KAAK,KAAK,KAAK,OAAO,EAAE,CAAC;AAClD,aAAS,KAAK,OAAO,EAClB,KAAK,MAAM,EACX,GAAG,UAAU,MAAM,eAAe,CAAC,EACnC,GAAG,SAAS,MAAM;AAAA,EACvB,CAAC;AAED,QAAM,WAAW,MAAM,UAAU,mBAAmB;AACpD,QAAM,QAAQ,SAAS,MAAM,MAAM,SAAS,KAAK,GAAG,KAAK,IAAI;AAE7D,QAAM,aAAa,GAAG,KAAK,GAAG;AAC9B,QAAM,SAAS,MAAMC,UAAS,YAAY,MAAM;AAChD,QAAM,SAAS,cAAc,QAAQ,KAAK,IAAI,KAAK,aAAa,KAAK;AACrE,QAAMC,WAAU,GAAG,KAAK,GAAG,kBAAkB,QAAQ,MAAM;AAE3D,SAAO,EAAE,KAAK,KAAK,IAAI;AACzB;AAGO,SAAS,cACd,QACA,IACA,cAAc,OACd,QAAQ,IACA;AACR,MAAI,MAAM,OACP,QAAQ,8BAA8B,KAAK,UAAU,GAAG,IAAI,CAAC,IAAI,EACjE,QAAQ,yBAAyB,KAAK,UAAU,GAAG,QAAQ,CAAC,IAAI,EAChE,QAAQ,yBAAyB,KAAK,UAAU,GAAG,QAAQ,CAAC,IAAI,EAChE,QAAQ,wDAAwD,OAAO,UAAU,GAAG,IAAI,CAAC,IAAI,EAC7F,QAAQ,sCAAsC,OAAO,UAAU,WAAW,CAAC,IAAI;AAElF,MAAI,OAAO;AACT,UAAM,QAAQ,IAAI,MAAM,IAAI;AAC5B,UAAM,QAAQ,MAAM,UAAU,CAAC,MAAM,EAAE,SAAS,WAAW,CAAC;AAC5D,QAAI,UAAU,IAAI;AAChB,UAAI,MAAM;AACV,aAAO,MAAM,MAAM,UAAU,MAAM,GAAG,EAAG,SAAS,WAAW,EAAG;AAChE,YAAM,OAAO,OAAO,MAAM,OAAO,KAAK;AACtC,YAAM,MAAM,KAAK,IAAI;AAAA,IACvB;AAAA,EACF;AAEA,SAAO;AACT;AAEA,SAAS,UAAU,GAAmB;AACpC,SAAO,EAAE,QAAQ,OAAO,MAAM,EAAE,QAAQ,MAAM,KAAK;AACrD;;;ACtGA,SAAS,OAAO,SAAAC,QAAO,YAAAC,WAAU,MAAAC,KAAI,aAAAC,kBAAiB;AACtD,SAAS,eAAe;AACxB,SAAS,QAAAC,aAAY;AAqBd,SAAS,iBAAyB;AACvC,SAAOA,MAAK,QAAQ,GAAG,SAAS;AAClC;AACO,SAAS,kBAA0B;AACxC,SAAOA,MAAK,eAAe,GAAG,aAAa;AAC7C;AAGO,SAAS,mBAA8B;AAC5C,SAAO;AAAA,IACL,QAAQ,QAAQ,IAAI;AAAA,IACpB,YAAY,QAAQ,IAAI;AAAA,EAC1B;AACF;AAGO,SAAS,eAAmC;AACjD,SAAO,QAAQ,IAAI,gBAAgB;AACrC;AAEA,eAAsB,kBAAoD;AACxE,MAAI;AACF,WAAO,KAAK,MAAM,MAAMH,UAAS,gBAAgB,GAAG,MAAM,CAAC;AAAA,EAC7D,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAEA,eAAsB,gBAAgB,OAAqC;AACzE,QAAM,OAAO,gBAAgB;AAC7B,QAAMD,OAAM,eAAe,GAAG,EAAE,WAAW,MAAM,MAAM,IAAM,CAAC;AAC9D,QAAMG,WAAU,MAAM,GAAG,KAAK,UAAU,OAAO,MAAM,CAAC,CAAC;AAAA,GAAM,EAAE,MAAM,IAAM,CAAC;AAC5E,QAAM,MAAM,MAAM,GAAK,EAAE,MAAM,MAAM,MAAS;AAC9C,SAAO;AACT;AAEA,eAAsB,mBAAkC;AACtD,QAAMD,IAAG,gBAAgB,GAAG,EAAE,OAAO,KAAK,CAAC;AAC7C;","names":["readFile","readdir","join","mkdir","readFile","writeFile","dirname","resolve","RUNTIMES","resolve","url","RUNTIMES","mkdir","readFile","writeFile","mkdir","readFile","rm","writeFile","join"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@roxyon/deploy-core",
3
- "version": "0.1.1",
3
+ "version": "0.3.0",
4
4
  "description": "Shared deploy pipeline for Roxyon tooling — project detection, archiving, and the build/upload/poll orchestration used by @roxyon/cli and @roxyon/mcp.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -29,7 +29,7 @@
29
29
  "dependencies": {
30
30
  "ignore": "^5.3.2",
31
31
  "tar": "^7.4.3",
32
- "@roxyon/api-client": "0.1.1"
32
+ "@roxyon/api-client": "0.3.0"
33
33
  },
34
34
  "devDependencies": {
35
35
  "@types/node": "^20.16.11",