@lynxship/cli 0.1.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.
Files changed (68) hide show
  1. package/README.md +122 -0
  2. package/dist/android-build.d.ts +14 -0
  3. package/dist/android-build.d.ts.map +1 -0
  4. package/dist/android-build.js +201 -0
  5. package/dist/artifact-name.d.ts +3 -0
  6. package/dist/artifact-name.d.ts.map +1 -0
  7. package/dist/artifact-name.js +4 -0
  8. package/dist/autolink.d.ts +14 -0
  9. package/dist/autolink.d.ts.map +1 -0
  10. package/dist/autolink.js +144 -0
  11. package/dist/config.d.ts +51 -0
  12. package/dist/config.d.ts.map +1 -0
  13. package/dist/config.js +62 -0
  14. package/dist/configure.d.ts +11 -0
  15. package/dist/configure.d.ts.map +1 -0
  16. package/dist/configure.js +172 -0
  17. package/dist/index.d.ts +3 -0
  18. package/dist/index.d.ts.map +1 -0
  19. package/dist/index.js +1082 -0
  20. package/dist/ios-build.d.ts +13 -0
  21. package/dist/ios-build.d.ts.map +1 -0
  22. package/dist/ios-build.js +174 -0
  23. package/dist/ota-assets.d.ts +3 -0
  24. package/dist/ota-assets.d.ts.map +1 -0
  25. package/dist/ota-assets.js +48 -0
  26. package/dist/ota-doctor.d.ts +10 -0
  27. package/dist/ota-doctor.d.ts.map +1 -0
  28. package/dist/ota-doctor.js +53 -0
  29. package/dist/paths.d.ts +2 -0
  30. package/dist/paths.d.ts.map +1 -0
  31. package/dist/paths.js +12 -0
  32. package/dist/process-runner.d.ts +18 -0
  33. package/dist/process-runner.d.ts.map +1 -0
  34. package/dist/process-runner.js +97 -0
  35. package/dist/prompt.d.ts +3 -0
  36. package/dist/prompt.d.ts.map +1 -0
  37. package/dist/prompt.js +58 -0
  38. package/dist/r2.d.ts +32 -0
  39. package/dist/r2.d.ts.map +1 -0
  40. package/dist/r2.js +135 -0
  41. package/dist/remote.d.ts +33 -0
  42. package/dist/remote.d.ts.map +1 -0
  43. package/dist/remote.js +116 -0
  44. package/dist/runtime-fingerprint.d.ts +10 -0
  45. package/dist/runtime-fingerprint.d.ts.map +1 -0
  46. package/dist/runtime-fingerprint.js +238 -0
  47. package/dist/secure-store.d.ts +32 -0
  48. package/dist/secure-store.d.ts.map +1 -0
  49. package/dist/secure-store.js +263 -0
  50. package/dist/ui/colors.d.ts +24 -0
  51. package/dist/ui/colors.d.ts.map +1 -0
  52. package/dist/ui/colors.js +64 -0
  53. package/dist/ui/components.d.ts +36 -0
  54. package/dist/ui/components.d.ts.map +1 -0
  55. package/dist/ui/components.js +268 -0
  56. package/dist/ui/index.d.ts +24 -0
  57. package/dist/ui/index.d.ts.map +1 -0
  58. package/dist/ui/index.js +68 -0
  59. package/dist/ui/logo.d.ts +3 -0
  60. package/dist/ui/logo.d.ts.map +1 -0
  61. package/dist/ui/logo.js +32 -0
  62. package/dist/ui/state.d.ts +5 -0
  63. package/dist/ui/state.d.ts.map +1 -0
  64. package/dist/ui/state.js +7 -0
  65. package/dist/ui/terminal.d.ts +12 -0
  66. package/dist/ui/terminal.d.ts.map +1 -0
  67. package/dist/ui/terminal.js +23 -0
  68. package/package.json +75 -0
@@ -0,0 +1,13 @@
1
+ import { type BuildJob } from "@lynxship/contracts";
2
+ import type { BuildProfile } from "./config.js";
3
+ interface IosBuildOptions {
4
+ root: string;
5
+ profile: BuildProfile;
6
+ quiet?: boolean;
7
+ onEvent?: (message: string) => void;
8
+ onProgress?: (value?: number, label?: string) => void;
9
+ }
10
+ export declare function hasIosHost(root: string, profile?: BuildProfile): boolean;
11
+ export declare function runRealIosBuild(job: BuildJob, options: IosBuildOptions): Promise<BuildJob>;
12
+ export {};
13
+ //# sourceMappingURL=ios-build.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ios-build.d.ts","sourceRoot":"","sources":["../src/ios-build.ts"],"names":[],"mappings":"AAGA,OAAO,EAAkB,KAAK,QAAQ,EAAE,MAAM,qBAAqB,CAAC;AAEpE,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAShD,UAAU,eAAe;IACvB,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,YAAY,CAAC;IACtB,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,OAAO,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;IACpC,UAAU,CAAC,EAAE,CAAC,KAAK,CAAC,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,MAAM,KAAK,IAAI,CAAC;CACvD;AAED,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,YAAY,GAAG,OAAO,CAcxE;AA0BD,wBAAsB,eAAe,CACnC,GAAG,EAAE,QAAQ,EACb,OAAO,EAAE,eAAe,GACvB,OAAO,CAAC,QAAQ,CAAC,CAsLnB"}
@@ -0,0 +1,174 @@
1
+ import { copyFile, mkdir, readdir, readFile } from "node:fs/promises";
2
+ import { existsSync, readdirSync } from "node:fs";
3
+ import { join, resolve } from "node:path";
4
+ import { assert, sha256 } from "@lynxship/contracts";
5
+ import { transitionBuild } from "@lynxship/build-orchestrator";
6
+ import { loadR2, uploadR2Artifact } from "./r2.js";
7
+ import { nativeArtifactName } from "./artifact-name.js";
8
+ import { commandExists, packageManagerScriptCommand, runProcess, } from "./process-runner.js";
9
+ export function hasIosHost(root, profile) {
10
+ if (process.platform !== "darwin")
11
+ return false;
12
+ const configured = profile?.ios?.workspace ?? profile?.ios?.project;
13
+ if (configured && existsSync(resolve(root, configured)))
14
+ return true;
15
+ return ["ios", "macos"].some((directory) => {
16
+ try {
17
+ return readdirSync(join(root, directory)).some((name) => name.endsWith(".xcworkspace") || name.endsWith(".xcodeproj"));
18
+ }
19
+ catch {
20
+ return false;
21
+ }
22
+ });
23
+ }
24
+ function findProject(root, profile) {
25
+ const configured = profile.ios?.workspace ?? profile.ios?.project;
26
+ if (configured)
27
+ return resolve(root, configured);
28
+ for (const directory of ["ios", "macos"]) {
29
+ try {
30
+ const candidate = readdirSync(join(root, directory)).find((name) => name.endsWith(".xcworkspace") || name.endsWith(".xcodeproj"));
31
+ if (candidate)
32
+ return join(root, directory, candidate);
33
+ }
34
+ catch {
35
+ // The platform host is optional until an iOS/macOS build is requested.
36
+ }
37
+ }
38
+ throw new Error("No Xcode workspace or project found under ios/ or macos/");
39
+ }
40
+ async function findIpa(directory) {
41
+ const files = await readdir(directory, { withFileTypes: true });
42
+ const ipa = files.find((file) => file.isFile() && file.name.endsWith(".ipa"));
43
+ if (!ipa)
44
+ throw new Error(`xcodebuild exported no IPA in ${directory}`);
45
+ return join(directory, ipa.name);
46
+ }
47
+ export async function runRealIosBuild(job, options) {
48
+ assert(process.platform === "darwin", "IOS_MACOS_REQUIRED", "iOS builds require macOS with Xcode");
49
+ assert(commandExists("xcodebuild"), "IOS_XCODE_REQUIRED", "xcodebuild was not found. Install Xcode and select its command-line tools.");
50
+ assert(commandExists("xcrun"), "IOS_XCRUN_REQUIRED", "xcrun was not found. Install Xcode command-line tools.");
51
+ const project = findProject(options.root, options.profile);
52
+ assert(existsSync(project), "IOS_PROJECT_REQUIRED", `Configured Xcode project was not found: ${project}`);
53
+ const ios = options.profile.ios ?? {};
54
+ const scheme = ios.scheme;
55
+ assert(scheme, "IOS_SCHEME_REQUIRED", "Configure build.<profile>.ios.scheme in lynxship.json");
56
+ const exportOptions = ios.exportOptionsPlist;
57
+ assert(exportOptions, "IOS_EXPORT_OPTIONS_REQUIRED", "Configure build.<profile>.ios.exportOptionsPlist for a signed IPA export");
58
+ const exportOptionsPath = resolve(options.root, exportOptions);
59
+ assert(existsSync(exportOptionsPath), "IOS_EXPORT_OPTIONS_REQUIRED", `Export options file was not found: ${exportOptionsPath}`);
60
+ const projectFlag = project.endsWith(".xcworkspace")
61
+ ? "-workspace"
62
+ : "-project";
63
+ const configuration = ios.configuration ?? "Release";
64
+ const archiveDirectory = join(options.root, ".lynxship", "ios", job.id);
65
+ const archivePath = join(archiveDirectory, `${job.id}.xcarchive`);
66
+ const exportPath = join(archiveDirectory, "export");
67
+ const step = (message, progress) => {
68
+ options.onEvent?.(message);
69
+ options.onProgress?.(progress, message);
70
+ };
71
+ try {
72
+ await loadR2(options.root);
73
+ await mkdir(archiveDirectory, { recursive: true });
74
+ transitionBuild(job, "uploading_source", "iOS source prepared");
75
+ step("Building Lynx bundle with Rspeedy…", 5);
76
+ const packageManager = packageManagerScriptCommand(options.root, "build");
77
+ await runProcess(packageManager.command, packageManager.args, {
78
+ cwd: options.root,
79
+ quiet: options.quiet,
80
+ onOutput: options.onEvent,
81
+ });
82
+ if (ios.bundleScript) {
83
+ step("Syncing bundle into the iOS host…", 8);
84
+ await runProcess(process.execPath, [resolve(options.root, ios.bundleScript)], {
85
+ cwd: options.root,
86
+ quiet: options.quiet,
87
+ onOutput: options.onEvent,
88
+ });
89
+ }
90
+ step("Preparing Xcode archive…", 10);
91
+ await runProcess("xcodebuild", [
92
+ projectFlag,
93
+ project,
94
+ "-scheme",
95
+ scheme,
96
+ "-configuration",
97
+ configuration,
98
+ "-sdk",
99
+ "iphoneos",
100
+ "-archivePath",
101
+ archivePath,
102
+ "archive",
103
+ ], { cwd: options.root, quiet: options.quiet, onOutput: options.onEvent });
104
+ transitionBuild(job, "queued", "iOS build queued locally");
105
+ transitionBuild(job, "provisioning", "Xcode signing configuration selected");
106
+ step("Exporting signed IPA…", 55);
107
+ await runProcess("xcodebuild", [
108
+ "-exportArchive",
109
+ "-archivePath",
110
+ archivePath,
111
+ "-exportOptionsPlist",
112
+ exportOptionsPath,
113
+ "-exportPath",
114
+ exportPath,
115
+ ], { cwd: options.root, quiet: options.quiet, onOutput: options.onEvent });
116
+ transitionBuild(job, "installing_dependencies", "iOS archive exported");
117
+ transitionBuild(job, "building", "IPA package created");
118
+ const exportedIpa = await findIpa(exportPath);
119
+ transitionBuild(job, "signing", "Xcode signed the IPA");
120
+ step("Verifying IPA signature…", 75);
121
+ await verifyIpa(options.root, exportedIpa, archiveDirectory);
122
+ transitionBuild(job, "uploading_artifacts", "iOS artifact collected");
123
+ const artifactName = nativeArtifactName("ipa");
124
+ const artifactPath = join(options.root, ".lynxship", "artifacts", artifactName);
125
+ await mkdir(join(options.root, ".lynxship", "artifacts"), {
126
+ recursive: true,
127
+ });
128
+ await copyFile(exportedIpa, artifactPath);
129
+ const content = await readFile(artifactPath);
130
+ const hash = sha256(content);
131
+ const uploaded = await uploadR2Artifact(options.root, job.projectId, job.id, artifactPath, "application/octet-stream", undefined, {
132
+ onProgress: (uploadedBytes, totalBytes) => {
133
+ const transfer = totalBytes === 0 ? 1 : uploadedBytes / totalBytes;
134
+ options.onProgress?.(80 + transfer * 19, `Uploading signed artifact to Cloudflare R2… ${Math.round(transfer * 10000) / 100}%`);
135
+ },
136
+ });
137
+ assert(uploaded.hash === hash, "BUILD_ARTIFACT_HASH", "R2 artifact hash mismatch");
138
+ job.attempts += 1;
139
+ job.artifact = {
140
+ name: artifactName,
141
+ hash,
142
+ path: artifactPath,
143
+ key: uploaded.key,
144
+ size: uploaded.size,
145
+ contentType: uploaded.contentType,
146
+ url: uploaded.url,
147
+ expiresAt: uploaded.expiresAt,
148
+ };
149
+ step(`Artifact ready: ${artifactName}`, 100);
150
+ return transitionBuild(job, "success", "real iOS artifact created");
151
+ }
152
+ catch (error) {
153
+ if (!["success", "failed", "canceled", "timed_out"].includes(job.state))
154
+ transitionBuild(job, "failed", error instanceof Error ? error.message : "iOS build failed");
155
+ job.logs.push({
156
+ level: "error",
157
+ message: error instanceof Error ? error.message : "iOS build failed",
158
+ at: new Date().toISOString(),
159
+ });
160
+ throw error;
161
+ }
162
+ }
163
+ async function verifyIpa(root, ipa, directory) {
164
+ const extracted = join(directory, "verified-payload");
165
+ await mkdir(extracted, { recursive: true });
166
+ await runProcess("unzip", ["-q", ipa, "-d", extracted], {
167
+ cwd: root,
168
+ quiet: true,
169
+ });
170
+ const payload = join(extracted, "Payload");
171
+ const app = (await readdir(payload, { withFileTypes: true })).find((file) => file.isDirectory() && file.name.endsWith(".app"));
172
+ assert(app, "IOS_SIGNATURE_INVALID", "The exported IPA does not contain an app bundle");
173
+ await runProcess("codesign", ["--verify", "--deep", "--strict", join(payload, app.name)], { cwd: root, quiet: true });
174
+ }
@@ -0,0 +1,3 @@
1
+ export declare function otaAssetPaths(root: string, explicit: string | null): Promise<string[]>;
2
+ export declare function otaAssetName(root: string, file: string): string;
3
+ //# sourceMappingURL=ota-assets.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ota-assets.d.ts","sourceRoot":"","sources":["../src/ota-assets.ts"],"names":[],"mappings":"AA8BA,wBAAsB,aAAa,CACjC,IAAI,EAAE,MAAM,EACZ,QAAQ,EAAE,MAAM,GAAG,IAAI,GACtB,OAAO,CAAC,MAAM,EAAE,CAAC,CAYnB;AAED,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,CAM/D"}
@@ -0,0 +1,48 @@
1
+ import { readdir } from "node:fs/promises";
2
+ import { basename, extname, join, relative, resolve, sep } from "node:path";
3
+ function normalized(value) {
4
+ return value.split(sep).join("/");
5
+ }
6
+ async function discover(directory, result) {
7
+ let entries;
8
+ try {
9
+ entries = await readdir(directory, { withFileTypes: true });
10
+ }
11
+ catch {
12
+ return;
13
+ }
14
+ for (const entry of entries) {
15
+ if ([".rsbuild", ".rspeedy", "node_modules"].includes(entry.name))
16
+ continue;
17
+ const file = join(directory, entry.name);
18
+ if (entry.isDirectory()) {
19
+ await discover(file, result);
20
+ continue;
21
+ }
22
+ if (entry.isFile() &&
23
+ [".bundle"].includes(extname(entry.name)) &&
24
+ entry.name.includes("lynx"))
25
+ result.push(file);
26
+ }
27
+ }
28
+ export async function otaAssetPaths(root, explicit) {
29
+ if (explicit)
30
+ return explicit
31
+ .split(",")
32
+ .map((value) => value.trim())
33
+ .filter(Boolean)
34
+ .map((value) => resolve(root, value));
35
+ const dist = join(root, "dist");
36
+ const discovered = [];
37
+ await discover(dist, discovered);
38
+ if (discovered.length > 0)
39
+ return discovered.sort();
40
+ return [join(dist, "main.lynx.bundle")];
41
+ }
42
+ export function otaAssetName(root, file) {
43
+ const dist = resolve(root, "dist");
44
+ const relativePath = relative(dist, resolve(file));
45
+ if (!relativePath || relativePath.startsWith(`..${sep}`))
46
+ return basename(file);
47
+ return normalized(relativePath);
48
+ }
@@ -0,0 +1,10 @@
1
+ import { type Platform } from "@lynxship/contracts";
2
+ export interface OtaHostStatus {
3
+ platform: Platform;
4
+ detected: boolean;
5
+ files: string[];
6
+ missing: string[];
7
+ }
8
+ export declare function inspectOtaHost(root: string, platform: Platform): Promise<OtaHostStatus>;
9
+ export declare function requireOtaHost(root: string, platform: Platform): Promise<OtaHostStatus>;
10
+ //# sourceMappingURL=ota-doctor.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ota-doctor.d.ts","sourceRoot":"","sources":["../src/ota-doctor.ts"],"names":[],"mappings":"AAEA,OAAO,EAAU,KAAK,QAAQ,EAAE,MAAM,qBAAqB,CAAC;AAE5D,MAAM,WAAW,aAAa;IAC5B,QAAQ,EAAE,QAAQ,CAAC;IACnB,QAAQ,EAAE,OAAO,CAAC;IAClB,KAAK,EAAE,MAAM,EAAE,CAAC;IAChB,OAAO,EAAE,MAAM,EAAE,CAAC;CACnB;AAmCD,wBAAsB,cAAc,CAClC,IAAI,EAAE,MAAM,EACZ,QAAQ,EAAE,QAAQ,GACjB,OAAO,CAAC,aAAa,CAAC,CAkBxB;AAED,wBAAsB,cAAc,CAClC,IAAI,EAAE,MAAM,EACZ,QAAQ,EAAE,QAAQ,GACjB,OAAO,CAAC,aAAa,CAAC,CASxB"}
@@ -0,0 +1,53 @@
1
+ import { readdir, readFile } from "node:fs/promises";
2
+ import { join } from "node:path";
3
+ import { assert } from "@lynxship/contracts";
4
+ const sourceExtensions = new Set([".java", ".kt", ".swift", ".m", ".mm"]);
5
+ async function sourceFiles(root, platform) {
6
+ const files = [];
7
+ const directory = join(root, platform);
8
+ async function visit(current) {
9
+ let entries;
10
+ try {
11
+ entries = await readdir(current, { withFileTypes: true });
12
+ }
13
+ catch {
14
+ return;
15
+ }
16
+ for (const entry of entries) {
17
+ if (["build", "Pods", "DerivedData", ".gradle"].includes(entry.name))
18
+ continue;
19
+ const file = join(current, entry.name);
20
+ if (entry.isDirectory()) {
21
+ await visit(file);
22
+ continue;
23
+ }
24
+ const extension = entry.name.slice(entry.name.lastIndexOf("."));
25
+ if (entry.isFile() && sourceExtensions.has(extension))
26
+ files.push(file);
27
+ }
28
+ }
29
+ await visit(directory);
30
+ return files;
31
+ }
32
+ export async function inspectOtaHost(root, platform) {
33
+ const files = await sourceFiles(root, platform);
34
+ const contents = await Promise.all(files.map(async (file) => readFile(file, "utf8").catch(() => "")));
35
+ const text = contents.join("\n");
36
+ const required = [
37
+ "LynxShipOtaClient",
38
+ "beginLaunch",
39
+ "markLaunchSuccess",
40
+ "openActiveAsset",
41
+ ];
42
+ return {
43
+ platform,
44
+ detected: files.length > 0,
45
+ files,
46
+ missing: required.filter((value) => !text.includes(value)),
47
+ };
48
+ }
49
+ export async function requireOtaHost(root, platform) {
50
+ const status = await inspectOtaHost(root, platform);
51
+ assert(status.detected && status.missing.length === 0, "OTA_HOST_INTEGRATION_REQUIRED", `The ${platform} native host is not integrated with the LynxShip OTA client. Missing: ${status.missing.join(", ") || "native host source"}`, status);
52
+ return status;
53
+ }
@@ -0,0 +1,2 @@
1
+ export declare function globalLynxShipDirectory(): string;
2
+ //# sourceMappingURL=paths.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"paths.d.ts","sourceRoot":"","sources":["../src/paths.ts"],"names":[],"mappings":"AAGA,wBAAgB,uBAAuB,IAAI,MAAM,CAYhD"}
package/dist/paths.js ADDED
@@ -0,0 +1,12 @@
1
+ import { homedir } from "node:os";
2
+ import { join } from "node:path";
3
+ export function globalLynxShipDirectory() {
4
+ if (process.env.LYNXSHIP_CONFIG_DIR)
5
+ return process.env.LYNXSHIP_CONFIG_DIR;
6
+ const home = homedir();
7
+ if (process.platform === "win32")
8
+ return join(process.env.APPDATA ?? join(home, "AppData", "Roaming"), "LynxShip");
9
+ if (process.platform === "darwin")
10
+ return join(home, "Library", "Application Support", "LynxShip");
11
+ return join(process.env.XDG_CONFIG_HOME ?? join(home, ".config"), "lynxship");
12
+ }
@@ -0,0 +1,18 @@
1
+ export interface ProcessOptions {
2
+ cwd: string;
3
+ env?: NodeJS.ProcessEnv;
4
+ quiet?: boolean;
5
+ onOutput?: (line: string) => void;
6
+ }
7
+ export declare function packageManagerCommand(root: string): {
8
+ command: string;
9
+ prefix: string[];
10
+ };
11
+ export declare function packageManagerScriptCommand(root: string, script: string, args?: string[]): {
12
+ command: string;
13
+ args: string[];
14
+ };
15
+ export declare function runProcess(command: string, args: string[], options: ProcessOptions): Promise<void>;
16
+ export declare function runRspeedy(root: string, subcommand: string, args: string[], options: Omit<ProcessOptions, "cwd">): Promise<void>;
17
+ export declare function commandExists(command: string): boolean;
18
+ //# sourceMappingURL=process-runner.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"process-runner.d.ts","sourceRoot":"","sources":["../src/process-runner.ts"],"names":[],"mappings":"AAKA,MAAM,WAAW,cAAc;IAC7B,GAAG,EAAE,MAAM,CAAC;IACZ,GAAG,CAAC,EAAE,MAAM,CAAC,UAAU,CAAC;IACxB,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,QAAQ,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;CACnC;AAED,wBAAgB,qBAAqB,CAAC,IAAI,EAAE,MAAM,GAAG;IACnD,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,EAAE,CAAC;CAClB,CAcA;AAED,wBAAgB,2BAA2B,CACzC,IAAI,EAAE,MAAM,EACZ,MAAM,EAAE,MAAM,EACd,IAAI,GAAE,MAAM,EAAO,GAClB;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,EAAE,CAAA;CAAE,CAGrC;AAsBD,wBAAgB,UAAU,CACxB,OAAO,EAAE,MAAM,EACf,IAAI,EAAE,MAAM,EAAE,EACd,OAAO,EAAE,cAAc,GACtB,OAAO,CAAC,IAAI,CAAC,CA8Cf;AAED,wBAAsB,UAAU,CAC9B,IAAI,EAAE,MAAM,EACZ,UAAU,EAAE,MAAM,EAClB,IAAI,EAAE,MAAM,EAAE,EACd,OAAO,EAAE,IAAI,CAAC,cAAc,EAAE,KAAK,CAAC,GACnC,OAAO,CAAC,IAAI,CAAC,CAOf;AAED,wBAAgB,aAAa,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAWtD"}
@@ -0,0 +1,97 @@
1
+ import { execFileSync } from "node:child_process";
2
+ import { spawn } from "node:child_process";
3
+ import { existsSync, readFileSync } from "node:fs";
4
+ import { join } from "node:path";
5
+ export function packageManagerCommand(root) {
6
+ const configured = process.env.LYNXSHIP_PACKAGE_MANAGER ?? process.env.LYNXSHIP_PNPM_PATH;
7
+ const packageManager = configured ?? detectPackageManager(root);
8
+ const command = process.platform === "win32" && !/\.(?:cmd|bat|exe)$/i.test(packageManager)
9
+ ? `${packageManager}.cmd`
10
+ : packageManager;
11
+ const normalized = packageManager.split(/[\\/]/).at(-1)?.toLowerCase();
12
+ return normalized === "yarn" || normalized === "yarn.cmd"
13
+ ? { command, prefix: [] }
14
+ : normalized === "npm" || normalized === "npm.cmd"
15
+ ? { command, prefix: ["exec", "--"] }
16
+ : { command, prefix: ["exec"] };
17
+ }
18
+ export function packageManagerScriptCommand(root, script, args = []) {
19
+ const manager = packageManagerCommand(root);
20
+ return { command: manager.command, args: ["run", script, ...args] };
21
+ }
22
+ function detectPackageManager(root) {
23
+ try {
24
+ const packageJson = JSON.parse(readFileSync(join(root, "package.json"), "utf8"));
25
+ if (packageJson.packageManager?.startsWith("npm@"))
26
+ return "npm";
27
+ if (packageJson.packageManager?.startsWith("yarn@"))
28
+ return "yarn";
29
+ if (packageJson.packageManager?.startsWith("pnpm@"))
30
+ return "pnpm";
31
+ }
32
+ catch {
33
+ // Lockfiles below are the next reliable signal.
34
+ }
35
+ try {
36
+ if (existsSync(join(root, "package-lock.json")))
37
+ return "npm";
38
+ if (existsSync(join(root, "yarn.lock")))
39
+ return "yarn";
40
+ }
41
+ catch {
42
+ // Default to pnpm for this workspace.
43
+ }
44
+ return "pnpm";
45
+ }
46
+ export function runProcess(command, args, options) {
47
+ const windowsCommand = process.platform === "win32" && /\.(?:bat|cmd)$/i.test(command);
48
+ const commandLine = [command, ...args]
49
+ .map((value) => /[\s"]/.test(value) ? `"${value.replaceAll('"', '\\"')}"` : value)
50
+ .join(" ");
51
+ return new Promise((resolve, reject) => {
52
+ const child = spawn(windowsCommand ? "cmd.exe" : command, windowsCommand ? ["/d", "/s", "/c", commandLine] : args, {
53
+ cwd: options.cwd,
54
+ env: options.env ?? process.env,
55
+ shell: false,
56
+ stdio: ["ignore", "pipe", "pipe"],
57
+ windowsHide: true,
58
+ });
59
+ let pending = "";
60
+ let output = "";
61
+ const forward = (chunk) => {
62
+ output += chunk.toString();
63
+ pending += chunk.toString().replace(/\r(?!\n)/g, "\n");
64
+ const lines = pending.split(/\r?\n/);
65
+ pending = lines.pop() ?? "";
66
+ for (const line of lines) {
67
+ const clean = line.replace(/\u001b\[[0-?]*[ -/]*[@-~]/g, "").trim();
68
+ if (clean && !options.quiet)
69
+ options.onOutput?.(clean);
70
+ }
71
+ };
72
+ child.stdout?.on("data", forward);
73
+ child.stderr?.on("data", forward);
74
+ child.once("error", reject);
75
+ child.once("exit", (code, signal) => {
76
+ if (pending.trim() && !options.quiet)
77
+ options.onOutput?.(pending.trim());
78
+ if (code === 0)
79
+ return resolve();
80
+ const lastLine = output.trim().split(/\r?\n/).at(-1) ?? "";
81
+ reject(new Error(`${command} ${args.join(" ")} failed (${signal ?? `exit ${code}`})${lastLine ? `: ${lastLine}` : ""}`));
82
+ });
83
+ });
84
+ }
85
+ export async function runRspeedy(root, subcommand, args, options) {
86
+ const manager = packageManagerCommand(root);
87
+ await runProcess(manager.command, [...manager.prefix, "rspeedy", subcommand, ...args], { ...options, cwd: root });
88
+ }
89
+ export function commandExists(command) {
90
+ try {
91
+ execFileSync(process.platform === "win32" ? "where.exe" : "which", [command], { stdio: "ignore" });
92
+ return true;
93
+ }
94
+ catch {
95
+ return false;
96
+ }
97
+ }
@@ -0,0 +1,3 @@
1
+ export declare function prompt(label: string, defaultValue?: string): Promise<string>;
2
+ export declare function secret(label: string): Promise<string>;
3
+ //# sourceMappingURL=prompt.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"prompt.d.ts","sourceRoot":"","sources":["../src/prompt.ts"],"names":[],"mappings":"AAGA,wBAAsB,MAAM,CAC1B,KAAK,EAAE,MAAM,EACb,YAAY,CAAC,EAAE,MAAM,GACpB,OAAO,CAAC,MAAM,CAAC,CAYjB;AAED,wBAAsB,MAAM,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CA2C3D"}
package/dist/prompt.js ADDED
@@ -0,0 +1,58 @@
1
+ import { createInterface } from "node:readline/promises";
2
+ import { c } from "./ui/state.js";
3
+ export async function prompt(label, defaultValue) {
4
+ const readline = createInterface({
5
+ input: process.stdin,
6
+ output: process.stdout,
7
+ });
8
+ try {
9
+ const suffix = defaultValue ? ` [${defaultValue}]` : "";
10
+ const value = await readline.question(`${c.brand(label)}${suffix}: `);
11
+ return value.trim() || defaultValue || "";
12
+ }
13
+ finally {
14
+ readline.close();
15
+ }
16
+ }
17
+ export async function secret(label) {
18
+ if (!process.stdin.isTTY || !process.stdin.setRawMode)
19
+ throw new Error("Secret input requires an interactive terminal; use environment variables in CI");
20
+ process.stdout.write(`${c.brand(label)}: `);
21
+ const input = process.stdin;
22
+ const previousEncoding = input.readableEncoding;
23
+ input.setRawMode(true);
24
+ input.setEncoding("utf8");
25
+ input.resume();
26
+ return new Promise((resolve, reject) => {
27
+ let value = "";
28
+ const cleanup = () => {
29
+ input.setRawMode?.(false);
30
+ input.pause();
31
+ input.removeListener("data", onData);
32
+ if (previousEncoding)
33
+ input.setEncoding(previousEncoding);
34
+ process.stdout.write("\n");
35
+ };
36
+ const onData = (chunk) => {
37
+ for (const character of chunk) {
38
+ if (character === "\u0003") {
39
+ cleanup();
40
+ reject(new Error("Input cancelled"));
41
+ return;
42
+ }
43
+ if (character === "\r" || character === "\n") {
44
+ cleanup();
45
+ resolve(value);
46
+ return;
47
+ }
48
+ if (character === "\u007f" || character === "\b") {
49
+ value = value.slice(0, -1);
50
+ continue;
51
+ }
52
+ if (character >= " ")
53
+ value += character;
54
+ }
55
+ };
56
+ input.on("data", onData);
57
+ });
58
+ }
package/dist/r2.d.ts ADDED
@@ -0,0 +1,32 @@
1
+ import { S3Client } from "@aws-sdk/client-s3";
2
+ import { type StoredCredentials } from "./secure-store.js";
3
+ export interface R2Config {
4
+ accountId: string;
5
+ bucket: string;
6
+ endpoint: string;
7
+ expiresIn: number;
8
+ }
9
+ export interface R2Artifact {
10
+ key: string;
11
+ hash: string;
12
+ size: number;
13
+ contentType: string;
14
+ url: string;
15
+ expiresAt: string;
16
+ }
17
+ export interface R2UploadOptions {
18
+ onProgress?: (uploadedBytes: number, totalBytes: number) => void;
19
+ }
20
+ declare function defaultEndpoint(accountId: string): string;
21
+ export declare function loadR2(root: string): Promise<{
22
+ config: R2Config;
23
+ credentials: NonNullable<StoredCredentials["r2"]>;
24
+ }>;
25
+ export declare function createR2Client(config: R2Config, credentials: NonNullable<StoredCredentials["r2"]>): S3Client;
26
+ export declare function verifyR2(config: R2Config, credentials: NonNullable<StoredCredentials["r2"]>): Promise<void>;
27
+ export declare function uploadR2Artifact(root: string, projectId: string, buildId: string, file: string, contentType: string, objectName?: string, options?: R2UploadOptions): Promise<R2Artifact>;
28
+ export declare function writeR2Config(root: string, config: R2Config, options?: {
29
+ global?: boolean;
30
+ }): Promise<void>;
31
+ export { defaultEndpoint };
32
+ //# sourceMappingURL=r2.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"r2.d.ts","sourceRoot":"","sources":["../src/r2.ts"],"names":[],"mappings":"AAEA,OAAO,EAGL,QAAQ,EACT,MAAM,oBAAoB,CAAC;AAI5B,OAAO,EAAmB,KAAK,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AAG5E,MAAM,WAAW,QAAQ;IACvB,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,UAAU;IACzB,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,GAAG,EAAE,MAAM,CAAC;IACZ,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,eAAe;IAC9B,UAAU,CAAC,EAAE,CAAC,aAAa,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,KAAK,IAAI,CAAC;CAClE;AAYD,iBAAS,eAAe,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,CAElD;AA0BD,wBAAsB,MAAM,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC;IAClD,MAAM,EAAE,QAAQ,CAAC;IACjB,WAAW,EAAE,WAAW,CAAC,iBAAiB,CAAC,IAAI,CAAC,CAAC,CAAC;CACnD,CAAC,CA0CD;AAED,wBAAgB,cAAc,CAC5B,MAAM,EAAE,QAAQ,EAChB,WAAW,EAAE,WAAW,CAAC,iBAAiB,CAAC,IAAI,CAAC,CAAC,GAChD,QAAQ,CAMV;AAED,wBAAsB,QAAQ,CAC5B,MAAM,EAAE,QAAQ,EAChB,WAAW,EAAE,WAAW,CAAC,iBAAiB,CAAC,IAAI,CAAC,CAAC,GAChD,OAAO,CAAC,IAAI,CAAC,CAOf;AAED,wBAAsB,gBAAgB,CACpC,IAAI,EAAE,MAAM,EACZ,SAAS,EAAE,MAAM,EACjB,OAAO,EAAE,MAAM,EACf,IAAI,EAAE,MAAM,EACZ,WAAW,EAAE,MAAM,EACnB,UAAU,CAAC,EAAE,MAAM,EACnB,OAAO,GAAE,eAAoB,GAC5B,OAAO,CAAC,UAAU,CAAC,CAkDrB;AAED,wBAAsB,aAAa,CACjC,IAAI,EAAE,MAAM,EACZ,MAAM,EAAE,QAAQ,EAChB,OAAO,GAAE;IAAE,MAAM,CAAC,EAAE,OAAO,CAAA;CAAO,GACjC,OAAO,CAAC,IAAI,CAAC,CAYf;AAED,OAAO,EAAE,eAAe,EAAE,CAAC"}