@pplethai/cli 0.4.0 → 0.4.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -17,34 +17,23 @@ body {
17
17
  max-width: 640px;
18
18
  margin: 0 auto;
19
19
  padding: 16px;
20
+ /* Navbar is position:fixed below the md breakpoint; give content room. */
21
+ padding-top: 4.5rem;
20
22
  padding-bottom: max(16px, env(safe-area-inset-bottom));
21
23
  min-height: 100vh;
22
24
  }
23
25
 
26
+ @media (min-width: 768px) {
27
+ .screen {
28
+ padding-top: 16px;
29
+ }
30
+ }
31
+
24
32
  .screen--center {
25
33
  display: grid;
26
34
  place-items: center;
27
35
  }
28
36
 
29
- .appbar {
30
- display: flex;
31
- align-items: center;
32
- gap: 12px;
33
- padding: 8px 4px 16px;
34
- }
35
-
36
- .appbar__logo {
37
- height: 28px;
38
- width: auto;
39
- color: #ff6a13; /* brand primary */
40
- }
41
-
42
- .appbar__title {
43
- font-weight: 600;
44
- font-size: 18px;
45
- margin-right: auto;
46
- }
47
-
48
37
  .stack {
49
38
  display: flex;
50
39
  flex-direction: column;
@@ -86,7 +75,9 @@ body {
86
75
  font-size: 14px;
87
76
  }
88
77
 
89
- .file-input {
90
- flex: 1;
91
- min-width: 0;
78
+ /* Icon defaults to text-foreground, which overrides the parent Button text
79
+ colour (primary-foreground on filled buttons, …). Inherit so the glyph
80
+ matches its label — including hover states on outline/ghost buttons. */
81
+ button svg.text-foreground {
82
+ color: inherit;
92
83
  }
@@ -26,7 +26,7 @@ function ErrorScreen({ message }: { message: string }) {
26
26
  return (
27
27
  <main className="screen screen--center">
28
28
  <Alert variant="destructive" className="card">
29
- <AlertTitle>Could not start</AlertTitle>
29
+ <AlertTitle>เริ่มต้นแอปไม่สำเร็จ</AlertTitle>
30
30
  <AlertDescription>{message}</AlertDescription>
31
31
  </Alert>
32
32
  </main>
@@ -30,6 +30,7 @@ export type Bootstrap =
30
30
  | { kind: "error"; message: string };
31
31
 
32
32
  let token: string | null = null;
33
+ let sdk: PPLEMiniApp | null = null;
33
34
 
34
35
  /** Establish the session. Call once at startup, before rendering the app. */
35
36
  export async function bootstrap(): Promise<Bootstrap> {
@@ -48,11 +49,11 @@ export async function bootstrap(): Promise<Bootstrap> {
48
49
  return {
49
50
  kind: "error",
50
51
  message:
51
- "This app is misconfigured: its PPLE ID sign-in settings were not set when it was built. Redeploy the app from the platform to fix it.",
52
+ "แอปตั้งค่าไม่ถูกต้อง: การตั้งค่าเข้าสู่ระบบ PPLE ID ไม่ได้ถูกตั้งตอนสร้างแอป โปรดดีพลอยใหม่จากแพลตฟอร์ม",
52
53
  };
53
54
  }
54
55
 
55
- const sdk = new PPLEMiniApp({
56
+ sdk = new PPLEMiniApp({
56
57
  oauthUrl: OAUTH_URL,
57
58
  oauthClientId: OAUTH_CLIENT_ID,
58
59
  oauthRedirectUri: OAUTH_REDIRECT_URI,
@@ -65,6 +66,16 @@ export async function bootstrap(): Promise<Bootstrap> {
65
66
  }
66
67
  }
67
68
 
69
+ /** Drop the session and return to the start (re-triggers PPLE ID login). */
70
+ export async function logout(): Promise<void> {
71
+ token = null;
72
+ if (sdk) {
73
+ await sdk.logout();
74
+ sdk = null;
75
+ }
76
+ window.location.href = "/";
77
+ }
78
+
68
79
  /**
69
80
  * Call your Worker API with the bearer token attached. Paths are relative
70
81
  * ("/api/notes"), since the SPA and the Worker share one origin.
package/dist/api.js DELETED
@@ -1,108 +0,0 @@
1
- // The Provisioner API as the CLI sees it. Every non-2xx answer becomes a
2
- // sentence a Builder can act on — this is the only place in the CLI that
3
- // knows the platform's error codes.
4
- import { CliError, SIGN_IN_EXPIRED } from "./errors.js";
5
- export class ProvisionerClient {
6
- deps;
7
- token;
8
- constructor(deps, token) {
9
- this.deps = deps;
10
- this.token = token;
11
- }
12
- me() {
13
- return this.request("GET", "/me");
14
- }
15
- app(slug) {
16
- return this.request("GET", `/apps/${encodeURIComponent(slug)}`, { slug });
17
- }
18
- /** Create a Builder App; the Provisioner assigns the App Slug (ADR-0001). */
19
- createApp(name) {
20
- return this.request("POST", "/apps", { body: { name } });
21
- }
22
- repoToken(slug) {
23
- return this.request("POST", `/apps/${encodeURIComponent(slug)}/repo-token`, { slug });
24
- }
25
- deploys(slug) {
26
- return this.request("GET", `/apps/${encodeURIComponent(slug)}/deploys`, { slug }).then((r) => r.deploys);
27
- }
28
- env(slug) {
29
- return this.request("GET", `/apps/${encodeURIComponent(slug)}/env`, { slug }).then((r) => r.env);
30
- }
31
- metrics(slug) {
32
- return this.request("GET", `/apps/${encodeURIComponent(slug)}/metrics`, { slug });
33
- }
34
- collaborators(slug) {
35
- return this.request("GET", `/apps/${encodeURIComponent(slug)}/collaborators`, { slug }).then((r) => r.collaborators);
36
- }
37
- requests(slug) {
38
- return this.request("GET", `/apps/${encodeURIComponent(slug)}/requests`, { slug }).then((r) => r.requests);
39
- }
40
- invites(slug) {
41
- return this.request("GET", `/apps/${encodeURIComponent(slug)}/invites`, { slug }).then((r) => r.invites);
42
- }
43
- setEnv(slug, key, value, secret) {
44
- return this.request("PUT", `/apps/${encodeURIComponent(slug)}/env/${encodeURIComponent(key)}`, { slug, body: { value, secret } });
45
- }
46
- deleteEnv(slug, key) {
47
- return this.request("DELETE", `/apps/${encodeURIComponent(slug)}/env/${encodeURIComponent(key)}`, { slug, envKey: key });
48
- }
49
- async request(method, path, context = {}) {
50
- const url = `${this.deps.config.apiBase}${path}`;
51
- const headers = {
52
- authorization: `Bearer ${this.token}`,
53
- };
54
- let body;
55
- if (context.body !== undefined) {
56
- headers["content-type"] = "application/json";
57
- body = JSON.stringify(context.body);
58
- }
59
- const res = await this.deps
60
- .fetch(url, { method, headers, body })
61
- .catch(() => null);
62
- if (!res) {
63
- throw new CliError("เชื่อมต่อ PPLE Platform ไม่ได้", "ตรวจสอบการเชื่อมต่ออินเทอร์เน็ตแล้วลองใหม่อีกครั้ง");
64
- }
65
- if (res.ok) {
66
- if (res.status === 204)
67
- return undefined;
68
- return (await res.json());
69
- }
70
- const errBody = (await res.json().catch(() => ({})));
71
- throw platformError(res.status, errBody.error, context);
72
- }
73
- }
74
- function platformError(status, code, context = {}) {
75
- const { slug, envKey } = context;
76
- if (status === 401)
77
- return SIGN_IN_EXPIRED;
78
- if (code === "not_org_member") {
79
- return new CliError("PPLE ID ของคุณยังไม่ได้อยู่ในองค์กรของพรรค จึงสร้างแอปไม่ได้", "กรุณาติดต่อหัวหน้าทีมของคุณเพื่อเพิ่มสิทธิ์ แล้วลองใหม่อีกครั้ง");
80
- }
81
- if (code === "agreement_required") {
82
- return new CliError("คุณยังไม่ได้ยอมรับข้อตกลงผู้พัฒนา", "ยอมรับได้ที่ https://console.ppleth.ai แล้วรันคำสั่งนี้อีกครั้ง");
83
- }
84
- if (code === "app_has_no_repo") {
85
- return new CliError(`${slug ? `"${slug}"` : "แอปนี้"} ยังตั้งค่าไม่เสร็จ`, "รอสักครู่แล้วลองใหม่อีกครั้ง หากยังเป็นแบบนี้ กรุณาติดต่อทีมแพลตฟอร์ม");
86
- }
87
- if (code === "invalid_key") {
88
- return new CliError("คีย์นี้ใช้เป็นชื่อไม่ได้", "คีย์ต้องขึ้นต้นด้วยตัวอักษรหรือขีดล่าง ตามด้วยตัวอักษร ตัวเลข หรือขีดล่าง");
89
- }
90
- if (code === "reserved_key") {
91
- return new CliError("คีย์นี้ถูกสงวนไว้โดยแพลตฟอร์ม", "เลือกชื่ออื่น — APP_SLUG, DB, FILES, NOTIFY_* และ PPLE_DEPLOY_ID ถูกใช้ไปแล้ว");
92
- }
93
- if (code === "invalid_value") {
94
- return new CliError("บันทึกค่านี้ไม่สำเร็จ", "ส่งค่ามาเป็น argument ผ่าน `--file` หรือทาง stdin");
95
- }
96
- if (status === 404) {
97
- if (envKey) {
98
- return new CliError(`แอปนี้ไม่ได้ตั้งค่า "${envKey}" ไว้`, "รัน `pple env list` เพื่อดูค่าที่ตั้งไว้");
99
- }
100
- return new CliError(slug
101
- ? `ไม่มีแอปชื่อ "${slug}" ที่เป็นของคุณ`
102
- : "ไม่พบสิ่งนี้บนแพลตฟอร์ม", "ตรวจสอบรายการแอปได้ที่ https://console.ppleth.ai");
103
- }
104
- if (status >= 500 || code === "sso_unavailable") {
105
- return new CliError("PPLE Platform กำลังมีปัญหาในขณะนี้", "รอสักครู่แล้วลองใหม่อีกครั้ง หากยังเกิดขึ้นอีก กรุณาแจ้งทีมแพลตฟอร์ม");
106
- }
107
- return new CliError("PPLE Platform ปฏิเสธคำขอนี้", code ? `ข้อความจากระบบ: ${code}` : undefined);
108
- }
package/dist/cli.js DELETED
@@ -1,151 +0,0 @@
1
- import * as auth from "./commands/auth.js";
2
- import { deploy } from "./commands/deploy.js";
3
- import * as env from "./commands/env.js";
4
- import { info } from "./commands/info.js";
5
- import { init } from "./commands/init.js";
6
- import { upgrade } from "./commands/upgrade.js";
7
- import { CliError } from "./errors.js";
8
- import { checkForUpdate, wantsUpdateCheck } from "./update.js";
9
- const USAGE = `pple — สร้างและส่งแอปขึ้น PPLE Platform
10
-
11
- pple auth login เข้าสู่ระบบด้วย PPLE ID ของคุณ
12
- pple auth logout ลบการเข้าสู่ระบบออกจากเครื่องนี้
13
- pple whoami ดูว่ากำลังเข้าสู่ระบบด้วยบัญชีใด
14
-
15
- pple init สร้างแอปและตั้งค่าโค้ดบนเครื่องนี้
16
- pple init --name "App" สร้างแอปโดยไม่ต้องถาม (สำหรับ agent/สคริปต์)
17
- pple init <app> ตั้งค่าแอปที่มีอยู่แล้วบนแพลตฟอร์ม
18
- pple deploy [-m "note"] ส่งการเปลี่ยนแปลงของคุณขึ้นใช้งานจริง
19
- pple info [<slug>] ดูสถานะ การใช้งาน การดีพลอย และคีย์ env ของแอปนี้
20
-
21
- pple env list ดูตัวแปรและ secret ของแอปนี้
22
- pple env set <KEY> [VAL] ตั้งค่าตัวแปร (หรือ --secret) รับค่าจาก arg, --file หรือ stdin
23
- pple env delete <KEY> ลบตัวแปรหรือ secret
24
-
25
- pple upgrade อัปเดต pple เป็นเวอร์ชันล่าสุด
26
-
27
- ตัวเลือก
28
- -m, --message <note> สิ่งที่เปลี่ยนแปลง แสดงในประวัติของแอป
29
- --name <App Name> สร้างแอปใหม่ด้วยชื่อนี้ (pple init)
30
- --dir <folder> ตำแหน่งที่จะวางโค้ดของแอป (pple init)
31
- --file <path> อ่านค่า env จากไฟล์ (pple env set)
32
- --secret เก็บค่าเป็น secret แบบเขียนอย่างเดียว (pple env set)
33
- -h, --help แสดงวิธีใช้นี้
34
- `;
35
- /**
36
- * Parse and run one command line. Returns the process exit code; expected
37
- * failures come back as 1 with a Builder-language message already printed,
38
- * and anything else throws for the entry point to report as a CLI bug.
39
- */
40
- export async function run(argv, deps) {
41
- try {
42
- // Before the command, never during it: an upgrade mid-deploy would swap
43
- // the CLI under its own feet, and this is the one point where handing the
44
- // whole command line to the new version costs nothing.
45
- if (wantsUpdateCheck(argv)) {
46
- const handled = await checkForUpdate(deps, argv);
47
- if (handled !== null)
48
- return handled;
49
- }
50
- await dispatch(argv, deps);
51
- return 0;
52
- }
53
- catch (e) {
54
- if (e instanceof CliError) {
55
- deps.out.fail(e);
56
- return 1;
57
- }
58
- throw e;
59
- }
60
- }
61
- async function dispatch(argv, deps) {
62
- const args = [...argv];
63
- if (args.length === 0 || args[0] === "--help" || args[0] === "-h") {
64
- deps.out.say(USAGE);
65
- return;
66
- }
67
- const command = args.shift();
68
- switch (command) {
69
- case "auth": {
70
- const sub = args.shift();
71
- if (sub === "login")
72
- return auth.login(deps);
73
- if (sub === "logout")
74
- return auth.logout(deps);
75
- if (sub === "whoami")
76
- return auth.whoami(deps);
77
- throw unknown(`auth ${sub ?? ""}`.trim());
78
- }
79
- case "whoami":
80
- return auth.whoami(deps);
81
- case "init": {
82
- const dir = takeOption(args, "--dir");
83
- const name = takeOption(args, "--name");
84
- const slug = args.shift();
85
- if (name !== undefined && slug) {
86
- throw new CliError("`pple init` รับได้เฉพาะชื่อแอปใหม่ หรือแอปที่มีอยู่แล้ว อย่างใดอย่างหนึ่งเท่านั้น", 'ใช้ `pple init --name "ชื่อแอปของคุณ"` เพื่อสร้างใหม่ หรือ `pple init <app>` สำหรับแอปที่มีอยู่แล้ว');
87
- }
88
- return init(deps, { slug, name, dir });
89
- }
90
- case "deploy": {
91
- const message = takeOption(args, "--message", "-m");
92
- return deploy(deps, { message });
93
- }
94
- case "info": {
95
- const slug = args.shift();
96
- return info(deps, slug);
97
- }
98
- case "env": {
99
- const sub = args.shift();
100
- if (sub === "list")
101
- return env.list(deps);
102
- if (sub === "set") {
103
- const secret = takeFlag(args, "--secret");
104
- const file = takeOption(args, "--file");
105
- const key = args.shift();
106
- const value = args.shift();
107
- if (!key) {
108
- throw new CliError("`pple env set` ต้องระบุ KEY", "ตัวอย่าง: `pple env set API_URL https://example.com`");
109
- }
110
- return env.set(deps, { key, value, secret, file });
111
- }
112
- if (sub === "delete") {
113
- const key = args.shift();
114
- if (!key) {
115
- throw new CliError("`pple env delete` ต้องระบุ KEY", "ตัวอย่าง: `pple env delete API_URL`");
116
- }
117
- return env.remove(deps, key);
118
- }
119
- throw unknown(`env ${sub ?? ""}`.trim());
120
- }
121
- case "upgrade":
122
- return upgrade(deps);
123
- case "help":
124
- deps.out.say(USAGE);
125
- return;
126
- default:
127
- throw unknown(command ?? "");
128
- }
129
- }
130
- function unknown(command) {
131
- return new CliError(`ไม่มีคำสั่ง \`pple ${command}\``, "รัน `pple --help` เพื่อดูคำสั่งทั้งหมด");
132
- }
133
- /** Pull `--name value` out of the argument list, wherever it appears. */
134
- function takeOption(args, long, short) {
135
- const index = args.findIndex((arg) => arg === long || (short && arg === short));
136
- if (index === -1)
137
- return undefined;
138
- const [, value] = args.splice(index, 2);
139
- if (value === undefined || value.startsWith("-")) {
140
- throw new CliError(`\`${long}\` ต้องระบุค่าตามหลัง`);
141
- }
142
- return value;
143
- }
144
- /** Pull a bare `--flag` out of the argument list, wherever it appears. */
145
- function takeFlag(args, flag) {
146
- const index = args.indexOf(flag);
147
- if (index === -1)
148
- return false;
149
- args.splice(index, 1);
150
- return true;
151
- }
@@ -1,32 +0,0 @@
1
- import { ProvisionerClient } from "../api.js";
2
- import { clearCredentials, saveCredentials } from "../credentials.js";
3
- import { awaitToken, requestDeviceCode } from "../device-flow.js";
4
- import { client } from "../session.js";
5
- export async function login(deps) {
6
- const device = await requestDeviceCode(deps);
7
- deps.out.say("เข้าสู่ระบบโดยเปิดหน้านี้แล้วกรอกรหัส:");
8
- deps.out.say();
9
- deps.out.say(` ${device.verificationUri}`);
10
- deps.out.say(` รหัส: ${device.userCode}`);
11
- deps.out.say();
12
- deps.out.say("กำลังรอให้คุณอนุมัติการเข้าสู่ระบบ…");
13
- await deps.openBrowser(device.verificationUriComplete ?? device.verificationUri);
14
- const tokens = await awaitToken(deps, device);
15
- await saveCredentials(deps, { ...tokens, ssoBase: deps.config.ssoBase });
16
- const me = await new ProvisionerClient(deps, tokens.accessToken).me();
17
- deps.out.say(`เข้าสู่ระบบในชื่อ ${me.name ?? me.sub} แล้ว`);
18
- if (!me.isBuilder) {
19
- deps.out.say("อีกหนึ่งขั้นตอนก่อนสร้างแอปได้: ยอมรับข้อตกลงผู้พัฒนาที่ https://console.ppleth.ai");
20
- }
21
- }
22
- export async function logout(deps) {
23
- const had = await clearCredentials(deps);
24
- deps.out.say(had ? "ออกจากระบบแล้ว" : "คุณยังไม่ได้เข้าสู่ระบบ");
25
- }
26
- export async function whoami(deps) {
27
- const me = await (await client(deps)).me();
28
- deps.out.say(`เข้าสู่ระบบในชื่อ ${me.name ?? me.sub} แล้ว`);
29
- if (!me.isBuilder) {
30
- deps.out.say("คุณยังไม่ได้ยอมรับข้อตกลงผู้พัฒนา — ยอมรับได้ที่ https://console.ppleth.ai");
31
- }
32
- }
@@ -1,113 +0,0 @@
1
- import { CliError } from "../errors.js";
2
- import { hasLocalChanges, headCommit, isUpToDateWithRemote, recordChanges, sendChanges, } from "../git.js";
3
- import { findLink } from "../link.js";
4
- import { client } from "../session.js";
5
- import { refreshSkills } from "../skills.js";
6
- /** How often to ask the platform how the build is going. */
7
- const POLL_INTERVAL_MS = 3000;
8
- /** How long to wait for CI to pick the push up before saying so. */
9
- const BUILD_START_TIMEOUT_MS = 3 * 60_000;
10
- /** How long a build itself may take before the CLI stops watching. */
11
- const BUILD_TIMEOUT_MS = 10 * 60_000;
12
- /** How much of a failed build's log to show; the rest is in the console. */
13
- const LOG_TAIL_LINES = 20;
14
- /**
15
- * Put the Builder's current work live: record it, send it, and watch the
16
- * platform build it — reporting the live URL, or a failure they can read.
17
- */
18
- export async function deploy(deps, options) {
19
- const link = await findLink(deps);
20
- if (link.apiBase !== "" && link.apiBase !== deps.config.apiBase) {
21
- throw new CliError("แอปนี้อยู่บน PPLE Platform คนละตัวกับที่คุณเข้าสู่ระบบอยู่", `แอปนี้ถูกตั้งค่าไว้กับ ${link.apiBase}`);
22
- }
23
- const api = await client(deps);
24
- const app = await api.app(link.slug);
25
- if (app.status === "retired") {
26
- throw new CliError(`"${app.name}" ถูกปลดระวางแล้ว`, "แอปที่ปลดระวางแล้วดีพลอยไม่ได้ หากต้องการสานงานนี้ต่อ ให้สร้างแอปใหม่ในคอนโซล");
27
- }
28
- if (app.status !== "ready") {
29
- throw new CliError(`"${app.name}" ยังไม่พร้อมรับการเปลี่ยนแปลง`, app.error
30
- ? `การตั้งค่าพบปัญหา: ${app.error}`
31
- : "ตรวจสอบได้ที่ https://console.ppleth.ai แล้วลองใหม่ในอีกสักครู่");
32
- }
33
- // One token for the whole command, kept in memory: it says both what this
34
- // machine may push and whose name the work goes out under (ADR-0005).
35
- const access = await api.repoToken(link.slug);
36
- // Keep the local Skill overlay current even when there is nothing new to
37
- // send — including first seed for folders set up before Skills shipped.
38
- await refreshSkills(deps, link.dir);
39
- if (await hasLocalChanges(deps, link.dir)) {
40
- deps.out.say("กำลังบันทึกการเปลี่ยนแปลงของคุณ…");
41
- await recordChanges(deps, link.dir, options.message ?? `อัปเดต ${app.name}`, access);
42
- }
43
- else if (await isUpToDateWithRemote(deps, link.dir, access)) {
44
- deps.out.say(`ไม่มีการเปลี่ยนแปลงใหม่ — ${app.name} ใช้งานอยู่แล้วที่ ${app.url}`);
45
- return;
46
- }
47
- deps.out.say("กำลังส่งการเปลี่ยนแปลงของคุณ…");
48
- await sendChanges(deps, link.dir, access);
49
- const commit = await headCommit(deps, link.dir);
50
- deps.out.say("กำลังบิลด์แอปของคุณ… (ปกติใช้เวลาราวหนึ่งนาที)");
51
- const result = await watchDeploy(deps, api, link.slug, commit);
52
- report(deps, app, result);
53
- }
54
- async function watchDeploy(deps, api, slug, commit) {
55
- const started = deps.now();
56
- let build;
57
- for (;;) {
58
- const deploys = await api.deploys(slug);
59
- const found = deploys.find((d) => d.commit === commit);
60
- if (found) {
61
- build = found;
62
- if (found.status !== "building")
63
- return found;
64
- }
65
- const waited = deps.now() - started;
66
- // Before CI has opened a deploy there is nothing to watch; after it has,
67
- // the clock that matters is the build's.
68
- const limit = build ? BUILD_TIMEOUT_MS : BUILD_START_TIMEOUT_MS;
69
- if (waited >= limit) {
70
- throw new CliError(build
71
- ? "ส่งการเปลี่ยนแปลงของคุณแล้ว แต่การบิลด์ใช้เวลานานกว่าที่คาดไว้"
72
- : "ส่งการเปลี่ยนแปลงของคุณแล้ว แต่แพลตฟอร์มยังไม่เริ่มบิลด์", `ติดตามจนเสร็จได้ที่ https://console.ppleth.ai/apps/${slug}`);
73
- }
74
- await deps.sleep(POLL_INTERVAL_MS);
75
- }
76
- }
77
- /** How the Provisioner marks a deploy that a newer one overtook. */
78
- const SUPERSEDED = "superseded by a newer deploy";
79
- function report(deps, app, build) {
80
- if (build.status === "failed") {
81
- if (build.error === SUPERSEDED) {
82
- throw new CliError("มีการดีพลอยครั้งใหม่มาแทนที่ครั้งที่คุณกำลังรออยู่", `ติดตามการดีพลอยครั้งนั้นได้ที่ https://console.ppleth.ai/apps/${app.slug}`);
83
- }
84
- // A build that compiled badly explains itself in its log; a deploy that
85
- // failed after the build — a bad bundle, a rejected upload — only has the
86
- // platform's own reason, and the Builder must still be told something.
87
- const detail = build.log ? tail(build.log, LOG_TAIL_LINES) : build.error;
88
- if (detail) {
89
- deps.out.say();
90
- deps.out.say(detail);
91
- deps.out.say();
92
- }
93
- throw new CliError(`บิลด์ ${app.name} ไม่สำเร็จ สิ่งที่ใช้งานอยู่จึงยังไม่เปลี่ยนแปลง`, detail
94
- ? "ส่งข้อความด้านบนให้ AI coding agent ของคุณ — ในนั้นบอกว่าต้องแก้อะไร"
95
- : `ดูรายละเอียดได้ที่ https://console.ppleth.ai/apps/${app.slug}`);
96
- }
97
- // Migrations are the part of a deploy a Builder can't see for themselves —
98
- // no Cloudflare account (ADR-0001/0002) — so say when some landed. Silence
99
- // means the database is unchanged (#75).
100
- const applied = build.migrations.applied ?? [];
101
- if (applied.length > 0) {
102
- deps.out.say(`ใช้ migration ${applied.length} รายการกับฐานข้อมูลของคุณแล้ว`);
103
- }
104
- if (build.status === "unverified") {
105
- deps.out.say(`อัปเดต ${app.name} แล้ว แต่แอปยังไม่ตอบสนอง`);
106
- deps.out.say(`รอสักครู่ แล้วเปิด ${app.url}`);
107
- return;
108
- }
109
- deps.out.say(`${app.name} ใช้งานได้แล้วที่ ${app.url}`);
110
- }
111
- function tail(log, lines) {
112
- return log.split("\n").slice(-lines).join("\n").trim();
113
- }
@@ -1,70 +0,0 @@
1
- import fs from "node:fs/promises";
2
- import path from "node:path";
3
- import { CliError } from "../errors.js";
4
- import { findLink } from "../link.js";
5
- import { client } from "../session.js";
6
- /**
7
- * Show the app's environment variables and secrets. Secrets report that a
8
- * key is set, never the value — same contract as the Console.
9
- */
10
- export async function list(deps) {
11
- const link = await findLink(deps);
12
- const api = await client(deps);
13
- const entries = await api.env(link.slug);
14
- if (entries.length === 0) {
15
- deps.out.say("ยังไม่ได้ตั้งค่าใด ๆ");
16
- deps.out.say("ตัวแปรที่คุณตั้งจะมีผลในการดีพลอยครั้งถัดไป");
17
- return;
18
- }
19
- for (const entry of entries) {
20
- if (entry.secret) {
21
- deps.out.say(`${entry.key}=(secret)`);
22
- }
23
- else {
24
- deps.out.say(`${entry.key}=${entry.value ?? ""}`);
25
- }
26
- }
27
- deps.out.say("");
28
- deps.out.say("จะมีผลในการดีพลอยครั้งถัดไป");
29
- }
30
- /**
31
- * Create or replace one env entry. The value comes from an argument, a file
32
- * (`--file`), or stdin — so multiline and secret values need not land in
33
- * shell history.
34
- */
35
- export async function set(deps, options) {
36
- const link = await findLink(deps);
37
- const value = await resolveValue(deps, options);
38
- const api = await client(deps);
39
- const result = await api.setEnv(link.slug, options.key, value, options.secret);
40
- deps.out.say(`บันทึก ${result.key}${result.secret ? " (secret)" : ""} แล้ว`);
41
- deps.out.say("จะมีผลในการดีพลอยครั้งถัดไป");
42
- }
43
- export async function remove(deps, key) {
44
- const link = await findLink(deps);
45
- const api = await client(deps);
46
- await api.deleteEnv(link.slug, key);
47
- deps.out.say(`ลบ ${key} แล้ว`);
48
- deps.out.say("จะมีผลในการดีพลอยครั้งถัดไป");
49
- }
50
- async function resolveValue(deps, options) {
51
- if (options.file !== undefined && options.value !== undefined) {
52
- throw new CliError("ส่งค่ามาเป็น argument หรือผ่าน `--file` อย่างใดอย่างหนึ่ง ไม่ใช่ทั้งสองอย่าง");
53
- }
54
- if (options.file !== undefined) {
55
- const filePath = path.resolve(deps.cwd, options.file);
56
- const raw = await fs.readFile(filePath, "utf8").catch(() => null);
57
- if (raw === null) {
58
- throw new CliError(`อ่านไฟล์ ${options.file} ไม่ได้`, "ตรวจสอบพาธแล้วลองใหม่อีกครั้ง");
59
- }
60
- return raw;
61
- }
62
- if (options.value !== undefined)
63
- return options.value;
64
- // A TTY with no value would hang waiting for EOF — tell the Builder how
65
- // to pass one instead.
66
- if (deps.interactive) {
67
- throw new CliError("`pple env set` ต้องระบุค่า", "ส่งค่ามาเป็น argument ผ่าน `--file` หรือ pipe ทาง stdin");
68
- }
69
- return deps.readStdin();
70
- }