@ebowwa/seedinstallation 0.2.4 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/clone.js CHANGED
@@ -1,74 +1,60 @@
1
- import { spawn } from "child_process";
2
- /**
3
- * Composable git clone with support for shallow, sparse, branch, and directory options.
4
- *
5
- * @example
6
- * // Basic clone
7
- * await clone({ repo: "https://github.com/org/repo.git" });
8
- *
9
- * // Shallow clone of a specific branch into a custom dir
10
- * await clone({ repo: "https://github.com/org/repo.git", ref: "main", depth: 1, dest: "my-repo" });
11
- *
12
- * // Sparse checkout — only pull specific subdirectories
13
- * await clone({ repo: "https://github.com/org/repo.git", sparse: ["src/lib", "src/types"], depth: 1 });
14
- */
15
- export async function clone(opts) {
16
- const { repo, dest, ref, depth, sparse, cwd } = opts;
17
- const repoName = dest ?? repoNameFrom(repo);
18
- const targetDir = cwd ? `${cwd}/${repoName}` : repoName;
19
- const args = ["git", "clone"];
20
- if (depth)
21
- args.push("--depth", String(depth));
22
- if (ref && !sparse)
23
- args.push("--branch", ref);
24
- if (sparse)
25
- args.push("--no-checkout", "--filter=blob:none");
26
- args.push(repo, targetDir);
27
- await run(args, cwd);
28
- if (sparse) {
29
- await run(["git", "sparse-checkout", "init", "--cone"], targetDir);
30
- await run(["git", "sparse-checkout", "set", ...sparse], targetDir);
31
- if (ref) {
32
- await run(["git", "checkout", ref], targetDir);
33
- }
34
- else {
35
- await run(["git", "checkout"], targetDir);
36
- }
1
+ var __defProp = Object.defineProperty;
2
+ var __export = (target, all) => {
3
+ for (var name in all)
4
+ __defProp(target, name, {
5
+ get: all[name],
6
+ enumerable: true,
7
+ configurable: true,
8
+ set: (newValue) => all[name] = () => newValue
9
+ });
10
+ };
11
+ var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
12
+
13
+ // clone.ts
14
+ async function clone(opts) {
15
+ const { repo, dest, ref, depth, sparse, cwd } = opts;
16
+ const repoName = dest ?? repoNameFrom(repo);
17
+ const targetDir = cwd ? `${cwd}/${repoName}` : repoName;
18
+ const args = ["git", "clone"];
19
+ if (depth)
20
+ args.push("--depth", String(depth));
21
+ if (ref && !sparse)
22
+ args.push("--branch", ref);
23
+ if (sparse)
24
+ args.push("--no-checkout", "--filter=blob:none");
25
+ args.push(repo, targetDir);
26
+ await run(args, cwd);
27
+ if (sparse) {
28
+ await run(["git", "sparse-checkout", "init", "--cone"], targetDir);
29
+ await run(["git", "sparse-checkout", "set", ...sparse], targetDir);
30
+ if (ref) {
31
+ await run(["git", "checkout", ref], targetDir);
32
+ } else {
33
+ await run(["git", "checkout"], targetDir);
37
34
  }
38
- const commit = (await run(["git", "rev-parse", "--short", "HEAD"], targetDir)).trim();
39
- const checkedRef = (await run(["git", "rev-parse", "--abbrev-ref", "HEAD"], targetDir)).trim();
40
- const absPath = (await run(["git", "rev-parse", "--show-toplevel"], targetDir)).trim();
41
- return { path: absPath, ref: checkedRef, commit };
35
+ }
36
+ const commit = (await run(["git", "rev-parse", "--short", "HEAD"], targetDir)).trim();
37
+ const checkedRef = (await run(["git", "rev-parse", "--abbrev-ref", "HEAD"], targetDir)).trim();
38
+ const absPath = (await run(["git", "rev-parse", "--show-toplevel"], targetDir)).trim();
39
+ return { path: absPath, ref: checkedRef, commit };
42
40
  }
43
- /** Extract repo name from a git URL */
44
41
  function repoNameFrom(url) {
45
- return url.replace(/\.git$/, "").split("/").pop();
42
+ return url.replace(/\.git$/, "").split("/").pop();
46
43
  }
47
- /** Run a command and return stdout, throw on failure */
48
44
  async function run(args, cwd) {
49
- return new Promise((resolve, reject) => {
50
- const proc = spawn(args[0], args.slice(1), {
51
- cwd: cwd || undefined,
52
- stdio: ["ignore", "pipe", "pipe"],
53
- });
54
- let stdout = "";
55
- let stderr = "";
56
- proc.stdout?.on("data", (data) => {
57
- stdout += data.toString();
58
- });
59
- proc.stderr?.on("data", (data) => {
60
- stderr += data.toString();
61
- });
62
- proc.on("close", (code) => {
63
- if (code === 0) {
64
- resolve(stdout);
65
- }
66
- else {
67
- reject(new Error(`${args.join(" ")} failed (exit ${code}): ${stderr}`));
68
- }
69
- });
70
- proc.on("error", (err) => {
71
- reject(new Error(`${args.join(" ")} failed: ${err.message}`));
72
- });
73
- });
45
+ const proc = Bun.spawn(args, {
46
+ cwd,
47
+ stdout: "pipe",
48
+ stderr: "pipe"
49
+ });
50
+ const exitCode = await proc.exited;
51
+ const stdout = await new Response(proc.stdout).text();
52
+ if (exitCode !== 0) {
53
+ const stderr = await new Response(proc.stderr).text();
54
+ throw new Error(`${args.join(" ")} failed (exit ${exitCode}): ${stderr}`);
55
+ }
56
+ return stdout;
74
57
  }
58
+ export {
59
+ clone
60
+ };
@@ -3,7 +3,7 @@
3
3
  * Handles CLI tools that use device-code auth (Doppler, GitHub, Tailscale, etc.).
4
4
  * Works with both local and SSH contexts via ExecContext from sudo.ts.
5
5
  */
6
- import type { SudoOptions } from "./sudo.js";
6
+ import type { SudoOptions } from "./sudo";
7
7
  export interface DeviceAuthConfig {
8
8
  /** CLI command name (e.g. "doppler", "gh", "tailscale") */
9
9
  cli: string;
@@ -39,9 +39,7 @@ export interface DeviceAuthResult {
39
39
  /** Error message if failed */
40
40
  error?: string;
41
41
  }
42
- export interface DeviceAuthPollOptions {
43
- /** Execution context for running commands */
44
- context: SudoOptions["context"];
42
+ export interface DeviceAuthPollOptions extends SudoOptions {
45
43
  /** Max poll attempts (default: 60) */
46
44
  maxAttempts?: number;
47
45
  /** Poll interval in ms (default: 2000) */
@@ -68,7 +66,7 @@ export declare function stripAnsi(text: string): string;
68
66
  * { context: { type: "ssh", host: "1.2.3.4" } }
69
67
  * );
70
68
  */
71
- export declare function deviceAuth(config: DeviceAuthConfig, opts: DeviceAuthPollOptions): Promise<DeviceAuthResult>;
69
+ export declare function deviceAuth(config: DeviceAuthConfig, opts?: DeviceAuthPollOptions): Promise<DeviceAuthResult>;
72
70
  /**
73
71
  * Check if a CLI is already authenticated.
74
72
  */
@@ -83,3 +81,4 @@ export declare const dopplerConfig: DeviceAuthConfig;
83
81
  export declare const githubConfig: DeviceAuthConfig;
84
82
  /** Tailscale CLI device-auth config */
85
83
  export declare const tailscaleConfig: DeviceAuthConfig;
84
+ //# sourceMappingURL=device-auth.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"device-auth.d.ts","sourceRoot":"","sources":["../device-auth.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,EAAe,WAAW,EAAc,MAAM,QAAQ,CAAC;AAYnE,MAAM,WAAW,gBAAgB;IAC/B,2DAA2D;IAC3D,GAAG,EAAE,MAAM,CAAC;IACZ,4EAA4E;IAC5E,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,qDAAqD;IACrD,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,8DAA8D;IAC9D,QAAQ,EAAE;QACR,uDAAuD;QACvD,GAAG,EAAE,MAAM,CAAC;QACZ,wDAAwD;QACxD,IAAI,EAAE,MAAM,CAAC;QACb,wDAAwD;QACxD,OAAO,EAAE,MAAM,CAAC;KACjB,CAAC;IACF,oFAAoF;IACpF,OAAO,EAAE,MAAM,CAAC;IAChB,gDAAgD;IAChD,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,qCAAqC;IACrC,WAAW,CAAC,EAAE,MAAM,EAAE,CAAC;CACxB;AAED,MAAM,WAAW,gBAAgB;IAC/B,mCAAmC;IACnC,OAAO,EAAE,OAAO,CAAC;IACjB,qCAAqC;IACrC,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,yBAAyB;IACzB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,iCAAiC;IACjC,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,8BAA8B;IAC9B,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,qBAAsB,SAAQ,WAAW;IACxD,sCAAsC;IACtC,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,0CAA0C;IAC1C,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,2CAA2C;IAC3C,MAAM,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,gBAAgB,KAAK,IAAI,CAAC;CAC9D;AAMD,8CAA8C;AAC9C,wBAAgB,SAAS,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAK9C;AAMD;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAsB,UAAU,CAC9B,MAAM,EAAE,gBAAgB,EACxB,IAAI,GAAE,qBAA0B,GAC/B,OAAO,CAAC,gBAAgB,CAAC,CAgF3B;AAeD;;GAEG;AACH,wBAAsB,QAAQ,CAC5B,MAAM,EAAE,IAAI,CAAC,gBAAgB,EAAE,KAAK,GAAG,WAAW,GAAG,UAAU,CAAC,EAChE,IAAI,EAAE,WAAW,GAChB,OAAO,CAAC,OAAO,CAAC,CAKlB;AAMD;;GAEG;AACH,wBAAsB,iBAAiB,CACrC,MAAM,EAAE,IAAI,CAAC,gBAAgB,EAAE,SAAS,GAAG,SAAS,CAAC,EACrD,IAAI,EAAE,WAAW,GAChB,OAAO,CAAC,IAAI,CAAC,CAaf;AAMD,qCAAqC;AACrC,eAAO,MAAM,aAAa,EAAE,gBAW3B,CAAC;AAEF,yCAAyC;AACzC,eAAO,MAAM,YAAY,EAAE,gBAW1B,CAAC;AAEF,uCAAuC;AACvC,eAAO,MAAM,eAAe,EAAE,gBAU7B,CAAC"}
@@ -1,176 +1,264 @@
1
- /**
2
- * Generic device-code authentication flow polling.
3
- * Handles CLI tools that use device-code auth (Doppler, GitHub, Tailscale, etc.).
4
- * Works with both local and SSH contexts via ExecContext from sudo.ts.
5
- */
6
- // Re-export helpers
1
+ var __defProp = Object.defineProperty;
2
+ var __export = (target, all) => {
3
+ for (var name in all)
4
+ __defProp(target, name, {
5
+ get: all[name],
6
+ enumerable: true,
7
+ configurable: true,
8
+ set: (newValue) => all[name] = () => newValue
9
+ });
10
+ };
11
+ var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
12
+
13
+ // sudo.ts
14
+ var exports_sudo = {};
15
+ __export(exports_sudo, {
16
+ writeFile: () => writeFile,
17
+ sudo: () => sudo,
18
+ serviceEnable: () => serviceEnable,
19
+ service: () => service,
20
+ pkgInstall: () => pkgInstall,
21
+ exec: () => exec
22
+ });
23
+ async function sudo(cmd, opts) {
24
+ const parts = Array.isArray(cmd) ? cmd : cmd.split(/\s+/);
25
+ const envPrefix = opts.env ? Object.entries(opts.env).map(([k, v]) => `${k}=${shellEscape(v)}`) : [];
26
+ const sudoCmd = ["sudo", ...envPrefix, ...parts];
27
+ return exec(sudoCmd, opts);
28
+ }
29
+ async function pkgInstall(packages, opts) {
30
+ const pm = opts.pm ?? "apt";
31
+ const envOverride = {
32
+ ...opts.env,
33
+ ...opts.nonInteractive !== false ? { DEBIAN_FRONTEND: "noninteractive" } : {}
34
+ };
35
+ await sudo(updateCmd[pm], { ...opts, env: envOverride, quiet: true });
36
+ return sudo([...installCmd[pm], ...packages], {
37
+ ...opts,
38
+ env: envOverride
39
+ });
40
+ }
41
+ async function writeFile(path, content, opts) {
42
+ const op = opts.append ? "-a" : "";
43
+ const teeCmd = `tee ${op} ${shellEscape(path)}`.trim();
44
+ const result = await execPipe(content, ["sudo", teeCmd], opts);
45
+ if (result.ok && opts.mode) {
46
+ await sudo(["chmod", opts.mode, path], opts);
47
+ }
48
+ if (result.ok && opts.owner) {
49
+ await sudo(["chown", opts.owner, path], opts);
50
+ }
51
+ return result;
52
+ }
53
+ async function service(name, action, opts) {
54
+ return sudo(["systemctl", action, name], opts);
55
+ }
56
+ async function serviceEnable(name, opts) {
57
+ return sudo(["systemctl", "enable", "--now", name], opts);
58
+ }
59
+ function buildSshPrefix(ctx) {
60
+ const parts = ["ssh", "-F", "/dev/null", "-o", "StrictHostKeyChecking=no", "-o", "UserKnownHostsFile=/dev/null"];
61
+ if (ctx.keyPath)
62
+ parts.push("-i", ctx.keyPath);
63
+ else if (ctx.key)
64
+ parts.push("-i", ctx.key);
65
+ if (ctx.port)
66
+ parts.push("-p", String(ctx.port));
67
+ parts.push(`${ctx.user ?? "root"}@${ctx.host}`);
68
+ return parts;
69
+ }
7
70
  async function exec(args, opts) {
8
- const { sudo } = await import("./sudo.js");
9
- return sudo(args, opts);
10
- }
11
- // ---------------------------------------------------------------------------
12
- // ANSI stripping
13
- // ---------------------------------------------------------------------------
14
- /** Remove ANSI escape codes from a string. */
15
- export function stripAnsi(text) {
16
- return text.replace(/[\u001b\u009b][[()#;?]*(?:[0-9]{1,4}(?:;[0-9]{0,4})*)?[0-9A-ORZcf-nqry=><]/g, "");
17
- }
18
- // ---------------------------------------------------------------------------
19
- // Device-code auth flow
20
- // ---------------------------------------------------------------------------
21
- /**
22
- * Initiate device-code authentication and poll for completion.
23
- *
24
- * @example
25
- * const result = await deviceAuth(
26
- * {
27
- * cli: "doppler",
28
- * patterns: {
29
- * url: /Go to:\s+(https:\/\/[^\s]+)/,
30
- * code: /code:\s+([A-Z0-9-]+)/,
31
- * success: /Status:\s*authenticated/i,
32
- * },
33
- * logFile: "/tmp/doppler-login.log",
34
- * },
35
- * { context: { type: "ssh", host: "1.2.3.4" } }
36
- * );
37
- */
38
- export async function deviceAuth(config, opts) {
39
- const { cli, loginCmd, statusCmd, patterns, logFile, pidFile, statusFlags, } = config;
40
- const maxAttempts = opts.maxAttempts ?? 60;
41
- const intervalMs = opts.intervalMs ?? 2000;
42
- // Step 1: Start login in background
43
- const loginCmdStr = loginCmd ?? `${cli} login`;
44
- const startResult = await exec([`bash`, `-lc`, `nohup ${loginCmdStr} > ${logFile} 2>&1 & echo $!`], opts);
45
- if (!startResult.ok) {
46
- return { success: false, error: `Failed to start login: ${startResult.stderr}` };
47
- }
48
- const pid = startResult.stdout.trim();
49
- // Save PID if configured
50
- if (pidFile) {
51
- await exec(["bash", `-lc`, `echo ${pid} > ${pidFile}`], opts);
52
- }
53
- // Step 2: Wait a moment for log file to populate
54
- await sleep(1000);
55
- // Step 3: Extract URL and code from log file
56
- const extractResult = await exec(["cat", logFile], { ...opts, quiet: false });
57
- const logContent = stripAnsi(extractResult.stdout);
58
- const urlMatch = patterns.url.exec(logContent);
59
- const codeMatch = patterns.code.exec(logContent);
60
- const url = urlMatch?.[1];
61
- const code = codeMatch?.[1];
62
- if (!url || !code) {
63
- return {
64
- success: false,
65
- error: `Could not extract auth URL/code from log output. Log content:\n${logContent}`,
66
- };
67
- }
68
- // Step 4: Poll for completion
69
- for (let attempt = 1; attempt <= maxAttempts; attempt++) {
70
- const statusCmdStr = statusCmd ?? `${cli} status`;
71
- const statusArgs = ["bash", "-lc", statusCmdStr];
72
- if (statusFlags?.length)
73
- statusArgs.push(...statusFlags);
74
- const statusResult = await exec(statusArgs, { ...opts, quiet: false });
75
- const statusOutput = stripAnsi(statusResult.stdout);
76
- if (patterns.success.test(statusOutput)) {
77
- return {
78
- success: true,
79
- url,
80
- code,
81
- status: statusOutput,
82
- };
83
- }
84
- opts.onPoll?.(attempt, { success: false, url, code, status: statusOutput });
85
- await sleep(intervalMs);
86
- }
71
+ const finalArgs = opts.context.type === "ssh" ? [...buildSshPrefix(opts.context), args.map(shellEscape).join(" ")] : args;
72
+ const proc = Bun.spawn(finalArgs, {
73
+ stdout: "pipe",
74
+ stderr: "pipe",
75
+ timeout: opts.timeout ?? 30000
76
+ });
77
+ const exitCode = await proc.exited;
78
+ const stdout = opts.quiet ? "" : await new Response(proc.stdout).text();
79
+ const stderr = await new Response(proc.stderr).text();
80
+ return { stdout, stderr, exitCode, ok: exitCode === 0 };
81
+ }
82
+ async function execPipe(input, args, opts) {
83
+ if (opts.context.type === "ssh") {
84
+ const sshPrefix = buildSshPrefix(opts.context);
85
+ const remoteCmd = args.join(" ");
86
+ const fullArgs = [...sshPrefix, remoteCmd];
87
+ const proc2 = Bun.spawn(fullArgs, {
88
+ stdin: new TextEncoder().encode(input),
89
+ stdout: "pipe",
90
+ stderr: "pipe",
91
+ timeout: opts.timeout ?? 30000
92
+ });
93
+ const exitCode2 = await proc2.exited;
94
+ const stdout2 = opts.quiet ? "" : await new Response(proc2.stdout).text();
95
+ const stderr2 = await new Response(proc2.stderr).text();
96
+ return { stdout: stdout2, stderr: stderr2, exitCode: exitCode2, ok: exitCode2 === 0 };
97
+ }
98
+ const proc = Bun.spawn(["sh", "-c", args.join(" ")], {
99
+ stdin: new TextEncoder().encode(input),
100
+ stdout: "pipe",
101
+ stderr: "pipe",
102
+ timeout: opts.timeout ?? 30000
103
+ });
104
+ const exitCode = await proc.exited;
105
+ const stdout = opts.quiet ? "" : await new Response(proc.stdout).text();
106
+ const stderr = await new Response(proc.stderr).text();
107
+ return { stdout, stderr, exitCode, ok: exitCode === 0 };
108
+ }
109
+ function shellEscape(s) {
110
+ if (/^[a-zA-Z0-9._\-\/=:@]+$/.test(s))
111
+ return s;
112
+ return `'${s.replace(/'/g, "'\\''")}'`;
113
+ }
114
+ var installCmd, updateCmd;
115
+ var init_sudo = __esm(() => {
116
+ installCmd = {
117
+ apt: ["apt-get", "install", "-y"],
118
+ dnf: ["dnf", "install", "-y"],
119
+ apk: ["apk", "add", "--no-cache"]
120
+ };
121
+ updateCmd = {
122
+ apt: ["apt-get", "update", "-qq"],
123
+ dnf: ["dnf", "check-update"],
124
+ apk: ["apk", "update"]
125
+ };
126
+ });
127
+
128
+ // device-auth.ts
129
+ async function exec2(args, opts) {
130
+ const { sudo: sudo2 } = await Promise.resolve().then(() => (init_sudo(), exports_sudo));
131
+ return sudo2(args, opts);
132
+ }
133
+ function stripAnsi(text) {
134
+ return text.replace(/[\u001b\u009b][[()#;?]*(?:[0-9]{1,4}(?:;[0-9]{0,4})*)?[0-9A-ORZcf-nqry=><]/g, "");
135
+ }
136
+ async function deviceAuth(config, opts = {}) {
137
+ const {
138
+ cli,
139
+ loginCmd,
140
+ statusCmd,
141
+ patterns,
142
+ logFile,
143
+ pidFile,
144
+ statusFlags
145
+ } = config;
146
+ const maxAttempts = opts.maxAttempts ?? 60;
147
+ const intervalMs = opts.intervalMs ?? 2000;
148
+ const loginCmdStr = loginCmd ?? `${cli} login`;
149
+ const startResult = await exec2([`bash`, `-lc`, `nohup ${loginCmdStr} > ${logFile} 2>&1 & echo $!`], opts);
150
+ if (!startResult.ok) {
151
+ return { success: false, error: `Failed to start login: ${startResult.stderr}` };
152
+ }
153
+ const pid = startResult.stdout.trim();
154
+ if (pidFile) {
155
+ await exec2(["bash", `-lc`, `echo ${pid} > ${pidFile}`], opts);
156
+ }
157
+ await sleep(1000);
158
+ const extractResult = await exec2(["cat", logFile], { ...opts, quiet: false });
159
+ const logContent = stripAnsi(extractResult.stdout);
160
+ const urlMatch = patterns.url.exec(logContent);
161
+ const codeMatch = patterns.code.exec(logContent);
162
+ const url = urlMatch?.[1];
163
+ const code = codeMatch?.[1];
164
+ if (!url || !code) {
87
165
  return {
88
- success: false,
166
+ success: false,
167
+ error: `Could not extract auth URL/code from log output. Log content:
168
+ ${logContent}`
169
+ };
170
+ }
171
+ for (let attempt = 1;attempt <= maxAttempts; attempt++) {
172
+ const statusCmdStr = statusCmd ?? `${cli} status`;
173
+ const statusArgs = ["bash", "-lc", statusCmdStr];
174
+ if (statusFlags?.length)
175
+ statusArgs.push(...statusFlags);
176
+ const statusResult = await exec2(statusArgs, { ...opts, quiet: false });
177
+ const statusOutput = stripAnsi(statusResult.stdout);
178
+ if (patterns.success.test(statusOutput)) {
179
+ return {
180
+ success: true,
89
181
  url,
90
182
  code,
91
- error: `Login did not complete after ${maxAttempts} attempts. Last status: ${await getStatusOutput(config, opts)}`,
92
- };
183
+ status: statusOutput
184
+ };
185
+ }
186
+ opts.onPoll?.(attempt, { success: false, url, code, status: statusOutput });
187
+ await sleep(intervalMs);
188
+ }
189
+ return {
190
+ success: false,
191
+ url,
192
+ code,
193
+ error: `Login did not complete after ${maxAttempts} attempts. Last status: ${await getStatusOutput(config, opts)}`
194
+ };
93
195
  }
94
196
  async function getStatusOutput(config, opts) {
95
- const statusCmdStr = config.statusCmd ?? `${config.cli} status`;
96
- const result = await exec(["bash", "-lc", statusCmdStr], { ...opts, quiet: false });
97
- return stripAnsi(result.stdout);
98
- }
99
- // ---------------------------------------------------------------------------
100
- // Status check
101
- // ---------------------------------------------------------------------------
102
- /**
103
- * Check if a CLI is already authenticated.
104
- */
105
- export async function isAuthed(config, opts) {
106
- const statusCmdStr = config.statusCmd ?? `${config.cli} status`;
107
- const result = await exec(["bash", "-lc", statusCmdStr], { ...opts, quiet: false });
108
- const output = stripAnsi(result.stdout);
109
- return config.patterns.success.test(output);
110
- }
111
- // ---------------------------------------------------------------------------
112
- // Cleanup
113
- // ---------------------------------------------------------------------------
114
- /**
115
- * Kill the background login process and clean up files.
116
- */
117
- export async function cleanupDeviceAuth(config, opts) {
118
- if (config.pidFile) {
119
- // Read PID and kill process
120
- const pidResult = await exec(["cat", config.pidFile], { ...opts, quiet: true });
121
- if (pidResult.ok) {
122
- const pid = pidResult.stdout.trim();
123
- await exec(["kill", pid], { ...opts, quiet: true });
124
- }
125
- await exec(["rm", "-f", config.pidFile], { ...opts, quiet: true });
197
+ const statusCmdStr = config.statusCmd ?? `${config.cli} status`;
198
+ const result = await exec2(["bash", "-lc", statusCmdStr], { ...opts, quiet: false });
199
+ return stripAnsi(result.stdout);
200
+ }
201
+ async function isAuthed(config, opts) {
202
+ const statusCmdStr = config.statusCmd ?? `${config.cli} status`;
203
+ const result = await exec2(["bash", "-lc", statusCmdStr], { ...opts, quiet: false });
204
+ const output = stripAnsi(result.stdout);
205
+ return config.patterns.success.test(output);
206
+ }
207
+ async function cleanupDeviceAuth(config, opts) {
208
+ if (config.pidFile) {
209
+ const pidResult = await exec2(["cat", config.pidFile], { ...opts, quiet: true });
210
+ if (pidResult.ok) {
211
+ const pid = pidResult.stdout.trim();
212
+ await exec2(["kill", pid], { ...opts, quiet: true });
126
213
  }
127
- // Remove log file
128
- await exec(["rm", "-f", config.logFile], { ...opts, quiet: true });
129
- }
130
- // ---------------------------------------------------------------------------
131
- // Common CLI presets
132
- // ---------------------------------------------------------------------------
133
- /** Doppler CLI device-auth config */
134
- export const dopplerConfig = {
135
- cli: "doppler",
136
- loginCmd: "doppler login -y",
137
- statusCmd: "doppler configure get token --scope /",
138
- patterns: {
139
- url: /https:\/\/(?:cli|dashboard)\.doppler\.com\/[a-z\-\/]+/i,
140
- code: /(?:Your authentication code is:|Your auth code is:)\s*([A-Z0-9_]+(?:-[A-Z0-9_]+)*)/i,
141
- success: /.{10,}/, // Any output >10 chars means token exists (authenticated)
142
- },
143
- logFile: "/tmp/doppler-login.log",
144
- pidFile: "/tmp/doppler-login.pid",
214
+ await exec2(["rm", "-f", config.pidFile], { ...opts, quiet: true });
215
+ }
216
+ await exec2(["rm", "-f", config.logFile], { ...opts, quiet: true });
217
+ }
218
+ var dopplerConfig = {
219
+ cli: "doppler",
220
+ loginCmd: "doppler login -y",
221
+ statusCmd: "doppler configure get token --scope /",
222
+ patterns: {
223
+ url: /https:\/\/(?:cli|dashboard)\.doppler\.com\/[a-z\-\/]+/i,
224
+ code: /(?:Your authentication code is:|Your auth code is:)\s*([A-Z0-9_]+(?:-[A-Z0-9_]+)*)/i,
225
+ success: /.{10,}/
226
+ },
227
+ logFile: "/tmp/doppler-login.log",
228
+ pidFile: "/tmp/doppler-login.pid"
145
229
  };
146
- /** GitHub CLI (gh) device-auth config */
147
- export const githubConfig = {
148
- cli: "gh",
149
- loginCmd: "GH_BROWSER=echo sh -c 'gh auth login -p https -h github.com < /dev/null'",
150
- statusCmd: "gh auth status",
151
- patterns: {
152
- url: /(https:\/\/github\.com\/login\/device)/i,
153
- code: /one-time code:\s*([A-Z0-9]{4}-[A-Z0-9]{4})/i,
154
- success: /Logged in (?:as|to)/i,
155
- },
156
- logFile: "/tmp/gh-login.log",
157
- pidFile: "/tmp/gh-login.pid",
230
+ var githubConfig = {
231
+ cli: "gh",
232
+ loginCmd: "GH_BROWSER=echo sh -c 'gh auth login -p https -h github.com < /dev/null'",
233
+ statusCmd: "gh auth status",
234
+ patterns: {
235
+ url: /(https:\/\/github\.com\/login\/device)/i,
236
+ code: /one-time code:\s*([A-Z0-9]{4}-[A-Z0-9]{4})/i,
237
+ success: /Logged in (?:as|to)/i
238
+ },
239
+ logFile: "/tmp/gh-login.log",
240
+ pidFile: "/tmp/gh-login.pid"
158
241
  };
159
- /** Tailscale CLI device-auth config */
160
- export const tailscaleConfig = {
161
- cli: "tailscale",
162
- loginCmd: "tailscale up --authkey",
163
- patterns: {
164
- url: /https:\/\/login\.tailscale\.com\/[a-z0-9\/]+/,
165
- code: /https:\/\/login\.tailscale\.com\/[a-z0-9\/]+/,
166
- success: /Tailscale is running/i,
167
- },
168
- logFile: "/tmp/tailscale-login.log",
169
- pidFile: "/tmp/tailscale-login.pid",
242
+ var tailscaleConfig = {
243
+ cli: "tailscale",
244
+ loginCmd: "tailscale up --authkey",
245
+ patterns: {
246
+ url: /https:\/\/login\.tailscale\.com\/[a-z0-9\/]+/,
247
+ code: /https:\/\/login\.tailscale\.com\/[a-z0-9\/]+/,
248
+ success: /Tailscale is running/i
249
+ },
250
+ logFile: "/tmp/tailscale-login.log",
251
+ pidFile: "/tmp/tailscale-login.pid"
170
252
  };
171
- // ---------------------------------------------------------------------------
172
- // Utilities
173
- // ---------------------------------------------------------------------------
174
253
  function sleep(ms) {
175
- return new Promise((resolve) => setTimeout(resolve, ms));
254
+ return new Promise((resolve) => setTimeout(resolve, ms));
176
255
  }
256
+ export {
257
+ tailscaleConfig,
258
+ stripAnsi,
259
+ isAuthed,
260
+ githubConfig,
261
+ dopplerConfig,
262
+ deviceAuth,
263
+ cleanupDeviceAuth
264
+ };
package/dist/index.d.ts CHANGED
@@ -1,12 +1,13 @@
1
- export { clone } from "./clone.js";
2
- export type { CloneOptions, CloneResult } from "./clone.js";
3
- export { sudo, pkgInstall, writeFile, service, serviceEnable } from "./sudo.js";
4
- export type { ExecContext, SudoOptions, ExecResult, PkgOptions, PackageManager, WriteFileOptions, ServiceAction, } from "./sudo.js";
5
- export { installBun, addSystemPath, symlink, linkBinaries, getBunVersion, commandExists, } from "./runtime.js";
6
- export type { InstallBunOptions, PathOptions, SymlinkOptions, } from "./runtime.js";
7
- export { generateServiceUnit, createServiceUnit, enableService, disableService, startService, stopService, restartService, reloadService, getServiceStatus, enableAndStartService, serviceExists, getServiceLogs, } from "./systemd.js";
8
- export type { ServiceUnitOptions, ServiceResult, ServiceStatus, } from "./systemd.js";
9
- export { deviceAuth, isAuthed, cleanupDeviceAuth, stripAnsi, dopplerConfig, githubConfig, tailscaleConfig, } from "./device-auth.js";
10
- export type { DeviceAuthConfig, DeviceAuthResult, DeviceAuthPollOptions, } from "./device-auth.js";
11
- export { getBootstrapStatus, parseBootstrapStatus, initBootstrap, startPhase, completePhase, failPhase, completeBootstrap, failBootstrap, checkMarker, setMarker, removeMarker, waitForBootstrap, waitForMarker, } from "./bootstrap.js";
12
- export type { BootstrapPhase, BootstrapStatus, BootstrapPollOptions, } from "./bootstrap.js";
1
+ export { clone } from "./clone";
2
+ export type { CloneOptions, CloneResult } from "./clone";
3
+ export { sudo, pkgInstall, writeFile, service, serviceEnable } from "./sudo";
4
+ export type { ExecContext, SudoOptions, ExecResult, PkgOptions, PackageManager, WriteFileOptions, ServiceAction, } from "./sudo";
5
+ export { installBun, addSystemPath, symlink, linkBinaries, getBunVersion, commandExists, } from "./runtime";
6
+ export type { InstallBunOptions, PathOptions, SymlinkOptions, } from "./runtime";
7
+ export { generateServiceUnit, createServiceUnit, enableService, disableService, startService, stopService, restartService, reloadService, getServiceStatus, enableAndStartService, serviceExists, getServiceLogs, hasSystemd, clearSystemdCache, safeSystemd, } from "./systemd";
8
+ export type { ServiceUnitOptions, ServiceResult, ServiceStatus, SystemdCheckResult, SafeSystemdOptions, } from "./systemd";
9
+ export { deviceAuth, isAuthed, cleanupDeviceAuth, stripAnsi, dopplerConfig, githubConfig, tailscaleConfig, } from "./device-auth";
10
+ export type { DeviceAuthConfig, DeviceAuthResult, DeviceAuthPollOptions, } from "./device-auth";
11
+ export { getBootstrapStatus, parseBootstrapStatus, initBootstrap, startPhase, completePhase, failPhase, completeBootstrap, failBootstrap, checkMarker, setMarker, removeMarker, waitForBootstrap, waitForMarker, } from "./bootstrap";
12
+ export type { BootstrapPhase, BootstrapStatus, BootstrapPollOptions, } from "./bootstrap";
13
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,SAAS,CAAC;AAChC,YAAY,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,SAAS,CAAC;AAEzD,OAAO,EAAE,IAAI,EAAE,UAAU,EAAE,SAAS,EAAE,OAAO,EAAE,aAAa,EAAE,MAAM,QAAQ,CAAC;AAC7E,YAAY,EACV,WAAW,EACX,WAAW,EACX,UAAU,EACV,UAAU,EACV,cAAc,EACd,gBAAgB,EAChB,aAAa,GACd,MAAM,QAAQ,CAAC;AAGhB,OAAO,EACL,UAAU,EACV,aAAa,EACb,OAAO,EACP,YAAY,EACZ,aAAa,EACb,aAAa,GACd,MAAM,WAAW,CAAC;AACnB,YAAY,EACV,iBAAiB,EACjB,WAAW,EACX,cAAc,GACf,MAAM,WAAW,CAAC;AAGnB,OAAO,EACL,mBAAmB,EACnB,iBAAiB,EACjB,aAAa,EACb,cAAc,EACd,YAAY,EACZ,WAAW,EACX,cAAc,EACd,aAAa,EACb,gBAAgB,EAChB,qBAAqB,EACrB,aAAa,EACb,cAAc,EAEd,UAAU,EACV,iBAAiB,EACjB,WAAW,GACZ,MAAM,WAAW,CAAC;AACnB,YAAY,EACV,kBAAkB,EAClB,aAAa,EACb,aAAa,EACb,kBAAkB,EAClB,kBAAkB,GACnB,MAAM,WAAW,CAAC;AAGnB,OAAO,EACL,UAAU,EACV,QAAQ,EACR,iBAAiB,EACjB,SAAS,EACT,aAAa,EACb,YAAY,EACZ,eAAe,GAChB,MAAM,eAAe,CAAC;AACvB,YAAY,EACV,gBAAgB,EAChB,gBAAgB,EAChB,qBAAqB,GACtB,MAAM,eAAe,CAAC;AAGvB,OAAO,EACL,kBAAkB,EAClB,oBAAoB,EACpB,aAAa,EACb,UAAU,EACV,aAAa,EACb,SAAS,EACT,iBAAiB,EACjB,aAAa,EACb,WAAW,EACX,SAAS,EACT,YAAY,EACZ,gBAAgB,EAChB,aAAa,GACd,MAAM,aAAa,CAAC;AACrB,YAAY,EACV,cAAc,EACd,eAAe,EACf,oBAAoB,GACrB,MAAM,aAAa,CAAC"}