@pplethai/cli 0.3.1 → 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 (39) hide show
  1. package/README.md +1 -1
  2. package/dist/bin.js +53 -15
  3. package/package.json +3 -2
  4. package/templates/golden-path/.dev.vars.example +10 -13
  5. package/templates/golden-path/CLAUDE.md +22 -7
  6. package/templates/golden-path/README.md +13 -8
  7. package/templates/golden-path/package.json +1 -0
  8. package/templates/golden-path/shared/api.ts +36 -3
  9. package/templates/golden-path/src/auth.ts +101 -16
  10. package/templates/golden-path/src/dev-personas.ts +88 -0
  11. package/templates/golden-path/src/env.ts +4 -2
  12. package/templates/golden-path/src/index.ts +8 -7
  13. package/templates/golden-path/src/vite-env.d.ts +14 -0
  14. package/templates/golden-path/test/harness.ts +6 -2
  15. package/templates/golden-path/test/smoke.test.ts +98 -15
  16. package/templates/golden-path/web/App.tsx +1 -1
  17. package/templates/golden-path/web/main.tsx +2 -2
  18. package/templates/golden-path/web/pple.ts +6 -5
  19. package/dist/api.js +0 -108
  20. package/dist/cli.js +0 -151
  21. package/dist/commands/auth.js +0 -32
  22. package/dist/commands/deploy.js +0 -113
  23. package/dist/commands/env.js +0 -70
  24. package/dist/commands/info.js +0 -238
  25. package/dist/commands/init.js +0 -130
  26. package/dist/commands/upgrade.js +0 -29
  27. package/dist/credentials.js +0 -53
  28. package/dist/deps.js +0 -87
  29. package/dist/device-flow.js +0 -122
  30. package/dist/errors.js +0 -16
  31. package/dist/git.js +0 -127
  32. package/dist/link.js +0 -59
  33. package/dist/node-deps.js +0 -107
  34. package/dist/npm.js +0 -127
  35. package/dist/scaffold.js +0 -58
  36. package/dist/session.js +0 -29
  37. package/dist/skills.js +0 -86
  38. package/dist/ui.js +0 -12
  39. package/dist/update.js +0 -142
@@ -1,53 +0,0 @@
1
- // Where the Builder's PPLE ID tokens live between commands.
2
- //
3
- // One file, owner-readable only, outside any app repo — a token that lands in
4
- // a working copy is a token that gets pushed to a party-owned repo.
5
- import fs from "node:fs/promises";
6
- import path from "node:path";
7
- const FILE = "credentials.json";
8
- const OWNER_ONLY_FILE = 0o600;
9
- const OWNER_ONLY_DIR = 0o700;
10
- function credentialsPath(deps) {
11
- return path.join(deps.configDir, FILE);
12
- }
13
- export async function loadCredentials(deps) {
14
- const raw = await fs.readFile(credentialsPath(deps), "utf8").catch(() => null);
15
- if (raw === null)
16
- return null;
17
- let parsed;
18
- try {
19
- parsed = JSON.parse(raw);
20
- }
21
- catch {
22
- return null;
23
- }
24
- if (typeof parsed.accessToken !== "string")
25
- return null;
26
- // A file written against another PPLE ID is as good as no file: it would
27
- // only produce confusing 401s against the current one.
28
- if (parsed.ssoBase !== deps.config.ssoBase)
29
- return null;
30
- return {
31
- accessToken: parsed.accessToken,
32
- refreshToken: typeof parsed.refreshToken === "string" ? parsed.refreshToken : null,
33
- expiresAt: typeof parsed.expiresAt === "number" ? parsed.expiresAt : null,
34
- ssoBase: parsed.ssoBase,
35
- };
36
- }
37
- export async function saveCredentials(deps, credentials) {
38
- await fs.mkdir(deps.configDir, { recursive: true, mode: OWNER_ONLY_DIR });
39
- await fs.writeFile(credentialsPath(deps), `${JSON.stringify(credentials, null, 2)}\n`, { mode: OWNER_ONLY_FILE });
40
- // mkdir/writeFile only apply the mode when they create; an existing file
41
- // keeps whatever it had, so tighten it explicitly.
42
- await fs.chmod(credentialsPath(deps), OWNER_ONLY_FILE);
43
- }
44
- /** Returns whether there was anything to forget. */
45
- export async function clearCredentials(deps) {
46
- try {
47
- await fs.unlink(credentialsPath(deps));
48
- return true;
49
- }
50
- catch {
51
- return false;
52
- }
53
- }
package/dist/deps.js DELETED
@@ -1,87 +0,0 @@
1
- import { existsSync, readFileSync } from "node:fs";
2
- import path from "node:path";
3
- import { fileURLToPath } from "node:url";
4
- export const DEFAULT_CONFIG = {
5
- apiBase: "https://api.ppleth.ai",
6
- ssoBase: "https://id.peoplesparty.or.th",
7
- clientId: "382216793067947795",
8
- repoBase: "https://github.com",
9
- registryBase: "https://registry.npmjs.org",
10
- };
11
- export function platformConfig(env) {
12
- return {
13
- apiBase: env.PPLE_API_BASE ?? DEFAULT_CONFIG.apiBase,
14
- ssoBase: env.PPLE_SSO_BASE ?? DEFAULT_CONFIG.ssoBase,
15
- clientId: env.PPLE_CLIENT_ID ?? DEFAULT_CONFIG.clientId,
16
- repoBase: env.PPLE_REPO_BASE ?? DEFAULT_CONFIG.repoBase,
17
- registryBase: env.PPLE_REGISTRY_BASE ?? DEFAULT_CONFIG.registryBase,
18
- };
19
- }
20
- /** XDG-style config home, so credentials never land in the app's repo. */
21
- export function configDir(env, homeDir) {
22
- const base = env.XDG_CONFIG_HOME?.trim();
23
- return path.join(base && base !== "" ? base : path.join(homeDir, ".config"), "pple");
24
- }
25
- /**
26
- * Whether the CLI may check npm for a newer version. `PPLE_NO_UPDATE_CHECK`
27
- * is the scripted-environment escape hatch; a local config file is the other
28
- * half of the opt-out, and lives with the check itself.
29
- */
30
- export function updateChecksAllowed(env) {
31
- const raw = env.PPLE_NO_UPDATE_CHECK?.trim().toLowerCase();
32
- return !raw || raw === "0" || raw === "false";
33
- }
34
- /** The CLI's own package directory — one above `src/` in a checkout, `dist/` once built. */
35
- export function packageRoot() {
36
- return path.dirname(path.dirname(fileURLToPath(import.meta.url)));
37
- }
38
- /**
39
- * The version this CLI was published as, read from its own `package.json`.
40
- * A checkout mid-build can be missing or unreadable — an unknown version
41
- * simply means upgrade treats anything on npm as newer.
42
- */
43
- export function packageVersion() {
44
- try {
45
- const manifest = readFileSync(path.join(packageRoot(), "package.json"), "utf8");
46
- return String(JSON.parse(manifest).version ?? "0.0.0");
47
- }
48
- catch {
49
- return "0.0.0";
50
- }
51
- }
52
- /**
53
- * The template ships inside the published package (`npm run build:template`
54
- * copies it there); running from a checkout, it is the repo's own directory.
55
- */
56
- export function resolveTemplateDir(env) {
57
- const override = env.PPLE_TEMPLATE_DIR?.trim();
58
- if (override)
59
- return override;
60
- const bundled = path.join(packageRoot(), "templates", "golden-path");
61
- if (existsSync(bundled))
62
- return bundled;
63
- // Running from a checkout of the platform repo, before `build:template`.
64
- return path.join(path.dirname(packageRoot()), "template");
65
- }
66
- /**
67
- * Platform Skills ship inside the published package (`npm run build:skills`
68
- * copies them there); running from a checkout, they are the repo's own
69
- * `skills/pple-platform/`.
70
- */
71
- export function resolveSkillsDir(env) {
72
- const override = env.PPLE_SKILLS_DIR?.trim();
73
- if (override)
74
- return override;
75
- const bundled = path.join(packageRoot(), "skills", "pple-platform");
76
- if (existsSync(bundled))
77
- return bundled;
78
- return path.join(path.dirname(packageRoot()), "skills", "pple-platform");
79
- }
80
- /**
81
- * The helper GIT_ASKPASS points at, which feeds git the app's repo token.
82
- * git execs it directly rather than through a shell, so it ships with its
83
- * executable bit set and finds node from its own shebang.
84
- */
85
- export function askpassHelper() {
86
- return path.join(packageRoot(), "askpass", "pple-askpass.mjs");
87
- }
@@ -1,122 +0,0 @@
1
- // OAuth 2.0 device authorization grant (RFC 8628) against PPLE ID.
2
- //
3
- // A terminal cannot receive a redirect, so the CLI asks PPLE ID for a code,
4
- // the Builder approves it in a browser (on any device), and the CLI polls
5
- // until the token is issued. Nothing sensitive is ever typed into the
6
- // terminal, and the CLI never handles the Builder's password.
7
- import { CliError } from "./errors.js";
8
- /** Zitadel's OAuth endpoints; the userinfo path the Provisioner uses too. */
9
- const DEVICE_AUTHORIZATION_PATH = "/oauth/v2/device_authorization";
10
- const TOKEN_PATH = "/oauth/v2/token";
11
- const DEVICE_CODE_GRANT = "urn:ietf:params:oauth:grant-type:device_code";
12
- /** `offline_access` is what makes a refresh token — and a durable login. */
13
- const SCOPES = "openid profile offline_access";
14
- function form(fields) {
15
- return {
16
- method: "POST",
17
- headers: { "content-type": "application/x-www-form-urlencoded" },
18
- body: new URLSearchParams(fields).toString(),
19
- };
20
- }
21
- /**
22
- * Name the host we failed to reach. "Check your internet connection" sends a
23
- * Builder to their wifi when the real fault is a bad `ssoBase` — the host it
24
- * couldn't reach is the one fact that tells the two apart.
25
- */
26
- function unreachable(ssoBase) {
27
- return new CliError(`เชื่อมต่อ PPLE ID (${ssoBase}) เพื่อเข้าสู่ระบบไม่ได้`, "ตรวจสอบการเชื่อมต่ออินเทอร์เน็ตแล้วลอง `pple auth login` อีกครั้ง");
28
- }
29
- export async function requestDeviceCode(deps) {
30
- const { ssoBase, clientId } = deps.config;
31
- const res = await deps
32
- .fetch(`${ssoBase}${DEVICE_AUTHORIZATION_PATH}`, form({ client_id: clientId, scope: SCOPES }))
33
- .catch(() => null);
34
- if (!res || !res.ok)
35
- throw unreachable(ssoBase);
36
- const body = (await res.json().catch(() => ({})));
37
- if (!body.device_code || !body.user_code || !body.verification_uri) {
38
- throw unreachable(ssoBase);
39
- }
40
- return {
41
- deviceCode: body.device_code,
42
- userCode: body.user_code,
43
- verificationUri: body.verification_uri,
44
- verificationUriComplete: body.verification_uri_complete ?? null,
45
- expiresInMs: (body.expires_in ?? 600) * 1000,
46
- // RFC 8628 says 5 seconds when the server doesn't specify.
47
- intervalMs: (body.interval ?? 5) * 1000,
48
- };
49
- }
50
- /**
51
- * Poll until the Builder approves in the browser. Backs off when told to
52
- * (`slow_down`), and gives up when the code expires rather than looping
53
- * forever in front of someone who walked away.
54
- */
55
- export async function awaitToken(deps, device) {
56
- const { ssoBase, clientId } = deps.config;
57
- const deadline = deps.now() + device.expiresInMs;
58
- let intervalMs = device.intervalMs;
59
- while (deps.now() < deadline) {
60
- await deps.sleep(intervalMs);
61
- const res = await deps
62
- .fetch(`${ssoBase}${TOKEN_PATH}`, form({
63
- client_id: clientId,
64
- grant_type: DEVICE_CODE_GRANT,
65
- device_code: device.deviceCode,
66
- }))
67
- .catch(() => null);
68
- if (!res)
69
- throw unreachable(ssoBase);
70
- const body = (await res.json().catch(() => ({})));
71
- if (res.ok && body.access_token)
72
- return toTokenSet(deps, body);
73
- switch (body.error) {
74
- case "authorization_pending":
75
- break;
76
- case "slow_down":
77
- intervalMs += 5000;
78
- break;
79
- case "access_denied":
80
- throw new CliError("การเข้าสู่ระบบถูกปฏิเสธในเบราว์เซอร์", "หากไม่ใช่คุณที่ปฏิเสธ ให้รัน `pple auth login` อีกครั้ง");
81
- case "expired_token":
82
- throw expiredCode();
83
- default:
84
- throw new CliError("PPLE ID ปฏิเสธการเข้าสู่ระบบ", body.error_description ?? "ลอง `pple auth login` อีกครั้ง");
85
- }
86
- }
87
- throw expiredCode();
88
- }
89
- function expiredCode() {
90
- return new CliError("รหัสเข้าสู่ระบบหมดอายุก่อนได้รับการอนุมัติ", "รัน `pple auth login` อีกครั้ง — ขั้นตอนในเบราว์เซอร์ต้องทำให้เสร็จภายในไม่กี่นาที");
91
- }
92
- /** Trade a refresh token for a fresh access token; null if it no longer works. */
93
- export async function refresh(deps, refreshToken) {
94
- const { ssoBase, clientId } = deps.config;
95
- const res = await deps
96
- .fetch(`${ssoBase}${TOKEN_PATH}`, form({
97
- client_id: clientId,
98
- grant_type: "refresh_token",
99
- refresh_token: refreshToken,
100
- scope: SCOPES,
101
- }))
102
- .catch(() => null);
103
- if (!res || !res.ok)
104
- return null;
105
- const body = (await res.json().catch(() => ({})));
106
- if (!body.access_token)
107
- return null;
108
- return {
109
- ...toTokenSet(deps, body),
110
- // Rotation is optional: keep the current token when none comes back.
111
- refreshToken: body.refresh_token ?? refreshToken,
112
- };
113
- }
114
- function toTokenSet(deps, body) {
115
- return {
116
- accessToken: body.access_token,
117
- refreshToken: body.refresh_token ?? null,
118
- expiresAt: typeof body.expires_in === "number"
119
- ? deps.now() + body.expires_in * 1000
120
- : null,
121
- };
122
- }
package/dist/errors.js DELETED
@@ -1,16 +0,0 @@
1
- /**
2
- * Every failure a Builder can see is a CliError: a sentence about their app,
3
- * optionally followed by the one thing to do next. Anything else escaping to
4
- * the top level is a bug in the CLI and is reported as such.
5
- */
6
- export class CliError extends Error {
7
- hint;
8
- constructor(message, hint) {
9
- super(message);
10
- this.hint = hint;
11
- this.name = "CliError";
12
- }
13
- }
14
- /** Both ends of the sign-in — the stored token, and what the platform says. */
15
- export const SIGNED_OUT = new CliError("คุณยังไม่ได้เข้าสู่ระบบ", "รัน `pple auth login` ก่อน");
16
- export const SIGN_IN_EXPIRED = new CliError("การเข้าสู่ระบบของคุณหมดอายุแล้ว", "รัน `pple auth login` เพื่อเข้าสู่ระบบอีกครั้ง");
package/dist/git.js DELETED
@@ -1,127 +0,0 @@
1
- // Git, masked.
2
- //
3
- // Builders are not expected to know git, so nothing above this module names
4
- // it: `pple init` "sets up the app's code", `pple deploy` "sends changes".
5
- // Every failure that a Builder could plausibly hit is translated here; the
6
- // raw git output only survives in the hint, where it helps whoever is asked
7
- // for help next.
8
- import { CliError } from "./errors.js";
9
- /** Builder Apps deploy from main; the workflow builds nothing else. */
10
- export const MAIN_BRANCH = "main";
11
- /** The username half of the Basic credential; GitHub ignores its value. */
12
- const TOKEN_USERNAME = "x-access-token";
13
- /**
14
- * Every git invocation runs with the app's token in its environment and none
15
- * of the machine's own credentials in play:
16
- *
17
- * - `GIT_ASKPASS` is how the token reaches git. Passing it in the URL would
18
- * put it in argv, which `ps` shows to every user on the machine, and in
19
- * `.git/config` once git wrote the remote down.
20
- * - `credential.helper=` empties the helper list, so a keychain entry left
21
- * over from someone's own GitHub account can't answer ahead of the platform.
22
- * - `GIT_TERMINAL_PROMPT=0` stops git asking a Builder for a password it is
23
- * the platform's job to supply.
24
- */
25
- async function git(deps, dir, args, access) {
26
- return deps.run("git", access ? ["-c", "credential.helper=", ...args] : args, {
27
- cwd: dir,
28
- env: {
29
- GIT_TERMINAL_PROMPT: "0",
30
- ...(access
31
- ? {
32
- GIT_ASKPASS: deps.gitAskpass,
33
- PPLE_GIT_USER: TOKEN_USERNAME,
34
- PPLE_GIT_TOKEN: access.token,
35
- }
36
- : {}),
37
- },
38
- });
39
- }
40
- /** Author every commit as the Builder, since the pusher is the platform's bot. */
41
- function authorArgs(access) {
42
- return [
43
- "-c",
44
- `user.name=${access.author.name}`,
45
- "-c",
46
- `user.email=${access.author.email}`,
47
- ];
48
- }
49
- /**
50
- * git failed because it was not allowed near the app's repo. The token is the
51
- * platform's to mint, so this is the platform having declined — not something
52
- * a Builder fixes with a credential of their own.
53
- */
54
- function isAccessFailure(result) {
55
- return /authentication|could not read|403|denied|not found/i.test(`${result.stdout}\n${result.stderr}`);
56
- }
57
- function noAccess(action, command) {
58
- return new CliError(`แพลตฟอร์มไม่อนุญาตให้เครื่องนี้${action}`, `ตรวจสอบว่าแอปยังเป็นของคุณอยู่ที่ https://console.ppleth.ai แล้วรัน \`${command}\` อีกครั้ง`);
59
- }
60
- function unreadableFolder(result) {
61
- return new CliError("อ่านโฟลเดอร์ของแอปไม่ได้", result.stderr.trim());
62
- }
63
- async function gitOrThrow(deps, dir, args, onFailure, access) {
64
- const result = await git(deps, dir, args, access).catch(() => {
65
- throw new CliError("เครื่องนี้ยังไม่มีเครื่องมือที่ PPLE CLI ต้องใช้", "ติดตั้ง git (https://git-scm.com/downloads) แล้วรันคำสั่งอีกครั้ง");
66
- });
67
- if (result.code !== 0)
68
- throw onFailure(result);
69
- return result;
70
- }
71
- /** Fetch an app's code into `targetDir`, which must not exist yet. */
72
- export async function cloneApp(deps, repoUrl, targetDir, access) {
73
- await gitOrThrow(deps, deps.cwd, ["clone", repoUrl, targetDir], (result) => isAccessFailure(result)
74
- ? noAccess("เข้าถึงโค้ดของแอปคุณ", "pple init")
75
- : new CliError("ดึงโค้ดของแอปไม่สำเร็จ", result.stderr.trim()), access);
76
- }
77
- /** True when the working copy holds changes that haven't been recorded yet. */
78
- export async function hasLocalChanges(deps, dir) {
79
- const result = await gitOrThrow(deps, dir, ["status", "--porcelain"], unreadableFolder);
80
- return result.stdout.trim() !== "";
81
- }
82
- /** Record every change in the folder as one entry in the app's history. */
83
- export async function recordChanges(deps, dir, message, access) {
84
- await gitOrThrow(deps, dir, ["add", "-A"], unreadableFolder);
85
- await gitOrThrow(deps, dir, [...authorArgs(access), "commit", "-m", message], (r) => new CliError("บันทึกการเปลี่ยนแปลงของคุณไม่สำเร็จ", r.stderr.trim()));
86
- }
87
- export async function headCommit(deps, dir) {
88
- const result = await gitOrThrow(deps, dir, ["rev-parse", "HEAD"], (r) => new CliError("อ่านประวัติของแอปไม่สำเร็จ", r.stderr.trim()));
89
- return result.stdout.trim();
90
- }
91
- /** True when the local branch already matches what the platform has. */
92
- export async function isUpToDateWithRemote(deps, dir, access) {
93
- await git(deps, dir, ["fetch", "origin", MAIN_BRANCH], access);
94
- const local = await git(deps, dir, ["rev-parse", "HEAD"]);
95
- const remote = await git(deps, dir, ["rev-parse", `origin/${MAIN_BRANCH}`]);
96
- if (local.code !== 0 || remote.code !== 0)
97
- return false;
98
- return local.stdout.trim() === remote.stdout.trim();
99
- }
100
- /**
101
- * Send the recorded changes to the platform. When the app has changes this
102
- * copy hasn't seen — a Collaborator deployed meanwhile — they are merged in
103
- * first, and only a genuine conflict reaches the Builder.
104
- */
105
- export async function sendChanges(deps, dir, access) {
106
- const push = ["push", "origin", `HEAD:${MAIN_BRANCH}`];
107
- const pushed = await git(deps, dir, push, access);
108
- if (pushed.code === 0)
109
- return;
110
- const output = `${pushed.stdout}\n${pushed.stderr}`;
111
- if (!/rejected|non-fast-forward|fetch first/i.test(output)) {
112
- if (isAccessFailure(pushed)) {
113
- throw noAccess("ส่งการเปลี่ยนแปลงไปยังแอปของคุณ", "pple deploy");
114
- }
115
- throw new CliError("ส่งการเปลี่ยนแปลงของคุณไม่สำเร็จ", pushed.stderr.trim());
116
- }
117
- deps.out.say("มีคนอื่นแก้ไขแอปนี้ — กำลังนำงานของเขาเข้ามาก่อน…");
118
- const rebase = await git(deps, dir, [...authorArgs(access), "pull", "--rebase", "origin", MAIN_BRANCH], access);
119
- if (rebase.code !== 0) {
120
- await git(deps, dir, ["rebase", "--abort"]);
121
- throw new CliError("การเปลี่ยนแปลงของคุณขัดแย้งกับที่คนอื่นดีพลอยไปแล้ว", "ให้ AI coding agent ของคุณรวมสองเวอร์ชันเข้าด้วยกัน แล้วรัน `pple deploy` อีกครั้ง");
122
- }
123
- const retried = await git(deps, dir, push, access);
124
- if (retried.code !== 0) {
125
- throw new CliError("ส่งการเปลี่ยนแปลงของคุณไม่สำเร็จ", retried.stderr.trim());
126
- }
127
- }
package/dist/link.js DELETED
@@ -1,59 +0,0 @@
1
- // The link between a folder on a Builder's computer and an app on the
2
- // platform. `pple init` writes it, `pple deploy` reads it — which is why
3
- // deploy needs no arguments and cannot ship the wrong app.
4
- import fs from "node:fs/promises";
5
- import path from "node:path";
6
- import { CliError } from "./errors.js";
7
- export const LINK_DIR = ".pple";
8
- export const LINK_FILE = "app.json";
9
- export async function writeLink(dir, link) {
10
- await fs.mkdir(path.join(dir, LINK_DIR), { recursive: true });
11
- await fs.writeFile(path.join(dir, LINK_DIR, LINK_FILE), `${JSON.stringify(link, null, 2)}\n`);
12
- }
13
- /**
14
- * Find the app this command is about by walking up from the current folder,
15
- * so `pple deploy` works from anywhere inside the app.
16
- */
17
- export async function findLink(deps) {
18
- const found = await peekLink(deps);
19
- if (found)
20
- return found;
21
- throw new CliError("โฟลเดอร์นี้ไม่ใช่แอป PPLE", "เข้าไปในโฟลเดอร์ของแอป หรือรัน `pple init <app>` เพื่อตั้งค่าแอปใหม่");
22
- }
23
- /**
24
- * Like `findLink`, but returns null when no App Link is nearby — used by
25
- * `pple init` to refuse creating a second app inside an already-linked folder.
26
- */
27
- export async function peekLink(deps) {
28
- let dir = path.resolve(deps.cwd);
29
- for (;;) {
30
- const candidate = path.join(dir, LINK_DIR, LINK_FILE);
31
- const raw = await fs.readFile(candidate, "utf8").catch(() => null);
32
- if (raw !== null) {
33
- const link = parseLink(raw);
34
- if (link)
35
- return { ...link, dir };
36
- throw new CliError("การเชื่อมโยงแอปนี้กับแพลตฟอร์มเสียหาย", "รัน `pple init <app>` ในโฟลเดอร์ใหม่เพื่อตั้งค่าอีกครั้ง");
37
- }
38
- const parent = path.dirname(dir);
39
- if (parent === dir)
40
- break;
41
- dir = parent;
42
- }
43
- return null;
44
- }
45
- function parseLink(raw) {
46
- try {
47
- const parsed = JSON.parse(raw);
48
- if (typeof parsed.slug !== "string" || parsed.slug === "")
49
- return null;
50
- return {
51
- slug: parsed.slug,
52
- apiBase: typeof parsed.apiBase === "string" ? parsed.apiBase : "",
53
- url: typeof parsed.url === "string" ? parsed.url : "",
54
- };
55
- }
56
- catch {
57
- return null;
58
- }
59
- }
package/dist/node-deps.js DELETED
@@ -1,107 +0,0 @@
1
- // The Node implementations behind `Deps` — the only part of the CLI that
2
- // touches the real filesystem, the real clock, and real subprocesses.
3
- import { spawn } from "node:child_process";
4
- import readline from "node:readline/promises";
5
- import { askpassHelper, configDir, packageRoot, packageVersion, platformConfig, resolveSkillsDir, resolveTemplateDir, updateChecksAllowed, } from "./deps.js";
6
- export function spawnRun(command, args, options) {
7
- return new Promise((resolve, reject) => {
8
- const child = spawn(command, args, {
9
- cwd: options.cwd,
10
- env: { ...process.env, ...options.env },
11
- stdio: ["ignore", "pipe", "pipe"],
12
- });
13
- let stdout = "";
14
- let stderr = "";
15
- child.stdout.on("data", (chunk) => (stdout += String(chunk)));
16
- child.stderr.on("data", (chunk) => (stderr += String(chunk)));
17
- child.on("error", reject);
18
- child.on("close", (code) => resolve({ code: code ?? 0, stdout, stderr }));
19
- });
20
- }
21
- /** Best-effort browser launch: login still works from the printed URL. */
22
- async function openBrowser(url) {
23
- const command = process.platform === "darwin"
24
- ? "open"
25
- : process.platform === "win32"
26
- ? "start"
27
- : "xdg-open";
28
- await spawnRun(command, [url], { cwd: process.cwd() }).catch(() => undefined);
29
- }
30
- /** A yes/no question at the terminal. Anything but yes means no. */
31
- async function confirm(question) {
32
- const rl = readline.createInterface({
33
- input: process.stdin,
34
- output: process.stdout,
35
- });
36
- try {
37
- const answer = await rl.question(`${question} [y/N] `);
38
- return /^y(es)?$/i.test(answer.trim());
39
- }
40
- finally {
41
- rl.close();
42
- }
43
- }
44
- /** A free-text question at the terminal (App Name, etc.). */
45
- async function ask(question) {
46
- const rl = readline.createInterface({
47
- input: process.stdin,
48
- output: process.stdout,
49
- });
50
- try {
51
- return await rl.question(`${question} `);
52
- }
53
- finally {
54
- rl.close();
55
- }
56
- }
57
- /** Drain stdin to EOF — for piping multiline or secret env values. */
58
- async function readStdin() {
59
- const chunks = [];
60
- for await (const chunk of process.stdin) {
61
- chunks.push(typeof chunk === "string" ? Buffer.from(chunk) : chunk);
62
- }
63
- return Buffer.concat(chunks).toString("utf8");
64
- }
65
- /**
66
- * Run the Builder's command line again on the `pple` that is installed now.
67
- * The child inherits the terminal, so a re-run deploy still prompts and
68
- * prints as if the Builder had typed it themselves.
69
- *
70
- * It re-runs the same script path deliberately: a global install replaces
71
- * what that path resolves to, so the old path is exactly how to reach the new
72
- * version — and the path this process was started from is the one the Builder
73
- * chose, whatever else is on PATH.
74
- */
75
- function reexec(argv) {
76
- return new Promise((resolve, reject) => {
77
- const child = spawn(process.execPath, [process.argv[1], ...argv], {
78
- stdio: "inherit",
79
- });
80
- child.on("error", reject);
81
- child.on("close", (code) => resolve(code ?? 0));
82
- });
83
- }
84
- export function nodeDeps(out, homeDir) {
85
- return {
86
- fetch: globalThis.fetch.bind(globalThis),
87
- now: () => Date.now(),
88
- sleep: (ms) => new Promise((resolve) => setTimeout(resolve, ms)),
89
- cwd: process.cwd(),
90
- configDir: configDir(process.env, homeDir),
91
- out,
92
- openBrowser,
93
- run: spawnRun,
94
- templateDir: resolveTemplateDir(process.env),
95
- skillsDir: resolveSkillsDir(process.env),
96
- packageDir: packageRoot(),
97
- version: packageVersion(),
98
- interactive: process.stdin.isTTY === true && process.stdout.isTTY === true,
99
- confirm,
100
- ask,
101
- readStdin,
102
- reexec,
103
- updateChecks: updateChecksAllowed(process.env),
104
- gitAskpass: askpassHelper(),
105
- config: platformConfig(process.env),
106
- };
107
- }