@blotch/angular-lynx 22.2.2 → 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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@blotch/angular-lynx",
3
- "version": "22.2.2",
3
+ "version": "22.2.3",
4
4
  "description": "Angular renderer for the Lynx cross-platform native UI framework",
5
5
  "homepage": "https://github.com/Blotch-Smart-Frames/AngularLynx#readme",
6
6
  "bugs": {
@@ -4,22 +4,26 @@ const schematics_1 = require("@angular-devkit/schematics");
4
4
  const dependency_1 = require("@schematics/angular/utility/dependency");
5
5
  const json_file_1 = require("@schematics/angular/utility/json-file");
6
6
  const workspace_1 = require("@schematics/angular/utility/workspace");
7
+ const extract_i18n_target_1 = require("../utility/extract-i18n-target");
7
8
  /**
8
9
  * Wires Angular's `@angular/localize` package into an AngularLynx app and
9
10
  * provides the Lynx-aware `provideLocale()` provider that reads the active
10
- * locale from Lynx's SystemInfo.
11
+ * locale from the host app's `appLocale` global prop.
11
12
  *
12
- * Four-step setup, each step idempotent so re-running is safe:
13
- * 1. **angular.json `i18n` block** — declares the source locale so any
14
- * `ng extract-i18n` runs produce the right XLIFF.
13
+ * Five-step setup, each step idempotent so re-running is safe:
14
+ * 1. **angular.json `i18n` block** — declares the source locale, which
15
+ * `ng extract-i18n` labels the messages with and `provideLocale()`
16
+ * falls back to.
15
17
  * 2. **`@angular/localize/init` polyfill** — must run before the app boots
16
18
  * so the global `$localize` function exists when components are
17
19
  * instantiated.
18
20
  * 3. **tsconfig `types`** — adds @angular/localize so $localize is typed
19
21
  * and TypeScript doesn't flag it as an undefined global.
20
22
  * 4. **provideLocale() in app.config** — registers the runtime provider
21
- * that reads SystemInfo.language / appLocale and applies it to Angular's
22
- * LOCALE_ID token.
23
+ * that reads `appLocale` (the apps `ng build --platform` makes send the
24
+ * phone's language) and applies it to Angular's LOCALE_ID token.
25
+ * 5. **`extract-i18n` target** — makes `ng extract-i18n` work, writing
26
+ * JSON to `src/locale`.
23
27
  */
24
28
  exports.default = (options) => async (tree) => {
25
29
  const workspace = await (0, workspace_1.getWorkspace)(tree);
@@ -33,10 +37,14 @@ exports.default = (options) => async (tree) => {
33
37
  if (!project) {
34
38
  throw new schematics_1.SchematicsException(`Project "${projectName}" not found in angular.json.`);
35
39
  }
36
- const sourceRoot = (project.sourceRoot ?? `${project.root}/src`);
40
+ // Relative to the workspace root, as angular.json's paths are: the
41
+ // extract-i18n target's outputPath is one, so a root project's must be
42
+ // `src`, not `/src`.
43
+ const sourceRoot = project.sourceRoot ?? (project.root ? `${project.root}/src` : 'src');
37
44
  const projectRoot = project.root;
38
45
  return (0, schematics_1.chain)([
39
46
  addI18nToAngularJson(projectName),
47
+ addExtractI18nTarget(projectName, sourceRoot),
40
48
  addLocalizeTypes(projectRoot),
41
49
  updateAppConfig(sourceRoot),
42
50
  addLocalizeDependency(),
@@ -72,6 +80,15 @@ const addI18nToAngularJson = (projectName) => {
72
80
  }
73
81
  };
74
82
  };
83
+ /**
84
+ * Gives the project an `extract-i18n` target (see useLynxExtractI18n) that
85
+ * writes JSON for `loadTranslations()`. `ng add` already wrote one with
86
+ * Angular's defaults (XLIFF at the workspace root), so this sets the format
87
+ * and path on it, unless the app set them itself.
88
+ */
89
+ const addExtractI18nTarget = (projectName, sourceRoot) => (0, workspace_1.updateWorkspace)((workspace) => {
90
+ (0, extract_i18n_target_1.useLynxExtractI18n)(workspace.projects.get(projectName), (0, extract_i18n_target_1.jsonMessagesOptions)(sourceRoot));
91
+ });
75
92
  const addLocalizeTypes = (projectRoot) => {
76
93
  return (tree) => {
77
94
  const tsconfigPath = `${projectRoot ? projectRoot + '/' : ''}tsconfig.app.json`;
@@ -42,15 +42,14 @@ exports.default = (options) => async (tree) => {
42
42
  const addTailwindConfig = (projectRoot) => {
43
43
  return (tree) => {
44
44
  const configPath = `${projectRoot ? projectRoot + '/' : ''}tailwind.config.ts`;
45
- // content: ['./src/**/*.ts'] scans only .ts files — AngularLynx components
46
- // inline their templates (no .html template files), so all class names
47
- // live in TypeScript source. Including .html in the glob would just slow
48
- // the watcher down without finding anything.
45
+ // The build runs Tailwind with this `content`, so it has to take in both
46
+ // inline templates (.ts) and the .html templates `ng generate component`
47
+ // writes by default, as in Angular's own Tailwind setup.
49
48
  const content = `import type { Config } from 'tailwindcss';
50
49
  import preset from '@lynx-js/tailwind-preset';
51
50
 
52
51
  const config: Config = {
53
- content: ['./src/**/*.ts'],
52
+ content: ['./src/**/*.{html,ts}'],
54
53
  presets: [preset],
55
54
  };
56
55
 
@@ -0,0 +1,56 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const workspace_1 = require("@schematics/angular/utility/workspace");
4
+ /**
5
+ * The singular name, as an attribute, a binding (`[accessibility-trait]`,
6
+ * `[attr.accessibility-trait]`) or a string, but not part of a longer name
7
+ * such as `accessibility-traits`.
8
+ */
9
+ const SINGULAR = /(?<![\w-])accessibility-trait(?![\w-])/g;
10
+ /** Folders that hold no source of the app's own. */
11
+ const SKIPPED_DIRS = new Set(['node_modules', 'dist']);
12
+ /**
13
+ * Calls `visit` with the path of every template and TypeScript file under
14
+ * `dir`, skipping dependencies, build output and dot folders.
15
+ */
16
+ const visitSources = (dir, visit) => {
17
+ for (const file of dir.subfiles) {
18
+ if (/\.(html|ts)$/.test(file) && !file.endsWith('.d.ts')) {
19
+ visit(dir.file(file).path);
20
+ }
21
+ }
22
+ for (const sub of dir.subdirs) {
23
+ if (!SKIPPED_DIRS.has(sub) && !sub.startsWith('.')) {
24
+ visitSources(dir.dir(sub), visit);
25
+ }
26
+ }
27
+ };
28
+ /**
29
+ * Renames `accessibility-trait` to `accessibility-traits` in the app's
30
+ * templates.
31
+ *
32
+ * `LYNX_ELEMENTS` declared the input in the singular, but iOS, Android and
33
+ * Harmony read only the plural, so the attribute did nothing. Now the input
34
+ * is `accessibility-traits`, and a `[accessibility-trait]` binding no longer
35
+ * compiles. This renames both the bindings and the static attributes, in
36
+ * `.html` templates and in the `.ts` files that hold inline ones.
37
+ */
38
+ exports.default = () => async (tree) => {
39
+ const workspace = await (0, workspace_1.getWorkspace)(tree);
40
+ const roots = new Set([
41
+ '',
42
+ ...[...workspace.projects.values()].map((project) => project.root),
43
+ ]);
44
+ // The workspace root takes in the projects under it, so a file can come up
45
+ // from more than one root.
46
+ const files = new Set();
47
+ for (const root of roots) {
48
+ visitSources(tree.getDir(root), (path) => files.add(path));
49
+ }
50
+ for (const file of files) {
51
+ const text = tree.readText(file);
52
+ const updated = text.replace(SINGULAR, 'accessibility-traits');
53
+ if (updated !== text)
54
+ tree.overwrite(file, updated);
55
+ }
56
+ };
@@ -0,0 +1,9 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const dev_logs_gitignore_1 = require("../../utility/dev-logs-gitignore");
4
+ /**
5
+ * Adds `.dev-logs`, where `ng serve` writes `LynxLogger`'s output, to the
6
+ * workspace's `.gitignore`. `ng add` now adds it to new apps; apps made before
7
+ * left it to the app, as the debugging guide said.
8
+ */
9
+ exports.default = () => (0, dev_logs_gitignore_1.ignoreDevLogs)();
@@ -0,0 +1,31 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const workspace_1 = require("@schematics/angular/utility/workspace");
4
+ const extract_i18n_target_1 = require("../../utility/extract-i18n-target");
5
+ /**
6
+ * Gives every AngularLynx app in the workspace an `extract-i18n` target.
7
+ *
8
+ * Angular CLI can't pick an extractor for the Lynx build builder by itself,
9
+ * and neither `ng add` nor `add-i18n` wrote a target, so `ng extract-i18n`
10
+ * failed with "Cannot determine project or target for command." Now both
11
+ * write one with the AngularLynx extract-i18n builder. This gives an app made
12
+ * before the target those schematics would write today: `add-i18n`'s (JSON
13
+ * in `src/locale`) when the app has an `i18n` block in angular.json, and
14
+ * `ng add`'s (Angular's defaults) otherwise. A target the app added by hand
15
+ * with Angular's builder, as the i18n guide showed, switches to the Lynx one,
16
+ * which also handles imported images, and its options stay as they are: the
17
+ * app already extracts the format it chose.
18
+ */
19
+ exports.default = () => (0, workspace_1.updateWorkspace)((workspace) => {
20
+ for (const project of workspace.projects.values()) {
21
+ if (project.targets.get('build')?.builder !==
22
+ '@blotch/angular-lynx-build:application') {
23
+ continue;
24
+ }
25
+ const sourceRoot = project.sourceRoot ?? (project.root ? `${project.root}/src` : 'src');
26
+ const isNew = !project.targets.has('extract-i18n');
27
+ (0, extract_i18n_target_1.useLynxExtractI18n)(project, isNew && project.extensions['i18n']
28
+ ? (0, extract_i18n_target_1.jsonMessagesOptions)(sourceRoot)
29
+ : {});
30
+ }
31
+ });
@@ -0,0 +1,44 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const workspace_1 = require("@schematics/angular/utility/workspace");
4
+ /** The Tailwind config files the build looks for. */
5
+ const CONFIG_FILES = [
6
+ 'tailwind.config.ts',
7
+ 'tailwind.config.js',
8
+ 'tailwind.config.mjs',
9
+ 'tailwind.config.cjs',
10
+ ];
11
+ /**
12
+ * The `content` glob that `ng add` and `add-tailwind` wrote, in either quote.
13
+ */
14
+ const TS_ONLY_GLOB = /(['"])\.\/src\/\*\*\/\*\.ts\1/g;
15
+ /**
16
+ * Adds `.html` templates to the Tailwind `content` glob the schematics wrote.
17
+ *
18
+ * The build used to scan the modules it bundled instead of `content`, so the
19
+ * glob didn't matter, and classes used only in `.html` templates got no CSS.
20
+ * Now the build runs Tailwind with `content`, as Angular's own Tailwind setup
21
+ * does, and the schematics write `./src/**\/*.{html,ts}`. This brings the
22
+ * glob of an app made before up to date. A glob the app changed is left
23
+ * alone.
24
+ */
25
+ exports.default = () => async (tree) => {
26
+ const workspace = await (0, workspace_1.getWorkspace)(tree);
27
+ // The schematics write the config at the project root, which is the
28
+ // workspace root for an app made by `ng new`.
29
+ const roots = new Set([
30
+ '',
31
+ ...[...workspace.projects.values()].map((project) => project.root),
32
+ ]);
33
+ for (const root of roots) {
34
+ for (const name of CONFIG_FILES) {
35
+ const file = root ? `${root}/${name}` : name;
36
+ if (!tree.exists(file))
37
+ continue;
38
+ const text = tree.readText(file);
39
+ const updated = text.replace(TS_ONLY_GLOB, (_glob, quote) => `${quote}./src/**/*.{html,ts}${quote}`);
40
+ if (updated !== text)
41
+ tree.overwrite(file, updated);
42
+ }
43
+ }
44
+ };
@@ -1,4 +1,25 @@
1
1
  {
2
2
  "$schema": "../node_modules/@angular-devkit/schematics/collection-schema.json",
3
- "schematics": {}
3
+ "schematics": {
4
+ "tailwind-html-templates": {
5
+ "version": "22.2.3",
6
+ "description": "Adds .html templates to the Tailwind content glob, which the build now reads",
7
+ "factory": "./migrations/tailwind-html-templates/index"
8
+ },
9
+ "accessibility-traits": {
10
+ "version": "22.2.3",
11
+ "description": "Renames accessibility-trait, which Lynx ignores, to accessibility-traits",
12
+ "factory": "./migrations/accessibility-traits/index"
13
+ },
14
+ "extract-i18n-target": {
15
+ "version": "22.2.3",
16
+ "description": "Adds the extract-i18n target that ng extract-i18n needs, with the AngularLynx builder",
17
+ "factory": "./migrations/extract-i18n-target/index"
18
+ },
19
+ "dev-logs-gitignore": {
20
+ "version": "22.2.3",
21
+ "description": "Adds .dev-logs, where ng serve writes LynxLogger's output, to .gitignore",
22
+ "factory": "./migrations/dev-logs-gitignore/index"
23
+ }
24
+ }
4
25
  }
@@ -4,14 +4,18 @@ const schematics_1 = require("@angular-devkit/schematics");
4
4
  const dependency_1 = require("@schematics/angular/utility/dependency");
5
5
  const json_file_1 = require("@schematics/angular/utility/json-file");
6
6
  const workspace_1 = require("@schematics/angular/utility/workspace");
7
+ const dev_logs_gitignore_1 = require("../utility/dev-logs-gitignore");
8
+ const extract_i18n_target_1 = require("../utility/extract-i18n-target");
7
9
  const versions_1 = require("./versions");
8
10
  /**
9
11
  * Transforms a standard `ng new` Angular project into a Lynx-native application.
10
12
  * The schematic replaces browser-specific bootstrap (main.ts, app.config.ts,
11
- * app.component.ts) with Lynx equivalents, points `ng build`, `ng serve` and
12
- * `ng test` at the AngularLynx builders and `ng generate` at its schematics,
13
- * and optionally sets up Tailwind with the Lynx preset. Everything else stays
14
- * as `ng new` left it: the app needs no build config besides angular.json.
13
+ * app.component.ts) with Lynx equivalents, points `ng build`, `ng serve`,
14
+ * `ng test` and `ng extract-i18n` at the AngularLynx builders and
15
+ * `ng generate` at its schematics, git-ignores the `.dev-logs` file that
16
+ * `ng serve` writes `LynxLogger`'s output to, and optionally sets up Tailwind
17
+ * with the Lynx preset. Everything else stays as `ng new` left it: the app
18
+ * needs no build config besides angular.json.
15
19
  * This is a one-shot destructive transform — the old browser-Angular files
16
20
  * are deleted because they reference DOM APIs and HTML elements that don't
17
21
  * exist on the Lynx runtime.
@@ -48,6 +52,7 @@ exports.default = (options) => async (tree) => {
48
52
  useLynxBuilders(projectName),
49
53
  useLynxSchematics(),
50
54
  updatePackageScripts(),
55
+ (0, dev_logs_gitignore_1.ignoreDevLogs)(),
51
56
  addCoreDependencies(),
52
57
  ];
53
58
  // `"false"` is how `ng add ... --tailwind=false` arrives (see schema.ts).
@@ -280,12 +285,14 @@ const withLynxBudgets = (options) => {
280
285
  };
281
286
  };
282
287
  /**
283
- * Points `ng build`, `ng serve` and `ng test` at the AngularLynx builders.
284
- * The first two run rspeedy with @blotch/rsbuild-plugin-angular-lynx added,
285
- * and `ng build --platform ios|android` also produces the native app; the
286
- * third runs Angular's unit-test builder set up for Lynx. The build target
287
- * keeps `ng new`'s Angular compiler options, which the build plugin and
288
- * `ng test` read, and gains the app's identity for native builds.
288
+ * Points `ng build`, `ng serve`, `ng test` and `ng extract-i18n` at the
289
+ * AngularLynx builders. The first two run rspeedy with
290
+ * @blotch/rsbuild-plugin-angular-lynx added, and
291
+ * `ng build --platform ios|android` also produces the native app; the other
292
+ * two run Angular's unit-test and extract-i18n builders set up for Lynx. The
293
+ * build target keeps `ng new`'s Angular compiler options, which the build
294
+ * plugin, `ng test` and `ng extract-i18n` read, and gains the app's identity
295
+ * for native builds.
289
296
  */
290
297
  const useLynxBuilders = (projectName) => (0, workspace_1.updateWorkspace)((workspace) => {
291
298
  const project = workspace.projects.get(projectName);
@@ -365,6 +372,7 @@ const useLynxBuilders = (projectName) => (0, workspace_1.updateWorkspace)((works
365
372
  defaultConfiguration: test.defaultConfiguration,
366
373
  }),
367
374
  });
375
+ (0, extract_i18n_target_1.useLynxExtractI18n)(project);
368
376
  });
369
377
  /**
370
378
  * Makes `ng generate` reach AngularLynx's schematics first, so
@@ -422,7 +430,7 @@ const addTailwindConfig = (projectRoot) => {
422
430
  import preset from '@lynx-js/tailwind-preset';
423
431
 
424
432
  const config: Config = {
425
- content: ['./src/**/*.ts'],
433
+ content: ['./src/**/*.{html,ts}'],
426
434
  presets: [preset],
427
435
  };
428
436
 
@@ -0,0 +1,25 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.ignoreDevLogs = void 0;
4
+ /** An entry that already ignores `.dev-logs`, anchored or not. */
5
+ const DEV_LOGS_ENTRY = /^\/?\.dev-logs\/?\s*$/m;
6
+ /**
7
+ * Adds `.dev-logs` to the workspace's `.gitignore`. `ng serve` writes
8
+ * `LynxLogger`'s output to that file, in the folder it runs from, and it
9
+ * shouldn't be committed. The entry has no leading slash, so it matches
10
+ * wherever the file is.
11
+ *
12
+ * A workspace without a `.gitignore` (`ng new --skip-git`) is left alone,
13
+ * and so is one that ignores the file already.
14
+ */
15
+ const ignoreDevLogs = () => (tree) => {
16
+ const file = '/.gitignore';
17
+ if (!tree.exists(file))
18
+ return;
19
+ const text = tree.readText(file);
20
+ if (DEV_LOGS_ENTRY.test(text))
21
+ return;
22
+ const separator = text === '' || text.endsWith('\n') ? '' : '\n';
23
+ tree.overwrite(file, `${text}${separator}\n# LynxLogger's output while \`ng serve\` runs\n.dev-logs\n`);
24
+ };
25
+ exports.ignoreDevLogs = ignoreDevLogs;
@@ -0,0 +1,58 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.jsonMessagesOptions = exports.useLynxExtractI18n = void 0;
4
+ const LYNX_EXTRACT_I18N_BUILDER = '@blotch/angular-lynx-build:extract-i18n';
5
+ /**
6
+ * Angular's extract-i18n builders. The Lynx builder hands its options to
7
+ * `@angular/build:extract-i18n`, and `@angular-devkit/build-angular`'s takes
8
+ * the same ones, so a target of either works with it unchanged.
9
+ */
10
+ const ANGULAR_EXTRACT_I18N_BUILDERS = [
11
+ '@angular/build:extract-i18n',
12
+ '@angular-devkit/build-angular:extract-i18n',
13
+ ];
14
+ /**
15
+ * Points a project's `extract-i18n` target at the AngularLynx builder, so
16
+ * `ng extract-i18n` works. Angular CLI picks an extractor by itself only for
17
+ * a project built with one of Angular's own builders; for the Lynx build
18
+ * builder it fails with "Cannot determine project or target for command."
19
+ * Angular's extractor would fail anyway on the first imported image, which
20
+ * the Lynx builder sets it up for.
21
+ *
22
+ * `options` are set where the target doesn't set them already, so a format
23
+ * or path the app chose stays. A target of Angular's switches builder; a
24
+ * target with any other builder is the app's own choice, and stays as it is.
25
+ */
26
+ const useLynxExtractI18n = (project, options = {}) => {
27
+ const target = project.targets.get('extract-i18n');
28
+ if (!target) {
29
+ project.targets.add({
30
+ name: 'extract-i18n',
31
+ builder: LYNX_EXTRACT_I18N_BUILDER,
32
+ ...(Object.keys(options).length > 0 && { options }),
33
+ });
34
+ return;
35
+ }
36
+ if (target.builder !== LYNX_EXTRACT_I18N_BUILDER &&
37
+ !ANGULAR_EXTRACT_I18N_BUILDERS.includes(target.builder)) {
38
+ return;
39
+ }
40
+ target.builder = LYNX_EXTRACT_I18N_BUILDER;
41
+ const missing = Object.entries(options).filter(([name]) => !(name in (target.options ?? {})));
42
+ if (missing.length > 0) {
43
+ target.options = { ...target.options, ...Object.fromEntries(missing) };
44
+ }
45
+ };
46
+ exports.useLynxExtractI18n = useLynxExtractI18n;
47
+ /**
48
+ * The options `add-i18n` gives the `extract-i18n` target: the messages go as
49
+ * JSON to `<sourceRoot>/locale/messages.json`, where Angular's i18n guide
50
+ * keeps translation files. Angular's default format is XLIFF, but an app on
51
+ * Lynx loads its translations at runtime with `loadTranslations()`, which
52
+ * takes the JSON file's `translations` object as it is.
53
+ */
54
+ const jsonMessagesOptions = (sourceRoot) => ({
55
+ format: 'json',
56
+ outputPath: `${sourceRoot}/locale`,
57
+ });
58
+ exports.jsonMessagesOptions = jsonMessagesOptions;