@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/README.md +47 -23
- package/fesm2022/blotch-angular-lynx-web-glass-ciDHw7Bw.mjs +961 -0
- package/fesm2022/blotch-angular-lynx-web-glass-ciDHw7Bw.mjs.map +1 -0
- package/fesm2022/blotch-angular-lynx.mjs +2426 -831
- package/fesm2022/blotch-angular-lynx.mjs.map +1 -1
- package/package.json +1 -1
- package/schematics/add-i18n/index.js +24 -7
- package/schematics/add-tailwind/index.js +14 -75
- package/schematics/add-tailwind/schema.json +6 -1
- package/schematics/collection.json +1 -1
- package/schematics/migrations/accessibility-traits/index.js +56 -0
- package/schematics/migrations/dev-logs-gitignore/index.js +9 -0
- package/schematics/migrations/extract-i18n-target/index.js +31 -0
- package/schematics/migrations/tailwind-html-templates/index.js +44 -0
- package/schematics/migrations.json +22 -1
- package/schematics/ng-add/index.js +20 -66
- package/schematics/ng-add/schema.json +7 -2
- package/schematics/ng-add/versions.js +8 -2
- package/schematics/utility/dev-logs-gitignore.js +25 -0
- package/schematics/utility/extract-i18n-target.js +58 -0
- package/schematics/utility/tailwind.js +167 -0
- package/types/blotch-angular-lynx.d.ts +950 -350
package/package.json
CHANGED
|
@@ -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
|
|
11
|
+
* locale from the host app's `appLocale` global prop.
|
|
11
12
|
*
|
|
12
|
-
*
|
|
13
|
-
* 1. **angular.json `i18n` block** — declares the source locale
|
|
14
|
-
* `ng extract-i18n`
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
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,
|
|
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
|
|
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
|
|
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
|
|
12
|
-
* `ng test`
|
|
13
|
-
*
|
|
14
|
-
*
|
|
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(
|
|
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
|
|
284
|
-
* The first two run rspeedy with
|
|
285
|
-
*
|
|
286
|
-
*
|
|
287
|
-
*
|
|
288
|
-
* `ng
|
|
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
|
|
19
|
+
"description": "Whether to add Tailwind CSS.",
|
|
20
20
|
"default": true,
|
|
21
21
|
"x-prompt": {
|
|
22
|
-
"message": "Would you like to add Tailwind CSS
|
|
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
|
|
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
|
|
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;
|