@blotch/angular-lynx 22.2.2 → 22.2.4

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.4",
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`;
@@ -1,23 +1,26 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  const schematics_1 = require("@angular-devkit/schematics");
4
- const dependency_1 = require("@schematics/angular/utility/dependency");
5
4
  const workspace_1 = require("@schematics/angular/utility/workspace");
6
- const versions_1 = require("../ng-add/versions");
5
+ const tailwind_1 = require("../utility/tailwind");
7
6
  /**
8
7
  * Adds Tailwind CSS to an existing AngularLynx project. This is split out
9
8
  * from `ng-add` because users may opt out of Tailwind at scaffold time
10
9
  * (`ng add @blotch/angular-lynx --tailwind=false`) but later change their
11
10
  * minds — running this schematic later is the supported recovery path.
12
11
  *
13
- * Three steps, each idempotent:
14
- * 1. **tailwind.config.ts** — written with `@lynx-js/tailwind-preset` as
15
- * the only preset. The preset narrows Tailwind's class output to the
16
- * subset Lynx's CSS engine can parse (no logical properties, no
17
- * `:has()` selectors, no advanced color functions).
18
- * 2. **styles.css/scss** — replaced with the Tailwind base + utilities
19
- * layer directives (no components layer; see ng-add for why).
20
- * 3. **package.json deps** — adds tailwindcss + the Lynx preset.
12
+ * It sets up the Tailwind version the project already depends on, else
13
+ * Tailwind 4, or the one `--tailwind-version` asks for:
14
+ *
15
+ * - **Tailwind 4**, as Angular CLI's own Tailwind schematic does: the
16
+ * stylesheet imports Tailwind (without its Preflight reset), and
17
+ * `.postcssrc.json` runs `@tailwindcss/postcss`. The build plugin rewrites
18
+ * the CSS it writes into CSS Lynx reads.
19
+ * - **Tailwind 3**, with `@lynx-js/tailwind-preset`, which narrows Tailwind's
20
+ * output to the CSS Lynx reads: `tailwind.config.ts` and the `@tailwind`
21
+ * directives.
22
+ *
23
+ * Each step is idempotent.
21
24
  */
22
25
  exports.default = (options) => async (tree) => {
23
26
  const workspace = await (0, workspace_1.getWorkspace)(tree);
@@ -33,69 +36,5 @@ exports.default = (options) => async (tree) => {
33
36
  }
34
37
  const sourceRoot = (project.sourceRoot ?? `${project.root}/src`);
35
38
  const projectRoot = project.root;
36
- return (0, schematics_1.chain)([
37
- addTailwindConfig(projectRoot),
38
- updateStylesCss(sourceRoot),
39
- addTailwindDependencies(),
40
- ]);
41
- };
42
- const addTailwindConfig = (projectRoot) => {
43
- return (tree) => {
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.
49
- const content = `import type { Config } from 'tailwindcss';
50
- import preset from '@lynx-js/tailwind-preset';
51
-
52
- const config: Config = {
53
- content: ['./src/**/*.ts'],
54
- presets: [preset],
55
- };
56
-
57
- export default config;
58
- `;
59
- if (tree.exists(configPath)) {
60
- tree.overwrite(configPath, content);
61
- }
62
- else {
63
- tree.create(configPath, content);
64
- }
65
- };
66
- };
67
- const updateStylesCss = (sourceRoot) => {
68
- return (tree) => {
69
- const cssPath = `${sourceRoot}/styles.css`;
70
- const scssPath = `${sourceRoot}/styles.scss`;
71
- // Prefer styles.scss if it exists (the user picked SCSS at `ng new` time)
72
- // — overwriting it with .css content would break their other imports.
73
- const targetPath = tree.exists(scssPath) ? scssPath : cssPath;
74
- // Only @tailwind base + utilities — no @tailwind components. Lynx's CSS
75
- // engine doesn't parse many component-layer reset rules (focus-visible
76
- // pseudo-class, form input pseudo-classes), which would produce
77
- // diagnostic noise at build time. The base layer ships with the Lynx
78
- // preset's own compatibility-safe normalize rules.
79
- const content = `@tailwind base;
80
- @tailwind utilities;
81
- `;
82
- if (tree.exists(targetPath)) {
83
- tree.overwrite(targetPath, content);
84
- }
85
- else {
86
- tree.create(cssPath, content);
87
- }
88
- };
89
- };
90
- const addTailwindDependencies = () => {
91
- return (0, schematics_1.chain)([
92
- (0, dependency_1.addDependency)('tailwindcss', versions_1.VERSIONS.tailwindcss, {
93
- type: dependency_1.DependencyType.Dev,
94
- install: dependency_1.InstallBehavior.Auto,
95
- }),
96
- (0, dependency_1.addDependency)('@lynx-js/tailwind-preset', versions_1.VERSIONS.tailwindPreset, {
97
- type: dependency_1.DependencyType.Dev,
98
- install: dependency_1.InstallBehavior.Auto,
99
- }),
100
- ]);
39
+ return (0, tailwind_1.setUpTailwind)({ projectName, projectRoot, sourceRoot }, (0, tailwind_1.tailwindVersionFor)(tree, options.tailwindVersion));
101
40
  };
@@ -4,7 +4,7 @@
4
4
  "title": "AngularLynx add-tailwind schematic",
5
5
  "type": "object",
6
6
  "additionalProperties": false,
7
- "description": "Adds Tailwind CSS with @lynx-js/tailwind-preset to an existing AngularLynx project.",
7
+ "description": "Adds Tailwind CSS to an existing AngularLynx project: Tailwind 4 by default, or Tailwind 3 with @lynx-js/tailwind-preset.",
8
8
  "properties": {
9
9
  "project": {
10
10
  "type": "string",
@@ -12,6 +12,11 @@
12
12
  "$default": {
13
13
  "$source": "projectName"
14
14
  }
15
+ },
16
+ "tailwindVersion": {
17
+ "type": ["string", "number"],
18
+ "enum": ["4", "3", 4, 3],
19
+ "description": "The Tailwind CSS major version to set up: 4 (with @tailwindcss/postcss), or 3 (with @lynx-js/tailwind-preset). Defaults to the version the project already depends on, else 4."
15
20
  }
16
21
  },
17
22
  "required": []
@@ -6,7 +6,7 @@
6
6
  "schema": "./ng-add/schema.json"
7
7
  },
8
8
  "add-tailwind": {
9
- "description": "Adds Tailwind CSS with @lynx-js/tailwind-preset to an existing project",
9
+ "description": "Adds Tailwind CSS to an existing project: Tailwind 4, or Tailwind 3 with @lynx-js/tailwind-preset",
10
10
  "factory": "./add-tailwind/index",
11
11
  "schema": "./add-tailwind/schema.json"
12
12
  },
@@ -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,19 @@ 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 tailwind_1 = require("../utility/tailwind");
9
+ const extract_i18n_target_1 = require("../utility/extract-i18n-target");
7
10
  const versions_1 = require("./versions");
8
11
  /**
9
12
  * Transforms a standard `ng new` Angular project into a Lynx-native application.
10
13
  * 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.
14
+ * app.component.ts) with Lynx equivalents, points `ng build`, `ng serve`,
15
+ * `ng test` and `ng extract-i18n` at the AngularLynx builders and
16
+ * `ng generate` at its schematics, git-ignores the `.dev-logs` file that
17
+ * `ng serve` writes `LynxLogger`'s output to, and optionally sets up Tailwind
18
+ * CSS (4, or 3 with the Lynx preset). Everything else stays as `ng new` left
19
+ * it: the app needs no build config besides angular.json.
15
20
  * This is a one-shot destructive transform — the old browser-Angular files
16
21
  * are deleted because they reference DOM APIs and HTML elements that don't
17
22
  * exist on the Lynx runtime.
@@ -48,11 +53,12 @@ exports.default = (options) => async (tree) => {
48
53
  useLynxBuilders(projectName),
49
54
  useLynxSchematics(),
50
55
  updatePackageScripts(),
56
+ (0, dev_logs_gitignore_1.ignoreDevLogs)(),
51
57
  addCoreDependencies(),
52
58
  ];
53
59
  // `"false"` is how `ng add ... --tailwind=false` arrives (see schema.ts).
54
60
  if (options.tailwind !== false && options.tailwind !== 'false') {
55
- rules.push(addTailwindConfig(projectRoot), updateStylesCss(sourceRoot), addTailwindDependencies());
61
+ rules.push((0, tailwind_1.setUpTailwind)({ projectName, projectRoot, sourceRoot }, (0, tailwind_1.tailwindVersionFor)(tree, options.tailwindVersion)));
56
62
  }
57
63
  return (0, schematics_1.chain)(rules);
58
64
  };
@@ -280,12 +286,14 @@ const withLynxBudgets = (options) => {
280
286
  };
281
287
  };
282
288
  /**
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.
289
+ * Points `ng build`, `ng serve`, `ng test` and `ng extract-i18n` at the
290
+ * AngularLynx builders. The first two run rspeedy with
291
+ * @blotch/rsbuild-plugin-angular-lynx added, and
292
+ * `ng build --platform ios|android` also produces the native app; the other
293
+ * two run Angular's unit-test and extract-i18n builders set up for Lynx. The
294
+ * build target keeps `ng new`'s Angular compiler options, which the build
295
+ * plugin, `ng test` and `ng extract-i18n` read, and gains the app's identity
296
+ * for native builds.
289
297
  */
290
298
  const useLynxBuilders = (projectName) => (0, workspace_1.updateWorkspace)((workspace) => {
291
299
  const project = workspace.projects.get(projectName);
@@ -365,6 +373,7 @@ const useLynxBuilders = (projectName) => (0, workspace_1.updateWorkspace)((works
365
373
  defaultConfiguration: test.defaultConfiguration,
366
374
  }),
367
375
  });
376
+ (0, extract_i18n_target_1.useLynxExtractI18n)(project);
368
377
  });
369
378
  /**
370
379
  * Makes `ng generate` reach AngularLynx's schematics first, so
@@ -415,58 +424,3 @@ const addCoreDependencies = () => {
415
424
  }),
416
425
  ]);
417
426
  };
418
- const addTailwindConfig = (projectRoot) => {
419
- return (tree) => {
420
- const configPath = `${projectRoot ? projectRoot + '/' : ''}tailwind.config.ts`;
421
- const content = `import type { Config } from 'tailwindcss';
422
- import preset from '@lynx-js/tailwind-preset';
423
-
424
- const config: Config = {
425
- content: ['./src/**/*.ts'],
426
- presets: [preset],
427
- };
428
-
429
- export default config;
430
- `;
431
- if (tree.exists(configPath)) {
432
- tree.overwrite(configPath, content);
433
- }
434
- else {
435
- tree.create(configPath, content);
436
- }
437
- };
438
- };
439
- const updateStylesCss = (sourceRoot) => {
440
- return (tree) => {
441
- const cssPath = `${sourceRoot}/styles.css`;
442
- const scssPath = `${sourceRoot}/styles.scss`;
443
- const targetPath = tree.exists(scssPath) ? scssPath : cssPath;
444
- // Only @tailwind base + utilities — no @tailwind components. Lynx doesn't
445
- // support all of Tailwind's component-layer reset rules (e.g. button
446
- // resets that reference focus-visible, form input pseudo-classes), and
447
- // bundling them in would generate CSS the runtime can't parse, producing
448
- // diagnostic noise. The base layer ships with @lynx-js/tailwind-preset's
449
- // own Lynx-compatible normalize rules.
450
- const content = `@tailwind base;
451
- @tailwind utilities;
452
- `;
453
- if (tree.exists(targetPath)) {
454
- tree.overwrite(targetPath, content);
455
- }
456
- else {
457
- tree.create(cssPath, content);
458
- }
459
- };
460
- };
461
- const addTailwindDependencies = () => {
462
- return (0, schematics_1.chain)([
463
- (0, dependency_1.addDependency)('tailwindcss', versions_1.VERSIONS.tailwindcss, {
464
- type: dependency_1.DependencyType.Dev,
465
- install: dependency_1.InstallBehavior.Auto,
466
- }),
467
- (0, dependency_1.addDependency)('@lynx-js/tailwind-preset', versions_1.VERSIONS.tailwindPreset, {
468
- type: dependency_1.DependencyType.Dev,
469
- install: dependency_1.InstallBehavior.Auto,
470
- }),
471
- ]);
472
- };
@@ -16,12 +16,17 @@
16
16
  "tailwind": {
17
17
  "type": ["boolean", "string"],
18
18
  "pattern": "^(true|false)$",
19
- "description": "Whether to add Tailwind CSS with @lynx-js/tailwind-preset.",
19
+ "description": "Whether to add Tailwind CSS.",
20
20
  "default": true,
21
21
  "x-prompt": {
22
- "message": "Would you like to add Tailwind CSS with Lynx preset?",
22
+ "message": "Would you like to add Tailwind CSS?",
23
23
  "type": "confirmation"
24
24
  }
25
+ },
26
+ "tailwindVersion": {
27
+ "type": ["string", "number"],
28
+ "enum": ["4", "3", 4, 3],
29
+ "description": "The Tailwind CSS major version to set up: 4 (with @tailwindcss/postcss), or 3 (with @lynx-js/tailwind-preset). Defaults to the version the project already depends on, else 4."
25
30
  }
26
31
  },
27
32
  "required": []
@@ -19,11 +19,17 @@ exports.VERSIONS = {
19
19
  */
20
20
  lynxTypes: '^4.3.0',
21
21
  /**
22
- * tailwindcss — utility-first CSS framework (optional, controlled by the `tailwind` schema option)
22
+ * tailwindcss and @tailwindcss/postcss, for Tailwind CSS 4: the default when
23
+ * the `tailwind` schema option is on. The build plugin rewrites what it
24
+ * writes into CSS Lynx reads.
25
+ */
26
+ tailwindcss4: '^4.3.3',
27
+ /**
28
+ * tailwindcss, for Tailwind CSS 3 (`--tailwind-version=3`)
23
29
  */
24
30
  tailwindcss: '^3.4.19',
25
31
  /**
26
- * @lynx-js/tailwind-preset — Lynx-specific Tailwind preset with native design tokens
32
+ * @lynx-js/tailwind-preset — Lynx's preset for Tailwind CSS 3
27
33
  */
28
34
  tailwindPreset: '^0.6.0',
29
35
  /**
@@ -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;