@roxyon/deploy-core 0.2.0 → 0.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -35,12 +35,14 @@ __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,
@@ -497,8 +499,53 @@ async function watch(roxyon, appId, opts) {
497
499
  };
498
500
  }
499
501
 
500
- // src/credentials.ts
502
+ // src/wordpress.ts
501
503
  var import_promises5 = require("fs/promises");
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");
502
549
  var import_node_os2 = require("os");
503
550
  var import_node_path5 = require("path");
504
551
  function credentialsDir() {
@@ -518,21 +565,21 @@ function tokenFromEnv() {
518
565
  }
519
566
  async function loadCredentials() {
520
567
  try {
521
- return JSON.parse(await (0, import_promises5.readFile)(credentialsPath(), "utf8"));
568
+ return JSON.parse(await (0, import_promises6.readFile)(credentialsPath(), "utf8"));
522
569
  } catch {
523
570
  return void 0;
524
571
  }
525
572
  }
526
573
  async function saveCredentials(creds) {
527
574
  const path = credentialsPath();
528
- await (0, import_promises5.mkdir)(credentialsDir(), { recursive: true, mode: 448 });
529
- 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)}
530
577
  `, { mode: 384 });
531
- await (0, import_promises5.chmod)(path, 384).catch(() => void 0);
578
+ await (0, import_promises6.chmod)(path, 384).catch(() => void 0);
532
579
  return path;
533
580
  }
534
581
  async function clearCredentials() {
535
- await (0, import_promises5.rm)(credentialsPath(), { force: true });
582
+ await (0, import_promises6.rm)(credentialsPath(), { force: true });
536
583
  }
537
584
  // Annotate the CommonJS export names for ESM import in node:
538
585
  0 && (module.exports = {
@@ -541,12 +588,14 @@ async function clearCredentials() {
541
588
  PROJECT_FILE,
542
589
  buildIgnore,
543
590
  buildProjectConfig,
591
+ buildWpConfig,
544
592
  clearCredentials,
545
593
  credentialsDir,
546
594
  credentialsPath,
547
595
  deployProject,
548
596
  detectRuntime,
549
597
  endpointsFromEnv,
598
+ installWordPress,
550
599
  listFiles,
551
600
  loadCredentials,
552
601
  loadProjectConfig,
@@ -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 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 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 { 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_os","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
@@ -155,6 +155,38 @@ declare function deployProject(params: DeployParams): Promise<DeployOutcome>;
155
155
  */
156
156
  declare function runShellCommand(command: string, cwd: string, reporter?: DeployReporter): Promise<number>;
157
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
+
158
190
  /**
159
191
  * Stored login for Roxyon tooling — `~/.roxyon/config.json`, mode 0600. Written
160
192
  * by `roxyon login`; read by the CLI and the MCP server alike.
@@ -182,4 +214,4 @@ declare function loadCredentials(): Promise<Credentials | undefined>;
182
214
  declare function saveCredentials(creds: Credentials): Promise<string>;
183
215
  declare function clearCredentials(): Promise<void>;
184
216
 
185
- export { ALWAYS_IGNORE, type BuildConfigInput, type Credentials, DeployError, type DeployOutcome, type DeployParams, type DeployReporter, type Detection, type Endpoints, type FileEntry, PROJECT_FILE, type PackResult, type ProjectConfig, buildIgnore, buildProjectConfig, clearCredentials, credentialsDir, credentialsPath, deployProject, detectRuntime, endpointsFromEnv, listFiles, loadCredentials, loadProjectConfig, measure, packDirectory, packFiles, 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
@@ -155,6 +155,38 @@ declare function deployProject(params: DeployParams): Promise<DeployOutcome>;
155
155
  */
156
156
  declare function runShellCommand(command: string, cwd: string, reporter?: DeployReporter): Promise<number>;
157
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
+
158
190
  /**
159
191
  * Stored login for Roxyon tooling — `~/.roxyon/config.json`, mode 0600. Written
160
192
  * by `roxyon login`; read by the CLI and the MCP server alike.
@@ -182,4 +214,4 @@ declare function loadCredentials(): Promise<Credentials | undefined>;
182
214
  declare function saveCredentials(creds: Credentials): Promise<string>;
183
215
  declare function clearCredentials(): Promise<void>;
184
216
 
185
- export { ALWAYS_IGNORE, type BuildConfigInput, type Credentials, DeployError, type DeployOutcome, type DeployParams, type DeployReporter, type Detection, type Endpoints, type FileEntry, PROJECT_FILE, type PackResult, type ProjectConfig, buildIgnore, buildProjectConfig, clearCredentials, credentialsDir, credentialsPath, deployProject, detectRuntime, endpointsFromEnv, listFiles, loadCredentials, loadProjectConfig, measure, packDirectory, packFiles, 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
@@ -444,8 +444,53 @@ async function watch(roxyon, appId, opts) {
444
444
  };
445
445
  }
446
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
+
447
492
  // src/credentials.ts
448
- import { chmod, mkdir as mkdir3, readFile as readFile4, rm as rm2, writeFile as writeFile3 } from "fs/promises";
493
+ import { chmod, mkdir as mkdir4, readFile as readFile5, rm as rm2, writeFile as writeFile4 } from "fs/promises";
449
494
  import { homedir } from "os";
450
495
  import { join as join3 } from "path";
451
496
  function credentialsDir() {
@@ -465,15 +510,15 @@ function tokenFromEnv() {
465
510
  }
466
511
  async function loadCredentials() {
467
512
  try {
468
- return JSON.parse(await readFile4(credentialsPath(), "utf8"));
513
+ return JSON.parse(await readFile5(credentialsPath(), "utf8"));
469
514
  } catch {
470
515
  return void 0;
471
516
  }
472
517
  }
473
518
  async function saveCredentials(creds) {
474
519
  const path = credentialsPath();
475
- await mkdir3(credentialsDir(), { recursive: true, mode: 448 });
476
- await writeFile3(path, `${JSON.stringify(creds, null, 2)}
520
+ await mkdir4(credentialsDir(), { recursive: true, mode: 448 });
521
+ await writeFile4(path, `${JSON.stringify(creds, null, 2)}
477
522
  `, { mode: 384 });
478
523
  await chmod(path, 384).catch(() => void 0);
479
524
  return path;
@@ -487,12 +532,14 @@ export {
487
532
  PROJECT_FILE,
488
533
  buildIgnore,
489
534
  buildProjectConfig,
535
+ buildWpConfig,
490
536
  clearCredentials,
491
537
  credentialsDir,
492
538
  credentialsPath,
493
539
  deployProject,
494
540
  detectRuntime,
495
541
  endpointsFromEnv,
542
+ installWordPress,
496
543
  listFiles,
497
544
  loadCredentials,
498
545
  loadProjectConfig,
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 { 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 { 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","mkdir","readFile","writeFile","dirname","resolve","RUNTIMES","resolve","url","RUNTIMES","mkdir","readFile","rm","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":"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","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.2.0",
3
+ "version": "0.3.1",
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.2.0"
32
+ "@roxyon/api-client": "0.4.0"
33
33
  },
34
34
  "devDependencies": {
35
35
  "@types/node": "^20.16.11",