@blotch/angular-lynx-native 0.0.0-placeholder → 22.2.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +39 -2
- package/dist/android/build.d.ts +28 -0
- package/dist/android/build.d.ts.map +1 -0
- package/dist/android/build.js +100 -0
- package/dist/android/project.d.ts +20 -0
- package/dist/android/project.d.ts.map +1 -0
- package/dist/android/project.js +84 -0
- package/dist/eject.d.ts +18 -0
- package/dist/eject.d.ts.map +1 -0
- package/dist/eject.js +68 -0
- package/dist/generate.d.ts +60 -0
- package/dist/generate.d.ts.map +1 -0
- package/dist/generate.js +111 -0
- package/dist/index.d.ts +41 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +72 -0
- package/dist/ios/build.d.ts +32 -0
- package/dist/ios/build.d.ts.map +1 -0
- package/dist/ios/build.js +149 -0
- package/dist/ios/frameworks.d.ts +31 -0
- package/dist/ios/frameworks.d.ts.map +1 -0
- package/dist/ios/frameworks.js +99 -0
- package/dist/ios/project.d.ts +11 -0
- package/dist/ios/project.d.ts.map +1 -0
- package/dist/ios/project.js +73 -0
- package/dist/options.d.ts +95 -0
- package/dist/options.d.ts.map +1 -0
- package/dist/options.js +112 -0
- package/dist/permissions.d.ts +17 -0
- package/dist/permissions.d.ts.map +1 -0
- package/dist/permissions.js +38 -0
- package/dist/png.d.ts +10 -0
- package/dist/png.d.ts.map +1 -0
- package/dist/png.js +24 -0
- package/dist/process.d.ts +17 -0
- package/dist/process.d.ts.map +1 -0
- package/dist/process.js +67 -0
- package/dist/xml.d.ts +10 -0
- package/dist/xml.d.ts.map +1 -0
- package/dist/xml.js +32 -0
- package/package.json +58 -3
- package/templates/android/README.md +42 -0
- package/templates/android/angular-lynx.properties +7 -0
- package/templates/android/app/build.gradle.kts +140 -0
- package/templates/android/app/proguard-rules.pro +65 -0
- package/templates/android/app/src/main/AndroidManifest.xml +36 -0
- package/templates/android/app/src/main/assets/.gitkeep +0 -0
- package/templates/android/app/src/main/java/dev/angularlynx/shell/BundleAssets.kt +45 -0
- package/templates/android/app/src/main/java/dev/angularlynx/shell/MainActivity.kt +96 -0
- package/templates/android/app/src/main/java/dev/angularlynx/shell/ResourceFetchers.kt +115 -0
- package/templates/android/app/src/main/java/dev/angularlynx/shell/ShellApplication.kt +39 -0
- package/templates/android/app/src/main/java/dev/angularlynx/shell/ShellModule.kt +40 -0
- package/templates/android/app/src/main/res/drawable/ic_launcher_background.xml +4 -0
- package/templates/android/app/src/main/res/drawable/ic_launcher_foreground.xml +10 -0
- package/templates/android/app/src/main/res/mipmap/ic_launcher.xml +6 -0
- package/templates/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml +6 -0
- package/templates/android/app/src/main/res/values/themes.xml +13 -0
- package/templates/android/app/src/main/res/values-night/themes.xml +8 -0
- package/templates/android/build.gradle.kts +5 -0
- package/templates/android/gradle/wrapper/gradle-wrapper.jar +0 -0
- package/templates/android/gradle/wrapper/gradle-wrapper.properties +10 -0
- package/templates/android/gradle.properties +7 -0
- package/templates/android/gradlew +248 -0
- package/templates/android/gradlew.bat +82 -0
- package/templates/android/settings.gradle.kts +28 -0
- package/templates/ios/App/AppDelegate.swift +51 -0
- package/templates/ios/App/Assets.xcassets/AppIcon.appiconset/AppIcon.png +0 -0
- package/templates/ios/App/Assets.xcassets/AppIcon.appiconset/Contents.json +14 -0
- package/templates/ios/App/Assets.xcassets/Contents.json +6 -0
- package/templates/ios/App/Assets.xcassets/LaunchBackground.colorset/Contents.json +20 -0
- package/templates/ios/App/BundleResourceFetcher.swift +136 -0
- package/templates/ios/App/Info.plist +48 -0
- package/templates/ios/App/LynxViewController.swift +68 -0
- package/templates/ios/App.xcodeproj/project.pbxproj +328 -0
- package/templates/ios/App.xcodeproj/project.xcworkspace/contents.xcworkspacedata +7 -0
- package/templates/ios/App.xcodeproj/xcshareddata/xcschemes/App.xcscheme +76 -0
- package/templates/ios/Config/App.xcconfig +9 -0
- package/templates/ios/LynxBundle/.gitkeep +0 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { mkdirSync, readFileSync, writeFileSync } from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { androidGenerator } from "./android/project.js";
|
|
4
|
+
import { buildAndroid, resolveAndroidSigning } from "./android/build.js";
|
|
5
|
+
import { prepareProject, removeLegacyWorkDir, } from "./generate.js";
|
|
6
|
+
import { buildIos, resolveIosSigning } from "./ios/build.js";
|
|
7
|
+
import { resolveLynxFrameworks } from "./ios/frameworks.js";
|
|
8
|
+
import { iosGenerator } from "./ios/project.js";
|
|
9
|
+
import { resolveAppOptions } from "./options.js";
|
|
10
|
+
export { EJECT_MARKER, copyBundle, prepareProject } from "./generate.js";
|
|
11
|
+
export { renderEjectedProject } from "./eject.js";
|
|
12
|
+
export { LYNX_IOS, FRAMEWORKS_ENV } from "./ios/frameworks.js";
|
|
13
|
+
export { NativeConfigError, resolveAppOptions } from "./options.js";
|
|
14
|
+
export { NativeBuildError } from "./process.js";
|
|
15
|
+
export const PACKAGE_VERSION = JSON.parse(readFileSync(new URL('../package.json', import.meta.url), 'utf8')).version;
|
|
16
|
+
/**
|
|
17
|
+
* Turns a built Lynx bundle into native binaries: generates (or reuses) the
|
|
18
|
+
* shell project, copies the bundle in, and runs xcodebuild or Gradle.
|
|
19
|
+
* Returns the paths of the binaries it wrote to `outputDir`.
|
|
20
|
+
*/
|
|
21
|
+
export const buildNativeApp = async (options) => {
|
|
22
|
+
const { platform, projectRoot, workDir, log } = options;
|
|
23
|
+
if (platform === 'ios' && process.platform !== 'darwin') {
|
|
24
|
+
throw new Error('Building for iOS needs macOS with Xcode installed: Apple only ships its build tools for macOS.');
|
|
25
|
+
}
|
|
26
|
+
const app = resolveAppOptions(options.app, {
|
|
27
|
+
projectName: options.projectName,
|
|
28
|
+
packageVersion: options.packageVersion,
|
|
29
|
+
});
|
|
30
|
+
// Resolved before generating anything, so a missing secret fails in
|
|
31
|
+
// milliseconds rather than after a native build.
|
|
32
|
+
const iosSigning = platform === 'ios' ? resolveIosSigning(app) : undefined;
|
|
33
|
+
const androidSigning = platform === 'android' ? resolveAndroidSigning(app) : undefined;
|
|
34
|
+
removeLegacyWorkDir(projectRoot, log);
|
|
35
|
+
const logFile = path.join(workDir, 'logs', `${platform}.log`);
|
|
36
|
+
mkdirSync(path.dirname(logFile), { recursive: true });
|
|
37
|
+
writeFileSync(logFile, '');
|
|
38
|
+
const project = prepareProject({
|
|
39
|
+
platform,
|
|
40
|
+
generator: platform === 'ios' ? iosGenerator : androidGenerator,
|
|
41
|
+
projectRoot,
|
|
42
|
+
workDir,
|
|
43
|
+
app,
|
|
44
|
+
bundleDir: options.bundleDir,
|
|
45
|
+
packageVersion: PACKAGE_VERSION,
|
|
46
|
+
});
|
|
47
|
+
log(project.ejected
|
|
48
|
+
? `Using the ejected ${platform}/ project`
|
|
49
|
+
: project.regenerated
|
|
50
|
+
? `Generated the ${platform} project`
|
|
51
|
+
: `Reusing the generated ${platform} project (no config changes)`);
|
|
52
|
+
const artifacts = platform === 'ios'
|
|
53
|
+
? await buildIos({
|
|
54
|
+
project,
|
|
55
|
+
app,
|
|
56
|
+
frameworksDir: await resolveLynxFrameworks(logFile, log),
|
|
57
|
+
buildDir: path.join(workDir, 'build', 'ios'),
|
|
58
|
+
outputDir: options.outputDir,
|
|
59
|
+
logFile,
|
|
60
|
+
log,
|
|
61
|
+
signing: iosSigning,
|
|
62
|
+
})
|
|
63
|
+
: await buildAndroid({
|
|
64
|
+
project,
|
|
65
|
+
app,
|
|
66
|
+
outputDir: options.outputDir,
|
|
67
|
+
logFile,
|
|
68
|
+
log,
|
|
69
|
+
signing: androidSigning,
|
|
70
|
+
});
|
|
71
|
+
return { artifacts, projectDir: project.dir };
|
|
72
|
+
};
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import type { PreparedProject } from '../generate.ts';
|
|
2
|
+
import type { ResolvedApp } from '../options.ts';
|
|
3
|
+
export type IosSigning = {
|
|
4
|
+
readonly teamId: string;
|
|
5
|
+
readonly keyPath: string;
|
|
6
|
+
readonly keyId: string;
|
|
7
|
+
readonly issuerId: string;
|
|
8
|
+
};
|
|
9
|
+
/**
|
|
10
|
+
* Reads the App Store Connect API key from the environment variables the
|
|
11
|
+
* options name. Returns undefined when signing isn't configured at all, so a
|
|
12
|
+
* build without credentials still produces the simulator app, and throws when
|
|
13
|
+
* it's configured halfway, which is always a mistake.
|
|
14
|
+
*/
|
|
15
|
+
export declare const resolveIosSigning: (app: ResolvedApp, env?: NodeJS.ProcessEnv) => IosSigning | undefined;
|
|
16
|
+
/**
|
|
17
|
+
* Builds the iOS shell: always a simulator `.app`, plus an App Store `.ipa`
|
|
18
|
+
* when signing is configured. Returns the artifacts' paths.
|
|
19
|
+
*/
|
|
20
|
+
export declare const buildIos: ({ project, app, frameworksDir, buildDir, outputDir, logFile, log, signing, }: {
|
|
21
|
+
readonly project: PreparedProject;
|
|
22
|
+
readonly app: ResolvedApp;
|
|
23
|
+
readonly frameworksDir: string;
|
|
24
|
+
/** Xcode's derived data, kept between builds for incremental builds. */
|
|
25
|
+
readonly buildDir: string;
|
|
26
|
+
readonly outputDir: string;
|
|
27
|
+
readonly logFile: string;
|
|
28
|
+
readonly log: (message: string) => void;
|
|
29
|
+
readonly signing: IosSigning | undefined;
|
|
30
|
+
}) => Promise<string[]>;
|
|
31
|
+
export declare const exportOptionsPlist: (teamId: string) => string;
|
|
32
|
+
//# sourceMappingURL=build.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"build.d.ts","sourceRoot":"","sources":["../../src/ios/build.ts"],"names":[],"mappings":"AAWA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAC;AACtD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAIjD,MAAM,MAAM,UAAU,GAAG;IACvB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;CAC3B,CAAC;AAEF;;;;;GAKG;AACH,eAAO,MAAM,iBAAiB,GAC5B,KAAK,WAAW,EAChB,MAAK,MAAM,CAAC,UAAwB,KACnC,UAAU,GAAG,SAgCf,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,QAAQ,GAAU,8EAS5B;IACD,QAAQ,CAAC,OAAO,EAAE,eAAe,CAAC;IAClC,QAAQ,CAAC,GAAG,EAAE,WAAW,CAAC;IAC1B,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;IAC/B,wEAAwE;IACxE,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,GAAG,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;IACxC,QAAQ,CAAC,OAAO,EAAE,UAAU,GAAG,SAAS,CAAC;CAC1C,KAAG,OAAO,CAAC,MAAM,EAAE,CAwFnB,CAAC;AAgBF,eAAO,MAAM,kBAAkB,GAC7B,QAAQ,MAAM,KACb,MAkBF,CAAC"}
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
import { cpSync, existsSync, lstatSync, mkdirSync, rmSync, symlinkSync, writeFileSync, } from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { run } from "../process.js";
|
|
4
|
+
import { escapeXml } from "../xml.js";
|
|
5
|
+
/**
|
|
6
|
+
* Reads the App Store Connect API key from the environment variables the
|
|
7
|
+
* options name. Returns undefined when signing isn't configured at all, so a
|
|
8
|
+
* build without credentials still produces the simulator app, and throws when
|
|
9
|
+
* it's configured halfway, which is always a mistake.
|
|
10
|
+
*/
|
|
11
|
+
export const resolveIosSigning = (app, env = process.env) => {
|
|
12
|
+
const signing = app.ios.signing;
|
|
13
|
+
if (!signing && !app.ios.teamId) {
|
|
14
|
+
return undefined;
|
|
15
|
+
}
|
|
16
|
+
const read = (option, name) => {
|
|
17
|
+
if (!name) {
|
|
18
|
+
throw new Error(`ios.signing.${option} must name the environment variable that holds it.`);
|
|
19
|
+
}
|
|
20
|
+
const value = env[name];
|
|
21
|
+
if (!value) {
|
|
22
|
+
throw new Error(`ios.signing.${option} names ${name}, which isn't set in the environment.`);
|
|
23
|
+
}
|
|
24
|
+
return value;
|
|
25
|
+
};
|
|
26
|
+
if (!app.ios.teamId) {
|
|
27
|
+
throw new Error('ios.signing is set, but ios.teamId is not.');
|
|
28
|
+
}
|
|
29
|
+
const keyPath = read('keyPathEnv', signing?.keyPathEnv);
|
|
30
|
+
if (!existsSync(keyPath)) {
|
|
31
|
+
throw new Error(`The App Store Connect key ${keyPath} doesn't exist.`);
|
|
32
|
+
}
|
|
33
|
+
return {
|
|
34
|
+
teamId: app.ios.teamId,
|
|
35
|
+
keyPath,
|
|
36
|
+
keyId: read('keyIdEnv', signing?.keyIdEnv),
|
|
37
|
+
issuerId: read('issuerIdEnv', signing?.issuerIdEnv),
|
|
38
|
+
};
|
|
39
|
+
};
|
|
40
|
+
/**
|
|
41
|
+
* Builds the iOS shell: always a simulator `.app`, plus an App Store `.ipa`
|
|
42
|
+
* when signing is configured. Returns the artifacts' paths.
|
|
43
|
+
*/
|
|
44
|
+
export const buildIos = async ({ project, app, frameworksDir, buildDir, outputDir, logFile, log, signing, }) => {
|
|
45
|
+
linkFrameworks(project.dir, frameworksDir);
|
|
46
|
+
const xcodebuild = (args) => run('xcodebuild', args, { cwd: project.dir, logFile });
|
|
47
|
+
const common = ['-project', 'App.xcodeproj', '-scheme', 'App'];
|
|
48
|
+
const artifacts = [];
|
|
49
|
+
mkdirSync(outputDir, { recursive: true });
|
|
50
|
+
log('Building the iOS Simulator app');
|
|
51
|
+
await xcodebuild([
|
|
52
|
+
'build',
|
|
53
|
+
...common,
|
|
54
|
+
'-configuration',
|
|
55
|
+
'Release',
|
|
56
|
+
'-sdk',
|
|
57
|
+
'iphonesimulator',
|
|
58
|
+
'-destination',
|
|
59
|
+
'generic/platform=iOS Simulator',
|
|
60
|
+
'-derivedDataPath',
|
|
61
|
+
buildDir,
|
|
62
|
+
]);
|
|
63
|
+
const simulatorApp = path.join(outputDir, `${app.productName}-simulator.app`);
|
|
64
|
+
rmSync(simulatorApp, { recursive: true, force: true });
|
|
65
|
+
cpSync(path.join(buildDir, 'Build/Products/Release-iphonesimulator', `${app.productName}.app`), simulatorApp, { recursive: true, verbatimSymlinks: true });
|
|
66
|
+
artifacts.push(simulatorApp);
|
|
67
|
+
if (!signing) {
|
|
68
|
+
log('Skipping the App Store .ipa: set ios.teamId and ios.signing in angular.json to build one.');
|
|
69
|
+
return artifacts;
|
|
70
|
+
}
|
|
71
|
+
// Headless signing: the API key lets xcodebuild create and download the
|
|
72
|
+
// certificate and provisioning profile itself, so nobody opens Xcode.
|
|
73
|
+
const auth = [
|
|
74
|
+
'-allowProvisioningUpdates',
|
|
75
|
+
'-authenticationKeyPath',
|
|
76
|
+
signing.keyPath,
|
|
77
|
+
'-authenticationKeyID',
|
|
78
|
+
signing.keyId,
|
|
79
|
+
'-authenticationKeyIssuerID',
|
|
80
|
+
signing.issuerId,
|
|
81
|
+
];
|
|
82
|
+
const archive = path.join(buildDir, `${app.productName}.xcarchive`);
|
|
83
|
+
log('Archiving the iOS app for the App Store');
|
|
84
|
+
await xcodebuild([
|
|
85
|
+
'archive',
|
|
86
|
+
...common,
|
|
87
|
+
'-configuration',
|
|
88
|
+
'Release',
|
|
89
|
+
'-destination',
|
|
90
|
+
'generic/platform=iOS',
|
|
91
|
+
'-archivePath',
|
|
92
|
+
archive,
|
|
93
|
+
'-derivedDataPath',
|
|
94
|
+
buildDir,
|
|
95
|
+
...auth,
|
|
96
|
+
`DEVELOPMENT_TEAM=${signing.teamId}`,
|
|
97
|
+
]);
|
|
98
|
+
const exportOptions = path.join(buildDir, 'ExportOptions.plist');
|
|
99
|
+
writeFileSync(exportOptions, exportOptionsPlist(signing.teamId));
|
|
100
|
+
const exportDir = path.join(buildDir, 'export');
|
|
101
|
+
rmSync(exportDir, { recursive: true, force: true });
|
|
102
|
+
log('Exporting the .ipa');
|
|
103
|
+
await xcodebuild([
|
|
104
|
+
'-exportArchive',
|
|
105
|
+
'-archivePath',
|
|
106
|
+
archive,
|
|
107
|
+
'-exportPath',
|
|
108
|
+
exportDir,
|
|
109
|
+
'-exportOptionsPlist',
|
|
110
|
+
exportOptions,
|
|
111
|
+
...auth,
|
|
112
|
+
]);
|
|
113
|
+
const ipa = path.join(outputDir, `${app.productName}.ipa`);
|
|
114
|
+
cpSync(path.join(exportDir, `${app.productName}.ipa`), ipa);
|
|
115
|
+
artifacts.push(ipa);
|
|
116
|
+
return artifacts;
|
|
117
|
+
};
|
|
118
|
+
/**
|
|
119
|
+
* Points the project's `Frameworks` folder at the cached XCFrameworks. A
|
|
120
|
+
* symlink, so the ~100 MB of frameworks aren't copied into every project. An
|
|
121
|
+
* ejected project that vendors its own Frameworks folder keeps it.
|
|
122
|
+
*/
|
|
123
|
+
const linkFrameworks = (projectDir, frameworksDir) => {
|
|
124
|
+
const link = path.join(projectDir, 'Frameworks');
|
|
125
|
+
if (existsSync(link) && !lstatSync(link).isSymbolicLink()) {
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
rmSync(link, { force: true });
|
|
129
|
+
symlinkSync(frameworksDir, link, 'dir');
|
|
130
|
+
};
|
|
131
|
+
export const exportOptionsPlist = (teamId) => `<?xml version="1.0" encoding="UTF-8"?>
|
|
132
|
+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
133
|
+
<plist version="1.0">
|
|
134
|
+
<dict>
|
|
135
|
+
<key>method</key>
|
|
136
|
+
<string>app-store-connect</string>
|
|
137
|
+
<key>destination</key>
|
|
138
|
+
<string>export</string>
|
|
139
|
+
<key>signingStyle</key>
|
|
140
|
+
<string>automatic</string>
|
|
141
|
+
<key>teamID</key>
|
|
142
|
+
<string>${escapeXml(teamId)}</string>
|
|
143
|
+
<key>uploadSymbols</key>
|
|
144
|
+
<true/>
|
|
145
|
+
<key>manageAppVersionAndBuildNumber</key>
|
|
146
|
+
<false/>
|
|
147
|
+
</dict>
|
|
148
|
+
</plist>
|
|
149
|
+
`;
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The prebuilt Lynx XCFrameworks the iOS shell links.
|
|
3
|
+
*
|
|
4
|
+
* Lynx publishes iOS only as source pods, and compiling them takes minutes and
|
|
5
|
+
* needs CocoaPods (and so Ruby). scripts/build-lynx-ios-frameworks.sh compiles
|
|
6
|
+
* them once per Lynx version in CI; the zip it produces is attached to a
|
|
7
|
+
* GitHub release and pinned here by SHA-256, so every user links the same
|
|
8
|
+
* binaries we tested.
|
|
9
|
+
*/
|
|
10
|
+
export declare const LYNX_IOS: {
|
|
11
|
+
readonly version: "4.1.0";
|
|
12
|
+
readonly url: "https://github.com/Blotch-Smart-Frames/AngularLynx/releases/download/lynx-ios-4.1.0/lynx-ios-4.1.0.zip";
|
|
13
|
+
readonly sha256: string | undefined;
|
|
14
|
+
};
|
|
15
|
+
/**
|
|
16
|
+
* The frameworks in the zip. The template project (APP_PROJECT in
|
|
17
|
+
* scripts/generate-ios-project.ts) and the frameworks build both read this
|
|
18
|
+
* list, so it is the only place to change it.
|
|
19
|
+
*/
|
|
20
|
+
export declare const LYNX_IOS_FRAMEWORKS: readonly ["Lynx", "LynxBase", "LynxServiceAPI", "LynxService", "PrimJS", "SDWebImage", "SDWebImageWebPCoder", "libwebp"];
|
|
21
|
+
/**
|
|
22
|
+
* Overrides the download with a local folder of XCFrameworks, e.g. the
|
|
23
|
+
* `out/LynxFrameworks` folder build-lynx-ios-frameworks.sh writes. For testing
|
|
24
|
+
* a new Lynx version before its zip is published.
|
|
25
|
+
*/
|
|
26
|
+
export declare const FRAMEWORKS_ENV = "ANGULAR_LYNX_IOS_FRAMEWORKS";
|
|
27
|
+
/** Returns a folder holding every XCFramework, downloading it on first use. */
|
|
28
|
+
export declare const resolveLynxFrameworks: (logFile: string, log: (message: string) => void) => Promise<string>;
|
|
29
|
+
/** The Lynx version a frameworks folder was built from, if it says. */
|
|
30
|
+
export declare const readFrameworksVersion: (dir: string) => string | undefined;
|
|
31
|
+
//# sourceMappingURL=frameworks.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"frameworks.d.ts","sourceRoot":"","sources":["../../src/ios/frameworks.ts"],"names":[],"mappings":"AAcA;;;;;;;;GAQG;AACH,eAAO,MAAM,QAAQ;;;qBAIf,MAAM,GACN,SAAS;CACL,CAAC;AAEX;;;;GAIG;AACH,eAAO,MAAM,mBAAmB,0HAStB,CAAC;AAEX;;;;GAIG;AACH,eAAO,MAAM,cAAc,gCAAgC,CAAC;AAS5D,+EAA+E;AAC/E,eAAO,MAAM,qBAAqB,GAChC,SAAS,MAAM,EACf,KAAK,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,KAC7B,OAAO,CAAC,MAAM,CAmDhB,CAAC;AAaF,uEAAuE;AACvE,eAAO,MAAM,qBAAqB,GAAI,KAAK,MAAM,KAAG,MAAM,GAAG,SAM5D,CAAC"}
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
2
|
+
import { existsSync, mkdirSync, readFileSync, renameSync, rmSync, writeFileSync, } from 'node:fs';
|
|
3
|
+
import os from 'node:os';
|
|
4
|
+
import path from 'node:path';
|
|
5
|
+
import { run } from "../process.js";
|
|
6
|
+
/**
|
|
7
|
+
* The prebuilt Lynx XCFrameworks the iOS shell links.
|
|
8
|
+
*
|
|
9
|
+
* Lynx publishes iOS only as source pods, and compiling them takes minutes and
|
|
10
|
+
* needs CocoaPods (and so Ruby). scripts/build-lynx-ios-frameworks.sh compiles
|
|
11
|
+
* them once per Lynx version in CI; the zip it produces is attached to a
|
|
12
|
+
* GitHub release and pinned here by SHA-256, so every user links the same
|
|
13
|
+
* binaries we tested.
|
|
14
|
+
*/
|
|
15
|
+
export const LYNX_IOS = {
|
|
16
|
+
version: '4.1.0',
|
|
17
|
+
url: 'https://github.com/Blotch-Smart-Frames/AngularLynx/releases/download/lynx-ios-4.1.0/lynx-ios-4.1.0.zip',
|
|
18
|
+
sha256: '8f00fbd9eddc885175ea526136197222b5a8bd892c6b4d97e131abf88b9a39f4',
|
|
19
|
+
};
|
|
20
|
+
/**
|
|
21
|
+
* The frameworks in the zip. The template project (APP_PROJECT in
|
|
22
|
+
* scripts/generate-ios-project.ts) and the frameworks build both read this
|
|
23
|
+
* list, so it is the only place to change it.
|
|
24
|
+
*/
|
|
25
|
+
export const LYNX_IOS_FRAMEWORKS = [
|
|
26
|
+
'Lynx',
|
|
27
|
+
'LynxBase',
|
|
28
|
+
'LynxServiceAPI',
|
|
29
|
+
'LynxService',
|
|
30
|
+
'PrimJS',
|
|
31
|
+
'SDWebImage',
|
|
32
|
+
'SDWebImageWebPCoder',
|
|
33
|
+
'libwebp',
|
|
34
|
+
];
|
|
35
|
+
/**
|
|
36
|
+
* Overrides the download with a local folder of XCFrameworks, e.g. the
|
|
37
|
+
* `out/LynxFrameworks` folder build-lynx-ios-frameworks.sh writes. For testing
|
|
38
|
+
* a new Lynx version before its zip is published.
|
|
39
|
+
*/
|
|
40
|
+
export const FRAMEWORKS_ENV = 'ANGULAR_LYNX_IOS_FRAMEWORKS';
|
|
41
|
+
const cacheRoot = () => path.join(process.env['XDG_CACHE_HOME'] ?? path.join(os.homedir(), '.cache'), 'angular-lynx', 'lynx-ios');
|
|
42
|
+
/** Returns a folder holding every XCFramework, downloading it on first use. */
|
|
43
|
+
export const resolveLynxFrameworks = async (logFile, log) => {
|
|
44
|
+
const override = process.env[FRAMEWORKS_ENV];
|
|
45
|
+
if (override) {
|
|
46
|
+
assertComplete(override, `${FRAMEWORKS_ENV} (${override})`);
|
|
47
|
+
return path.resolve(override);
|
|
48
|
+
}
|
|
49
|
+
const dir = path.join(cacheRoot(), LYNX_IOS.version);
|
|
50
|
+
const frameworks = path.join(dir, 'LynxFrameworks');
|
|
51
|
+
if (existsSync(path.join(dir, '.complete'))) {
|
|
52
|
+
return frameworks;
|
|
53
|
+
}
|
|
54
|
+
if (!LYNX_IOS.sha256) {
|
|
55
|
+
throw new Error(`Prebuilt Lynx ${LYNX_IOS.version} frameworks for iOS aren't published yet. Build them with packages/native/scripts/build-lynx-ios-frameworks.sh from the AngularLynx repository, then set ${FRAMEWORKS_ENV} to the LynxFrameworks folder it creates.`);
|
|
56
|
+
}
|
|
57
|
+
log(`Downloading Lynx ${LYNX_IOS.version} for iOS (once per Lynx version)`);
|
|
58
|
+
const response = await fetch(LYNX_IOS.url);
|
|
59
|
+
if (!response.ok) {
|
|
60
|
+
throw new Error(`Downloading ${LYNX_IOS.url} failed with HTTP ${response.status}.`);
|
|
61
|
+
}
|
|
62
|
+
const zip = Buffer.from(await response.arrayBuffer());
|
|
63
|
+
const digest = createHash('sha256').update(zip).digest('hex');
|
|
64
|
+
if (digest !== LYNX_IOS.sha256) {
|
|
65
|
+
throw new Error(`${LYNX_IOS.url} has SHA-256 ${digest}, not the pinned ${LYNX_IOS.sha256}. Not using it.`);
|
|
66
|
+
}
|
|
67
|
+
// Extract next to the final folder and rename, so an interrupted download
|
|
68
|
+
// never leaves a half-extracted cache that looks complete.
|
|
69
|
+
const staging = `${dir}.partial`;
|
|
70
|
+
rmSync(staging, { recursive: true, force: true });
|
|
71
|
+
mkdirSync(staging, { recursive: true });
|
|
72
|
+
const zipFile = path.join(staging, 'frameworks.zip');
|
|
73
|
+
writeFileSync(zipFile, zip);
|
|
74
|
+
// ditto keeps the frameworks' symlinks and code signatures intact.
|
|
75
|
+
await run('ditto', ['-x', '-k', zipFile, staging], {
|
|
76
|
+
cwd: staging,
|
|
77
|
+
logFile,
|
|
78
|
+
});
|
|
79
|
+
rmSync(zipFile);
|
|
80
|
+
assertComplete(path.join(staging, 'LynxFrameworks'), LYNX_IOS.url);
|
|
81
|
+
writeFileSync(path.join(staging, '.complete'), LYNX_IOS.sha256);
|
|
82
|
+
rmSync(dir, { recursive: true, force: true });
|
|
83
|
+
renameSync(staging, dir);
|
|
84
|
+
return frameworks;
|
|
85
|
+
};
|
|
86
|
+
const assertComplete = (dir, source) => {
|
|
87
|
+
const missing = LYNX_IOS_FRAMEWORKS.filter((name) => !existsSync(path.join(dir, `${name}.xcframework`, 'Info.plist')));
|
|
88
|
+
if (missing.length > 0) {
|
|
89
|
+
throw new Error(`${source} is missing ${missing.map((name) => `${name}.xcframework`).join(', ')}.`);
|
|
90
|
+
}
|
|
91
|
+
};
|
|
92
|
+
/** The Lynx version a frameworks folder was built from, if it says. */
|
|
93
|
+
export const readFrameworksVersion = (dir) => {
|
|
94
|
+
const versions = path.join(dir, 'VERSIONS');
|
|
95
|
+
if (!existsSync(versions)) {
|
|
96
|
+
return undefined;
|
|
97
|
+
}
|
|
98
|
+
return /^lynx=(.+)$/m.exec(readFileSync(versions, 'utf8'))?.[1];
|
|
99
|
+
};
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { PlatformGenerator } from '../generate.ts';
|
|
2
|
+
import type { ResolvedApp } from '../options.ts';
|
|
3
|
+
/**
|
|
4
|
+
* Writes the app options into the iOS template: build settings go in
|
|
5
|
+
* Config/App.xcconfig (which App.xcodeproj reads), and Info.plist entries go
|
|
6
|
+
* between the template's `angular-lynx:info` markers.
|
|
7
|
+
*/
|
|
8
|
+
export declare const iosGenerator: PlatformGenerator;
|
|
9
|
+
export declare const xcconfig: (app: ResolvedApp) => string;
|
|
10
|
+
export declare const infoPlistEntries: (app: ResolvedApp) => string;
|
|
11
|
+
//# sourceMappingURL=project.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"project.d.ts","sourceRoot":"","sources":["../../src/ios/project.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,gBAAgB,CAAC;AAExD,OAAO,KAAK,EAAe,WAAW,EAAE,MAAM,eAAe,CAAC;AAiB9D;;;;GAIG;AACH,eAAO,MAAM,YAAY,EAAE,iBA6B1B,CAAC;AAQF,eAAO,MAAM,QAAQ,GAAI,KAAK,WAAW,KAAG,MAY9B,CAAC;AAEf,eAAO,MAAM,gBAAgB,GAAI,KAAK,WAAW,KAAG,MAqCnD,CAAC"}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { copyFileSync, readFileSync, writeFileSync } from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { readPngSize } from "../png.js";
|
|
4
|
+
import { PERMISSION_MAP } from "../permissions.js";
|
|
5
|
+
import { escapeXml, replaceMarker } from "../xml.js";
|
|
6
|
+
const ORIENTATIONS = {
|
|
7
|
+
portrait: ['UIInterfaceOrientationPortrait'],
|
|
8
|
+
landscape: [
|
|
9
|
+
'UIInterfaceOrientationLandscapeLeft',
|
|
10
|
+
'UIInterfaceOrientationLandscapeRight',
|
|
11
|
+
],
|
|
12
|
+
all: [
|
|
13
|
+
'UIInterfaceOrientationPortrait',
|
|
14
|
+
'UIInterfaceOrientationLandscapeLeft',
|
|
15
|
+
'UIInterfaceOrientationLandscapeRight',
|
|
16
|
+
],
|
|
17
|
+
};
|
|
18
|
+
/**
|
|
19
|
+
* Writes the app options into the iOS template: build settings go in
|
|
20
|
+
* Config/App.xcconfig (which App.xcodeproj reads), and Info.plist entries go
|
|
21
|
+
* between the template's `angular-lynx:info` markers.
|
|
22
|
+
*/
|
|
23
|
+
export const iosGenerator = {
|
|
24
|
+
configure: (dir, app, projectRoot) => {
|
|
25
|
+
writeFileSync(path.join(dir, 'Config/App.xcconfig'), xcconfig(app));
|
|
26
|
+
const plistPath = path.join(dir, 'App/Info.plist');
|
|
27
|
+
writeFileSync(plistPath, replaceMarker(readFileSync(plistPath, 'utf8'), 'info', infoPlistEntries(app)));
|
|
28
|
+
if (app.icon) {
|
|
29
|
+
const icon = path.resolve(projectRoot, app.icon);
|
|
30
|
+
const size = readPngSize(icon);
|
|
31
|
+
if (size.width !== 1024 || size.height !== 1024) {
|
|
32
|
+
throw new Error(`${app.icon} is ${size.width}×${size.height}. The app icon must be a 1024×1024 PNG.`);
|
|
33
|
+
}
|
|
34
|
+
copyFileSync(icon, path.join(dir, 'App/Assets.xcassets/AppIcon.appiconset/AppIcon.png'));
|
|
35
|
+
}
|
|
36
|
+
},
|
|
37
|
+
bundleDir: (dir) => path.join(dir, 'LynxBundle'),
|
|
38
|
+
};
|
|
39
|
+
/**
|
|
40
|
+
* xcconfig values can't be quoted; `$` and `//` would start a variable or a comment.
|
|
41
|
+
*/
|
|
42
|
+
const xcconfigValue = (value) => value.replace(/\$/g, '$$$$').replace(/\/\//g, '/$()/');
|
|
43
|
+
export const xcconfig = (app) => [
|
|
44
|
+
'// Written by @blotch/angular-lynx-native from the builder options in',
|
|
45
|
+
'// angular.json on every build. Edits here are overwritten; change',
|
|
46
|
+
'// angular.json instead (or eject).',
|
|
47
|
+
`ANGULAR_LYNX_PRODUCT_NAME = ${app.productName}`,
|
|
48
|
+
`ANGULAR_LYNX_DISPLAY_NAME = ${xcconfigValue(app.name)}`,
|
|
49
|
+
`PRODUCT_BUNDLE_IDENTIFIER = ${app.id}`,
|
|
50
|
+
`MARKETING_VERSION = ${app.version}`,
|
|
51
|
+
`CURRENT_PROJECT_VERSION = ${app.buildNumber}`,
|
|
52
|
+
`DEVELOPMENT_TEAM = ${app.ios.teamId ?? ''}`,
|
|
53
|
+
'',
|
|
54
|
+
].join('\n');
|
|
55
|
+
export const infoPlistEntries = (app) => {
|
|
56
|
+
const lines = [];
|
|
57
|
+
const array = (key, values) => {
|
|
58
|
+
lines.push(`<key>${key}</key>`, '<array>');
|
|
59
|
+
values.forEach((value) => lines.push(`\t<string>${escapeXml(value)}</string>`));
|
|
60
|
+
lines.push('</array>');
|
|
61
|
+
};
|
|
62
|
+
array('UISupportedInterfaceOrientations', ORIENTATIONS[app.orientation]);
|
|
63
|
+
for (const [permission, reason] of Object.entries(app.permissions)) {
|
|
64
|
+
for (const key of PERMISSION_MAP[permission]
|
|
65
|
+
.ios) {
|
|
66
|
+
lines.push(`<key>${key}</key>`, `<string>${escapeXml(reason)}</string>`);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
if (app.deepLinkSchemes.length > 0) {
|
|
70
|
+
lines.push('<key>CFBundleURLTypes</key>', '<array>', '\t<dict>', '\t\t<key>CFBundleURLName</key>', `\t\t<string>${escapeXml(app.id)}</string>`, '\t\t<key>CFBundleURLSchemes</key>', '\t\t<array>', ...app.deepLinkSchemes.map((scheme) => `\t\t\t<string>${escapeXml(scheme)}</string>`), '\t\t</array>', '\t</dict>', '</array>');
|
|
71
|
+
}
|
|
72
|
+
return lines.join('\n');
|
|
73
|
+
};
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The app identity and native settings a project declares in angular.json, as
|
|
3
|
+
* options of the `@blotch/angular-lynx-build:application` builder.
|
|
4
|
+
*
|
|
5
|
+
* The options are flat on purpose. Angular merges a `configurations` entry
|
|
6
|
+
* into the base options one key deep, so a nested `app: { id }` override in a
|
|
7
|
+
* "staging" configuration would drop every other `app` field. With flat keys,
|
|
8
|
+
* `"staging": { "appId": "com.example.app.staging" }` changes only the ID.
|
|
9
|
+
* The `ios` and `android` objects are still replaced as a whole when a
|
|
10
|
+
* configuration sets them.
|
|
11
|
+
*/
|
|
12
|
+
export type NativeAppOptions = {
|
|
13
|
+
/** The name under the icon. Defaults to the project name. */
|
|
14
|
+
readonly appName?: string;
|
|
15
|
+
/** iOS bundle ID and Android applicationId, e.g. `com.example.myapp`. */
|
|
16
|
+
readonly appId?: string;
|
|
17
|
+
/** The user-facing version (CFBundleShortVersionString / versionName). */
|
|
18
|
+
readonly version?: string;
|
|
19
|
+
/** The store build number (CFBundleVersion / versionCode). */
|
|
20
|
+
readonly buildNumber?: number;
|
|
21
|
+
readonly orientation?: Orientation;
|
|
22
|
+
/** Each permission the app uses, with the reason shown to the user. */
|
|
23
|
+
readonly permissions?: Partial<Record<Permission, string>>;
|
|
24
|
+
/** Custom URL schemes that open the app, e.g. `["myapp"]`. */
|
|
25
|
+
readonly deepLinkSchemes?: readonly string[];
|
|
26
|
+
/** A 1024×1024 PNG without transparency, relative to the project root. */
|
|
27
|
+
readonly icon?: string;
|
|
28
|
+
readonly ios?: IosOptions;
|
|
29
|
+
readonly android?: AndroidOptions;
|
|
30
|
+
};
|
|
31
|
+
export type Orientation = 'portrait' | 'landscape' | 'all';
|
|
32
|
+
export type Permission = 'camera' | 'microphone' | 'location' | 'photos' | 'contacts' | 'notifications';
|
|
33
|
+
export type IosOptions = {
|
|
34
|
+
/** The Apple Developer team that signs the app. */
|
|
35
|
+
readonly teamId?: string;
|
|
36
|
+
/**
|
|
37
|
+
* Headless signing with an App Store Connect API key. Each field names the
|
|
38
|
+
* environment variable that holds the value, so no secret is ever written
|
|
39
|
+
* to angular.json.
|
|
40
|
+
*/
|
|
41
|
+
readonly signing?: {
|
|
42
|
+
readonly keyIdEnv?: string;
|
|
43
|
+
readonly issuerIdEnv?: string;
|
|
44
|
+
readonly keyPathEnv?: string;
|
|
45
|
+
};
|
|
46
|
+
};
|
|
47
|
+
export type AndroidOptions = {
|
|
48
|
+
/**
|
|
49
|
+
* The upload key for Google Play. The `*Env` fields name environment
|
|
50
|
+
* variables, so no secret is ever written to angular.json.
|
|
51
|
+
*/
|
|
52
|
+
readonly signing?: {
|
|
53
|
+
readonly storeFileEnv?: string;
|
|
54
|
+
readonly storePasswordEnv?: string;
|
|
55
|
+
readonly keyAlias?: string;
|
|
56
|
+
readonly keyPasswordEnv?: string;
|
|
57
|
+
};
|
|
58
|
+
};
|
|
59
|
+
/** {@link NativeAppOptions} with every default applied and every value checked. */
|
|
60
|
+
export type ResolvedApp = {
|
|
61
|
+
readonly name: string;
|
|
62
|
+
readonly id: string;
|
|
63
|
+
readonly version: string;
|
|
64
|
+
readonly buildNumber: number;
|
|
65
|
+
readonly orientation: Orientation;
|
|
66
|
+
readonly permissions: Partial<Record<Permission, string>>;
|
|
67
|
+
readonly deepLinkSchemes: readonly string[];
|
|
68
|
+
readonly icon?: string;
|
|
69
|
+
/** `MyApp`: the iOS product (and so .app/.ipa) name. */
|
|
70
|
+
readonly productName: string;
|
|
71
|
+
/** `my-app`: the base name of Android artifacts. */
|
|
72
|
+
readonly artifactName: string;
|
|
73
|
+
readonly ios: IosOptions;
|
|
74
|
+
readonly android: AndroidOptions;
|
|
75
|
+
};
|
|
76
|
+
export declare class NativeConfigError extends Error {
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Applies defaults and validates the options, so the generators never see a
|
|
80
|
+
* value that would produce a project Xcode or Gradle rejects later with a
|
|
81
|
+
* less helpful message.
|
|
82
|
+
*/
|
|
83
|
+
export declare const resolveAppOptions: (rawOptions: NativeAppOptions, defaults: {
|
|
84
|
+
readonly projectName: string;
|
|
85
|
+
readonly packageVersion?: string;
|
|
86
|
+
}) => ResolvedApp;
|
|
87
|
+
/**
|
|
88
|
+
* Drops `undefined` values and the objects left empty by that. Angular CLI
|
|
89
|
+
* fills in every property its schema declares for a nested option object,
|
|
90
|
+
* as `undefined` when the user didn't set it, so `ios: {}` arrives as
|
|
91
|
+
* `{ teamId: undefined, signing: { keyIdEnv: undefined, ... } }`, which
|
|
92
|
+
* would read as "signing configured, but incomplete".
|
|
93
|
+
*/
|
|
94
|
+
export declare const compact: (value: unknown) => unknown;
|
|
95
|
+
//# sourceMappingURL=options.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"options.d.ts","sourceRoot":"","sources":["../src/options.ts"],"names":[],"mappings":"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"}
|