@blotch/angular-lynx-build 0.0.0-stage → 22.2.3
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 +110 -2
- package/builders/angular-build.js +117 -0
- 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 +24 -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/extract-i18n/index.js +70 -0
- package/builders/extract-i18n/schema.js +2 -0
- package/builders/extract-i18n/schema.json +49 -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 +96 -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
|
@@ -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,24 @@
|
|
|
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
|
+
"extract-i18n": {
|
|
19
|
+
"implementation": "./extract-i18n/index",
|
|
20
|
+
"schema": "./extract-i18n/schema.json",
|
|
21
|
+
"description": "Extracts the i18n messages with Angular's extract-i18n builder, set up for AngularLynx"
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
}
|
|
@@ -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);
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "http://json-schema.org/draft-07/schema",
|
|
3
|
+
"$id": "AngularLynxDevServerBuilder",
|
|
4
|
+
"title": "AngularLynx dev-server builder",
|
|
5
|
+
"description": "Runs the rspeedy dev server, which Lynx Explorer and the web preview load the app from.",
|
|
6
|
+
"type": "object",
|
|
7
|
+
"additionalProperties": false,
|
|
8
|
+
"properties": {
|
|
9
|
+
"buildTarget": {
|
|
10
|
+
"type": "string",
|
|
11
|
+
"description": "The build target to serve, as `project:target[:configuration]`, e.g. `my-app:build:development`. Its options (the Angular compiler options, `config`) configure the build, and its `optimization` picks a production or development build. Defaults to the project's build target with its development configuration, as Angular's own dev server does.",
|
|
12
|
+
"pattern": "^[^:\\s]*:[^:\\s]*(:[^\\s]+)?$"
|
|
13
|
+
},
|
|
14
|
+
"port": {
|
|
15
|
+
"type": "number",
|
|
16
|
+
"description": "The port to serve on. Overrides `server.port` in lynx.config.ts."
|
|
17
|
+
},
|
|
18
|
+
"host": {
|
|
19
|
+
"type": "string",
|
|
20
|
+
"description": "The host to listen on. Overrides `server.host` in lynx.config.ts."
|
|
21
|
+
},
|
|
22
|
+
"environment": {
|
|
23
|
+
"type": "array",
|
|
24
|
+
"items": { "type": "string" },
|
|
25
|
+
"description": "Only build these rspeedy environments (e.g. lynx). Defaults to the build target's `environment`, or all of them."
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
}
|
|
@@ -0,0 +1,70 @@
|
|
|
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.runExtractI18n = exports.hasLocalize = void 0;
|
|
7
|
+
const architect_1 = require("@angular-devkit/architect");
|
|
8
|
+
const node_module_1 = require("node:module");
|
|
9
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
10
|
+
const angular_build_1 = require("../angular-build");
|
|
11
|
+
/** Whether the project has @angular/localize, which the extractor runs. */
|
|
12
|
+
const hasLocalize = (projectRoot) => {
|
|
13
|
+
try {
|
|
14
|
+
(0, node_module_1.createRequire)(node_path_1.default.join(projectRoot, 'package.json')).resolve('@angular/localize/package.json');
|
|
15
|
+
return true;
|
|
16
|
+
}
|
|
17
|
+
catch {
|
|
18
|
+
return false;
|
|
19
|
+
}
|
|
20
|
+
};
|
|
21
|
+
exports.hasLocalize = hasLocalize;
|
|
22
|
+
const defaultDeps = {
|
|
23
|
+
importAngularBuild: (projectRoot) => (0, angular_build_1.importAngularBuild)(projectRoot),
|
|
24
|
+
hasLocalize: exports.hasLocalize,
|
|
25
|
+
};
|
|
26
|
+
/**
|
|
27
|
+
* Angular's own message for a project without @angular/localize suggests
|
|
28
|
+
* `ng add @angular/localize`, which only sets up Angular's builders: on a
|
|
29
|
+
* Lynx app, `$localize` would then be missing at runtime.
|
|
30
|
+
*/
|
|
31
|
+
const NO_LOCALIZE = 'i18n extraction requires the @angular/localize package. Run `ng generate @blotch/angular-lynx:add-i18n` to install it and set up i18n.';
|
|
32
|
+
/**
|
|
33
|
+
* `ng extract-i18n`: Angular's extract-i18n builder
|
|
34
|
+
* (`@angular/build:extract-i18n`), so the options, formats and messages file
|
|
35
|
+
* are the ones Angular developers know. Angular CLI picks that builder on its
|
|
36
|
+
* own only for a project built with one of Angular's builders, so without a
|
|
37
|
+
* target that names this one, `ng extract-i18n` fails with "Cannot determine
|
|
38
|
+
* project or target for command."
|
|
39
|
+
*
|
|
40
|
+
* Angular's extractor compiles the app with Angular's application builder
|
|
41
|
+
* from the build target's options. The Lynx build target reaches it as an
|
|
42
|
+
* Angular one, as in `ng test`: Angular's builder neither rejects the
|
|
43
|
+
* Lynx-only options nor fails on an imported image, which it has no loader
|
|
44
|
+
* for by default. No Lynx polyfills load, since the extractor only reads the
|
|
45
|
+
* compiled code and never runs it.
|
|
46
|
+
*/
|
|
47
|
+
const runExtractI18n = async (options, context, deps = defaultDeps) => {
|
|
48
|
+
const projectName = context.target?.project;
|
|
49
|
+
if (!projectName) {
|
|
50
|
+
return { success: false, error: 'The builder needs a project target.' };
|
|
51
|
+
}
|
|
52
|
+
const metadata = await context.getProjectMetadata(projectName);
|
|
53
|
+
const projectRoot = node_path_1.default.join(context.workspaceRoot, metadata['root'] ?? '');
|
|
54
|
+
if (!deps.hasLocalize(projectRoot)) {
|
|
55
|
+
return { success: false, error: NO_LOCALIZE };
|
|
56
|
+
}
|
|
57
|
+
let angularBuild;
|
|
58
|
+
try {
|
|
59
|
+
angularBuild = await deps.importAngularBuild(projectRoot);
|
|
60
|
+
}
|
|
61
|
+
catch (error) {
|
|
62
|
+
return {
|
|
63
|
+
success: false,
|
|
64
|
+
error: error instanceof Error ? error.message : String(error),
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
return angularBuild.executeExtractI18nBuilder(options, (0, angular_build_1.asAngularBuildTargets)(context));
|
|
68
|
+
};
|
|
69
|
+
exports.runExtractI18n = runExtractI18n;
|
|
70
|
+
exports.default = (0, architect_1.createBuilder)((options, context) => (0, exports.runExtractI18n)(options, context));
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "http://json-schema.org/draft-07/schema",
|
|
3
|
+
"$id": "AngularLynxExtractI18nBuilder",
|
|
4
|
+
"title": "AngularLynx extract-i18n builder",
|
|
5
|
+
"description": "Extracts the i18n messages with Angular's extract-i18n builder (@angular/build:extract-i18n), set up for AngularLynx. The options are Angular's: this file copies its schema.json, and a test keeps the two in step.",
|
|
6
|
+
"type": "object",
|
|
7
|
+
"properties": {
|
|
8
|
+
"buildTarget": {
|
|
9
|
+
"type": "string",
|
|
10
|
+
"description": "A builder target to extract i18n messages in the format of `project:target[:configuration]`. You can also pass in more than one configuration name as a comma-separated list. Example: `project:target:production,staging`.",
|
|
11
|
+
"pattern": "^[^:\\s]*:[^:\\s]*(:[^\\s]+)?$"
|
|
12
|
+
},
|
|
13
|
+
"format": {
|
|
14
|
+
"type": "string",
|
|
15
|
+
"description": "Output format for the generated file.",
|
|
16
|
+
"default": "xlf",
|
|
17
|
+
"enum": [
|
|
18
|
+
"xmb",
|
|
19
|
+
"xlf",
|
|
20
|
+
"xlif",
|
|
21
|
+
"xliff",
|
|
22
|
+
"xlf2",
|
|
23
|
+
"xliff2",
|
|
24
|
+
"json",
|
|
25
|
+
"arb",
|
|
26
|
+
"legacy-migrate"
|
|
27
|
+
]
|
|
28
|
+
},
|
|
29
|
+
"progress": {
|
|
30
|
+
"type": "boolean",
|
|
31
|
+
"description": "Log progress to the console.",
|
|
32
|
+
"default": true
|
|
33
|
+
},
|
|
34
|
+
"outputPath": {
|
|
35
|
+
"type": "string",
|
|
36
|
+
"description": "Path where output will be placed."
|
|
37
|
+
},
|
|
38
|
+
"outFile": {
|
|
39
|
+
"type": "string",
|
|
40
|
+
"description": "Name of the file to output."
|
|
41
|
+
},
|
|
42
|
+
"i18nDuplicateTranslation": {
|
|
43
|
+
"type": "string",
|
|
44
|
+
"description": "How to handle duplicate translations.",
|
|
45
|
+
"enum": ["error", "warning", "ignore"]
|
|
46
|
+
}
|
|
47
|
+
},
|
|
48
|
+
"additionalProperties": false
|
|
49
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
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.importFromProject = 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
|
+
const node_url_1 = require("node:url");
|
|
11
|
+
/**
|
|
12
|
+
* Imports a package installed in the user's project (rspeedy), not one this
|
|
13
|
+
* package depends on. The project pins its own rspeedy version in
|
|
14
|
+
* package.json, and that is the version `ng build` must run, just as
|
|
15
|
+
* `rspeedy build` would.
|
|
16
|
+
*
|
|
17
|
+
* rspeedy is ESM-only, and its `exports` have no `require` condition, so
|
|
18
|
+
* `require.resolve` can't find its entry point. Its package.json is exported,
|
|
19
|
+
* though: resolve that and read the `import` entry from it.
|
|
20
|
+
*/
|
|
21
|
+
const importFromProject = async (packageName, projectRoot, installHint) => {
|
|
22
|
+
const require = (0, node_module_1.createRequire)(node_path_1.default.join(projectRoot, 'package.json'));
|
|
23
|
+
let packageJsonPath;
|
|
24
|
+
try {
|
|
25
|
+
packageJsonPath = require.resolve(`${packageName}/package.json`);
|
|
26
|
+
}
|
|
27
|
+
catch {
|
|
28
|
+
throw new Error(`${packageName} is not installed. ${installHint}`);
|
|
29
|
+
}
|
|
30
|
+
const packageJson = JSON.parse((0, node_fs_1.readFileSync)(packageJsonPath, 'utf8'));
|
|
31
|
+
const entry = resolveImportEntry(packageJson);
|
|
32
|
+
if (!entry) {
|
|
33
|
+
throw new Error(`${packageName} has no ESM entry point.`);
|
|
34
|
+
}
|
|
35
|
+
return (await import((0, node_url_1.pathToFileURL)(node_path_1.default.join(node_path_1.default.dirname(packageJsonPath), entry)).href));
|
|
36
|
+
};
|
|
37
|
+
exports.importFromProject = importFromProject;
|
|
38
|
+
const resolveImportEntry = (packageJson) => {
|
|
39
|
+
const exports = packageJson.exports;
|
|
40
|
+
const root = exports && typeof exports === 'object' && '.' in exports
|
|
41
|
+
? exports['.']
|
|
42
|
+
: exports;
|
|
43
|
+
return pick(root ?? null) ?? packageJson.module ?? packageJson.main;
|
|
44
|
+
};
|
|
45
|
+
/**
|
|
46
|
+
* Follows `import` (then `default`) conditions down to a file path.
|
|
47
|
+
*/
|
|
48
|
+
const pick = (value) => {
|
|
49
|
+
if (typeof value === 'string') {
|
|
50
|
+
return value;
|
|
51
|
+
}
|
|
52
|
+
if (!value) {
|
|
53
|
+
return undefined;
|
|
54
|
+
}
|
|
55
|
+
for (const condition of ['import', 'node', 'default']) {
|
|
56
|
+
if (condition in value) {
|
|
57
|
+
const found = pick(value[condition]);
|
|
58
|
+
if (found) {
|
|
59
|
+
return found;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
return undefined;
|
|
64
|
+
};
|