@blotch/angular-lynx-build 0.0.0-stage → 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 +100 -2
- package/builders/application/index.js +165 -0
- package/builders/application/native-cache.js +56 -0
- package/builders/application/schema.js +2 -0
- package/builders/application/schema.json +229 -0
- package/builders/builders.json +19 -0
- package/builders/dev-server/index.js +82 -0
- package/builders/dev-server/schema.js +2 -0
- package/builders/dev-server/schema.json +28 -0
- package/builders/package.json +3 -0
- package/builders/project-import.js +64 -0
- package/builders/rspeedy.js +162 -0
- package/builders/unit-test/index.js +207 -0
- package/builders/unit-test/lynx-globals.d.mts +3 -0
- package/builders/unit-test/lynx-globals.mjs +18 -0
- package/builders/unit-test/schema.js +2 -0
- package/builders/unit-test/schema.json +311 -0
- package/client.d.ts +8 -0
- package/dist/index.d.ts +15 -0
- package/dist/index.js +14 -0
- package/package.json +80 -4
package/README.md
CHANGED
|
@@ -1,3 +1,101 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @blotch/angular-lynx-build
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
The Angular CLI builders for [AngularLynx](https://github.com/Blotch-Smart-Frames/AngularLynx): `ng build`, `ng serve` and `ng test` for Lynx, and `ng build --platform ios|android|all` for native apps.
|
|
4
|
+
|
|
5
|
+
```bash
|
|
6
|
+
ng add @blotch/angular-lynx # installs this package and sets up angular.json
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
It is the only build dependency an AngularLynx app needs, as `@angular/build` is for a web app: it brings rspeedy, the rsbuild plugin that compiles Angular, the QR code plugin and the native app builder with it.
|
|
10
|
+
|
|
11
|
+
## Builders
|
|
12
|
+
|
|
13
|
+
```jsonc
|
|
14
|
+
// angular.json, as `ng add` writes it
|
|
15
|
+
"build": {
|
|
16
|
+
"builder": "@blotch/angular-lynx-build:application",
|
|
17
|
+
"options": { "browser": "src/main.ts", "tsConfig": "tsconfig.app.json" /* ... */ },
|
|
18
|
+
"configurations": {
|
|
19
|
+
"production": { "budgets": [/* ... */], "outputHashing": "all" },
|
|
20
|
+
"development": { "optimization": false, "extractLicenses": false, "sourceMap": true }
|
|
21
|
+
},
|
|
22
|
+
"defaultConfiguration": "production"
|
|
23
|
+
},
|
|
24
|
+
"serve": {
|
|
25
|
+
"builder": "@blotch/angular-lynx-build:dev-server",
|
|
26
|
+
"configurations": {
|
|
27
|
+
"production": { "buildTarget": "my-app:build:production" },
|
|
28
|
+
"development": { "buildTarget": "my-app:build:development" }
|
|
29
|
+
},
|
|
30
|
+
"defaultConfiguration": "development"
|
|
31
|
+
},
|
|
32
|
+
"test": {
|
|
33
|
+
"builder": "@blotch/angular-lynx-build:unit-test"
|
|
34
|
+
}
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
| Builder | Command | What it does |
|
|
38
|
+
| ------------- | ------------------------- | ------------------------------------------------------------------------------------------------------------ |
|
|
39
|
+
| `application` | `ng build` | Builds the Lynx bundle (and the web preview bundle) into `dist/` with [rspeedy](https://lynxjs.org/rspeedy/) |
|
|
40
|
+
| | `ng build --watch` | Rebuilds the bundle whenever a file changes |
|
|
41
|
+
| | `ng build --platform ios` | Then builds the iOS app (`--platform android` or `all` for the others) into `dist/<platform>/` |
|
|
42
|
+
| `dev-server` | `ng serve` | Runs rspeedy's dev server for Lynx Explorer and the web preview, and prints the QR code Lynx Explorer scans |
|
|
43
|
+
| `unit-test` | `ng test` | Runs the unit tests with Angular's own unit-test builder (Vitest), set up for Lynx |
|
|
44
|
+
|
|
45
|
+
They behave like Angular's own builders:
|
|
46
|
+
|
|
47
|
+
- **Configurations and overrides apply.** The build target's options reach the compiler as Angular CLI resolved them, so `ng build -c development` and flags like `--optimization=false` work.
|
|
48
|
+
- **`optimization` decides between a production and a development build.** There is no `mode` option: as in Angular, a build that optimizes scripts is a production build, and one that doesn't is a development build with dev mode on. `ng build -c development` is a development build, and `ng serve -c production` serves a production one.
|
|
49
|
+
- **`ng serve` builds its `buildTarget`**, by default the build target's `development` configuration. `--port` / `--host` override `server` in lynx.config.ts.
|
|
50
|
+
- **Budgets are enforced.** The `budgets` in the build target are checked against the Lynx bundle and the component stylesheets, with Angular's messages; `ng add` sizes the `initial` budget for a Lynx bundle. See the plugin's [README](../rsbuild-plugin-angular-lynx/README.md#budgets).
|
|
51
|
+
- **`--watch` rebuilds only the bundle.** It can't be combined with `--platform`: use `ng serve` while developing, and `ng build --platform` for the app.
|
|
52
|
+
|
|
53
|
+
The options are in [`builders/application/schema.json`](builders/application/schema.json), [`builders/dev-server/schema.json`](builders/dev-server/schema.json) and [`builders/unit-test/schema.json`](builders/unit-test/schema.json).
|
|
54
|
+
|
|
55
|
+
## ng test
|
|
56
|
+
|
|
57
|
+
The `unit-test` builder is Angular's `@angular/build:unit-test`, set up for Lynx. Its options are Angular's (`include`, `watch`, `coverage`, `setupFiles`, `providersFile`, ...): its schema is a copy of Angular's, and a test keeps the two in step. It finds `*.spec.ts` and `*.test.ts` files, and the tests compile ahead of time, like the app. On top of Angular's setup it:
|
|
58
|
+
|
|
59
|
+
- defines the compile-time flags the AngularLynx runtime reads (`__DEV__`, `__MAIN_THREAD__`, ...), through a polyfill (`@blotch/angular-lynx-build/unit-test/lynx-globals`) that loads before anything else;
|
|
60
|
+
- loads `@blotch/angular-lynx-testing-library`'s setup when the project has the library, so its `render()` works with no setup file or Vitest config in the app;
|
|
61
|
+
- hands Angular the build target as an Angular one, with only the options both builders share, so Angular neither warns about an unknown builder nor rejects the Lynx-only options;
|
|
62
|
+
- loads imported images, fonts and media as file URLs, as rspeedy does in the app.
|
|
63
|
+
|
|
64
|
+
Two things work as in any Angular app, and differ from a plain Vitest setup:
|
|
65
|
+
|
|
66
|
+
- **Templates are checked strictly.** A component that uses Lynx elements imports `LYNX_ELEMENTS`, as the Angular Language Service already requires (generated components do). The app build is laxer: it adds `CUSTOM_ELEMENTS_SCHEMA` to every component, which Angular's test build has no hook for.
|
|
67
|
+
- **Test code is type-checked**, and test files share one module graph (Angular's runner isn't isolated by default). `vi.mock` of a relative import isn't supported, and mocking `@angular/core` would leak into other test files.
|
|
68
|
+
|
|
69
|
+
## lynx.config.ts
|
|
70
|
+
|
|
71
|
+
An app doesn't need one. Without it, `ng build` and `ng serve` take what rspeedy needs from angular.json and the defaults an AngularLynx app wants:
|
|
72
|
+
|
|
73
|
+
- `source.entry` is the build target's `browser` option;
|
|
74
|
+
- the environments are `lynx` (the native bundle) and `web` (the browser preview);
|
|
75
|
+
- `ng serve` adds the QR code plugin, in full screen.
|
|
76
|
+
|
|
77
|
+
Add a lynx.config.ts to change any of these or set anything else rspeedy supports; each default only applies where it doesn't set its own. The builders add [`@blotch/rsbuild-plugin-angular-lynx`](../rsbuild-plugin-angular-lynx), which compiles Angular, either way. List the plugin yourself only to pass it options. Import it and `defineConfig` from this package, which depends on both:
|
|
78
|
+
|
|
79
|
+
```ts
|
|
80
|
+
import { defineConfig, pluginAngularLynx } from '@blotch/angular-lynx-build';
|
|
81
|
+
|
|
82
|
+
export default defineConfig({
|
|
83
|
+
plugins: [pluginAngularLynx({ enableSSR: true })],
|
|
84
|
+
});
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
For the types of what the bundler handles (asset and CSS module imports, `import.meta.env`), reference them from here too, as `ng add` does in `src/rspeedy-env.d.ts`:
|
|
88
|
+
|
|
89
|
+
```ts
|
|
90
|
+
/// <reference types="@blotch/angular-lynx-build/client" />
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
## How it's put together
|
|
94
|
+
|
|
95
|
+
This package is the Angular CLI layer. The work happens in its dependencies:
|
|
96
|
+
|
|
97
|
+
- **[`@blotch/rsbuild-plugin-angular-lynx`](../rsbuild-plugin-angular-lynx)** compiles Angular inside rspeedy. It also works on its own, for projects that run rspeedy directly.
|
|
98
|
+
- **[`@blotch/angular-lynx-native`](../native)** turns the Lynx bundle into iOS and Android apps.
|
|
99
|
+
- **[rspeedy](https://lynxjs.org/rspeedy/)** and its QR code plugin. The builders always run the versions they were released and tested with, so an app doesn't install them.
|
|
100
|
+
|
|
101
|
+
`ng test` runs Angular's unit-test builder from the project's own `@angular/build`, which `ng new` installs: it must match the project's Angular version.
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.runApplication = void 0;
|
|
7
|
+
const architect_1 = require("@angular-devkit/architect");
|
|
8
|
+
const node_fs_1 = require("node:fs");
|
|
9
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
10
|
+
const rspeedy_1 = require("../rspeedy");
|
|
11
|
+
const native_cache_1 = require("./native-cache");
|
|
12
|
+
/**
|
|
13
|
+
* Like the rsbuild plugin, the native builder is this package's own
|
|
14
|
+
* dependency, resolved from here. Imported only for `--platform`, so a plain
|
|
15
|
+
* bundle build never loads it.
|
|
16
|
+
*/
|
|
17
|
+
const importNative = () => import('@blotch/angular-lynx-native');
|
|
18
|
+
const defaultDeps = {
|
|
19
|
+
importRspeedy: rspeedy_1.importRspeedy,
|
|
20
|
+
importPlugin: rspeedy_1.importPlugin,
|
|
21
|
+
importNative,
|
|
22
|
+
};
|
|
23
|
+
const errorOutput = (error) => ({
|
|
24
|
+
success: false,
|
|
25
|
+
error: error instanceof Error ? error.message : String(error),
|
|
26
|
+
});
|
|
27
|
+
/**
|
|
28
|
+
* `ng build`: builds the Lynx bundle with rspeedy, as `rspeedy build` would,
|
|
29
|
+
* then, with `--platform`, the native binaries. The Angular compile happens
|
|
30
|
+
* inside rspeedy (through @blotch/rsbuild-plugin-angular-lynx, which
|
|
31
|
+
* `createRspeedy` adds when lynx.config.ts doesn't), so this builder only
|
|
32
|
+
* orchestrates.
|
|
33
|
+
*
|
|
34
|
+
* With `--watch` it reports a result after every rebuild and keeps running
|
|
35
|
+
* until Angular CLI tears it down, as Angular's own application builder does.
|
|
36
|
+
*/
|
|
37
|
+
const runApplication = async function* (options, context, deps = defaultDeps) {
|
|
38
|
+
const projectName = context.target?.project;
|
|
39
|
+
if (!projectName) {
|
|
40
|
+
yield { success: false, error: 'The builder needs a project target.' };
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
const metadata = await context.getProjectMetadata(projectName);
|
|
44
|
+
const projectRoot = node_path_1.default.join(context.workspaceRoot, metadata['root'] ?? '');
|
|
45
|
+
const platforms = options.platform === 'all'
|
|
46
|
+
? ['ios', 'android']
|
|
47
|
+
: options.platform
|
|
48
|
+
? [options.platform]
|
|
49
|
+
: [];
|
|
50
|
+
if (options.watch && platforms.length > 0) {
|
|
51
|
+
// A native app is built once from a finished bundle; rebuilding the
|
|
52
|
+
// Xcode or Gradle project on every save would take minutes each time.
|
|
53
|
+
yield {
|
|
54
|
+
success: false,
|
|
55
|
+
error: "--watch rebuilds only the Lynx bundle and can't be combined with --platform. Use `ng serve` while developing, and `ng build --platform` for the app.",
|
|
56
|
+
};
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
let rspeedy;
|
|
60
|
+
let native;
|
|
61
|
+
let workDir = '';
|
|
62
|
+
try {
|
|
63
|
+
// Load the native builder first, so a broken install fails before a
|
|
64
|
+
// minute of bundling rather than after it.
|
|
65
|
+
if (platforms.length > 0) {
|
|
66
|
+
native = await deps.importNative();
|
|
67
|
+
workDir = (0, native_cache_1.nativeCacheDir)({
|
|
68
|
+
metadata,
|
|
69
|
+
workspaceRoot: context.workspaceRoot,
|
|
70
|
+
projectRoot,
|
|
71
|
+
projectName,
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
rspeedy = await (0, rspeedy_1.createRspeedy)({
|
|
75
|
+
rspeedy: await deps.importRspeedy(),
|
|
76
|
+
plugin: await deps.importPlugin(),
|
|
77
|
+
projectRoot,
|
|
78
|
+
// Angular CLI already merged these: base options, the configurations
|
|
79
|
+
// chosen with -c (or the default one), then command-line overrides.
|
|
80
|
+
angularCli: {
|
|
81
|
+
workspaceRoot: context.workspaceRoot,
|
|
82
|
+
project: projectName,
|
|
83
|
+
buildOptions: options,
|
|
84
|
+
},
|
|
85
|
+
config: options.config,
|
|
86
|
+
// Native shells only load the Lynx bundle; skipping the web
|
|
87
|
+
// environment halves the bundling time.
|
|
88
|
+
environment: platforms.length > 0 ? ['lynx'] : options.environment,
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
catch (error) {
|
|
92
|
+
yield errorOutput(error);
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
if (options.watch) {
|
|
96
|
+
yield* watchBundle(rspeedy, context);
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
try {
|
|
100
|
+
context.reportStatus('Building the Lynx bundle');
|
|
101
|
+
const { close } = await rspeedy.build();
|
|
102
|
+
await close();
|
|
103
|
+
const bundleDir = rspeedy.context.distPath;
|
|
104
|
+
for (const platform of platforms) {
|
|
105
|
+
context.reportStatus(`Building the ${platform} app`);
|
|
106
|
+
const { artifacts } = await native.buildNativeApp({
|
|
107
|
+
platform,
|
|
108
|
+
projectRoot,
|
|
109
|
+
workDir,
|
|
110
|
+
projectName,
|
|
111
|
+
packageVersion: readPackageVersion(projectRoot),
|
|
112
|
+
bundleDir,
|
|
113
|
+
outputDir: node_path_1.default.join(bundleDir, platform),
|
|
114
|
+
app: options,
|
|
115
|
+
log: (message) => context.logger.info(message),
|
|
116
|
+
});
|
|
117
|
+
for (const artifact of artifacts) {
|
|
118
|
+
context.logger.info(` ✓ ${node_path_1.default.relative(context.workspaceRoot, artifact)}`);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
yield { success: true };
|
|
122
|
+
}
|
|
123
|
+
catch (error) {
|
|
124
|
+
yield errorOutput(error);
|
|
125
|
+
}
|
|
126
|
+
};
|
|
127
|
+
exports.runApplication = runApplication;
|
|
128
|
+
/**
|
|
129
|
+
* `ng build --watch`: rspeedy's watch build, reporting each compile's result.
|
|
130
|
+
* Compile errors fail that result but keep watching, as in Angular, so the
|
|
131
|
+
* next save can fix them. Angular CLI ends the run (Ctrl+C) through the
|
|
132
|
+
* teardown, which stops the watcher.
|
|
133
|
+
*/
|
|
134
|
+
const watchBundle = async function* (rspeedy, context) {
|
|
135
|
+
const results = [];
|
|
136
|
+
let wake;
|
|
137
|
+
rspeedy.onAfterBuild(({ stats }) => {
|
|
138
|
+
results.push({ success: !stats?.hasErrors() });
|
|
139
|
+
wake?.();
|
|
140
|
+
});
|
|
141
|
+
try {
|
|
142
|
+
context.reportStatus('Watching the Lynx bundle');
|
|
143
|
+
const { close } = await rspeedy.build({ watch: true });
|
|
144
|
+
context.addTeardown(close);
|
|
145
|
+
}
|
|
146
|
+
catch (error) {
|
|
147
|
+
yield errorOutput(error);
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
150
|
+
for (;;) {
|
|
151
|
+
while (results.length > 0) {
|
|
152
|
+
yield results.shift();
|
|
153
|
+
}
|
|
154
|
+
await new Promise((resolve) => (wake = resolve));
|
|
155
|
+
}
|
|
156
|
+
};
|
|
157
|
+
const readPackageVersion = (projectRoot) => {
|
|
158
|
+
try {
|
|
159
|
+
return JSON.parse((0, node_fs_1.readFileSync)(node_path_1.default.join(projectRoot, 'package.json'), 'utf8')).version;
|
|
160
|
+
}
|
|
161
|
+
catch {
|
|
162
|
+
return undefined;
|
|
163
|
+
}
|
|
164
|
+
};
|
|
165
|
+
exports.default = (0, architect_1.createBuilder)((options, context) => (0, exports.runApplication)(options, context));
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.nativeCacheDir = void 0;
|
|
7
|
+
const node_fs_1 = require("node:fs");
|
|
8
|
+
const node_module_1 = require("node:module");
|
|
9
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
10
|
+
/**
|
|
11
|
+
* Where `ng build --platform` keeps the generated Xcode and Gradle projects,
|
|
12
|
+
* their build state and the logs: the project's folder in Angular's build
|
|
13
|
+
* cache, `.angular/cache/<Angular version>/<project>/lynx-native` by default.
|
|
14
|
+
*
|
|
15
|
+
* Inside Angular's cache rather than a folder of our own at the project
|
|
16
|
+
* root, so a Lynx app's root looks like any Angular app's: `ng new` already
|
|
17
|
+
* git-ignores the cache, `ng cache clean` clears it, and `cli.cache.path`
|
|
18
|
+
* moves it.
|
|
19
|
+
*
|
|
20
|
+
* The version folder isn't optional. Angular's application and dev-server
|
|
21
|
+
* builders delete every folder in the cache except the current version's,
|
|
22
|
+
* so a `lynx-native` folder beside it would be deleted by the next Angular
|
|
23
|
+
* build. Under it, an Angular update clears the native projects too, which
|
|
24
|
+
* costs one regeneration.
|
|
25
|
+
*
|
|
26
|
+
* Angular shares one cache between a repository's linked checkouts, using
|
|
27
|
+
* the main checkout's. This stays in the current checkout instead: two
|
|
28
|
+
* checkouts building at once would overwrite each other's generated
|
|
29
|
+
* project. And it's used even when `cli.cache.enabled` is
|
|
30
|
+
* false, because the native build needs a working folder either way.
|
|
31
|
+
*/
|
|
32
|
+
const nativeCacheDir = ({ metadata, workspaceRoot, projectRoot, projectName, }) => node_path_1.default.join(node_path_1.default.resolve(workspaceRoot, cachePath(metadata)), angularBuildVersion(projectRoot), projectName, 'lynx-native');
|
|
33
|
+
exports.nativeCacheDir = nativeCacheDir;
|
|
34
|
+
/**
|
|
35
|
+
* angular.json's `cli.cache.path`, defaulting as Angular does.
|
|
36
|
+
*/
|
|
37
|
+
const cachePath = (metadata) => {
|
|
38
|
+
const cli = metadata['cli'];
|
|
39
|
+
const value = cli?.cache?.path;
|
|
40
|
+
return typeof value === 'string' ? value : '.angular/cache';
|
|
41
|
+
};
|
|
42
|
+
/**
|
|
43
|
+
* The version Angular names its cache folder after: @angular/build's, which
|
|
44
|
+
* this package needs as a peer dependency (`ng new` installs it).
|
|
45
|
+
*/
|
|
46
|
+
const angularBuildVersion = (projectRoot) => {
|
|
47
|
+
const require = (0, node_module_1.createRequire)(node_path_1.default.join(projectRoot, 'package.json'));
|
|
48
|
+
let packageJsonPath;
|
|
49
|
+
try {
|
|
50
|
+
packageJsonPath = require.resolve('@angular/build/package.json');
|
|
51
|
+
}
|
|
52
|
+
catch {
|
|
53
|
+
throw new Error('@angular/build is not installed. Run `npm install --save-dev @angular/build` (`ng new` adds it).');
|
|
54
|
+
}
|
|
55
|
+
return JSON.parse((0, node_fs_1.readFileSync)(packageJsonPath, 'utf8')).version;
|
|
56
|
+
};
|
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "http://json-schema.org/draft-07/schema",
|
|
3
|
+
"$id": "AngularLynxApplicationBuilder",
|
|
4
|
+
"title": "AngularLynx application builder",
|
|
5
|
+
"description": "Builds the Lynx bundle with rspeedy and, with --platform, the iOS and Android binaries.",
|
|
6
|
+
"type": "object",
|
|
7
|
+
"additionalProperties": false,
|
|
8
|
+
"properties": {
|
|
9
|
+
"platform": {
|
|
10
|
+
"type": "string",
|
|
11
|
+
"enum": ["ios", "android", "all"],
|
|
12
|
+
"description": "Also build native binaries into dist/<platform>/. Without it, only the bundle is built, as `rspeedy build` does."
|
|
13
|
+
},
|
|
14
|
+
"watch": {
|
|
15
|
+
"type": "boolean",
|
|
16
|
+
"default": false,
|
|
17
|
+
"description": "Rebuild the bundle when files change, as Angular's `ng build --watch` does. Can't be combined with `platform`."
|
|
18
|
+
},
|
|
19
|
+
"config": {
|
|
20
|
+
"type": "string",
|
|
21
|
+
"description": "The rspeedy config file, relative to the project root. Defaults to lynx.config.ts (or .js, .mts, .mjs) when the project has one. Without one, the app builds from angular.json: `browser` is the entry, and both the lynx and web environments are built."
|
|
22
|
+
},
|
|
23
|
+
"environment": {
|
|
24
|
+
"type": "array",
|
|
25
|
+
"items": {
|
|
26
|
+
"type": "string"
|
|
27
|
+
},
|
|
28
|
+
"description": "Only build these rspeedy environments (e.g. lynx). With --platform, only the lynx environment is built."
|
|
29
|
+
},
|
|
30
|
+
"browser": {
|
|
31
|
+
"type": "string",
|
|
32
|
+
"description": "Angular: the app's entry point. Read by @blotch/rsbuild-plugin-angular-lynx and `ng test`, and rspeedy's `source.entry` unless lynx.config.ts sets one."
|
|
33
|
+
},
|
|
34
|
+
"tsConfig": {
|
|
35
|
+
"type": "string",
|
|
36
|
+
"description": "Angular: the tsconfig the app compiles with."
|
|
37
|
+
},
|
|
38
|
+
"polyfills": {
|
|
39
|
+
"type": ["array", "string"],
|
|
40
|
+
"items": {
|
|
41
|
+
"type": "string"
|
|
42
|
+
},
|
|
43
|
+
"description": "Angular: modules loaded before the app (e.g. @angular/localize/init)."
|
|
44
|
+
},
|
|
45
|
+
"styles": {
|
|
46
|
+
"type": "array",
|
|
47
|
+
"items": {
|
|
48
|
+
"type": "string"
|
|
49
|
+
},
|
|
50
|
+
"description": "Angular: global stylesheets."
|
|
51
|
+
},
|
|
52
|
+
"assets": {
|
|
53
|
+
"type": "array",
|
|
54
|
+
"description": "Angular: used by `ng test` only. Lynx bundles reference assets through imports."
|
|
55
|
+
},
|
|
56
|
+
"scripts": {
|
|
57
|
+
"type": "array",
|
|
58
|
+
"description": "Angular: used by `ng test` only."
|
|
59
|
+
},
|
|
60
|
+
"inlineStyleLanguage": {
|
|
61
|
+
"type": "string",
|
|
62
|
+
"description": "Angular: the language of inline component styles."
|
|
63
|
+
},
|
|
64
|
+
"fileReplacements": {
|
|
65
|
+
"type": "array",
|
|
66
|
+
"items": {
|
|
67
|
+
"type": "object",
|
|
68
|
+
"properties": {
|
|
69
|
+
"replace": {
|
|
70
|
+
"type": "string"
|
|
71
|
+
},
|
|
72
|
+
"with": {
|
|
73
|
+
"type": "string"
|
|
74
|
+
}
|
|
75
|
+
},
|
|
76
|
+
"required": ["replace", "with"]
|
|
77
|
+
},
|
|
78
|
+
"description": "Angular: files swapped at build time, e.g. environment files."
|
|
79
|
+
},
|
|
80
|
+
"aot": {
|
|
81
|
+
"type": "boolean",
|
|
82
|
+
"description": "Angular: ahead-of-time compilation. Defaults to true."
|
|
83
|
+
},
|
|
84
|
+
"optimization": {
|
|
85
|
+
"type": ["boolean", "object"],
|
|
86
|
+
"description": "Angular: script and style optimization. Also sets rspeedy's mode: production when scripts are optimized (the default), development otherwise."
|
|
87
|
+
},
|
|
88
|
+
"sourceMap": {
|
|
89
|
+
"type": ["boolean", "object"],
|
|
90
|
+
"description": "Angular: source maps."
|
|
91
|
+
},
|
|
92
|
+
"outputHashing": {
|
|
93
|
+
"type": "string",
|
|
94
|
+
"enum": ["none", "all", "media", "bundles"],
|
|
95
|
+
"description": "Angular: hashes in emitted file names."
|
|
96
|
+
},
|
|
97
|
+
"i18nMissingTranslation": {
|
|
98
|
+
"type": "string",
|
|
99
|
+
"enum": ["warning", "error", "ignore"],
|
|
100
|
+
"description": "Angular: how to report missing translations."
|
|
101
|
+
},
|
|
102
|
+
"budgets": {
|
|
103
|
+
"type": "array",
|
|
104
|
+
"description": "Angular: size budgets, checked against the Lynx bundles (each entry's bundle is its own `initial` budget) and component stylesheets."
|
|
105
|
+
},
|
|
106
|
+
"extractLicenses": {
|
|
107
|
+
"type": "boolean",
|
|
108
|
+
"description": "Angular: accepted for compatibility with `ng new`."
|
|
109
|
+
},
|
|
110
|
+
"outputPath": {
|
|
111
|
+
"type": ["string", "object"],
|
|
112
|
+
"description": "Angular: the output folder. Defaults to rspeedy's dist/."
|
|
113
|
+
},
|
|
114
|
+
"index": {
|
|
115
|
+
"type": ["boolean", "string", "object"],
|
|
116
|
+
"description": "Angular: accepted for compatibility; Lynx apps have no index.html."
|
|
117
|
+
},
|
|
118
|
+
"appName": {
|
|
119
|
+
"type": "string",
|
|
120
|
+
"description": "The app's name under its icon. Defaults to the project name."
|
|
121
|
+
},
|
|
122
|
+
"appId": {
|
|
123
|
+
"type": "string",
|
|
124
|
+
"description": "The iOS bundle ID and Android applicationId, e.g. com.example.myapp."
|
|
125
|
+
},
|
|
126
|
+
"version": {
|
|
127
|
+
"type": "string",
|
|
128
|
+
"description": "The user-facing version, e.g. 1.2.0. Defaults to package.json's version."
|
|
129
|
+
},
|
|
130
|
+
"buildNumber": {
|
|
131
|
+
"type": "integer",
|
|
132
|
+
"minimum": 1,
|
|
133
|
+
"description": "The store build number (CFBundleVersion / versionCode). Must increase with every store upload."
|
|
134
|
+
},
|
|
135
|
+
"orientation": {
|
|
136
|
+
"type": "string",
|
|
137
|
+
"enum": ["portrait", "landscape", "all"],
|
|
138
|
+
"default": "portrait",
|
|
139
|
+
"description": "The screen orientations the app supports."
|
|
140
|
+
},
|
|
141
|
+
"icon": {
|
|
142
|
+
"type": "string",
|
|
143
|
+
"description": "The app icon: a 1024×1024 PNG without transparency, relative to the project root."
|
|
144
|
+
},
|
|
145
|
+
"permissions": {
|
|
146
|
+
"type": "object",
|
|
147
|
+
"description": "Each permission the app uses, with the reason shown to the user.",
|
|
148
|
+
"additionalProperties": false,
|
|
149
|
+
"properties": {
|
|
150
|
+
"camera": {
|
|
151
|
+
"type": "string"
|
|
152
|
+
},
|
|
153
|
+
"microphone": {
|
|
154
|
+
"type": "string"
|
|
155
|
+
},
|
|
156
|
+
"location": {
|
|
157
|
+
"type": "string"
|
|
158
|
+
},
|
|
159
|
+
"photos": {
|
|
160
|
+
"type": "string"
|
|
161
|
+
},
|
|
162
|
+
"contacts": {
|
|
163
|
+
"type": "string"
|
|
164
|
+
},
|
|
165
|
+
"notifications": {
|
|
166
|
+
"type": "string"
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
},
|
|
170
|
+
"deepLinkSchemes": {
|
|
171
|
+
"type": "array",
|
|
172
|
+
"items": {
|
|
173
|
+
"type": "string"
|
|
174
|
+
},
|
|
175
|
+
"description": "Custom URL schemes that open the app, e.g. [\"myapp\"]."
|
|
176
|
+
},
|
|
177
|
+
"ios": {
|
|
178
|
+
"type": "object",
|
|
179
|
+
"additionalProperties": false,
|
|
180
|
+
"properties": {
|
|
181
|
+
"teamId": {
|
|
182
|
+
"type": "string",
|
|
183
|
+
"description": "The Apple Developer team ID that signs the app."
|
|
184
|
+
},
|
|
185
|
+
"signing": {
|
|
186
|
+
"type": "object",
|
|
187
|
+
"description": "An App Store Connect API key for headless signing. Each field names the environment variable holding the value.",
|
|
188
|
+
"additionalProperties": false,
|
|
189
|
+
"properties": {
|
|
190
|
+
"keyIdEnv": {
|
|
191
|
+
"type": "string"
|
|
192
|
+
},
|
|
193
|
+
"issuerIdEnv": {
|
|
194
|
+
"type": "string"
|
|
195
|
+
},
|
|
196
|
+
"keyPathEnv": {
|
|
197
|
+
"type": "string"
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
},
|
|
203
|
+
"android": {
|
|
204
|
+
"type": "object",
|
|
205
|
+
"additionalProperties": false,
|
|
206
|
+
"properties": {
|
|
207
|
+
"signing": {
|
|
208
|
+
"type": "object",
|
|
209
|
+
"description": "The Google Play upload key. The *Env fields name the environment variables holding the values.",
|
|
210
|
+
"additionalProperties": false,
|
|
211
|
+
"properties": {
|
|
212
|
+
"storeFileEnv": {
|
|
213
|
+
"type": "string"
|
|
214
|
+
},
|
|
215
|
+
"storePasswordEnv": {
|
|
216
|
+
"type": "string"
|
|
217
|
+
},
|
|
218
|
+
"keyAlias": {
|
|
219
|
+
"type": "string"
|
|
220
|
+
},
|
|
221
|
+
"keyPasswordEnv": {
|
|
222
|
+
"type": "string"
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
{
|
|
2
|
+
"builders": {
|
|
3
|
+
"application": {
|
|
4
|
+
"implementation": "./application/index",
|
|
5
|
+
"schema": "./application/schema.json",
|
|
6
|
+
"description": "Builds the Lynx bundle with rspeedy (--watch to rebuild on changes) and, with --platform, the iOS and Android binaries"
|
|
7
|
+
},
|
|
8
|
+
"dev-server": {
|
|
9
|
+
"implementation": "./dev-server/index",
|
|
10
|
+
"schema": "./dev-server/schema.json",
|
|
11
|
+
"description": "Runs the rspeedy dev server for Lynx Explorer and the web preview"
|
|
12
|
+
},
|
|
13
|
+
"unit-test": {
|
|
14
|
+
"implementation": "./unit-test/index",
|
|
15
|
+
"schema": "./unit-test/schema.json",
|
|
16
|
+
"description": "Runs the unit tests with Angular's unit-test builder, set up for AngularLynx"
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.runDevServer = void 0;
|
|
7
|
+
const architect_1 = require("@angular-devkit/architect");
|
|
8
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
9
|
+
const rspeedy_1 = require("../rspeedy");
|
|
10
|
+
/**
|
|
11
|
+
* Starts rspeedy's dev server in this process, as `rspeedy dev` would, and
|
|
12
|
+
* closes it when Angular CLI tears the builder down (Ctrl+C). rspeedy's own
|
|
13
|
+
* plugins keep working unchanged, and live reload is rspeedy's. The QR code
|
|
14
|
+
* plugin, which prints the link Lynx Explorer opens, is added unless
|
|
15
|
+
* lynx.config.ts lists it.
|
|
16
|
+
*
|
|
17
|
+
* Like Angular's own dev server, it builds what `buildTarget` describes: its
|
|
18
|
+
* options configure the compile, so `ng serve -c production` serves the
|
|
19
|
+
* production configuration.
|
|
20
|
+
*/
|
|
21
|
+
const startDevServer = async (options, context, deps) => {
|
|
22
|
+
const projectName = context.target?.project;
|
|
23
|
+
if (!projectName) {
|
|
24
|
+
return { success: false, error: 'The builder needs a project target.' };
|
|
25
|
+
}
|
|
26
|
+
const metadata = await context.getProjectMetadata(projectName);
|
|
27
|
+
const projectRoot = node_path_1.default.join(context.workspaceRoot, metadata['root'] ?? '');
|
|
28
|
+
try {
|
|
29
|
+
// The same default as Angular's dev server: the project's build target
|
|
30
|
+
// with its development configuration.
|
|
31
|
+
const buildTarget = (0, architect_1.targetFromTargetString)(options.buildTarget ?? '::development', projectName, 'build');
|
|
32
|
+
// Validated against the build builder's schema, which also fills in its
|
|
33
|
+
// defaults, as the build itself would see them.
|
|
34
|
+
const buildOptions = (await context.validateOptions(await context.getTargetOptions(buildTarget), await context.getBuilderNameForTarget(buildTarget)));
|
|
35
|
+
const rspeedy = await (0, rspeedy_1.createRspeedy)({
|
|
36
|
+
rspeedy: await deps.importRspeedy(),
|
|
37
|
+
plugin: await deps.importPlugin(),
|
|
38
|
+
qrCode: await deps.importQRCodePlugin(),
|
|
39
|
+
projectRoot,
|
|
40
|
+
angularCli: {
|
|
41
|
+
workspaceRoot: context.workspaceRoot,
|
|
42
|
+
project: buildTarget.project,
|
|
43
|
+
buildOptions,
|
|
44
|
+
},
|
|
45
|
+
config: buildOptions.config,
|
|
46
|
+
environment: options.environment ?? buildOptions.environment,
|
|
47
|
+
server: { port: options.port, host: options.host },
|
|
48
|
+
});
|
|
49
|
+
const { server, urls } = await rspeedy.startDevServer();
|
|
50
|
+
context.addTeardown(() => server.close());
|
|
51
|
+
return { success: true, baseUrl: urls[0] };
|
|
52
|
+
}
|
|
53
|
+
catch (error) {
|
|
54
|
+
return {
|
|
55
|
+
success: false,
|
|
56
|
+
error: error instanceof Error ? error.message : String(error),
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
};
|
|
60
|
+
/**
|
|
61
|
+
* `ng serve`. Angular CLI keeps a target running for as long as its outputs
|
|
62
|
+
* haven't ended, so this reports the server's start and then never ends,
|
|
63
|
+
* unless the server failed to start.
|
|
64
|
+
*/
|
|
65
|
+
const runDevServer = (options, context, deps = { importRspeedy: rspeedy_1.importRspeedy, importPlugin: rspeedy_1.importPlugin, importQRCodePlugin: rspeedy_1.importQRCodePlugin }) => ({
|
|
66
|
+
[Symbol.asyncIterator]: () => {
|
|
67
|
+
let started;
|
|
68
|
+
return {
|
|
69
|
+
next: async () => {
|
|
70
|
+
if (!started) {
|
|
71
|
+
started = startDevServer(options, context, deps);
|
|
72
|
+
return { value: await started, done: false };
|
|
73
|
+
}
|
|
74
|
+
return (await started).success
|
|
75
|
+
? new Promise(() => { })
|
|
76
|
+
: { value: undefined, done: true };
|
|
77
|
+
},
|
|
78
|
+
};
|
|
79
|
+
},
|
|
80
|
+
});
|
|
81
|
+
exports.runDevServer = runDevServer;
|
|
82
|
+
exports.default = (0, architect_1.createBuilder)(exports.runDevServer);
|