percher 0.3.0 → 0.4.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.
Files changed (178) hide show
  1. package/dist/bin/percher.js +111952 -18543
  2. package/dist/package.json +15 -6
  3. package/dist/src/cli-utils.d.ts +9 -0
  4. package/dist/src/cli-utils.d.ts.map +1 -1
  5. package/dist/src/cli-utils.js +18 -0
  6. package/dist/src/cli-utils.js.map +1 -1
  7. package/dist/src/commands/account.d.ts.map +1 -1
  8. package/dist/src/commands/account.js +63 -7
  9. package/dist/src/commands/account.js.map +1 -1
  10. package/dist/src/commands/admin.d.ts +17 -0
  11. package/dist/src/commands/admin.d.ts.map +1 -0
  12. package/dist/src/commands/admin.js +92 -0
  13. package/dist/src/commands/admin.js.map +1 -0
  14. package/dist/src/commands/apps.d.ts.map +1 -1
  15. package/dist/src/commands/apps.js +75 -7
  16. package/dist/src/commands/apps.js.map +1 -1
  17. package/dist/src/commands/deploys.d.ts.map +1 -1
  18. package/dist/src/commands/deploys.js +12 -2
  19. package/dist/src/commands/deploys.js.map +1 -1
  20. package/dist/src/commands/migrate.d.ts +4 -0
  21. package/dist/src/commands/migrate.d.ts.map +1 -0
  22. package/dist/src/commands/migrate.js +142 -0
  23. package/dist/src/commands/migrate.js.map +1 -0
  24. package/dist/src/commands/sharing.d.ts +23 -0
  25. package/dist/src/commands/sharing.d.ts.map +1 -0
  26. package/dist/src/commands/sharing.js +162 -0
  27. package/dist/src/commands/sharing.js.map +1 -0
  28. package/dist/src/commands/status.d.ts +4 -0
  29. package/dist/src/commands/status.d.ts.map +1 -0
  30. package/dist/src/commands/status.js +53 -0
  31. package/dist/src/commands/status.js.map +1 -0
  32. package/dist/src/commands/transfers.d.ts +20 -0
  33. package/dist/src/commands/transfers.d.ts.map +1 -0
  34. package/dist/src/commands/transfers.js +117 -0
  35. package/dist/src/commands/transfers.js.map +1 -0
  36. package/dist/src/index.d.ts.map +1 -1
  37. package/dist/src/index.js +44 -4
  38. package/dist/src/index.js.map +1 -1
  39. package/dist/templates/astro/Caddyfile +6 -0
  40. package/dist/templates/astro/Dockerfile +34 -0
  41. package/dist/templates/astro/astro.config.mjs +3 -0
  42. package/dist/templates/astro/package.json +14 -0
  43. package/dist/templates/astro/percher.toml +11 -0
  44. package/dist/templates/astro/src/pages/index.astro +15 -0
  45. package/dist/templates/bun/index.ts +23 -0
  46. package/dist/templates/bun/package.json +8 -0
  47. package/dist/templates/bun/percher.toml +10 -0
  48. package/dist/templates/django/Procfile +1 -0
  49. package/dist/templates/django/app/__init__.py +0 -0
  50. package/dist/templates/django/app/settings.py +44 -0
  51. package/dist/templates/django/app/urls.py +21 -0
  52. package/dist/templates/django/app/wsgi.py +5 -0
  53. package/dist/templates/django/manage.py +19 -0
  54. package/dist/templates/django/percher.toml +11 -0
  55. package/dist/templates/django/requirements.txt +2 -0
  56. package/dist/templates/dotnet/Dockerfile +18 -0
  57. package/dist/templates/dotnet/Program.cs +23 -0
  58. package/dist/templates/dotnet/app.csproj +9 -0
  59. package/dist/templates/dotnet/percher.toml +11 -0
  60. package/dist/templates/eleventy/.eleventy.js +12 -0
  61. package/dist/templates/eleventy/Caddyfile +6 -0
  62. package/dist/templates/eleventy/Dockerfile +34 -0
  63. package/dist/templates/eleventy/package.json +12 -0
  64. package/dist/templates/eleventy/percher.toml +11 -0
  65. package/dist/templates/eleventy/src/_includes/base.njk +12 -0
  66. package/dist/templates/eleventy/src/assets/style.css +12 -0
  67. package/dist/templates/eleventy/src/index.md +10 -0
  68. package/dist/templates/elysia/index.ts +8 -0
  69. package/dist/templates/elysia/package.json +12 -0
  70. package/dist/templates/elysia/percher.toml +11 -0
  71. package/dist/templates/express/index.ts +16 -0
  72. package/dist/templates/express/package.json +11 -0
  73. package/dist/templates/express/percher.toml +11 -0
  74. package/dist/templates/fastify/index.ts +13 -0
  75. package/dist/templates/fastify/package.json +11 -0
  76. package/dist/templates/fastify/percher.toml +11 -0
  77. package/dist/templates/flask/main.py +23 -0
  78. package/dist/templates/flask/percher.toml +11 -0
  79. package/dist/templates/flask/requirements.txt +1 -0
  80. package/dist/templates/go/Dockerfile +11 -0
  81. package/dist/templates/go/go.mod +3 -0
  82. package/dist/templates/go/main.go +53 -0
  83. package/dist/templates/go/percher.toml +11 -0
  84. package/dist/templates/hono/index.ts +12 -0
  85. package/dist/templates/hono/package.json +11 -0
  86. package/dist/templates/hono/percher.toml +11 -0
  87. package/dist/templates/htmx/index.ts +32 -0
  88. package/dist/templates/htmx/package.json +15 -0
  89. package/dist/templates/htmx/percher.toml +11 -0
  90. package/dist/templates/hugo/Caddyfile +6 -0
  91. package/dist/templates/hugo/Dockerfile +32 -0
  92. package/dist/templates/hugo/content/_index.md +7 -0
  93. package/dist/templates/hugo/hugo.toml +11 -0
  94. package/dist/templates/hugo/layouts/index.html +22 -0
  95. package/dist/templates/hugo/percher.toml +11 -0
  96. package/dist/templates/java/Dockerfile +15 -0
  97. package/dist/templates/java/Main.java +77 -0
  98. package/dist/templates/java/percher.toml +11 -0
  99. package/dist/templates/laravel/Dockerfile +15 -0
  100. package/dist/templates/laravel/composer.json +15 -0
  101. package/dist/templates/laravel/percher.toml +11 -0
  102. package/dist/templates/laravel/public/index.php +37 -0
  103. package/dist/templates/nextjs/app/api/health/route.ts +3 -0
  104. package/dist/templates/nextjs/app/layout.tsx +11 -0
  105. package/dist/templates/nextjs/app/page.tsx +10 -0
  106. package/dist/templates/nextjs/next.config.ts +5 -0
  107. package/dist/templates/nextjs/package.json +15 -0
  108. package/dist/templates/nextjs/percher.toml +15 -0
  109. package/dist/templates/node/index.js +26 -0
  110. package/dist/templates/node/index.ts +12 -0
  111. package/dist/templates/node/package.json +8 -0
  112. package/dist/templates/node/percher.toml +10 -0
  113. package/dist/templates/nuxt/app.vue +6 -0
  114. package/dist/templates/nuxt/nuxt.config.ts +4 -0
  115. package/dist/templates/nuxt/package.json +15 -0
  116. package/dist/templates/nuxt/percher.toml +15 -0
  117. package/dist/templates/nuxt/server/api/health.ts +1 -0
  118. package/dist/templates/phoenix/Dockerfile +15 -0
  119. package/dist/templates/phoenix/lib/application.ex +15 -0
  120. package/dist/templates/phoenix/lib/router.ex +39 -0
  121. package/dist/templates/phoenix/mix.exs +30 -0
  122. package/dist/templates/phoenix/percher.toml +11 -0
  123. package/dist/templates/preact/Caddyfile +7 -0
  124. package/dist/templates/preact/Dockerfile +34 -0
  125. package/dist/templates/preact/index.html +12 -0
  126. package/dist/templates/preact/package.json +19 -0
  127. package/dist/templates/preact/percher.toml +11 -0
  128. package/dist/templates/preact/src/main.tsx +13 -0
  129. package/dist/templates/preact/vite.config.ts +6 -0
  130. package/dist/templates/python/main.py +13 -0
  131. package/dist/templates/python/percher.toml +11 -0
  132. package/dist/templates/python/requirements.txt +2 -0
  133. package/dist/templates/rails/Dockerfile +11 -0
  134. package/dist/templates/rails/Gemfile +12 -0
  135. package/dist/templates/rails/config.ru +32 -0
  136. package/dist/templates/rails/percher.toml +11 -0
  137. package/dist/templates/remix/app/root.tsx +19 -0
  138. package/dist/templates/remix/app/routes/_index.tsx +10 -0
  139. package/dist/templates/remix/app/routes/health.tsx +3 -0
  140. package/dist/templates/remix/package.json +30 -0
  141. package/dist/templates/remix/percher.toml +15 -0
  142. package/dist/templates/remix/vite.config.ts +7 -0
  143. package/dist/templates/rust/Cargo.toml +19 -0
  144. package/dist/templates/rust/Dockerfile +14 -0
  145. package/dist/templates/rust/percher.toml +11 -0
  146. package/dist/templates/rust/src/main.rs +45 -0
  147. package/dist/templates/solid/Caddyfile +7 -0
  148. package/dist/templates/solid/Dockerfile +34 -0
  149. package/dist/templates/solid/index.html +12 -0
  150. package/dist/templates/solid/package.json +19 -0
  151. package/dist/templates/solid/percher.toml +11 -0
  152. package/dist/templates/solid/src/main.tsx +13 -0
  153. package/dist/templates/solid/vite.config.ts +6 -0
  154. package/dist/templates/static/index.html +15 -0
  155. package/dist/templates/static/percher.toml +6 -0
  156. package/dist/templates/static/style.css +32 -0
  157. package/dist/templates/sveltekit/package.json +19 -0
  158. package/dist/templates/sveltekit/percher.toml +15 -0
  159. package/dist/templates/sveltekit/src/app.html +12 -0
  160. package/dist/templates/sveltekit/src/routes/+page.svelte +2 -0
  161. package/dist/templates/sveltekit/src/routes/health/+server.ts +5 -0
  162. package/dist/templates/sveltekit/svelte.config.js +8 -0
  163. package/dist/templates/sveltekit/vite.config.js +6 -0
  164. package/dist/templates/vite-react/Caddyfile +7 -0
  165. package/dist/templates/vite-react/Dockerfile +34 -0
  166. package/dist/templates/vite-react/index.html +12 -0
  167. package/dist/templates/vite-react/package.json +22 -0
  168. package/dist/templates/vite-react/percher.toml +11 -0
  169. package/dist/templates/vite-react/src/main.tsx +20 -0
  170. package/dist/templates/vite-react/vite.config.ts +6 -0
  171. package/dist/templates/vite-vue/Caddyfile +7 -0
  172. package/dist/templates/vite-vue/Dockerfile +34 -0
  173. package/dist/templates/vite-vue/index.html +12 -0
  174. package/dist/templates/vite-vue/package.json +19 -0
  175. package/dist/templates/vite-vue/percher.toml +11 -0
  176. package/dist/templates/vite-vue/src/main.ts +12 -0
  177. package/dist/templates/vite-vue/vite.config.ts +6 -0
  178. package/package.json +15 -6
@@ -0,0 +1,20 @@
1
+ import type { Context } from "@percher/core";
2
+ import type { CAC } from "cac";
3
+ /**
4
+ * FUTURE15 Fas D — CLI surface for ownership transfer.
5
+ *
6
+ * percher transfer <email> [--app <name>]
7
+ * percher transfer --cancel <transferId> [--app <name>]
8
+ * percher transfer --list [--app <name>]
9
+ *
10
+ * percher transfers (bare = incoming list)
11
+ * percher transfers accept <transferId>
12
+ * percher transfers decline <transferId>
13
+ *
14
+ * The `transfer` (singular) command operates on ONE app; the
15
+ * `transfers` (plural) command is recipient-managed across apps.
16
+ * Same parent-action-dispatcher pattern as the share/invites
17
+ * commands so cac's first-token-only matching works.
18
+ */
19
+ export declare function registerTransfersCommands(cli: CAC, ctx: Context): void;
20
+ //# sourceMappingURL=transfers.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"transfers.d.ts","sourceRoot":"","sources":["../../../src/commands/transfers.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,eAAe,CAAC;AAE7C,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,KAAK,CAAC;AAI/B;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,yBAAyB,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,OAAO,GAAG,IAAI,CAqItE"}
@@ -0,0 +1,117 @@
1
+ import * as core from "@percher/core";
2
+ import pc from "picocolors";
3
+ import { writeLine } from "../cli-utils";
4
+ /**
5
+ * FUTURE15 Fas D — CLI surface for ownership transfer.
6
+ *
7
+ * percher transfer <email> [--app <name>]
8
+ * percher transfer --cancel <transferId> [--app <name>]
9
+ * percher transfer --list [--app <name>]
10
+ *
11
+ * percher transfers (bare = incoming list)
12
+ * percher transfers accept <transferId>
13
+ * percher transfers decline <transferId>
14
+ *
15
+ * The `transfer` (singular) command operates on ONE app; the
16
+ * `transfers` (plural) command is recipient-managed across apps.
17
+ * Same parent-action-dispatcher pattern as the share/invites
18
+ * commands so cac's first-token-only matching works.
19
+ */
20
+ export function registerTransfersCommands(cli, ctx) {
21
+ // ── transfer (singular, owner-managed for one app) ─────────────────
22
+ cli
23
+ .command("transfer [emailOrTransferId]", "Transfer ownership of an app: `transfer <email>` initiates; `transfer --cancel <id>` cancels; `transfer --list` shows history")
24
+ .option("--app <name>", "App name (overrides percher.toml)")
25
+ .option("--cancel", "Cancel a pending transfer by id (positional arg is the transferId)")
26
+ .option("--list", "List the transfer history for this app")
27
+ .action(async (emailOrTransferId, options) => {
28
+ if (options.list) {
29
+ const rows = await core.transferListForApp(ctx, { app: options.app });
30
+ if (rows.length === 0) {
31
+ await writeLine(ctx, pc.dim("No transfers for this app."));
32
+ return;
33
+ }
34
+ for (const r of rows) {
35
+ const status = r.status === "pending"
36
+ ? pc.yellow("pending")
37
+ : r.status === "accepted"
38
+ ? pc.green("accepted")
39
+ : pc.dim(r.status);
40
+ await writeLine(ctx, `${r.id} ${status.padEnd(20)} → ${r.toEmail} (${r.createdAt})`);
41
+ }
42
+ return;
43
+ }
44
+ if (options.cancel) {
45
+ if (!emailOrTransferId) {
46
+ throw new Error("Usage: percher transfer --cancel <transferId> [--app <name>]");
47
+ }
48
+ await core.transferCancel(ctx, {
49
+ app: options.app,
50
+ transferId: emailOrTransferId,
51
+ });
52
+ await writeLine(ctx, `${pc.green("✓")} Cancelled transfer ${emailOrTransferId}`);
53
+ return;
54
+ }
55
+ // No flag → initiate. Positional arg is the recipient email.
56
+ if (!emailOrTransferId) {
57
+ throw new Error("Usage: percher transfer <recipient-email> [--app <name>] | percher transfer --cancel <id> | percher transfer --list");
58
+ }
59
+ const initiated = await core.transferInitiate(ctx, {
60
+ app: options.app,
61
+ email: emailOrTransferId,
62
+ });
63
+ await writeLine(ctx, `${pc.green("✓")} Transfer initiated to ${initiated.toEmail} (id ${initiated.id})`);
64
+ await writeLine(ctx, pc.dim(`Expires ${new Date(initiated.expiresAt).toLocaleDateString()}. The recipient must accept via 'percher transfers accept ${initiated.id}'.`));
65
+ if (initiated.notes) {
66
+ await writeLine(ctx, pc.yellow(` ⚠ ${initiated.notes}`));
67
+ }
68
+ });
69
+ // ── transfers (plural, recipient-managed across apps) ──────────────
70
+ cli
71
+ .command("transfers [action] [transferId]", "Incoming ownership transfers: list (default) | accept <transferId> | decline <transferId>")
72
+ .action(async (action, transferId) => {
73
+ if (action === undefined || action === "list") {
74
+ const rows = await core.transfersIncoming(ctx, {});
75
+ if (rows.length === 0) {
76
+ await writeLine(ctx, pc.dim("No pending incoming transfers."));
77
+ return;
78
+ }
79
+ for (const r of rows) {
80
+ await writeLine(ctx, `${r.id} ${r.appName.padEnd(24)} from ${r.fromEmail} (expires ${new Date(r.expiresAt).toLocaleDateString()})`);
81
+ }
82
+ return;
83
+ }
84
+ if (action === "accept") {
85
+ if (!transferId) {
86
+ throw new Error("Usage: percher transfers accept <transferId>");
87
+ }
88
+ const accepted = await core.transfersAccept(ctx, { transferId });
89
+ await writeLine(ctx, `${pc.green("✓")} You now own ${pc.cyan(accepted.appName)}. Run 'percher open --app ${accepted.appName}' to see it.`);
90
+ return;
91
+ }
92
+ if (action === "decline") {
93
+ if (!transferId) {
94
+ throw new Error("Usage: percher transfers decline <transferId>");
95
+ }
96
+ await core.transfersDecline(ctx, { transferId });
97
+ await writeLine(ctx, `${pc.green("✓")} Declined transfer ${transferId}.`);
98
+ return;
99
+ }
100
+ throw new Error(`Unknown transfers action "${action}". Use list | accept <id> | decline <id>.`);
101
+ });
102
+ // Help-only registrations for --help discoverability.
103
+ cli.command("transfers list", "List pending incoming ownership transfers").action(async () => {
104
+ /* unreachable */
105
+ });
106
+ cli
107
+ .command("transfers accept [transferId]", "Accept an incoming ownership transfer")
108
+ .action(async () => {
109
+ /* unreachable */
110
+ });
111
+ cli
112
+ .command("transfers decline [transferId]", "Decline an incoming ownership transfer")
113
+ .action(async () => {
114
+ /* unreachable */
115
+ });
116
+ }
117
+ //# sourceMappingURL=transfers.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"transfers.js","sourceRoot":"","sources":["../../../src/commands/transfers.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,eAAe,CAAC;AAe7C,wBAAsB,GAAG,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,WAAW,CAAC,EAAE,OAAO,GAAG,OAAO,CAAC,MAAM,CAAC,CAmChF"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,eAAe,CAAC;AAoB7C,wBAAsB,GAAG,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,WAAW,CAAC,EAAE,OAAO,GAAG,OAAO,CAAC,MAAM,CAAC,CAmEhF"}
package/dist/src/index.js CHANGED
@@ -1,16 +1,20 @@
1
- import * as core from "@percher/core";
2
1
  import cac from "cac";
3
- import { SilentExitError } from "./cli-utils";
2
+ import { SilentExitError, writeUnknownCommand } from "./cli-utils";
4
3
  import { registerAccountCommands } from "./commands/account";
4
+ import { registerAdminCommands } from "./commands/admin";
5
5
  import { registerAppsCommands } from "./commands/apps";
6
6
  import { registerAuthCommands } from "./commands/auth";
7
7
  import { registerDataCommands } from "./commands/data";
8
8
  import { registerDeploysCommands } from "./commands/deploys";
9
9
  import { registerEnvCommands } from "./commands/env";
10
10
  import { registerGithubCommands } from "./commands/github";
11
+ import { registerMigrateCommands } from "./commands/migrate";
12
+ import { registerSharingCommands } from "./commands/sharing";
13
+ import { registerStatusCommands } from "./commands/status";
14
+ import { registerTransfersCommands } from "./commands/transfers";
11
15
  import { createCliContext } from "./context";
12
16
  import { renderError } from "./output";
13
- import { VERSION, showTelemetryNotice, trackEvent } from "./telemetry";
17
+ import { showTelemetryNotice, trackEvent, VERSION } from "./telemetry";
14
18
  import { checkForUpdates } from "./version-check";
15
19
  export async function run(argv, ctxOverride) {
16
20
  const cli = cac("percher");
@@ -22,13 +26,49 @@ export async function run(argv, ctxOverride) {
22
26
  registerAuthCommands(cli, ctx);
23
27
  registerAccountCommands(cli, ctx);
24
28
  registerGithubCommands(cli, ctx);
29
+ registerMigrateCommands(cli, ctx);
30
+ registerSharingCommands(cli, ctx);
31
+ registerStatusCommands(cli, ctx);
32
+ registerTransfersCommands(cli, ctx);
33
+ registerAdminCommands(cli, ctx);
25
34
  cli.help();
26
35
  cli.version(VERSION);
27
36
  const command = argv[0] ?? "help";
28
37
  try {
29
38
  await showTelemetryNotice();
30
39
  cli.parse(["percher", "percher", ...argv], { run: false });
31
- await cli.runMatchedCommand();
40
+ // cac silently no-ops when no command matches: runMatchedCommand()
41
+ // returns early on an undefined matchedCommand, so `percher
42
+ // bogus-cmd` used to exit 0 with no output. cac also unsets
43
+ // matchedCommand after printing help/version, so a typo paired
44
+ // with `--help` (`percher bogus-cmd --help`) looked identical to a
45
+ // valid `percher open --help`. Resolve the ambiguity by checking
46
+ // the requested positional against the registered command names
47
+ // directly, not by inspecting matchedCommand or the help flag.
48
+ // (dogfood Fas 2 F2-#1; --help variant caught in Codex review.)
49
+ const requested = argv[0];
50
+ if (cli.matchedCommand) {
51
+ await cli.runMatchedCommand();
52
+ }
53
+ else if (requested === undefined || requested === "help") {
54
+ // Bare `percher` / `percher help` — show the command list.
55
+ cli.outputHelp();
56
+ }
57
+ else if (requested.startsWith("-")) {
58
+ // A bare flag (`--help` / `--version`) — cli.parse() already
59
+ // printed the relevant output and unset matchedCommand.
60
+ }
61
+ else if (cli.commands.some((cmd) => cmd.isMatched(requested))) {
62
+ // A real command whose action was pre-empted by `--help` /
63
+ // `--version`: cli.parse() prints that command's help and unsets
64
+ // matchedCommand. The output already happened — nothing to do.
65
+ }
66
+ else {
67
+ // Genuinely unknown command, with or without a trailing --help.
68
+ // Usage hint on stderr, exit 1 — a script must see the failure.
69
+ await writeUnknownCommand(ctx, requested);
70
+ throw new SilentExitError(1);
71
+ }
32
72
  trackEvent(command, "success");
33
73
  await checkForUpdates();
34
74
  return 0;
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,IAAI,MAAM,eAAe,CAAC;AAEtC,OAAO,GAAG,MAAM,KAAK,CAAC;AACtB,OAAO,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAC9C,OAAO,EAAE,uBAAuB,EAAE,MAAM,oBAAoB,CAAC;AAC7D,OAAO,EAAE,oBAAoB,EAAE,MAAM,iBAAiB,CAAC;AACvD,OAAO,EAAE,oBAAoB,EAAE,MAAM,iBAAiB,CAAC;AACvD,OAAO,EAAE,oBAAoB,EAAE,MAAM,iBAAiB,CAAC;AACvD,OAAO,EAAE,uBAAuB,EAAE,MAAM,oBAAoB,CAAC;AAC7D,OAAO,EAAE,mBAAmB,EAAE,MAAM,gBAAgB,CAAC;AACrD,OAAO,EAAE,sBAAsB,EAAE,MAAM,mBAAmB,CAAC;AAC3D,OAAO,EAAE,gBAAgB,EAAE,MAAM,WAAW,CAAC;AAC7C,OAAO,EAAE,WAAW,EAAE,MAAM,UAAU,CAAC;AACvC,OAAO,EAAE,OAAO,EAAE,mBAAmB,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACvE,OAAO,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC;AAElD,MAAM,CAAC,KAAK,UAAU,GAAG,CAAC,IAAc,EAAE,WAAqB;IAC7D,MAAM,GAAG,GAAG,GAAG,CAAC,SAAS,CAAC,CAAC;IAC3B,MAAM,GAAG,GAAG,WAAW,IAAI,CAAC,MAAM,gBAAgB,EAAE,CAAC,CAAC;IAEtD,oBAAoB,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC;IAC/B,uBAAuB,CAAC,GAAG,EAAE,GAAG,EAAE,IAAI,CAAC,CAAC;IACxC,mBAAmB,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC;IAC9B,oBAAoB,CAAC,GAAG,EAAE,GAAG,EAAE,IAAI,CAAC,CAAC;IACrC,oBAAoB,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC;IAC/B,uBAAuB,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC;IAClC,sBAAsB,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC;IAEjC,GAAG,CAAC,IAAI,EAAE,CAAC;IACX,GAAG,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC;IAErB,MAAM,OAAO,GAAG,IAAI,CAAC,CAAC,CAAC,IAAI,MAAM,CAAC;IAElC,IAAI,CAAC;QACH,MAAM,mBAAmB,EAAE,CAAC;QAC5B,GAAG,CAAC,KAAK,CAAC,CAAC,SAAS,EAAE,SAAS,EAAE,GAAG,IAAI,CAAC,EAAE,EAAE,GAAG,EAAE,KAAK,EAAE,CAAC,CAAC;QAC3D,MAAM,GAAG,CAAC,iBAAiB,EAAE,CAAC;QAC9B,UAAU,CAAC,OAAO,EAAE,SAAS,CAAC,CAAC;QAC/B,MAAM,eAAe,EAAE,CAAC;QACxB,OAAO,CAAC,CAAC;IACX,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,yDAAyD;QACzD,IAAI,GAAG,YAAY,eAAe,EAAE,CAAC;YACnC,UAAU,CAAC,OAAO,EAAE,OAAO,EAAE,OAAO,CAAC,CAAC;YACtC,OAAO,GAAG,CAAC,QAAQ,CAAC;QACtB,CAAC;QACD,MAAM,OAAO,GAAG,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,WAAW,CAAC,IAAI,CAAC,CAAC,CAAC,SAAS,CAAC;QACxE,UAAU,CAAC,OAAO,EAAE,OAAO,EAAE,OAAO,CAAC,CAAC;QACtC,MAAM,WAAW,CAAC,GAAG,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;QACnC,OAAO,CAAC,CAAC;IACX,CAAC;AACH,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,6 @@
1
+ :8080 {
2
+ bind 0.0.0.0
3
+ root * /srv
4
+ encode gzip zstd
5
+ file_server
6
+ }
@@ -0,0 +1,34 @@
1
+ # Multi-stage build: a Bun stage compiles the static bundle, then a
2
+ # minimal Caddy image serves it. This project uses runtime = "docker"
3
+ # (not "static") because Percher's static runtime serves pre-built
4
+ # files only — it does not run a build command. Generated by Percher;
5
+ # safe to edit and own.
6
+
7
+ # --- Build stage: install deps and build the static bundle -----------
8
+ FROM oven/bun:1-alpine AS build
9
+ WORKDIR /app
10
+ COPY . .
11
+ RUN bun install && bun run build
12
+
13
+ # --- Serve stage: minimal Caddy image --------------------------------
14
+ # caddy:2-alpine ships /usr/bin/caddy with cap_net_bind_service file
15
+ # caps. Percher runs every container with --security-opt
16
+ # no-new-privileges, which makes execve refuse a cap-bearing binary
17
+ # (an "operation not permitted" crash-loop). Copy Caddy onto a plain
18
+ # alpine base and strip the caps with setcap -r — Caddy never needs
19
+ # the cap here, it listens on :8080 (>= 1024).
20
+ FROM caddy:2-alpine AS caddy-source
21
+ FROM alpine:3.21
22
+ COPY --from=caddy-source /usr/bin/caddy /usr/bin/caddy
23
+ RUN apk add --no-cache libcap \
24
+ && setcap -r /usr/bin/caddy \
25
+ && apk del libcap \
26
+ && rm -rf /var/cache/apk/*
27
+ COPY Caddyfile /etc/caddy/Caddyfile
28
+ COPY --from=build /app/dist /srv
29
+ # HOME points at the writable tmpfs so non-root Caddy can persist its
30
+ # XDG state (storage-clean lock, config autosave) under Percher's
31
+ # read-only root filesystem.
32
+ ENV HOME=/tmp
33
+ EXPOSE 8080
34
+ CMD ["caddy", "run", "--config", "/etc/caddy/Caddyfile", "--adapter", "caddyfile"]
@@ -0,0 +1,3 @@
1
+ import { defineConfig } from "astro/config";
2
+
3
+ export default defineConfig({});
@@ -0,0 +1,14 @@
1
+ {
2
+ "name": "__APP_NAME__",
3
+ "version": "0.1.0",
4
+ "private": true,
5
+ "type": "module",
6
+ "scripts": {
7
+ "dev": "astro dev",
8
+ "build": "astro build",
9
+ "preview": "astro preview"
10
+ },
11
+ "dependencies": {
12
+ "astro": "^5.0.0"
13
+ }
14
+ }
@@ -0,0 +1,11 @@
1
+ [app]
2
+ name = "__APP_NAME__"
3
+ runtime = "docker"
4
+ framework = "astro"
5
+
6
+ [web]
7
+ port = 8080
8
+ health = "/"
9
+
10
+ [data]
11
+ mode = "none"
@@ -0,0 +1,15 @@
1
+ ---
2
+ const _title = "__APP_NAME__";
3
+ ---
4
+
5
+ <html lang="en">
6
+ <head>
7
+ <meta charset="utf-8" />
8
+ <meta name="viewport" content="width=device-width" />
9
+ <title>{title}</title>
10
+ </head>
11
+ <body>
12
+ <h1>Hello from {title}!</h1>
13
+ <p>Built with Astro on Percher.</p>
14
+ </body>
15
+ </html>
@@ -0,0 +1,23 @@
1
+ const port = Number(process.env.PORT ?? 3000);
2
+
3
+ Bun.serve({
4
+ port,
5
+ hostname: "0.0.0.0",
6
+ fetch(req) {
7
+ const url = new URL(req.url);
8
+
9
+ if (url.pathname === "/health") {
10
+ return Response.json({ status: "ok" });
11
+ }
12
+
13
+ if (url.pathname === "/") {
14
+ return new Response("Hello from __APP_NAME__!", {
15
+ headers: { "Content-Type": "text/plain" },
16
+ });
17
+ }
18
+
19
+ return new Response("Not found", { status: 404 });
20
+ },
21
+ });
22
+
23
+ console.log(`[__APP_NAME__] listening on http://0.0.0.0:${port}`);
@@ -0,0 +1,8 @@
1
+ {
2
+ "name": "__APP_NAME__",
3
+ "version": "0.1.0",
4
+ "private": true,
5
+ "scripts": {
6
+ "start": "bun run index.ts"
7
+ }
8
+ }
@@ -0,0 +1,10 @@
1
+ [app]
2
+ name = "__APP_NAME__"
3
+ runtime = "bun"
4
+
5
+ [web]
6
+ port = 3000
7
+ health = "/health"
8
+
9
+ [data]
10
+ mode = "none"
@@ -0,0 +1 @@
1
+ web: gunicorn app.wsgi:application --bind 0.0.0.0:8000
File without changes
@@ -0,0 +1,44 @@
1
+ """Minimal Django settings for Percher deploys."""
2
+ import os
3
+ from pathlib import Path
4
+
5
+ BASE_DIR = Path(__file__).resolve().parent.parent
6
+
7
+ SECRET_KEY = os.environ.get("DJANGO_SECRET_KEY", "change-me-set-via-percher-env")
8
+ DEBUG = os.environ.get("DJANGO_DEBUG", "").lower() == "true"
9
+ ALLOWED_HOSTS = ["*"]
10
+
11
+ INSTALLED_APPS = [
12
+ "django.contrib.contenttypes",
13
+ "django.contrib.auth",
14
+ "django.contrib.staticfiles",
15
+ ]
16
+
17
+ MIDDLEWARE = [
18
+ "django.middleware.common.CommonMiddleware",
19
+ ]
20
+
21
+ ROOT_URLCONF = "app.urls"
22
+
23
+ TEMPLATES = [
24
+ {
25
+ "BACKEND": "django.template.backends.django.DjangoTemplates",
26
+ "DIRS": [],
27
+ "APP_DIRS": True,
28
+ "OPTIONS": {},
29
+ },
30
+ ]
31
+
32
+ WSGI_APPLICATION = "app.wsgi.application"
33
+
34
+ DATABASES = {
35
+ "default": {
36
+ "ENGINE": "django.db.backends.sqlite3",
37
+ "NAME": BASE_DIR / "/app/data/db.sqlite3",
38
+ }
39
+ }
40
+
41
+ STATIC_URL = "static/"
42
+ STATIC_ROOT = BASE_DIR / "staticfiles"
43
+
44
+ DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField"
@@ -0,0 +1,21 @@
1
+ from django.http import JsonResponse, HttpResponse
2
+ from django.urls import path
3
+
4
+
5
+ def index(_request):
6
+ return HttpResponse(
7
+ "<main style='font-family: system-ui; padding: 40px;'>"
8
+ "<h1>Hello from __APP_NAME__</h1>"
9
+ "<p>Django on Percher.</p>"
10
+ "</main>"
11
+ )
12
+
13
+
14
+ def health(_request):
15
+ return JsonResponse({"status": "ok"})
16
+
17
+
18
+ urlpatterns = [
19
+ path("", index),
20
+ path("health/", health),
21
+ ]
@@ -0,0 +1,5 @@
1
+ import os
2
+ from django.core.wsgi import get_wsgi_application
3
+
4
+ os.environ.setdefault("DJANGO_SETTINGS_MODULE", "app.settings")
5
+ application = get_wsgi_application()
@@ -0,0 +1,19 @@
1
+ #!/usr/bin/env python
2
+ """Django's command-line utility for administrative tasks."""
3
+ import os
4
+ import sys
5
+
6
+
7
+ def main():
8
+ os.environ.setdefault("DJANGO_SETTINGS_MODULE", "app.settings")
9
+ try:
10
+ from django.core.management import execute_from_command_line
11
+ except ImportError as exc:
12
+ raise ImportError(
13
+ "Couldn't import Django. Is it installed and on PYTHONPATH?"
14
+ ) from exc
15
+ execute_from_command_line(sys.argv)
16
+
17
+
18
+ if __name__ == "__main__":
19
+ main()
@@ -0,0 +1,11 @@
1
+ [app]
2
+ name = "__APP_NAME__"
3
+ runtime = "python"
4
+ framework = "django"
5
+
6
+ [web]
7
+ port = 8000
8
+ health = "/health/"
9
+
10
+ [data]
11
+ mode = "none"
@@ -0,0 +1,2 @@
1
+ Django>=5.0,<6.0
2
+ gunicorn>=22.0
@@ -0,0 +1,18 @@
1
+ FROM mcr.microsoft.com/dotnet/sdk:8.0-alpine AS build
2
+ WORKDIR /src
3
+ COPY app.csproj ./
4
+ RUN dotnet restore
5
+ COPY Program.cs ./
6
+ RUN dotnet publish -c Release -o /out --no-restore
7
+
8
+ FROM mcr.microsoft.com/dotnet/aspnet:8.0-alpine
9
+ WORKDIR /app
10
+ # aspnet:8.0-alpine ships with a non-root `app` user (uid 1654)
11
+ # pre-baked. We explicitly chown + USER here so the image runs as
12
+ # non-root regardless of any future base-image USER-default change.
13
+ COPY --from=build --chown=app:app /out ./
14
+ USER app
15
+ ENV ASPNETCORE_URLS=http://+:8080
16
+ ENV ASPNETCORE_ENVIRONMENT=Production
17
+ EXPOSE 8080
18
+ ENTRYPOINT ["dotnet", "app.dll"]
@@ -0,0 +1,23 @@
1
+ var builder = WebApplication.CreateBuilder(args);
2
+ var app = builder.Build();
3
+
4
+ app.MapGet("/health", () => Results.Json(new { ok = true }));
5
+
6
+ app.MapGet("/", () => Results.Content(@"<!doctype html>
7
+ <html lang=""en"">
8
+ <head>
9
+ <meta charset=""utf-8"" />
10
+ <title>__APP_NAME__</title>
11
+ <style>
12
+ body { font-family: system-ui, sans-serif; max-width: 640px; margin: 48px auto; padding: 0 24px; line-height: 1.6; }
13
+ h1 { letter-spacing: -0.5px; }
14
+ </style>
15
+ </head>
16
+ <body>
17
+ <h1>__APP_NAME__</h1>
18
+ <p>A .NET app deployed on Percher.</p>
19
+ <p>Add endpoints in <code>Program.cs</code> and push to redeploy.</p>
20
+ </body>
21
+ </html>", "text/html; charset=utf-8"));
22
+
23
+ app.Run();
@@ -0,0 +1,9 @@
1
+ <Project Sdk="Microsoft.NET.Sdk.Web">
2
+ <PropertyGroup>
3
+ <TargetFramework>net8.0</TargetFramework>
4
+ <Nullable>enable</Nullable>
5
+ <ImplicitUsings>enable</ImplicitUsings>
6
+ <AssemblyName>app</AssemblyName>
7
+ <RootNamespace>PercherApp</RootNamespace>
8
+ </PropertyGroup>
9
+ </Project>
@@ -0,0 +1,11 @@
1
+ [app]
2
+ name = "__APP_NAME__"
3
+ runtime = "docker"
4
+ framework = "dotnet"
5
+
6
+ [web]
7
+ port = 8080
8
+ health = "/health"
9
+
10
+ [data]
11
+ mode = "none"
@@ -0,0 +1,12 @@
1
+ module.exports = (eleventyConfig) => {
2
+ eleventyConfig.addPassthroughCopy("src/assets");
3
+ return {
4
+ dir: {
5
+ input: "src",
6
+ output: "_site",
7
+ includes: "_includes",
8
+ },
9
+ markdownTemplateEngine: "njk",
10
+ htmlTemplateEngine: "njk",
11
+ };
12
+ };
@@ -0,0 +1,6 @@
1
+ :8080 {
2
+ bind 0.0.0.0
3
+ root * /srv
4
+ encode gzip zstd
5
+ file_server
6
+ }
@@ -0,0 +1,34 @@
1
+ # Multi-stage build: a Bun stage compiles the static bundle, then a
2
+ # minimal Caddy image serves it. This project uses runtime = "docker"
3
+ # (not "static") because Percher's static runtime serves pre-built
4
+ # files only — it does not run a build command. Generated by Percher;
5
+ # safe to edit and own.
6
+
7
+ # --- Build stage: install deps and build the static bundle -----------
8
+ FROM oven/bun:1-alpine AS build
9
+ WORKDIR /app
10
+ COPY . .
11
+ RUN bun install && bun run build
12
+
13
+ # --- Serve stage: minimal Caddy image --------------------------------
14
+ # caddy:2-alpine ships /usr/bin/caddy with cap_net_bind_service file
15
+ # caps. Percher runs every container with --security-opt
16
+ # no-new-privileges, which makes execve refuse a cap-bearing binary
17
+ # (an "operation not permitted" crash-loop). Copy Caddy onto a plain
18
+ # alpine base and strip the caps with setcap -r — Caddy never needs
19
+ # the cap here, it listens on :8080 (>= 1024).
20
+ FROM caddy:2-alpine AS caddy-source
21
+ FROM alpine:3.21
22
+ COPY --from=caddy-source /usr/bin/caddy /usr/bin/caddy
23
+ RUN apk add --no-cache libcap \
24
+ && setcap -r /usr/bin/caddy \
25
+ && apk del libcap \
26
+ && rm -rf /var/cache/apk/*
27
+ COPY Caddyfile /etc/caddy/Caddyfile
28
+ COPY --from=build /app/_site /srv
29
+ # HOME points at the writable tmpfs so non-root Caddy can persist its
30
+ # XDG state (storage-clean lock, config autosave) under Percher's
31
+ # read-only root filesystem.
32
+ ENV HOME=/tmp
33
+ EXPOSE 8080
34
+ CMD ["caddy", "run", "--config", "/etc/caddy/Caddyfile", "--adapter", "caddyfile"]
@@ -0,0 +1,12 @@
1
+ {
2
+ "name": "__APP_NAME__",
3
+ "version": "1.0.0",
4
+ "private": true,
5
+ "scripts": {
6
+ "build": "eleventy",
7
+ "dev": "eleventy --serve"
8
+ },
9
+ "devDependencies": {
10
+ "@11ty/eleventy": "^3.0.0"
11
+ }
12
+ }