percher 0.5.11 → 0.5.13

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.
@@ -106091,6 +106091,17 @@ class AppsResource {
106091
106091
  deployLatestCommit(appRef, input) {
106092
106092
  return this.http.post(`/apps/${encodeURIComponent(appRef)}/deploys/from-latest-commit`, input ?? {});
106093
106093
  }
106094
+ publishFromGit(input) {
106095
+ const params = new URLSearchParams;
106096
+ params.set("url", input.url);
106097
+ if (input.name)
106098
+ params.set("name", input.name);
106099
+ if (input.branch)
106100
+ params.set("branch", input.branch);
106101
+ if (input.note)
106102
+ params.set("note", input.note);
106103
+ return this.http.post(`/apps/publish-from-git?${params.toString()}`, {});
106104
+ }
106094
106105
  setCrashAnalysisEnabled(appRef, enabled) {
106095
106106
  return this.http.put(`/apps/${encodeURIComponent(appRef)}/crash-analysis`, {
106096
106107
  crashAnalysisEnabled: enabled
@@ -106395,6 +106406,15 @@ class DataResource {
106395
106406
  getAdminLink(appRef) {
106396
106407
  return this.http.get(`/apps/${encodeURIComponent(appRef)}/data/admin-link`);
106397
106408
  }
106409
+ setOAuthProvider(appRef, body) {
106410
+ return this.http.post(`/apps/${encodeURIComponent(appRef)}/data/oauth`, body);
106411
+ }
106412
+ getOAuthStatus(appRef) {
106413
+ return this.http.get(`/apps/${encodeURIComponent(appRef)}/data/oauth`);
106414
+ }
106415
+ removeOAuthProvider(appRef, provider) {
106416
+ return this.http.delete(`/apps/${encodeURIComponent(appRef)}/data/oauth/${encodeURIComponent(provider)}`);
106417
+ }
106398
106418
  }
106399
106419
 
106400
106420
  // ../client/dist/resources/env.js
@@ -106552,10 +106572,10 @@ class LogsResource {
106552
106572
  const query = buildLogFilterQuery(options ?? {}, { lines: String(lines) });
106553
106573
  return this.http.get(`/apps/${encodeURIComponent(appId)}/logs?${query}`);
106554
106574
  }
106555
- streamRaw(appRef, options) {
106575
+ streamRaw(appRef, options, signal) {
106556
106576
  const query = buildLogFilterQuery(options ?? {});
106557
106577
  const suffix = query ? `?${query}` : "";
106558
- return this.http.stream(`/apps/${encodeURIComponent(appRef)}/logs/stream${suffix}`);
106578
+ return this.http.stream(`/apps/${encodeURIComponent(appRef)}/logs/stream${suffix}`, signal);
106559
106579
  }
106560
106580
  async* stream(appId, options) {
106561
106581
  const query = buildLogFilterQuery(options ?? {});
@@ -106995,6 +107015,49 @@ async function dataExport(ctx, input) {
106995
107015
  exportedAt: new Date().toISOString()
106996
107016
  };
106997
107017
  }
107018
+ // ../core/dist/commands/data-oauth.js
107019
+ var dataOauthInputSchema = exports_external.object({
107020
+ app: exports_external.string().optional(),
107021
+ provider: exports_external.string().default("google"),
107022
+ clientId: exports_external.string().optional(),
107023
+ clientSecret: exports_external.string().optional(),
107024
+ remove: exports_external.boolean().optional(),
107025
+ json: exports_external.boolean().optional()
107026
+ });
107027
+ async function dataOauth(ctx, input = { provider: "google" }) {
107028
+ const name = input.app ?? readPercherTomlAppName(ctx.cwd);
107029
+ if (!name) {
107030
+ throw new PercherCoreError("No app specified and no percher.toml found", {
107031
+ code: "data.no-app",
107032
+ hint: "Pass an app name or run from a directory with percher.toml."
107033
+ });
107034
+ }
107035
+ const provider = input.provider || "google";
107036
+ if (input.remove) {
107037
+ await ctx.client.data.removeOAuthProvider(name, provider);
107038
+ return { action: "removed", provider };
107039
+ }
107040
+ if (input.clientId && input.clientSecret) {
107041
+ const res = await ctx.client.data.setOAuthProvider(name, {
107042
+ provider,
107043
+ clientId: input.clientId,
107044
+ clientSecret: input.clientSecret
107045
+ });
107046
+ return {
107047
+ action: "set",
107048
+ provider: res.provider,
107049
+ redirectUri: res.redirectUri,
107050
+ note: res.note
107051
+ };
107052
+ }
107053
+ const status = await ctx.client.data.getOAuthStatus(name);
107054
+ return {
107055
+ action: "status",
107056
+ provider,
107057
+ providers: status.providers,
107058
+ redirectUri: status.redirectUri
107059
+ };
107060
+ }
106998
107061
  // ../core/dist/commands/delete.js
106999
107062
  var deleteInputSchema = exports_external.object({
107000
107063
  app: exports_external.string().optional().describe("App name to delete (defaults to percher.toml app name)"),
@@ -109928,12 +109991,12 @@ import { basename as basename3, join as join17 } from "node:path";
109928
109991
  // ../core/dist/detect.js
109929
109992
  import { existsSync as existsSync12, readFileSync as readFileSync9 } from "node:fs";
109930
109993
  import { join as join16 } from "node:path";
109931
- function exists(dir, ...names) {
109932
- return names.some((n) => existsSync12(join16(dir, n)));
109933
- }
109934
- function readJson(path) {
109994
+ function readPackageJson(src) {
109995
+ const raw = src.text("package.json");
109996
+ if (raw === null)
109997
+ return null;
109935
109998
  try {
109936
- return JSON.parse(readFileSync9(path, "utf8"));
109999
+ return JSON.parse(raw);
109937
110000
  } catch {
109938
110001
  return null;
109939
110002
  }
@@ -109945,8 +110008,8 @@ function hasDependency(pkg, name) {
109945
110008
  const dev2 = pkg.devDependencies ?? {};
109946
110009
  return name in deps || name in dev2;
109947
110010
  }
109948
- function detectDataMode(pkg, cwd) {
109949
- if (exists(cwd, "convex.json", "convex/_generated")) {
110011
+ function detectDataMode(pkg, src) {
110012
+ if (src.has("convex.json", "convex/_generated")) {
109950
110013
  return { mode: "convex" };
109951
110014
  }
109952
110015
  if (hasDependency(pkg, "convex")) {
@@ -109957,36 +110020,35 @@ function detectDataMode(pkg, cwd) {
109957
110020
  }
109958
110021
  return { mode: "none" };
109959
110022
  }
109960
- async function detectFramework(cwd) {
109961
- const pkgPath = join16(cwd, "package.json");
109962
- const pkg = existsSync12(pkgPath) ? readJson(pkgPath) : null;
109963
- if (exists(cwd, "Dockerfile")) {
110023
+ function detectFromSource(src) {
110024
+ const pkg = readPackageJson(src);
110025
+ if (src.has("Dockerfile")) {
109964
110026
  return {
109965
110027
  runtime: "docker",
109966
110028
  framework: "docker",
109967
110029
  web: { port: 8080, health: "/health" },
109968
- data: detectDataMode(pkg, cwd)
110030
+ data: detectDataMode(pkg, src)
109969
110031
  };
109970
110032
  }
109971
- if (exists(cwd, "next.config.js", "next.config.ts", "next.config.mjs")) {
110033
+ if (src.has("next.config.js", "next.config.ts", "next.config.mjs")) {
109972
110034
  return {
109973
110035
  runtime: "node",
109974
110036
  framework: "nextjs",
109975
110037
  build: { command: "bun run build", output: ".next" },
109976
110038
  web: { port: 3000, health: "/api/health" },
109977
- data: detectDataMode(pkg, cwd)
110039
+ data: detectDataMode(pkg, src)
109978
110040
  };
109979
110041
  }
109980
- if (exists(cwd, "svelte.config.js", "svelte.config.ts")) {
110042
+ if (src.has("svelte.config.js", "svelte.config.ts")) {
109981
110043
  return {
109982
110044
  runtime: "node",
109983
110045
  framework: "sveltekit",
109984
110046
  build: { command: "bun run build", output: "build" },
109985
110047
  web: { port: 3000, health: "/health" },
109986
- data: detectDataMode(pkg, cwd)
110048
+ data: detectDataMode(pkg, src)
109987
110049
  };
109988
110050
  }
109989
- if (exists(cwd, "astro.config.mjs", "astro.config.ts", "astro.config.js")) {
110051
+ if (src.has("astro.config.mjs", "astro.config.ts", "astro.config.js")) {
109990
110052
  const ssr = hasDependency(pkg, "@astrojs/node");
109991
110053
  if (ssr) {
109992
110054
  return {
@@ -109994,7 +110056,7 @@ async function detectFramework(cwd) {
109994
110056
  framework: "astro",
109995
110057
  build: { command: "bun run build", output: "dist" },
109996
110058
  web: { port: 4321, health: "/health" },
109997
- data: detectDataMode(pkg, cwd)
110059
+ data: detectDataMode(pkg, src)
109998
110060
  };
109999
110061
  }
110000
110062
  return {
@@ -110002,25 +110064,25 @@ async function detectFramework(cwd) {
110002
110064
  framework: "astro",
110003
110065
  web: { port: STATIC_DOCKER_PORT, health: "/" },
110004
110066
  staticBuild: { outputDir: "dist", spa: false },
110005
- data: detectDataMode(pkg, cwd)
110067
+ data: detectDataMode(pkg, src)
110006
110068
  };
110007
110069
  }
110008
- if (exists(cwd, "remix.config.js", "remix.config.ts") || hasDependency(pkg, "@remix-run/node")) {
110070
+ if (src.has("remix.config.js", "remix.config.ts") || hasDependency(pkg, "@remix-run/node")) {
110009
110071
  return {
110010
110072
  runtime: "node",
110011
110073
  framework: "remix",
110012
110074
  build: { command: "bun run build", output: "build" },
110013
110075
  web: { port: 3000, health: "/health" },
110014
- data: detectDataMode(pkg, cwd)
110076
+ data: detectDataMode(pkg, src)
110015
110077
  };
110016
110078
  }
110017
- if (exists(cwd, "vite.config.js", "vite.config.ts")) {
110079
+ if (src.has("vite.config.js", "vite.config.ts")) {
110018
110080
  return {
110019
110081
  runtime: "docker",
110020
110082
  framework: "vite",
110021
110083
  web: { port: STATIC_DOCKER_PORT, health: "/" },
110022
110084
  staticBuild: { outputDir: "dist", spa: true },
110023
- data: detectDataMode(pkg, cwd)
110085
+ data: detectDataMode(pkg, src)
110024
110086
  };
110025
110087
  }
110026
110088
  if (hasDependency(pkg, "express")) {
@@ -110028,7 +110090,7 @@ async function detectFramework(cwd) {
110028
110090
  runtime: "node",
110029
110091
  framework: "express",
110030
110092
  web: { port: 3000, health: "/health" },
110031
- data: detectDataMode(pkg, cwd)
110093
+ data: detectDataMode(pkg, src)
110032
110094
  };
110033
110095
  }
110034
110096
  if (hasDependency(pkg, "fastify")) {
@@ -110036,7 +110098,7 @@ async function detectFramework(cwd) {
110036
110098
  runtime: "node",
110037
110099
  framework: "fastify",
110038
110100
  web: { port: 3000, health: "/health" },
110039
- data: detectDataMode(pkg, cwd)
110101
+ data: detectDataMode(pkg, src)
110040
110102
  };
110041
110103
  }
110042
110104
  if (hasDependency(pkg, "hono")) {
@@ -110044,14 +110106,14 @@ async function detectFramework(cwd) {
110044
110106
  runtime: "node",
110045
110107
  framework: "hono",
110046
110108
  web: { port: 3000, health: "/health" },
110047
- data: detectDataMode(pkg, cwd)
110109
+ data: detectDataMode(pkg, src)
110048
110110
  };
110049
110111
  }
110050
110112
  if (pkg) {
110051
110113
  const detected = {
110052
110114
  runtime: "node",
110053
110115
  web: { port: 3000, health: "/health" },
110054
- data: detectDataMode(pkg, cwd)
110116
+ data: detectDataMode(pkg, src)
110055
110117
  };
110056
110118
  const scripts = pkg.scripts ?? {};
110057
110119
  if (scripts.build) {
@@ -110059,65 +110121,64 @@ async function detectFramework(cwd) {
110059
110121
  }
110060
110122
  return detected;
110061
110123
  }
110062
- const hasPython = exists(cwd, "requirements.txt", "pyproject.toml");
110124
+ const hasPython = src.has("requirements.txt", "pyproject.toml");
110063
110125
  if (hasPython) {
110064
- if (existsSync12(join16(cwd, "manage.py"))) {
110126
+ if (src.has("manage.py")) {
110065
110127
  return {
110066
110128
  runtime: "python",
110067
110129
  framework: "django",
110068
110130
  web: { port: 8000, health: "/health" },
110069
- data: detectDataMode(pkg, cwd)
110131
+ data: detectDataMode(pkg, src)
110070
110132
  };
110071
110133
  }
110072
- const mainPy = existsSync12(join16(cwd, "main.py")) ? readFileSync9(join16(cwd, "main.py"), "utf8") : "";
110134
+ const mainPy = src.text("main.py") ?? "";
110073
110135
  if (/FastAPI\s*\(/.test(mainPy) || /from\s+fastapi/.test(mainPy)) {
110074
110136
  return {
110075
110137
  runtime: "python",
110076
110138
  framework: "fastapi",
110077
110139
  web: { port: 8000, health: "/health" },
110078
- data: detectDataMode(pkg, cwd)
110140
+ data: detectDataMode(pkg, src)
110079
110141
  };
110080
110142
  }
110081
- const appPy = existsSync12(join16(cwd, "app.py")) ? readFileSync9(join16(cwd, "app.py"), "utf8") : "";
110143
+ const appPy = src.text("app.py") ?? "";
110082
110144
  if (/Flask\s*\(/.test(appPy) || /from\s+flask/.test(appPy)) {
110083
110145
  return {
110084
110146
  runtime: "python",
110085
110147
  framework: "flask",
110086
110148
  web: { port: 5000, health: "/health" },
110087
- data: detectDataMode(pkg, cwd)
110149
+ data: detectDataMode(pkg, src)
110088
110150
  };
110089
110151
  }
110090
110152
  return {
110091
110153
  runtime: "python",
110092
110154
  web: { port: 8000, health: "/health" },
110093
- data: detectDataMode(pkg, cwd)
110155
+ data: detectDataMode(pkg, src)
110094
110156
  };
110095
110157
  }
110096
- if (existsSync12(join16(cwd, "index.html"))) {
110158
+ if (src.has("index.html")) {
110097
110159
  return { runtime: "static" };
110098
110160
  }
110099
110161
  return null;
110100
110162
  }
110163
+ async function detectFramework(cwd) {
110164
+ const src = {
110165
+ has: (...names) => names.some((n) => existsSync12(join16(cwd, n))),
110166
+ text: (name) => {
110167
+ try {
110168
+ return readFileSync9(join16(cwd, name), "utf8");
110169
+ } catch {
110170
+ return null;
110171
+ }
110172
+ }
110173
+ };
110174
+ return detectFromSource(src);
110175
+ }
110101
110176
  function refineBunRuntime(detected, cwd) {
110102
- if (detected.runtime === "node" && exists(cwd, "bun.lockb", "bun.lock")) {
110177
+ if (detected.runtime === "node" && (existsSync12(join16(cwd, "bun.lockb")) || existsSync12(join16(cwd, "bun.lock")))) {
110103
110178
  return { ...detected, runtime: "bun" };
110104
110179
  }
110105
110180
  return detected;
110106
110181
  }
110107
-
110108
- // ../core/dist/commands/init.js
110109
- function isSuspiciousAutoName(name) {
110110
- if (/-\d{8,}$/.test(name))
110111
- return true;
110112
- if (/\d{4}-?\d{2}-?\d{2}/.test(name))
110113
- return true;
110114
- if (name.length >= 30)
110115
- return true;
110116
- return false;
110117
- }
110118
- function detectInteractive() {
110119
- return process.stdin?.isTTY === true && process.stdout?.isTTY === true;
110120
- }
110121
110182
  function buildDataConfig(mode) {
110122
110183
  if (mode === "convex") {
110123
110184
  return {
@@ -110133,6 +110194,32 @@ function buildDataConfig(mode) {
110133
110194
  }
110134
110195
  return { mode };
110135
110196
  }
110197
+ function configFromDetection(name, detected, dataMode) {
110198
+ return {
110199
+ app: {
110200
+ name,
110201
+ runtime: detected?.runtime ?? "node",
110202
+ ...detected?.framework ? { framework: detected.framework } : {}
110203
+ },
110204
+ ...detected?.build ? { build: detected.build } : {},
110205
+ ...detected?.web ? { web: detected.web } : {},
110206
+ ...detected?.data || dataMode ? { data: buildDataConfig(dataMode ?? detected?.data?.mode ?? "none") } : {}
110207
+ };
110208
+ }
110209
+
110210
+ // ../core/dist/commands/init.js
110211
+ function isSuspiciousAutoName(name) {
110212
+ if (/-\d{8,}$/.test(name))
110213
+ return true;
110214
+ if (/\d{4}-?\d{2}-?\d{2}/.test(name))
110215
+ return true;
110216
+ if (name.length >= 30)
110217
+ return true;
110218
+ return false;
110219
+ }
110220
+ function detectInteractive() {
110221
+ return process.stdin?.isTTY === true && process.stdout?.isTTY === true;
110222
+ }
110136
110223
  var initInputSchema = exports_external.object({
110137
110224
  force: exports_external.boolean().optional(),
110138
110225
  name: exports_external.string().optional(),
@@ -110144,8 +110231,8 @@ function scanEnvVars(_cwd) {
110144
110231
  }
110145
110232
  async function init(ctx, input = {}) {
110146
110233
  const path = join17(ctx.cwd, "percher.toml");
110147
- const exists2 = existsSync13(path);
110148
- if (exists2 && !input.force) {
110234
+ const exists = existsSync13(path);
110235
+ if (exists && !input.force) {
110149
110236
  throw new PercherCoreError("percher.toml already exists", {
110150
110237
  code: "init.exists",
110151
110238
  hint: "Run with --force to overwrite."
@@ -110175,18 +110262,7 @@ async function init(ctx, input = {}) {
110175
110262
  name = suggestion;
110176
110263
  }
110177
110264
  }
110178
- const config2 = {
110179
- app: {
110180
- name,
110181
- runtime: detected?.runtime ?? "node",
110182
- ...detected?.framework ? { framework: detected.framework } : {}
110183
- },
110184
- ...detected?.build ? { build: detected.build } : {},
110185
- ...detected?.web ? { web: detected.web } : {},
110186
- ...detected?.data || input.dataMode ? {
110187
- data: buildDataConfig(input.dataMode ?? detected?.data?.mode ?? "none")
110188
- } : {}
110189
- };
110265
+ const config2 = configFromDetection(name, detected, input.dataMode);
110190
110266
  const envVars = scanEnvVars(ctx.cwd);
110191
110267
  if (Object.keys(envVars).length > 0) {
110192
110268
  config2.env = envVars;
@@ -110218,7 +110294,7 @@ async function init(ctx, input = {}) {
110218
110294
  path,
110219
110295
  config: config2,
110220
110296
  detected,
110221
- overwritten: exists2,
110297
+ overwritten: exists,
110222
110298
  scaffoldedFiles
110223
110299
  };
110224
110300
  }
@@ -114565,6 +114641,27 @@ function tomlErrorToRecoveryProblems(err) {
114565
114641
  // ../core/dist/commands/env-scan.js
114566
114642
  import { readdirSync as readdirSync3, readFileSync as readFileSync10, statSync as statSync3 } from "node:fs";
114567
114643
  import { join as join22 } from "node:path";
114644
+ function platformInjectedClientEnvKeys(config2) {
114645
+ const data2 = config2.data;
114646
+ if (!data2)
114647
+ return [];
114648
+ switch (data2.mode) {
114649
+ case "pocketbase":
114650
+ return ["VITE_POCKETBASE_URL", "VITE_PERCHER_AI_URL"];
114651
+ case "convex":
114652
+ return data2.convex?.deployment_url ? ["VITE_CONVEX_URL"] : [];
114653
+ case "supabase": {
114654
+ const keys = [];
114655
+ if (data2.supabase?.url)
114656
+ keys.push("VITE_SUPABASE_URL");
114657
+ if (data2.supabase?.anon_key)
114658
+ keys.push("VITE_SUPABASE_ANON_KEY");
114659
+ return keys;
114660
+ }
114661
+ default:
114662
+ return [];
114663
+ }
114664
+ }
114568
114665
  var SOURCE_EXTS = new Set([
114569
114666
  ".ts",
114570
114667
  ".tsx",
@@ -115264,6 +115361,8 @@ ${bullets}`,
115264
115361
  let missingBuildEnvKeys = [];
115265
115362
  try {
115266
115363
  const existingEnvKeys = new Set(Object.keys(await ctx.client.env.list(app.id)));
115364
+ for (const k of platformInjectedClientEnvKeys(config2))
115365
+ existingEnvKeys.add(k);
115267
115366
  missingBuildEnvKeys = scanForMissingBuildEnvRefs(ctx.cwd, existingEnvKeys);
115268
115367
  } catch {}
115269
115368
  if (missingBuildEnvKeys.length > 0 && !input.force) {
@@ -115786,13 +115885,22 @@ function emitPreflight(ctx, config2, tarball) {
115786
115885
  ctx.status(line);
115787
115886
  }
115788
115887
  }
115888
+ // ../core/dist/commands/publish-from-repo.js
115889
+ var publishFromRepoInputSchema = exports_external.object({
115890
+ url: exports_external.string().min(1).describe("Public https git repo URL to clone and deploy (one-shot — no webhook is created)."),
115891
+ name: exports_external.string().optional().describe("App name. Overrides the repo's percher.toml [app].name; required if the repo omits it."),
115892
+ branch: exports_external.string().optional().describe("Branch to deploy. Defaults to main."),
115893
+ note: exports_external.string().optional().describe("Optional note recorded against the deploy."),
115894
+ waitForLive: exports_external.boolean().optional().describe("If false (default on the hosted connector), returns once the deploy is queued — resume with percher_wait_for_deploy. If true, block until live or failed.")
115895
+ });
115789
115896
  // ../core/dist/commands/publish-inline.js
115790
115897
  var publishInlineInputSchema = exports_external.object({
115791
- name: exports_external.string().optional().describe("Override the app name from percher.toml. Optional when percher.toml sets [app].name."),
115898
+ name: exports_external.string().optional().describe("App name. Overrides [app].name when percher.toml is present; REQUIRED when no percher.toml is provided (so the auto-detected app can be named)."),
115792
115899
  files: exports_external.array(exports_external.object({
115793
115900
  path: exports_external.string().min(1).describe("Repo-relative path (no leading / and no ..)."),
115794
- content: exports_external.string().describe("UTF-8 file contents.")
115795
- })).min(1).describe("Inline source files. MUST include a percher.toml."),
115901
+ content: exports_external.string().describe("File contents — UTF-8 text, or base64-encoded bytes when encoding is 'base64'."),
115902
+ encoding: exports_external.enum(["utf8", "base64"]).optional().describe("Content encoding. Default 'utf8' (text). Use 'base64' for binary assets (images, fonts).")
115903
+ })).min(1).describe("Inline source files. Include a percher.toml ([app].name + runtime), or omit it and Percher auto-detects the runtime from the files (then `name` is required)."),
115796
115904
  preview: exports_external.boolean().optional().describe("Deploy as a preview (does not replace the live version)."),
115797
115905
  message: exports_external.string().optional().describe("Deploy note (visible in deploy history)."),
115798
115906
  noCache: exports_external.boolean().optional().describe("Skip the image cache and force a fresh build."),
@@ -118187,7 +118295,7 @@ function registerAuthCommands(cli, ctx) {
118187
118295
  // src/commands/data.ts
118188
118296
  var import_picocolors6 = __toESM(require_picocolors(), 1);
118189
118297
  function registerDataCommands(cli, ctx, argv) {
118190
- cli.command("data [app] [...rest]", "Show app data status and collections").option("--json", "Output as JSON").option("--reveal", "(reset-superuser only) also print the plaintext password once").option("--out <path>", "(export only) Output file (default: ./<app>-pb-<timestamp>.tar.gz)").action(async (app, _rest, options) => {
118298
+ cli.command("data [app] [...rest]", "Show app data status and collections").option("--json", "Output as JSON").option("--reveal", "(reset-superuser only) also print the plaintext password once").option("--out <path>", "(export only) Output file (default: ./<app>-pb-<timestamp>.tar.gz)").option("--provider <name>", "(oauth only) OAuth provider (default: google)").option("--app <name>", "(oauth only) target app (default: read percher.toml)").option("--client-id <id>", "(oauth only) OAuth client id").option("--client-secret <secret>", "(oauth only) OAuth client secret").option("--remove", "(oauth only) remove the provider instead of setting it").action(async (app, _rest, options) => {
118191
118299
  if (app === "reset-superuser") {
118192
118300
  const idx = argv.indexOf("reset-superuser");
118193
118301
  const realApp = idx >= 0 ? firstNonFlagAfter(argv, idx) : undefined;
@@ -118208,6 +118316,37 @@ function registerDataCommands(cli, ctx, argv) {
118208
118316
  await writeLine(ctx, ` Size: ${mb} MB`);
118209
118317
  return;
118210
118318
  }
118319
+ if (app === "oauth") {
118320
+ const idx = argv.indexOf("oauth");
118321
+ const provider = (idx >= 0 ? firstNonFlagAfter(argv, idx) : undefined) ?? options.provider ?? "google";
118322
+ const result2 = await dataOauth(ctx, {
118323
+ app: options.app,
118324
+ provider,
118325
+ clientId: options.clientId,
118326
+ clientSecret: options.clientSecret,
118327
+ remove: options.remove
118328
+ });
118329
+ if (result2.action === "removed") {
118330
+ await writeLine(ctx, `${import_picocolors6.default.green("✓")} Removed ${result2.provider} sign-in.`);
118331
+ } else if (result2.action === "set") {
118332
+ await writeLine(ctx, `${import_picocolors6.default.green("✓")} ${result2.provider} sign-in enabled.`);
118333
+ await writeLine(ctx, "");
118334
+ await writeLine(ctx, import_picocolors6.default.bold("One more step — add this redirect URI in the provider console:"));
118335
+ await writeLine(ctx, ` ${result2.redirectUri}`);
118336
+ } else {
118337
+ if (!result2.providers?.length) {
118338
+ await writeLine(ctx, import_picocolors6.default.dim("No OAuth providers configured."));
118339
+ } else {
118340
+ await writeLine(ctx, import_picocolors6.default.bold("Configured providers"));
118341
+ for (const p of result2.providers) {
118342
+ await writeLine(ctx, ` ${p.provider.padEnd(12)} ${import_picocolors6.default.dim(p.clientId)}`);
118343
+ }
118344
+ }
118345
+ await writeLine(ctx, "");
118346
+ await writeLine(ctx, `Redirect URI: ${result2.redirectUri}`);
118347
+ }
118348
+ return;
118349
+ }
118211
118350
  const result = await data(ctx, { app, json: options.json });
118212
118351
  if (options.json) {
118213
118352
  await writeLine(ctx, JSON.stringify(result, null, 2));
@@ -118237,6 +118376,7 @@ function registerDataCommands(cli, ctx, argv) {
118237
118376
  const result = await resetSuperuser(ctx, { app, reveal: options.reveal });
118238
118377
  await printSuperuserResetResult(ctx, result);
118239
118378
  });
118379
+ cli.command("data oauth [provider]", "Configure capsule OAuth sign-in (e.g. `data oauth google --client-id … --client-secret …`)").action(async () => {});
118240
118380
  cli.command("data export [app]", "Download a snapshot of your app's PocketBase data as a .tar.gz").action(async () => {});
118241
118381
  cli.command("backups [app]", "List available backups for an app").action(async (app) => {
118242
118382
  const { backups, skipped } = await listBackups(ctx, { app });
@@ -119191,7 +119331,7 @@ async function renderError(stderr, error48) {
119191
119331
  // package.json
119192
119332
  var package_default = {
119193
119333
  name: "percher",
119194
- version: "0.5.11",
119334
+ version: "0.5.13",
119195
119335
  description: "CLI for the Percher hosting platform — publish apps with one command",
119196
119336
  type: "module",
119197
119337
  bin: {
@@ -119215,8 +119355,8 @@ var package_default = {
119215
119355
  typecheck: "tsc --noEmit"
119216
119356
  },
119217
119357
  dependencies: {
119218
- "@percher/client": "^0.2.10",
119219
- "@percher/core": "^0.4.5",
119358
+ "@percher/client": "^0.2.16",
119359
+ "@percher/core": "^0.4.17",
119220
119360
  "@percher/toml": "^0.1.5",
119221
119361
  cac: "^7.0.0",
119222
119362
  picocolors: "^1.1.1"
package/dist/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "percher",
3
- "version": "0.5.11",
3
+ "version": "0.5.13",
4
4
  "description": "CLI for the Percher hosting platform \u2014 publish apps with one command",
5
5
  "type": "module",
6
6
  "bin": {
@@ -24,8 +24,8 @@
24
24
  "typecheck": "tsc --noEmit"
25
25
  },
26
26
  "dependencies": {
27
- "@percher/client": "^0.2.10",
28
- "@percher/core": "^0.4.5",
27
+ "@percher/client": "^0.2.16",
28
+ "@percher/core": "^0.4.17",
29
29
  "@percher/toml": "^0.1.5",
30
30
  "cac": "^7.0.0",
31
31
  "picocolors": "^1.1.1"
@@ -1 +1 @@
1
- {"version":3,"file":"data.d.ts","sourceRoot":"","sources":["../../../src/commands/data.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,eAAe,CAAC;AAE7C,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,KAAK,CAAC;AAI/B,wBAAgB,oBAAoB,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,GAAG,IAAI,CA4LjF"}
1
+ {"version":3,"file":"data.d.ts","sourceRoot":"","sources":["../../../src/commands/data.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,eAAe,CAAC;AAE7C,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,KAAK,CAAC;AAI/B,wBAAgB,oBAAoB,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,GAAG,IAAI,CAkPjF"}
@@ -15,6 +15,11 @@ export function registerDataCommands(cli, ctx, argv) {
15
15
  .option("--json", "Output as JSON")
16
16
  .option("--reveal", "(reset-superuser only) also print the plaintext password once")
17
17
  .option("--out <path>", "(export only) Output file (default: ./<app>-pb-<timestamp>.tar.gz)")
18
+ .option("--provider <name>", "(oauth only) OAuth provider (default: google)")
19
+ .option("--app <name>", "(oauth only) target app (default: read percher.toml)")
20
+ .option("--client-id <id>", "(oauth only) OAuth client id")
21
+ .option("--client-secret <secret>", "(oauth only) OAuth client secret")
22
+ .option("--remove", "(oauth only) remove the provider instead of setting it")
18
23
  .action(async (app, _rest, options) => {
19
24
  // cac matches commands by single first-token equality, so
20
25
  // `percher data reset-superuser <app>` would otherwise hit this
@@ -49,6 +54,43 @@ export function registerDataCommands(cli, ctx, argv) {
49
54
  await writeLine(ctx, ` Size: ${mb} MB`);
50
55
  return;
51
56
  }
57
+ if (app === "oauth") {
58
+ // The positional after `oauth` is the PROVIDER — `percher data oauth
59
+ // google` (as advertised), NOT an app name. The app comes from `--app`
60
+ // or percher.toml (the capsule flow runs from the app dir).
61
+ const idx = argv.indexOf("oauth");
62
+ const provider = (idx >= 0 ? firstNonFlagAfter(argv, idx) : undefined) ?? options.provider ?? "google";
63
+ const result = await core.dataOauth(ctx, {
64
+ app: options.app,
65
+ provider,
66
+ clientId: options.clientId,
67
+ clientSecret: options.clientSecret,
68
+ remove: options.remove,
69
+ });
70
+ if (result.action === "removed") {
71
+ await writeLine(ctx, `${pc.green("✓")} Removed ${result.provider} sign-in.`);
72
+ }
73
+ else if (result.action === "set") {
74
+ await writeLine(ctx, `${pc.green("✓")} ${result.provider} sign-in enabled.`);
75
+ await writeLine(ctx, "");
76
+ await writeLine(ctx, pc.bold("One more step — add this redirect URI in the provider console:"));
77
+ await writeLine(ctx, ` ${result.redirectUri}`);
78
+ }
79
+ else {
80
+ if (!result.providers?.length) {
81
+ await writeLine(ctx, pc.dim("No OAuth providers configured."));
82
+ }
83
+ else {
84
+ await writeLine(ctx, pc.bold("Configured providers"));
85
+ for (const p of result.providers) {
86
+ await writeLine(ctx, ` ${p.provider.padEnd(12)} ${pc.dim(p.clientId)}`);
87
+ }
88
+ }
89
+ await writeLine(ctx, "");
90
+ await writeLine(ctx, `Redirect URI: ${result.redirectUri}`);
91
+ }
92
+ return;
93
+ }
52
94
  const result = await core.data(ctx, { app, json: options.json });
53
95
  if (options.json) {
54
96
  await writeLine(ctx, JSON.stringify(result, null, 2));
@@ -85,6 +127,12 @@ export function registerDataCommands(cli, ctx, argv) {
85
127
  const result = await core.resetSuperuser(ctx, { app, reveal: options.reveal });
86
128
  await printSuperuserResetResult(ctx, result);
87
129
  });
130
+ cli
131
+ .command("data oauth [provider]", "Configure capsule OAuth sign-in (e.g. `data oauth google --client-id … --client-secret …`)")
132
+ .action(async () => {
133
+ // Unreachable in practice — handled by the "data [app] [...rest]" action
134
+ // above via the `app === "oauth"` branch. Registered here so --help lists it.
135
+ });
88
136
  cli
89
137
  .command("data export [app]", "Download a snapshot of your app's PocketBase data as a .tar.gz")
90
138
  // --out is registered on the parent `data [app] [...rest]` so cac
@@ -1 +1 @@
1
- {"version":3,"file":"data.js","sourceRoot":"","sources":["../../../src/commands/data.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"data.js","sourceRoot":"","sources":["../../../src/commands/data.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,7 @@
1
+ :8080 {
2
+ bind 0.0.0.0
3
+ root * /srv
4
+ encode gzip zstd
5
+ try_files {path} /index.html
6
+ file_server
7
+ }
@@ -0,0 +1,39 @@
1
+ # Capsule build: a Bun stage compiles the static Preact bundle, then a minimal
2
+ # Caddy image serves it. runtime = "docker" (not "static") because the static
3
+ # runtime serves pre-built files only — it does not run a build command.
4
+ # Generated by Percher; safe to edit and own.
5
+
6
+ # --- Build stage: install deps and build the static bundle -----------
7
+ FROM oven/bun:1-alpine AS build
8
+ WORKDIR /app
9
+ COPY . .
10
+ # Percher injects VITE_POCKETBASE_URL (the app's own pb-<app>.<suffix> origin)
11
+ # as a build-arg. A runtime=docker build only sees it if the Dockerfile DECLARES
12
+ # the ARG before the build — without this, db.ts compiles against `undefined`.
13
+ ARG VITE_POCKETBASE_URL
14
+ ENV VITE_POCKETBASE_URL=$VITE_POCKETBASE_URL
15
+ # Managed inference endpoint (Slice 3) — also a public URL, baked in the same way.
16
+ ARG VITE_PERCHER_AI_URL
17
+ ENV VITE_PERCHER_AI_URL=$VITE_PERCHER_AI_URL
18
+ RUN bun install && bun run build
19
+
20
+ # --- Serve stage: minimal Caddy image --------------------------------
21
+ # caddy:2-alpine ships /usr/bin/caddy with cap_net_bind_service file caps.
22
+ # Percher runs every container with --security-opt no-new-privileges, which
23
+ # makes execve refuse a cap-bearing binary. Strip the caps with setcap -r —
24
+ # Caddy listens on :8080 (>= 1024) and never needs the cap.
25
+ FROM caddy:2-alpine AS caddy-source
26
+ FROM alpine:3.21
27
+ COPY --from=caddy-source /usr/bin/caddy /usr/bin/caddy
28
+ RUN apk add --no-cache libcap \
29
+ && (setcap -r /usr/bin/caddy 2>/dev/null || true) \
30
+ && test -z "$(getcap /usr/bin/caddy)" \
31
+ && apk del libcap \
32
+ && rm -rf /var/cache/apk/*
33
+ COPY Caddyfile /etc/caddy/Caddyfile
34
+ COPY --from=build /app/dist /srv
35
+ # HOME points at the writable tmpfs so non-root Caddy can persist its XDG state
36
+ # under Percher's read-only root filesystem.
37
+ ENV HOME=/tmp
38
+ EXPOSE 8080
39
+ CMD ["caddy", "run", "--config", "/etc/caddy/Caddyfile", "--adapter", "caddyfile"]
@@ -0,0 +1,12 @@
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
+ <title>__APP_NAME__</title>
7
+ </head>
8
+ <body>
9
+ <div id="app"></div>
10
+ <script type="module" src="/src/main.tsx"></script>
11
+ </body>
12
+ </html>
@@ -0,0 +1,20 @@
1
+ {
2
+ "name": "__APP_NAME__",
3
+ "version": "0.1.0",
4
+ "private": true,
5
+ "type": "module",
6
+ "scripts": {
7
+ "dev": "vite",
8
+ "build": "vite build",
9
+ "preview": "vite preview"
10
+ },
11
+ "dependencies": {
12
+ "pocketbase": "^0.26.0",
13
+ "preact": "^10.22.0"
14
+ },
15
+ "devDependencies": {
16
+ "@preact/preset-vite": "^2.9.0",
17
+ "typescript": "^5.5.0",
18
+ "vite": "^5.3.0"
19
+ }
20
+ }
@@ -0,0 +1,18 @@
1
+ {
2
+ "version": 1,
3
+ "collections": [
4
+ {
5
+ "name": "messages",
6
+ "type": "base",
7
+ "fields": [
8
+ { "name": "text", "type": "text", "required": true, "max": 500 },
9
+ { "name": "author", "type": "text", "required": true }
10
+ ],
11
+ "listRule": "",
12
+ "viewRule": "",
13
+ "createRule": "@request.auth.id != \"\"",
14
+ "updateRule": "author = @request.auth.id",
15
+ "deleteRule": "author = @request.auth.id"
16
+ }
17
+ ]
18
+ }
@@ -0,0 +1,13 @@
1
+ [app]
2
+ name = "__APP_NAME__"
3
+ runtime = "docker"
4
+ framework = "vite"
5
+
6
+ [web]
7
+ port = 8080
8
+ health = "/"
9
+
10
+ # A capsule auto-provisions a PocketBase sidecar (db + auth + realtime + file
11
+ # storage). Percher applies pb/schema.json before the app goes live.
12
+ [data]
13
+ mode = "pocketbase"
@@ -0,0 +1,65 @@
1
+ import { useEffect, useState } from "preact/hooks";
2
+ import { Auth } from "./components/Auth";
3
+ import { db, pb } from "./lib/db";
4
+ import { subscribe } from "./lib/realtime";
5
+
6
+ interface Message {
7
+ id: string;
8
+ text: string;
9
+ author: string;
10
+ }
11
+
12
+ // The guestbook demo — email/password sign-in + db + realtime, working the
13
+ // moment `scaffold → publish` finishes (Percher provisions the schema before
14
+ // the app goes live). Only signed-in users can post (pb/schema.json createRule);
15
+ // each row is owner-editable (updateRule/deleteRule key off author).
16
+ export function App() {
17
+ const [messages, setMessages] = useState<Message[]>([]);
18
+ const [text, setText] = useState("");
19
+ // Force a re-render when auth state changes (sign in/out).
20
+ const [, bump] = useState(0);
21
+
22
+ async function load() {
23
+ setMessages(await db.collection<Message>("messages").getFullList());
24
+ }
25
+
26
+ useEffect(() => {
27
+ load();
28
+ const off = subscribe<Message>("messages", () => load());
29
+ return () => {
30
+ void off.then((fn) => fn());
31
+ };
32
+ }, []);
33
+
34
+ async function post() {
35
+ const author = pb.authStore.record?.id;
36
+ if (!author || !text.trim()) return;
37
+ await db.collection("messages").create({ text, author });
38
+ setText("");
39
+ }
40
+
41
+ return (
42
+ <main style={{ fontFamily: "system-ui", padding: 40, maxWidth: 640, margin: "0 auto" }}>
43
+ <h1>__APP_NAME__</h1>
44
+ <p>A Percher capsule — PocketBase db + auth + realtime, pre-wired.</p>
45
+ <Auth onChange={() => bump((n) => n + 1)} />
46
+ {pb.authStore.isValid && (
47
+ <div style={{ display: "flex", gap: 8, margin: "16px 0" }}>
48
+ <input
49
+ value={text}
50
+ placeholder="Leave a message"
51
+ onInput={(e) => setText((e.target as HTMLInputElement).value)}
52
+ />
53
+ <button type="button" onClick={post}>
54
+ Post
55
+ </button>
56
+ </div>
57
+ )}
58
+ <ul>
59
+ {messages.map((m) => (
60
+ <li key={m.id}>{m.text}</li>
61
+ ))}
62
+ </ul>
63
+ </main>
64
+ );
65
+ }
@@ -0,0 +1,75 @@
1
+ import { useState } from "preact/hooks";
2
+ import { pb } from "../lib/db";
3
+ import { SignInWithGoogle } from "./SignInWithGoogle";
4
+
5
+ /**
6
+ * Email/password sign-up + sign-in against PocketBase's built-in `users` auth
7
+ * collection — works on day one with ZERO provider config. Google sign-in is a
8
+ * convenience added on top (Slice 2), never a prerequisite.
9
+ */
10
+ export function Auth({ onChange }: { onChange?: () => void }) {
11
+ const [email, setEmail] = useState("");
12
+ const [password, setPassword] = useState("");
13
+ const [error, setError] = useState("");
14
+
15
+ async function signIn() {
16
+ setError("");
17
+ try {
18
+ await pb.collection("users").authWithPassword(email, password);
19
+ onChange?.();
20
+ } catch (e) {
21
+ setError((e as Error).message);
22
+ }
23
+ }
24
+ async function signUp() {
25
+ setError("");
26
+ try {
27
+ await pb.collection("users").create({ email, password, passwordConfirm: password });
28
+ await signIn();
29
+ } catch (e) {
30
+ setError((e as Error).message);
31
+ }
32
+ }
33
+ function signOut() {
34
+ pb.authStore.clear();
35
+ onChange?.();
36
+ }
37
+
38
+ if (pb.authStore.isValid) {
39
+ return (
40
+ <div>
41
+ <span>Signed in as {pb.authStore.record?.email}</span>{" "}
42
+ <button type="button" onClick={signOut}>
43
+ Sign out
44
+ </button>
45
+ </div>
46
+ );
47
+ }
48
+
49
+ return (
50
+ <div style={{ display: "grid", gap: 8, maxWidth: 280 }}>
51
+ <input
52
+ type="email"
53
+ placeholder="Email"
54
+ value={email}
55
+ onInput={(e) => setEmail((e.target as HTMLInputElement).value)}
56
+ />
57
+ <input
58
+ type="password"
59
+ placeholder="Password"
60
+ value={password}
61
+ onInput={(e) => setPassword((e.target as HTMLInputElement).value)}
62
+ />
63
+ <div style={{ display: "flex", gap: 8 }}>
64
+ <button type="button" onClick={signIn}>
65
+ Sign in
66
+ </button>
67
+ <button type="button" onClick={signUp}>
68
+ Sign up
69
+ </button>
70
+ </div>
71
+ <SignInWithGoogle onChange={onChange} />
72
+ {error && <p style={{ color: "crimson" }}>{error}</p>}
73
+ </div>
74
+ );
75
+ }
@@ -0,0 +1,27 @@
1
+ import { pb } from "../lib/db";
2
+
3
+ /**
4
+ * One-click Google sign-in — the bonus path on top of email/password. It lights
5
+ * up once an operator runs `percher data oauth google` (Slice 2) to configure the
6
+ * provider in this app's PocketBase. Until then it renders disabled with a hint;
7
+ * the disabled state never blocks the email/password path.
8
+ */
9
+ export function SignInWithGoogle({ onChange }: { onChange?: () => void }) {
10
+ async function signIn() {
11
+ await pb.collection("users").authWithOAuth2({ provider: "google" });
12
+ onChange?.();
13
+ }
14
+
15
+ // Flip to true once `percher data oauth google` has configured the provider.
16
+ const enabled = false;
17
+ return (
18
+ <button
19
+ type="button"
20
+ disabled={!enabled}
21
+ onClick={enabled ? signIn : undefined}
22
+ title="Run `percher data oauth google` to enable Google sign-in"
23
+ >
24
+ Sign in with Google
25
+ </button>
26
+ );
27
+ }
@@ -0,0 +1,32 @@
1
+ import { pb } from "./db";
2
+
3
+ // Managed inference endpoint, injected at build time (a public URL, not a
4
+ // secret). The Dockerfile declares ARG VITE_PERCHER_AI_URL.
5
+ const AI_URL = import.meta.env.VITE_PERCHER_AI_URL;
6
+
7
+ /**
8
+ * Keyless managed inference — NO API key, billed to the app owner's Percher
9
+ * plan. Only works for SIGNED-IN users (the proxy validates the current PB
10
+ * session against this app's sidecar). Returns the completion text.
11
+ *
12
+ * const reply = await complete("Summarize: " + text);
13
+ */
14
+ export async function complete(
15
+ prompt: string,
16
+ opts?: { model?: string; maxTokens?: number },
17
+ ): Promise<string> {
18
+ if (!AI_URL) throw new Error("Managed inference isn't configured for this app.");
19
+ if (!pb.authStore.isValid) throw new Error("Sign in to use AI.");
20
+ const res = await fetch(AI_URL, {
21
+ method: "POST",
22
+ headers: { "content-type": "application/json", authorization: pb.authStore.token },
23
+ body: JSON.stringify({
24
+ model: opts?.model,
25
+ max_tokens: opts?.maxTokens ?? 512,
26
+ messages: [{ role: "user", content: prompt }],
27
+ }),
28
+ });
29
+ if (!res.ok) throw new Error(`AI request failed (${res.status})`);
30
+ const data = (await res.json()) as { choices?: Array<{ message?: { content?: string } }> };
31
+ return data.choices?.[0]?.message?.content ?? "";
32
+ }
@@ -0,0 +1,14 @@
1
+ import PocketBase from "pocketbase";
2
+
3
+ // The app's own PocketBase, injected at build time via the Dockerfile's
4
+ // ARG VITE_POCKETBASE_URL (the pb-<app>.<suffix> origin). Auth state persists in
5
+ // localStorage automatically.
6
+ export const pb = new PocketBase(import.meta.env.VITE_POCKETBASE_URL);
7
+
8
+ /**
9
+ * Typed data client. Write `db.collection<Message>("messages").create(...)`
10
+ * instead of hand-rolling the REST API.
11
+ */
12
+ export const db = {
13
+ collection: <T = Record<string, unknown>>(name: string) => pb.collection<T>(name),
14
+ };
@@ -0,0 +1,16 @@
1
+ import { pb } from "./db";
2
+
3
+ /**
4
+ * Subscribe to live changes on a collection — one call closes the cross-tab /
5
+ * cross-user realtime glue. The callback fires on every create/update/delete;
6
+ * resolves to an unsubscribe function.
7
+ *
8
+ * const off = await subscribe<Message>("messages", (e) => refresh());
9
+ * // later: off();
10
+ */
11
+ export function subscribe<T = Record<string, unknown>>(
12
+ collection: string,
13
+ cb: (e: { action: string; record: T }) => void,
14
+ ) {
15
+ return pb.collection<T>(collection).subscribe("*", cb);
16
+ }
@@ -0,0 +1,5 @@
1
+ import { render } from "preact";
2
+ import { App } from "./App";
3
+
4
+ const root = document.getElementById("app");
5
+ if (root) render(<App />, root);
@@ -0,0 +1,15 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ESNext",
4
+ "module": "ESNext",
5
+ "moduleResolution": "bundler",
6
+ "jsx": "react-jsx",
7
+ "jsxImportSource": "preact",
8
+ "lib": ["ESNext", "DOM", "DOM.Iterable"],
9
+ "types": ["vite/client"],
10
+ "strict": true,
11
+ "skipLibCheck": true,
12
+ "noEmit": true
13
+ },
14
+ "include": ["src"]
15
+ }
@@ -0,0 +1,6 @@
1
+ import preact from "@preact/preset-vite";
2
+ import { defineConfig } from "vite";
3
+
4
+ export default defineConfig({
5
+ plugins: [preact()],
6
+ });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "percher",
3
- "version": "0.5.11",
3
+ "version": "0.5.13",
4
4
  "description": "CLI for the Percher hosting platform \u2014 publish apps with one command",
5
5
  "type": "module",
6
6
  "bin": {
@@ -24,8 +24,8 @@
24
24
  "typecheck": "tsc --noEmit"
25
25
  },
26
26
  "dependencies": {
27
- "@percher/client": "^0.2.10",
28
- "@percher/core": "^0.4.5",
27
+ "@percher/client": "^0.2.16",
28
+ "@percher/core": "^0.4.17",
29
29
  "@percher/toml": "^0.1.5",
30
30
  "cac": "^7.0.0",
31
31
  "picocolors": "^1.1.1"