@lynxship/cli 0.1.4 → 0.1.7
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/README.md +90 -2
- package/dist/android-build.d.ts +1 -0
- package/dist/android-build.d.ts.map +1 -1
- package/dist/android-build.js +12 -19
- package/dist/android-host.d.ts +12 -0
- package/dist/android-host.d.ts.map +1 -0
- package/dist/android-host.js +70 -0
- package/dist/bundle-build.d.ts +7 -0
- package/dist/bundle-build.d.ts.map +1 -0
- package/dist/bundle-build.js +24 -0
- package/dist/guidance.d.ts +6 -0
- package/dist/guidance.d.ts.map +1 -0
- package/dist/guidance.js +354 -0
- package/dist/index.js +349 -93
- package/dist/ios-build.d.ts +1 -0
- package/dist/ios-build.d.ts.map +1 -1
- package/dist/ios-build.js +38 -12
- package/dist/ios-host.d.ts +17 -0
- package/dist/ios-host.d.ts.map +1 -0
- package/dist/ios-host.js +93 -0
- package/dist/ui/components.d.ts +2 -0
- package/dist/ui/components.d.ts.map +1 -1
- package/dist/ui/components.js +30 -16
- package/dist/ui/index.d.ts +3 -1
- package/dist/ui/index.d.ts.map +1 -1
- package/dist/ui/index.js +6 -1
- package/package.json +5 -4
- package/templates/android-host/app/build.gradle +38 -0
- package/templates/android-host/app/proguard-rules.pro +2 -0
- package/templates/android-host/app/src/main/AndroidManifest.xml +20 -0
- package/templates/android-host/app/src/main/java/template/LynxShipApplication.java +13 -0
- package/templates/android-host/app/src/main/java/template/MainActivity.java +19 -0
- package/templates/android-host/app/src/main/java/template/ProjectTemplateProvider.java +32 -0
- package/templates/android-host/app/src/main/res/values/strings.xml +3 -0
- package/templates/android-host/app/src/main/res/values/themes.xml +6 -0
- package/templates/android-host/build.gradle +3 -0
- package/templates/android-host/gradle/wrapper/gradle-wrapper.jar +0 -0
- package/templates/android-host/gradle/wrapper/gradle-wrapper.properties +7 -0
- package/templates/android-host/gradle.properties +3 -0
- package/templates/android-host/gradlew +185 -0
- package/templates/android-host/gradlew.bat +89 -0
- package/templates/android-host/settings.gradle +20 -0
- package/templates/ios-host/ExportOptions.plist +16 -0
- package/templates/ios-host/Podfile +42 -0
- package/templates/ios-host/__IOS_TARGET_NAME__/AppDelegate.swift +11 -0
- package/templates/ios-host/__IOS_TARGET_NAME__/Assets.xcassets/AccentColor.colorset/Contents.json +11 -0
- package/templates/ios-host/__IOS_TARGET_NAME__/Assets.xcassets/AppIcon.appiconset/Contents.json +13 -0
- package/templates/ios-host/__IOS_TARGET_NAME__/Assets.xcassets/Contents.json +6 -0
- package/templates/ios-host/__IOS_TARGET_NAME__/Base.lproj/LaunchScreen.storyboard +25 -0
- package/templates/ios-host/__IOS_TARGET_NAME__/Base.lproj/Main.storyboard +24 -0
- package/templates/ios-host/__IOS_TARGET_NAME__/DemoLynxProvider.swift +18 -0
- package/templates/ios-host/__IOS_TARGET_NAME__/Hello-Lynx-Bridging-Header.h +4 -0
- package/templates/ios-host/__IOS_TARGET_NAME__/Info.plist +25 -0
- package/templates/ios-host/__IOS_TARGET_NAME__/SceneDelegate.swift +49 -0
- package/templates/ios-host/__IOS_TARGET_NAME__/ViewController.swift +22 -0
- package/templates/ios-host/__IOS_TARGET_NAME__.xcodeproj/project.pbxproj +452 -0
- package/templates/ios-host/sync-bundle.mjs +11 -0
package/dist/ios-build.js
CHANGED
|
@@ -5,7 +5,8 @@ import { assert, sha256 } from "@lynxship/contracts";
|
|
|
5
5
|
import { transitionBuild } from "@lynxship/build-orchestrator";
|
|
6
6
|
import { loadR2, uploadR2Artifact } from "./r2.js";
|
|
7
7
|
import { nativeArtifactName } from "./artifact-name.js";
|
|
8
|
-
import { commandExists,
|
|
8
|
+
import { commandExists, runProcess } from "./process-runner.js";
|
|
9
|
+
import { buildLynxBundle } from "./bundle-build.js";
|
|
9
10
|
export function hasIosHost(root, profile) {
|
|
10
11
|
if (process.platform !== "darwin")
|
|
11
12
|
return false;
|
|
@@ -23,8 +24,15 @@ export function hasIosHost(root, profile) {
|
|
|
23
24
|
}
|
|
24
25
|
function findProject(root, profile) {
|
|
25
26
|
const configured = profile.ios?.workspace ?? profile.ios?.project;
|
|
26
|
-
if (configured)
|
|
27
|
-
|
|
27
|
+
if (configured) {
|
|
28
|
+
const configuredPath = resolve(root, configured);
|
|
29
|
+
if (configuredPath.endsWith(".xcodeproj")) {
|
|
30
|
+
const workspace = configuredPath.replace(/\.xcodeproj$/, ".xcworkspace");
|
|
31
|
+
if (existsSync(workspace))
|
|
32
|
+
return workspace;
|
|
33
|
+
}
|
|
34
|
+
return configuredPath;
|
|
35
|
+
}
|
|
28
36
|
for (const directory of ["ios", "macos"]) {
|
|
29
37
|
try {
|
|
30
38
|
const candidate = readdirSync(join(root, directory)).find((name) => name.endsWith(".xcworkspace") || name.endsWith(".xcodeproj"));
|
|
@@ -37,6 +45,19 @@ function findProject(root, profile) {
|
|
|
37
45
|
}
|
|
38
46
|
throw new Error("No Xcode workspace or project found under ios/ or macos/");
|
|
39
47
|
}
|
|
48
|
+
async function installCocoaPods(root, quiet, onEvent) {
|
|
49
|
+
const iosDirectory = join(root, "ios");
|
|
50
|
+
const podfile = join(iosDirectory, "Podfile");
|
|
51
|
+
if (!existsSync(podfile))
|
|
52
|
+
return;
|
|
53
|
+
assert(commandExists("pod"), "IOS_COCOAPODS_REQUIRED", "CocoaPods was not found. Install CocoaPods on macOS, then rerun the build.");
|
|
54
|
+
onEvent?.("Installing iOS CocoaPods dependencies…");
|
|
55
|
+
await runProcess("pod", ["install"], {
|
|
56
|
+
cwd: iosDirectory,
|
|
57
|
+
quiet,
|
|
58
|
+
onOutput: onEvent,
|
|
59
|
+
});
|
|
60
|
+
}
|
|
40
61
|
async function findIpa(directory) {
|
|
41
62
|
const files = await readdir(directory, { withFileTypes: true });
|
|
42
63
|
const ipa = files.find((file) => file.isFile() && file.name.endsWith(".ipa"));
|
|
@@ -50,6 +71,8 @@ export async function runRealIosBuild(job, options) {
|
|
|
50
71
|
assert(commandExists("xcrun"), "IOS_XCRUN_REQUIRED", "xcrun was not found. Install Xcode command-line tools.");
|
|
51
72
|
const project = findProject(options.root, options.profile);
|
|
52
73
|
assert(existsSync(project), "IOS_PROJECT_REQUIRED", `Configured Xcode project was not found: ${project}`);
|
|
74
|
+
await installCocoaPods(options.root, options.quiet, options.onEvent);
|
|
75
|
+
const resolvedProject = findProject(options.root, options.profile);
|
|
53
76
|
const ios = options.profile.ios ?? {};
|
|
54
77
|
const scheme = ios.scheme;
|
|
55
78
|
assert(scheme, "IOS_SCHEME_REQUIRED", "Configure build.<profile>.ios.scheme in lynxship.json");
|
|
@@ -57,7 +80,7 @@ export async function runRealIosBuild(job, options) {
|
|
|
57
80
|
assert(exportOptions, "IOS_EXPORT_OPTIONS_REQUIRED", "Configure build.<profile>.ios.exportOptionsPlist for a signed IPA export");
|
|
58
81
|
const exportOptionsPath = resolve(options.root, exportOptions);
|
|
59
82
|
assert(existsSync(exportOptionsPath), "IOS_EXPORT_OPTIONS_REQUIRED", `Export options file was not found: ${exportOptionsPath}`);
|
|
60
|
-
const projectFlag =
|
|
83
|
+
const projectFlag = resolvedProject.endsWith(".xcworkspace")
|
|
61
84
|
? "-workspace"
|
|
62
85
|
: "-project";
|
|
63
86
|
const configuration = ios.configuration ?? "Release";
|
|
@@ -74,13 +97,16 @@ export async function runRealIosBuild(job, options) {
|
|
|
74
97
|
await loadR2(options.root);
|
|
75
98
|
await mkdir(archiveDirectory, { recursive: true });
|
|
76
99
|
transitionBuild(job, "uploading_source", "iOS source prepared");
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
100
|
+
if (options.skipBundleBuild) {
|
|
101
|
+
step("Using shared Lynx bundle", 5);
|
|
102
|
+
}
|
|
103
|
+
else {
|
|
104
|
+
step("Building Lynx bundle with Rspeedy…", 5);
|
|
105
|
+
await buildLynxBundle(options.root, {
|
|
106
|
+
quiet: options.quiet,
|
|
107
|
+
onOutput: options.onEvent,
|
|
108
|
+
});
|
|
109
|
+
}
|
|
84
110
|
if (ios.bundleScript) {
|
|
85
111
|
step("Syncing bundle into the iOS host…", 8);
|
|
86
112
|
await runProcess(process.execPath, [resolve(options.root, ios.bundleScript)], {
|
|
@@ -92,7 +118,7 @@ export async function runRealIosBuild(job, options) {
|
|
|
92
118
|
step("Preparing Xcode archive…", 10);
|
|
93
119
|
await runProcess("xcodebuild", [
|
|
94
120
|
projectFlag,
|
|
95
|
-
|
|
121
|
+
resolvedProject,
|
|
96
122
|
"-scheme",
|
|
97
123
|
scheme,
|
|
98
124
|
"-configuration",
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export interface IosHostOptions {
|
|
2
|
+
bundleIdentifier: string;
|
|
3
|
+
appName: string;
|
|
4
|
+
}
|
|
5
|
+
export interface IosHostResult {
|
|
6
|
+
directory: string;
|
|
7
|
+
targetName: string;
|
|
8
|
+
bundleIdentifier: string;
|
|
9
|
+
project: string;
|
|
10
|
+
scheme: string;
|
|
11
|
+
exportOptionsPlist: string;
|
|
12
|
+
bundleScript: string;
|
|
13
|
+
configUpdated: boolean;
|
|
14
|
+
}
|
|
15
|
+
export declare function initializeIosHost(root: string, options: IosHostOptions): Promise<IosHostResult>;
|
|
16
|
+
export declare function suggestedIosBundleIdentifier(root: string): string;
|
|
17
|
+
//# sourceMappingURL=ios-host.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ios-host.d.ts","sourceRoot":"","sources":["../src/ios-host.ts"],"names":[],"mappings":"AAiBA,MAAM,WAAW,cAAc;IAC7B,gBAAgB,EAAE,MAAM,CAAC;IACzB,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,aAAa;IAC5B,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;IACnB,gBAAgB,EAAE,MAAM,CAAC;IACzB,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;IACf,kBAAkB,EAAE,MAAM,CAAC;IAC3B,YAAY,EAAE,MAAM,CAAC;IACrB,aAAa,EAAE,OAAO,CAAC;CACxB;AAoED,wBAAsB,iBAAiB,CACrC,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,cAAc,GACtB,OAAO,CAAC,aAAa,CAAC,CA0CxB;AAED,wBAAgB,4BAA4B,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAKjE"}
|
package/dist/ios-host.js
ADDED
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import { access, cp, readdir, readFile, rename, writeFile, } from "node:fs/promises";
|
|
2
|
+
import { fileURLToPath } from "node:url";
|
|
3
|
+
import { basename, join } from "node:path";
|
|
4
|
+
import { assert } from "@lynxship/contracts";
|
|
5
|
+
const templateRoot = fileURLToPath(new URL("../templates/ios-host/", import.meta.url));
|
|
6
|
+
function validateBundleIdentifier(bundleIdentifier) {
|
|
7
|
+
assert(/^[a-zA-Z][a-zA-Z0-9-]*(\.[a-zA-Z][a-zA-Z0-9-]*)+$/.test(bundleIdentifier), "IOS_BUNDLE_IDENTIFIER_INVALID", "iOS bundle identifier must contain at least two dot-separated segments, for example com.example.myapp.");
|
|
8
|
+
}
|
|
9
|
+
async function exists(path) {
|
|
10
|
+
try {
|
|
11
|
+
await access(path);
|
|
12
|
+
return true;
|
|
13
|
+
}
|
|
14
|
+
catch {
|
|
15
|
+
return false;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
function targetName(value) {
|
|
19
|
+
const words = value.match(/[a-zA-Z0-9]+/g) ?? [];
|
|
20
|
+
const result = words
|
|
21
|
+
.map((word) => `${word[0]?.toUpperCase() ?? ""}${word.slice(1)}`)
|
|
22
|
+
.join("");
|
|
23
|
+
if (result && /^[A-Za-z]/.test(result))
|
|
24
|
+
return result;
|
|
25
|
+
return `LynxShip${result || "App"}`;
|
|
26
|
+
}
|
|
27
|
+
async function textFiles(directory) {
|
|
28
|
+
const entries = await readdir(directory, { withFileTypes: true });
|
|
29
|
+
const files = [];
|
|
30
|
+
for (const entry of entries) {
|
|
31
|
+
const path = join(directory, entry.name);
|
|
32
|
+
if (entry.isDirectory())
|
|
33
|
+
files.push(...(await textFiles(path)));
|
|
34
|
+
else if ([".h", ".m", ".plist", ".pbxproj", ".swift", ".rb", ".mjs"].some((extension) => entry.name.endsWith(extension)) ||
|
|
35
|
+
entry.name === "Podfile")
|
|
36
|
+
files.push(path);
|
|
37
|
+
}
|
|
38
|
+
return files;
|
|
39
|
+
}
|
|
40
|
+
async function updateProjectConfig(root, result) {
|
|
41
|
+
const configPath = join(root, "lynxship.json");
|
|
42
|
+
if (!(await exists(configPath)))
|
|
43
|
+
return false;
|
|
44
|
+
const config = JSON.parse(await readFile(configPath, "utf8"));
|
|
45
|
+
config.build ??= {};
|
|
46
|
+
config.build.production ??= {};
|
|
47
|
+
config.build.production.ios = {
|
|
48
|
+
...config.build.production.ios,
|
|
49
|
+
project: `ios/${result.targetName}.xcodeproj`,
|
|
50
|
+
scheme: result.scheme,
|
|
51
|
+
configuration: "Release",
|
|
52
|
+
exportOptionsPlist: "ios/ExportOptions.plist",
|
|
53
|
+
bundleScript: "ios/sync-bundle.mjs",
|
|
54
|
+
};
|
|
55
|
+
await writeFile(configPath, `${JSON.stringify(config, null, 2)}\n`, "utf8");
|
|
56
|
+
return true;
|
|
57
|
+
}
|
|
58
|
+
export async function initializeIosHost(root, options) {
|
|
59
|
+
validateBundleIdentifier(options.bundleIdentifier);
|
|
60
|
+
const ios = join(root, "ios");
|
|
61
|
+
assert(!(await exists(ios)), "IOS_HOST_EXISTS", `The iOS host already exists at ${ios}. LynxShip will not overwrite it.`);
|
|
62
|
+
const scheme = targetName(options.appName);
|
|
63
|
+
await cp(templateRoot, ios, { recursive: true, force: false });
|
|
64
|
+
await rename(join(ios, "__IOS_TARGET_NAME__"), join(ios, scheme));
|
|
65
|
+
await rename(join(ios, "__IOS_TARGET_NAME__.xcodeproj"), join(ios, `${scheme}.xcodeproj`));
|
|
66
|
+
await rename(join(ios, scheme, "Hello-Lynx-Bridging-Header.h"), join(ios, scheme, `${scheme}-Bridging-Header.h`));
|
|
67
|
+
const files = await textFiles(ios);
|
|
68
|
+
for (const file of files) {
|
|
69
|
+
let content = await readFile(file, "utf8");
|
|
70
|
+
content = content
|
|
71
|
+
.replaceAll("test.Hello-Lynx", options.bundleIdentifier)
|
|
72
|
+
.replaceAll("Hello-Lynx", scheme);
|
|
73
|
+
await writeFile(file, content, "utf8");
|
|
74
|
+
}
|
|
75
|
+
const project = `ios/${scheme}.xcodeproj`;
|
|
76
|
+
const result = {
|
|
77
|
+
directory: ios,
|
|
78
|
+
targetName: scheme,
|
|
79
|
+
bundleIdentifier: options.bundleIdentifier,
|
|
80
|
+
project,
|
|
81
|
+
scheme,
|
|
82
|
+
exportOptionsPlist: "ios/ExportOptions.plist",
|
|
83
|
+
bundleScript: "ios/sync-bundle.mjs",
|
|
84
|
+
};
|
|
85
|
+
const configUpdated = await updateProjectConfig(root, result);
|
|
86
|
+
return { ...result, configUpdated };
|
|
87
|
+
}
|
|
88
|
+
export function suggestedIosBundleIdentifier(root) {
|
|
89
|
+
const project = basename(root)
|
|
90
|
+
.toLowerCase()
|
|
91
|
+
.replace(/[^a-z0-9]+/g, "");
|
|
92
|
+
return `com.example.${project || "lynxapp"}`;
|
|
93
|
+
}
|
package/dist/ui/components.d.ts
CHANGED
|
@@ -18,6 +18,7 @@ export interface ProgressHandle {
|
|
|
18
18
|
update(value?: number, label?: string): void;
|
|
19
19
|
stop(): void;
|
|
20
20
|
}
|
|
21
|
+
export declare function formatEvent(message: string): string;
|
|
21
22
|
export declare function createProgress(label: string, enabled: boolean): ProgressHandle;
|
|
22
23
|
export interface SpinnerHandle {
|
|
23
24
|
succeed(message: string): void;
|
|
@@ -32,5 +33,6 @@ export declare const pill: {
|
|
|
32
33
|
error: (text: string) => string;
|
|
33
34
|
};
|
|
34
35
|
export declare function finalLine(message: string, success?: boolean): void;
|
|
36
|
+
export declare function nextSteps(commands: string[], note?: string): void;
|
|
35
37
|
export declare function downloadArtifact(url: string, expiresAt?: string, enabled?: boolean): void;
|
|
36
38
|
//# sourceMappingURL=components.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"components.d.ts","sourceRoot":"","sources":["../../src/ui/components.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAI7C,MAAM,WAAW,MAAM;IACrB,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,UAAU,CAAC,EAAE,SAAS,CAAC;CACxB;AAED,wBAAgB,aAAa,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAEjD;AAED,eAAO,MAAM,GAAG;qBACG,MAAM,GAAG,IAAI;kBAGhB,MAAM,GAAG,IAAI;mBAGZ,MAAM,GAAG,IAAI;kBAGd,MAAM,GAAG,IAAI;mBAGZ,MAAM,GAAG,IAAI;CAG7B,CAAC;AAgBF,wBAAgB,UAAU,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,GAAG,IAAI,CA6C9D;AAED,MAAM,WAAW,cAAc;IAC7B,KAAK,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,MAAM,CAAC,KAAK,CAAC,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7C,IAAI,IAAI,IAAI,CAAC;CACd;
|
|
1
|
+
{"version":3,"file":"components.d.ts","sourceRoot":"","sources":["../../src/ui/components.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAI7C,MAAM,WAAW,MAAM;IACrB,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,UAAU,CAAC,EAAE,SAAS,CAAC;CACxB;AAED,wBAAgB,aAAa,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAEjD;AAED,eAAO,MAAM,GAAG;qBACG,MAAM,GAAG,IAAI;kBAGhB,MAAM,GAAG,IAAI;mBAGZ,MAAM,GAAG,IAAI;kBAGd,MAAM,GAAG,IAAI;mBAGZ,MAAM,GAAG,IAAI;CAG7B,CAAC;AAgBF,wBAAgB,UAAU,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,GAAG,IAAI,CA6C9D;AAED,MAAM,WAAW,cAAc;IAC7B,KAAK,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,MAAM,CAAC,KAAK,CAAC,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7C,IAAI,IAAI,IAAI,CAAC;CACd;AA6CD,wBAAgB,WAAW,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAoBnD;AAyBD,wBAAgB,cAAc,CAC5B,KAAK,EAAE,MAAM,EACb,OAAO,EAAE,OAAO,GACf,cAAc,CAkEhB;AAED,MAAM,WAAW,aAAa;IAC5B,OAAO,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IAC/B,IAAI,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,IAAI,IAAI,IAAI,CAAC;CACd;AAED,wBAAgB,IAAI,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,GAAG,aAAa,CA2BlE;AAYD,eAAO,MAAM,IAAI;oBACC,MAAM;iBACT,MAAM;iBACN,MAAM;kBACL,MAAM;CACrB,CAAC;AAEF,wBAAgB,SAAS,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,UAAO,GAAG,IAAI,CAE/D;AAED,wBAAgB,SAAS,CAAC,QAAQ,EAAE,MAAM,EAAE,EAAE,IAAI,CAAC,EAAE,MAAM,GAAG,IAAI,CAOjE;AAED,wBAAgB,gBAAgB,CAC9B,GAAG,EAAE,MAAM,EACX,SAAS,CAAC,EAAE,MAAM,EAClB,OAAO,UAAO,GACb,IAAI,CAQN"}
|
package/dist/ui/components.js
CHANGED
|
@@ -10,13 +10,13 @@ export function sectionHeader(title) {
|
|
|
10
10
|
}
|
|
11
11
|
export const log = {
|
|
12
12
|
success(message) {
|
|
13
|
-
console.log(` ${c.
|
|
13
|
+
console.log(` ${c.green("◆")} ${c.text(message)}`);
|
|
14
14
|
},
|
|
15
15
|
warn(message) {
|
|
16
|
-
console.log(` ${c.yellow("
|
|
16
|
+
console.log(` ${c.yellow("!")} ${c.yellow(message)}`);
|
|
17
17
|
},
|
|
18
18
|
error(message) {
|
|
19
|
-
console.error(` ${c.red("
|
|
19
|
+
console.error(` ${c.red("x")} ${c.red(message)}`);
|
|
20
20
|
},
|
|
21
21
|
info(message) {
|
|
22
22
|
console.log(` ${c.muted(message)}`);
|
|
@@ -105,7 +105,7 @@ function eventTone(message) {
|
|
|
105
105
|
return "step";
|
|
106
106
|
return "output";
|
|
107
107
|
}
|
|
108
|
-
function formatEvent(message) {
|
|
108
|
+
export function formatEvent(message) {
|
|
109
109
|
const colors = {
|
|
110
110
|
command: c.blue,
|
|
111
111
|
step: c.teal,
|
|
@@ -115,17 +115,16 @@ function formatEvent(message) {
|
|
|
115
115
|
output: c.text,
|
|
116
116
|
};
|
|
117
117
|
const tone = eventTone(message);
|
|
118
|
-
const tag = {
|
|
119
|
-
command: "[CMD]",
|
|
120
|
-
step: "[STEP]",
|
|
121
|
-
success: "[OK]",
|
|
122
|
-
warning: "[WARN]",
|
|
123
|
-
error: "[ERROR]",
|
|
124
|
-
output: "[LOG]",
|
|
125
|
-
}[tone];
|
|
126
118
|
const normalized = message.trim().replace(/^\$\s*/, "");
|
|
127
|
-
const
|
|
128
|
-
|
|
119
|
+
const marker = {
|
|
120
|
+
command: supportsUnicode() ? "➜" : ">",
|
|
121
|
+
step: "-",
|
|
122
|
+
success: supportsUnicode() ? "◆" : "*",
|
|
123
|
+
warning: "!",
|
|
124
|
+
error: "x",
|
|
125
|
+
output: supportsUnicode() ? "│" : "|",
|
|
126
|
+
}[tone];
|
|
127
|
+
return ` ${colors[tone](marker)} ${colors[tone](normalized)}`;
|
|
129
128
|
}
|
|
130
129
|
const activeAnimations = new Set();
|
|
131
130
|
let cleanupHooksInstalled = false;
|
|
@@ -162,6 +161,7 @@ export function createProgress(label, enabled) {
|
|
|
162
161
|
let spinnerIndex = 0;
|
|
163
162
|
let currentValue = 0;
|
|
164
163
|
let hasMeasuredValue = false;
|
|
164
|
+
let previousTone;
|
|
165
165
|
const formatPercent = (value) => `${value.toFixed(2).replace(/\.?0+$/, "")}%`;
|
|
166
166
|
const bar = new cliProgress.SingleBar({
|
|
167
167
|
format: ` {spinner} {stage} ${c.teal("{bar}")} ${c.muted("{percent}")}`,
|
|
@@ -191,9 +191,13 @@ export function createProgress(label, enabled) {
|
|
|
191
191
|
const logEvent = (message) => {
|
|
192
192
|
if (!bar.isActive)
|
|
193
193
|
return;
|
|
194
|
+
const tone = eventTone(message);
|
|
194
195
|
bar.stop();
|
|
196
|
+
if (tone === "output" && previousTone === "output")
|
|
197
|
+
console.log();
|
|
195
198
|
console.log(formatEvent(message));
|
|
196
199
|
bar.start(100, currentValue, payload(currentLabel));
|
|
200
|
+
previousTone = tone;
|
|
197
201
|
};
|
|
198
202
|
return {
|
|
199
203
|
event: logEvent,
|
|
@@ -228,11 +232,11 @@ export function spin(text, enabled) {
|
|
|
228
232
|
const untrack = trackAnimation(() => spinner.stop());
|
|
229
233
|
return {
|
|
230
234
|
succeed: (message) => {
|
|
231
|
-
spinner.succeed(`${c.
|
|
235
|
+
spinner.succeed(`${c.green("◆")} ${c.text(message)}`);
|
|
232
236
|
untrack();
|
|
233
237
|
},
|
|
234
238
|
fail: (message) => {
|
|
235
|
-
spinner.fail(`${c.red("
|
|
239
|
+
spinner.fail(`${c.red("x")} ${c.red(message)}`);
|
|
236
240
|
untrack();
|
|
237
241
|
},
|
|
238
242
|
stop: () => {
|
|
@@ -255,6 +259,16 @@ export const pill = {
|
|
|
255
259
|
export function finalLine(message, success = true) {
|
|
256
260
|
console.log(`\n${success ? c.brand("◆") : c.red("◆")} ${c.text(message)}`);
|
|
257
261
|
}
|
|
262
|
+
export function nextSteps(commands, note) {
|
|
263
|
+
if (commands.length === 0)
|
|
264
|
+
return;
|
|
265
|
+
console.error(`\n${c.yellow("Next steps")}`);
|
|
266
|
+
commands.forEach((command, index) => {
|
|
267
|
+
console.error(` ${c.muted(`${index + 1}.`)} ${c.teal(command)}`);
|
|
268
|
+
});
|
|
269
|
+
if (note)
|
|
270
|
+
console.error(` ${c.muted(note)}`);
|
|
271
|
+
}
|
|
258
272
|
export function downloadArtifact(url, expiresAt, enabled = true) {
|
|
259
273
|
if (!enabled)
|
|
260
274
|
return;
|
package/dist/ui/index.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { type BoxRow, type ProgressHandle, type SpinnerHandle } from "./components.js";
|
|
2
2
|
import { terminalOptions, type TerminalOptions } from "./terminal.js";
|
|
3
|
+
import type { CliGuidance } from "../guidance.js";
|
|
3
4
|
export declare class CliUi {
|
|
4
5
|
readonly options: TerminalOptions;
|
|
5
6
|
readonly interactive: boolean;
|
|
@@ -11,6 +12,7 @@ export declare class CliUi {
|
|
|
11
12
|
info(message: string): void;
|
|
12
13
|
debug(message: string): void;
|
|
13
14
|
error(message: string): void;
|
|
15
|
+
nextSteps(guidance: CliGuidance): void;
|
|
14
16
|
summary(title: string, rows: BoxRow[]): void;
|
|
15
17
|
configurationStatus(rows: BoxRow[]): void;
|
|
16
18
|
progress(label: string): ProgressHandle;
|
|
@@ -20,5 +22,5 @@ export declare class CliUi {
|
|
|
20
22
|
}
|
|
21
23
|
export declare function createCliUi(args: string[]): CliUi;
|
|
22
24
|
export { terminalOptions };
|
|
23
|
-
export type { BoxRow, TerminalOptions };
|
|
25
|
+
export type { BoxRow, ProgressHandle, TerminalOptions };
|
|
24
26
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/ui/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/ui/index.ts"],"names":[],"mappings":"AAAA,OAAO,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/ui/index.ts"],"names":[],"mappings":"AAAA,OAAO,EASL,KAAK,MAAM,EACX,KAAK,cAAc,EACnB,KAAK,aAAa,EACnB,MAAM,iBAAiB,CAAC;AAGzB,OAAO,EAEL,eAAe,EACf,KAAK,eAAe,EACrB,MAAM,eAAe,CAAC;AACvB,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAElD,qBAAa,KAAK;IAChB,QAAQ,CAAC,OAAO,EAAE,eAAe,CAAC;IAElC,QAAQ,CAAC,WAAW,EAAE,OAAO,CAAC;gBAElB,IAAI,EAAE,MAAM,EAAE;IAU1B,MAAM,IAAI,IAAI;IAId,MAAM,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI;IAI3B,OAAO,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI;IAI9B,IAAI,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI;IAI3B,IAAI,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI;IAI3B,KAAK,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI;IAK5B,KAAK,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI;IAI5B,SAAS,CAAC,QAAQ,EAAE,WAAW,GAAG,IAAI;IAKtC,OAAO,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,GAAG,IAAI;IAI5C,mBAAmB,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,IAAI;IAKzC,QAAQ,CAAC,KAAK,EAAE,MAAM,GAAG,cAAc;IAIvC,OAAO,CAAC,IAAI,EAAE,MAAM,GAAG,aAAa;IAIpC,IAAI,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,UAAO,GAAG,IAAI;IAI3C,gBAAgB,CAAC,GAAG,EAAE,MAAM,EAAE,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI;CAGxD;AAED,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,KAAK,CAEjD;AAED,OAAO,EAAE,eAAe,EAAE,CAAC;AAE3B,YAAY,EAAE,MAAM,EAAE,cAAc,EAAE,eAAe,EAAE,CAAC"}
|
package/dist/ui/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { finalLine, log, sectionHeader, summaryBox, createProgress, spin, downloadArtifact, } from "./components.js";
|
|
1
|
+
import { finalLine, log, sectionHeader, summaryBox, nextSteps, createProgress, spin, downloadArtifact, } from "./components.js";
|
|
2
2
|
import { printBanner } from "./logo.js";
|
|
3
3
|
import { setColors } from "./state.js";
|
|
4
4
|
import { isInteractive, terminalOptions, } from "./terminal.js";
|
|
@@ -40,6 +40,11 @@ export class CliUi {
|
|
|
40
40
|
if (!this.options.json)
|
|
41
41
|
log.error(message);
|
|
42
42
|
}
|
|
43
|
+
nextSteps(guidance) {
|
|
44
|
+
if (this.options.json || this.options.quiet)
|
|
45
|
+
return;
|
|
46
|
+
nextSteps(guidance.commands, guidance.note);
|
|
47
|
+
}
|
|
43
48
|
summary(title, rows) {
|
|
44
49
|
if (this.interactive)
|
|
45
50
|
summaryBox(title, rows);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lynxship/cli",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.7",
|
|
4
4
|
"description": "Build, sign, store, submit and update LynxJS applications from the terminal.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -25,7 +25,8 @@
|
|
|
25
25
|
],
|
|
26
26
|
"files": [
|
|
27
27
|
"dist",
|
|
28
|
-
"README.md"
|
|
28
|
+
"README.md",
|
|
29
|
+
"templates"
|
|
29
30
|
],
|
|
30
31
|
"engines": {
|
|
31
32
|
"node": ">=22.0.0"
|
|
@@ -68,10 +69,10 @@
|
|
|
68
69
|
"ora": "8.2.0",
|
|
69
70
|
"qrcode-terminal": "0.12.0",
|
|
70
71
|
"@lynxship/build-orchestrator": "0.1.0",
|
|
71
|
-
"@lynxship/db": "0.1.0",
|
|
72
72
|
"@lynxship/contracts": "0.1.0",
|
|
73
73
|
"@lynxship/signing": "0.1.0",
|
|
74
|
-
"@lynxship/submit": "0.1.0"
|
|
74
|
+
"@lynxship/submit": "0.1.0",
|
|
75
|
+
"@lynxship/db": "0.1.0"
|
|
75
76
|
},
|
|
76
77
|
"devDependencies": {
|
|
77
78
|
"@types/cli-progress": "3.11.6",
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
plugins {
|
|
2
|
+
id "com.android.application"
|
|
3
|
+
}
|
|
4
|
+
|
|
5
|
+
android {
|
|
6
|
+
namespace "__APPLICATION_ID__"
|
|
7
|
+
compileSdk 35
|
|
8
|
+
|
|
9
|
+
defaultConfig {
|
|
10
|
+
applicationId "__APPLICATION_ID__"
|
|
11
|
+
minSdk 24
|
|
12
|
+
targetSdk 35
|
|
13
|
+
versionCode 1
|
|
14
|
+
versionName "0.1.0"
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
buildTypes {
|
|
18
|
+
release {
|
|
19
|
+
minifyEnabled false
|
|
20
|
+
proguardFiles getDefaultProguardFile("proguard-android-optimize.txt"),
|
|
21
|
+
"proguard-rules.pro"
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
compileOptions {
|
|
26
|
+
sourceCompatibility JavaVersion.VERSION_17
|
|
27
|
+
targetCompatibility JavaVersion.VERSION_17
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
dependencies {
|
|
32
|
+
implementation "org.lynxsdk.lynx:lynx:4.0.0"
|
|
33
|
+
implementation "org.lynxsdk.lynx:lynx-jssdk:4.0.0"
|
|
34
|
+
implementation "org.lynxsdk.lynx:lynx-trace:4.0.0"
|
|
35
|
+
implementation "org.lynxsdk.lynx:primjs:4.0.0"
|
|
36
|
+
implementation "org.lynxsdk.lynx:lynx-service-log:4.0.0"
|
|
37
|
+
implementation "org.lynxsdk.lynx:lynx-service-http:4.0.0"
|
|
38
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
<?xml version="1.0" encoding="utf-8"?>
|
|
2
|
+
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
|
3
|
+
<uses-permission android:name="android.permission.INTERNET" />
|
|
4
|
+
|
|
5
|
+
<application
|
|
6
|
+
android:allowBackup="false"
|
|
7
|
+
android:name=".LynxShipApplication"
|
|
8
|
+
android:label="@string/app_name"
|
|
9
|
+
android:supportsRtl="true"
|
|
10
|
+
android:theme="@style/AppTheme">
|
|
11
|
+
<activity
|
|
12
|
+
android:name=".MainActivity"
|
|
13
|
+
android:exported="true">
|
|
14
|
+
<intent-filter>
|
|
15
|
+
<action android:name="android.intent.action.MAIN" />
|
|
16
|
+
<category android:name="android.intent.category.LAUNCHER" />
|
|
17
|
+
</intent-filter>
|
|
18
|
+
</activity>
|
|
19
|
+
</application>
|
|
20
|
+
</manifest>
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
package __PACKAGE_NAME__;
|
|
2
|
+
|
|
3
|
+
import android.app.Application;
|
|
4
|
+
|
|
5
|
+
import com.lynx.tasm.LynxEnv;
|
|
6
|
+
|
|
7
|
+
public final class LynxShipApplication extends Application {
|
|
8
|
+
@Override
|
|
9
|
+
public void onCreate() {
|
|
10
|
+
super.onCreate();
|
|
11
|
+
LynxEnv.inst().init(this, null, null, null);
|
|
12
|
+
}
|
|
13
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
package __PACKAGE_NAME__;
|
|
2
|
+
|
|
3
|
+
import android.app.Activity;
|
|
4
|
+
import android.os.Bundle;
|
|
5
|
+
|
|
6
|
+
import com.lynx.tasm.LynxView;
|
|
7
|
+
import com.lynx.tasm.LynxViewBuilder;
|
|
8
|
+
|
|
9
|
+
public final class MainActivity extends Activity {
|
|
10
|
+
@Override
|
|
11
|
+
protected void onCreate(Bundle savedInstanceState) {
|
|
12
|
+
super.onCreate(savedInstanceState);
|
|
13
|
+
LynxView lynxView = new LynxViewBuilder()
|
|
14
|
+
.setTemplateProvider(new ProjectTemplateProvider(this))
|
|
15
|
+
.build(this);
|
|
16
|
+
setContentView(lynxView);
|
|
17
|
+
lynxView.renderTemplateUrl("main.lynx.bundle", "");
|
|
18
|
+
}
|
|
19
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
package __PACKAGE_NAME__;
|
|
2
|
+
|
|
3
|
+
import android.content.Context;
|
|
4
|
+
|
|
5
|
+
import com.lynx.tasm.provider.AbsTemplateProvider;
|
|
6
|
+
|
|
7
|
+
import java.io.ByteArrayOutputStream;
|
|
8
|
+
import java.io.IOException;
|
|
9
|
+
import java.io.InputStream;
|
|
10
|
+
|
|
11
|
+
public final class ProjectTemplateProvider extends AbsTemplateProvider {
|
|
12
|
+
private final Context context;
|
|
13
|
+
|
|
14
|
+
public ProjectTemplateProvider(Context context) {
|
|
15
|
+
this.context = context.getApplicationContext();
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
@Override
|
|
19
|
+
public void loadTemplate(String uri, Callback callback) {
|
|
20
|
+
new Thread(() -> {
|
|
21
|
+
try (InputStream input = context.getAssets().open(uri);
|
|
22
|
+
ByteArrayOutputStream output = new ByteArrayOutputStream()) {
|
|
23
|
+
byte[] buffer = new byte[8192];
|
|
24
|
+
int read;
|
|
25
|
+
while ((read = input.read(buffer)) != -1) output.write(buffer, 0, read);
|
|
26
|
+
callback.onSuccess(output.toByteArray());
|
|
27
|
+
} catch (IOException error) {
|
|
28
|
+
callback.onFailed(error.getMessage());
|
|
29
|
+
}
|
|
30
|
+
}, "lynx-template-loader").start();
|
|
31
|
+
}
|
|
32
|
+
}
|
|
Binary file
|