@blotch/angular-lynx 22.2.1 → 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 +3 -1
- package/fesm2022/blotch-angular-lynx-web-glass-8MFrhPz0.mjs +951 -0
- package/fesm2022/blotch-angular-lynx-web-glass-8MFrhPz0.mjs.map +1 -0
- package/fesm2022/blotch-angular-lynx.mjs +8084 -0
- package/fesm2022/blotch-angular-lynx.mjs.map +1 -0
- package/package.json +11 -6
- package/schematics/add-i18n/index.js +168 -0
- package/schematics/add-i18n/schema.js +2 -0
- package/schematics/add-tailwind/index.js +100 -0
- package/schematics/add-tailwind/schema.js +2 -0
- package/schematics/add-testing/index.js +80 -0
- package/schematics/add-testing/schema.js +2 -0
- package/schematics/collection.json +7 -2
- package/schematics/component/index.js +110 -0
- package/schematics/component/schema.js +2 -0
- package/schematics/component/schema.json +102 -25
- package/schematics/eject/index.js +87 -0
- package/schematics/eject/schema.js +2 -0
- package/schematics/eject/schema.json +24 -0
- package/schematics/files/src/rspeedy-env.d.ts.template +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 +25 -0
- package/schematics/ng-add/index.js +480 -0
- package/schematics/ng-add/schema.js +2 -0
- package/schematics/ng-add/schema.json +6 -2
- package/schematics/ng-add/versions.js +57 -0
- package/schematics/package.json +3 -0
- package/schematics/utility/dev-logs-gitignore.js +25 -0
- package/schematics/utility/extract-i18n-target.js +58 -0
- package/types/blotch-angular-lynx.d.ts +4789 -0
- package/schematics/files/lynx.config.ts.template +0 -17
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@blotch/angular-lynx",
|
|
3
|
-
"version": "22.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": {
|
|
@@ -12,11 +12,6 @@
|
|
|
12
12
|
"url": "git+https://github.com/Blotch-Smart-Frames/AngularLynx.git",
|
|
13
13
|
"directory": "packages/runtime"
|
|
14
14
|
},
|
|
15
|
-
"files": [
|
|
16
|
-
"dist",
|
|
17
|
-
"schematics",
|
|
18
|
-
"README.md"
|
|
19
|
-
],
|
|
20
15
|
"sideEffects": false,
|
|
21
16
|
"exports": {
|
|
22
17
|
"./package.json": {
|
|
@@ -46,6 +41,16 @@
|
|
|
46
41
|
"optional": true
|
|
47
42
|
}
|
|
48
43
|
},
|
|
44
|
+
"ng-update": {
|
|
45
|
+
"migrations": "./schematics/migrations.json",
|
|
46
|
+
"packageGroup": [
|
|
47
|
+
"@blotch/angular-lynx",
|
|
48
|
+
"@blotch/angular-lynx-build",
|
|
49
|
+
"@blotch/angular-lynx-testing-library",
|
|
50
|
+
"@blotch/rsbuild-plugin-angular-lynx",
|
|
51
|
+
"@blotch/angular-lynx-native"
|
|
52
|
+
]
|
|
53
|
+
},
|
|
49
54
|
"schematics": "./schematics/collection.json",
|
|
50
55
|
"module": "fesm2022/blotch-angular-lynx.mjs",
|
|
51
56
|
"typings": "types/blotch-angular-lynx.d.ts",
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const schematics_1 = require("@angular-devkit/schematics");
|
|
4
|
+
const dependency_1 = require("@schematics/angular/utility/dependency");
|
|
5
|
+
const json_file_1 = require("@schematics/angular/utility/json-file");
|
|
6
|
+
const workspace_1 = require("@schematics/angular/utility/workspace");
|
|
7
|
+
const extract_i18n_target_1 = require("../utility/extract-i18n-target");
|
|
8
|
+
/**
|
|
9
|
+
* Wires Angular's `@angular/localize` package into an AngularLynx app and
|
|
10
|
+
* provides the Lynx-aware `provideLocale()` provider that reads the active
|
|
11
|
+
* locale from the host app's `appLocale` global prop.
|
|
12
|
+
*
|
|
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.
|
|
17
|
+
* 2. **`@angular/localize/init` polyfill** — must run before the app boots
|
|
18
|
+
* so the global `$localize` function exists when components are
|
|
19
|
+
* instantiated.
|
|
20
|
+
* 3. **tsconfig `types`** — adds @angular/localize so $localize is typed
|
|
21
|
+
* and TypeScript doesn't flag it as an undefined global.
|
|
22
|
+
* 4. **provideLocale() in app.config** — registers the runtime provider
|
|
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`.
|
|
27
|
+
*/
|
|
28
|
+
exports.default = (options) => async (tree) => {
|
|
29
|
+
const workspace = await (0, workspace_1.getWorkspace)(tree);
|
|
30
|
+
const projectName = options.project ??
|
|
31
|
+
workspace.extensions['defaultProject'] ??
|
|
32
|
+
[...workspace.projects.keys()][0];
|
|
33
|
+
if (!projectName) {
|
|
34
|
+
throw new schematics_1.SchematicsException('No project found. Run this schematic inside an Angular workspace.');
|
|
35
|
+
}
|
|
36
|
+
const project = workspace.projects.get(projectName);
|
|
37
|
+
if (!project) {
|
|
38
|
+
throw new schematics_1.SchematicsException(`Project "${projectName}" not found in angular.json.`);
|
|
39
|
+
}
|
|
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');
|
|
44
|
+
const projectRoot = project.root;
|
|
45
|
+
return (0, schematics_1.chain)([
|
|
46
|
+
addI18nToAngularJson(projectName),
|
|
47
|
+
addExtractI18nTarget(projectName, sourceRoot),
|
|
48
|
+
addLocalizeTypes(projectRoot),
|
|
49
|
+
updateAppConfig(sourceRoot),
|
|
50
|
+
addLocalizeDependency(),
|
|
51
|
+
]);
|
|
52
|
+
};
|
|
53
|
+
const addI18nToAngularJson = (projectName) => {
|
|
54
|
+
return (tree) => {
|
|
55
|
+
const angularJson = new json_file_1.JSONFile(tree, '/angular.json');
|
|
56
|
+
const existingI18n = angularJson.get(['projects', projectName, 'i18n']);
|
|
57
|
+
if (!existingI18n) {
|
|
58
|
+
angularJson.modify(['projects', projectName, 'i18n'], {
|
|
59
|
+
sourceLocale: 'en-US',
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
// `@angular/localize/init` must be in `polyfills`, not a regular import.
|
|
63
|
+
// It patches the global `$localize` function before the app boots — if
|
|
64
|
+
// it's loaded lazily via a normal import, some translated strings can be
|
|
65
|
+
// called before the patch is in place and throw at runtime.
|
|
66
|
+
const polyfillsPath = [
|
|
67
|
+
'projects',
|
|
68
|
+
projectName,
|
|
69
|
+
'architect',
|
|
70
|
+
'build',
|
|
71
|
+
'options',
|
|
72
|
+
'polyfills',
|
|
73
|
+
];
|
|
74
|
+
const polyfills = angularJson.get(polyfillsPath) ?? [];
|
|
75
|
+
if (!polyfills.includes('@angular/localize/init')) {
|
|
76
|
+
angularJson.modify(polyfillsPath, [
|
|
77
|
+
...polyfills,
|
|
78
|
+
'@angular/localize/init',
|
|
79
|
+
]);
|
|
80
|
+
}
|
|
81
|
+
};
|
|
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
|
+
});
|
|
92
|
+
const addLocalizeTypes = (projectRoot) => {
|
|
93
|
+
return (tree) => {
|
|
94
|
+
const tsconfigPath = `${projectRoot ? projectRoot + '/' : ''}tsconfig.app.json`;
|
|
95
|
+
if (!tree.exists(tsconfigPath)) {
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
const tsconfig = new json_file_1.JSONFile(tree, tsconfigPath);
|
|
99
|
+
const types = tsconfig.get(['compilerOptions', 'types']) ?? [];
|
|
100
|
+
if (!types.includes('@angular/localize')) {
|
|
101
|
+
tsconfig.modify(['compilerOptions', 'types'], [...types, '@angular/localize']);
|
|
102
|
+
}
|
|
103
|
+
};
|
|
104
|
+
};
|
|
105
|
+
const updateAppConfig = (sourceRoot) => {
|
|
106
|
+
return (tree) => {
|
|
107
|
+
const configPath = `${sourceRoot}/app/app.config.ts`;
|
|
108
|
+
if (!tree.exists(configPath)) {
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
let content = tree.readText(configPath);
|
|
112
|
+
// Idempotency guard: if the user has already imported provideLocale
|
|
113
|
+
// (either from a previous run or by hand), bail out — re-running would
|
|
114
|
+
// duplicate the import and the providers entry, breaking the file.
|
|
115
|
+
if (content.includes('provideLocale')) {
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
// Two paths for adding the symbol to imports:
|
|
119
|
+
// - If `@blotch/angular-lynx` is already imported (the common case
|
|
120
|
+
// after `ng add @blotch/angular-lynx`), append provideLocale to the
|
|
121
|
+
// existing destructured import list. We split on `,` and rebuild so
|
|
122
|
+
// trailing commas and whitespace are normalized regardless of the
|
|
123
|
+
// user's formatting preferences.
|
|
124
|
+
// - Otherwise prepend a brand-new import line at the top of the file.
|
|
125
|
+
// This branch shouldn't normally fire (add-i18n is meant to run on
|
|
126
|
+
// projects that already went through ng-add), but it keeps the
|
|
127
|
+
// schematic self-contained if someone runs it standalone.
|
|
128
|
+
if (content.includes("from '@blotch/angular-lynx'")) {
|
|
129
|
+
content = content.replace(/import\s*\{([^}]*)\}\s*from\s*['"]@blotch\/angular-lynx['"]/, (match, imports) => {
|
|
130
|
+
const importList = imports
|
|
131
|
+
.split(',')
|
|
132
|
+
.map((s) => s.trim())
|
|
133
|
+
.filter(Boolean);
|
|
134
|
+
importList.push('provideLocale');
|
|
135
|
+
return `import { ${importList.join(', ')} } from '@blotch/angular-lynx'`;
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
else {
|
|
139
|
+
content =
|
|
140
|
+
`import { provideLocale } from '@blotch/angular-lynx';\n` + content;
|
|
141
|
+
}
|
|
142
|
+
// Insert provideLocale() as the FIRST provider so it runs before
|
|
143
|
+
// anything that might call $localize during construction (e.g. a service
|
|
144
|
+
// that formats a date with a localized pattern at module load time).
|
|
145
|
+
// The regex matches `providers: [` and inserts on a new line right
|
|
146
|
+
// after, preserving the user's indentation/style for the rest of the array.
|
|
147
|
+
content = content.replace(/providers:\s*\[/, 'providers: [\n provideLocale(),');
|
|
148
|
+
tree.overwrite(configPath, content);
|
|
149
|
+
};
|
|
150
|
+
};
|
|
151
|
+
const addLocalizeDependency = () => {
|
|
152
|
+
return (tree) => {
|
|
153
|
+
// @angular/localize must match the rest of Angular, so it takes the app's
|
|
154
|
+
// own @angular/core range, as Angular Material's `ng add` does for the
|
|
155
|
+
// Angular packages it adds. A range written out here would install the
|
|
156
|
+
// wrong major once the app moves to the next Angular.
|
|
157
|
+
const pkg = new json_file_1.JSONFile(tree, '/package.json');
|
|
158
|
+
const coreRange = pkg.get(['dependencies', '@angular/core']) ??
|
|
159
|
+
pkg.get(['devDependencies', '@angular/core']);
|
|
160
|
+
if (typeof coreRange !== 'string') {
|
|
161
|
+
throw new schematics_1.SchematicsException("package.json doesn't list @angular/core, so add-i18n can't tell which @angular/localize version matches the app's Angular.");
|
|
162
|
+
}
|
|
163
|
+
return (0, dependency_1.addDependency)('@angular/localize', coreRange, {
|
|
164
|
+
type: dependency_1.DependencyType.Dev,
|
|
165
|
+
install: dependency_1.InstallBehavior.Auto,
|
|
166
|
+
});
|
|
167
|
+
};
|
|
168
|
+
};
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const schematics_1 = require("@angular-devkit/schematics");
|
|
4
|
+
const dependency_1 = require("@schematics/angular/utility/dependency");
|
|
5
|
+
const workspace_1 = require("@schematics/angular/utility/workspace");
|
|
6
|
+
const versions_1 = require("../ng-add/versions");
|
|
7
|
+
/**
|
|
8
|
+
* Adds Tailwind CSS to an existing AngularLynx project. This is split out
|
|
9
|
+
* from `ng-add` because users may opt out of Tailwind at scaffold time
|
|
10
|
+
* (`ng add @blotch/angular-lynx --tailwind=false`) but later change their
|
|
11
|
+
* minds — running this schematic later is the supported recovery path.
|
|
12
|
+
*
|
|
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.
|
|
21
|
+
*/
|
|
22
|
+
exports.default = (options) => async (tree) => {
|
|
23
|
+
const workspace = await (0, workspace_1.getWorkspace)(tree);
|
|
24
|
+
const projectName = options.project ??
|
|
25
|
+
workspace.extensions['defaultProject'] ??
|
|
26
|
+
[...workspace.projects.keys()][0];
|
|
27
|
+
if (!projectName) {
|
|
28
|
+
throw new schematics_1.SchematicsException('No project found. Run this schematic inside an Angular workspace.');
|
|
29
|
+
}
|
|
30
|
+
const project = workspace.projects.get(projectName);
|
|
31
|
+
if (!project) {
|
|
32
|
+
throw new schematics_1.SchematicsException(`Project "${projectName}" not found in angular.json.`);
|
|
33
|
+
}
|
|
34
|
+
const sourceRoot = (project.sourceRoot ?? `${project.root}/src`);
|
|
35
|
+
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
|
+
// 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.
|
|
48
|
+
const content = `import type { Config } from 'tailwindcss';
|
|
49
|
+
import preset from '@lynx-js/tailwind-preset';
|
|
50
|
+
|
|
51
|
+
const config: Config = {
|
|
52
|
+
content: ['./src/**/*.{html,ts}'],
|
|
53
|
+
presets: [preset],
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
export default config;
|
|
57
|
+
`;
|
|
58
|
+
if (tree.exists(configPath)) {
|
|
59
|
+
tree.overwrite(configPath, content);
|
|
60
|
+
}
|
|
61
|
+
else {
|
|
62
|
+
tree.create(configPath, content);
|
|
63
|
+
}
|
|
64
|
+
};
|
|
65
|
+
};
|
|
66
|
+
const updateStylesCss = (sourceRoot) => {
|
|
67
|
+
return (tree) => {
|
|
68
|
+
const cssPath = `${sourceRoot}/styles.css`;
|
|
69
|
+
const scssPath = `${sourceRoot}/styles.scss`;
|
|
70
|
+
// Prefer styles.scss if it exists (the user picked SCSS at `ng new` time)
|
|
71
|
+
// — overwriting it with .css content would break their other imports.
|
|
72
|
+
const targetPath = tree.exists(scssPath) ? scssPath : cssPath;
|
|
73
|
+
// Only @tailwind base + utilities — no @tailwind components. Lynx's CSS
|
|
74
|
+
// engine doesn't parse many component-layer reset rules (focus-visible
|
|
75
|
+
// pseudo-class, form input pseudo-classes), which would produce
|
|
76
|
+
// diagnostic noise at build time. The base layer ships with the Lynx
|
|
77
|
+
// preset's own compatibility-safe normalize rules.
|
|
78
|
+
const content = `@tailwind base;
|
|
79
|
+
@tailwind utilities;
|
|
80
|
+
`;
|
|
81
|
+
if (tree.exists(targetPath)) {
|
|
82
|
+
tree.overwrite(targetPath, content);
|
|
83
|
+
}
|
|
84
|
+
else {
|
|
85
|
+
tree.create(cssPath, content);
|
|
86
|
+
}
|
|
87
|
+
};
|
|
88
|
+
};
|
|
89
|
+
const addTailwindDependencies = () => {
|
|
90
|
+
return (0, schematics_1.chain)([
|
|
91
|
+
(0, dependency_1.addDependency)('tailwindcss', versions_1.VERSIONS.tailwindcss, {
|
|
92
|
+
type: dependency_1.DependencyType.Dev,
|
|
93
|
+
install: dependency_1.InstallBehavior.Auto,
|
|
94
|
+
}),
|
|
95
|
+
(0, dependency_1.addDependency)('@lynx-js/tailwind-preset', versions_1.VERSIONS.tailwindPreset, {
|
|
96
|
+
type: dependency_1.DependencyType.Dev,
|
|
97
|
+
install: dependency_1.InstallBehavior.Auto,
|
|
98
|
+
}),
|
|
99
|
+
]);
|
|
100
|
+
};
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const schematics_1 = require("@angular-devkit/schematics");
|
|
4
|
+
const dependency_1 = require("@schematics/angular/utility/dependency");
|
|
5
|
+
const json_file_1 = require("@schematics/angular/utility/json-file");
|
|
6
|
+
const workspace_1 = require("@schematics/angular/utility/workspace");
|
|
7
|
+
const versions_1 = require("../ng-add/versions");
|
|
8
|
+
exports.default = (options) => async (tree) => {
|
|
9
|
+
const workspace = await (0, workspace_1.getWorkspace)(tree);
|
|
10
|
+
const projectName = options.project ??
|
|
11
|
+
workspace.extensions['defaultProject'] ??
|
|
12
|
+
[...workspace.projects.keys()][0];
|
|
13
|
+
if (!projectName) {
|
|
14
|
+
throw new schematics_1.SchematicsException('No project found. Run this schematic inside an Angular workspace.');
|
|
15
|
+
}
|
|
16
|
+
const project = workspace.projects.get(projectName);
|
|
17
|
+
if (!project) {
|
|
18
|
+
throw new schematics_1.SchematicsException(`Project "${projectName}" not found in angular.json.`);
|
|
19
|
+
}
|
|
20
|
+
return (0, schematics_1.chain)([
|
|
21
|
+
useLynxTestBuilder(projectName),
|
|
22
|
+
updatePackageScripts(),
|
|
23
|
+
addTestingDependencies(),
|
|
24
|
+
]);
|
|
25
|
+
};
|
|
26
|
+
/**
|
|
27
|
+
* Tests run with `ng test`, through the AngularLynx unit-test builder (which
|
|
28
|
+
* `ng add` already sets up; this covers projects set up before it). It sets
|
|
29
|
+
* up the testing library's Lynx environment itself when the package is
|
|
30
|
+
* installed, so the project needs no Vitest config or setup file of its own.
|
|
31
|
+
*/
|
|
32
|
+
const useLynxTestBuilder = (projectName) => (0, workspace_1.updateWorkspace)((workspace) => {
|
|
33
|
+
const project = workspace.projects.get(projectName);
|
|
34
|
+
const test = project.targets.get('test');
|
|
35
|
+
if (test?.builder === '@blotch/angular-lynx-build:unit-test') {
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
if (test) {
|
|
39
|
+
project.targets.delete('test');
|
|
40
|
+
}
|
|
41
|
+
project.targets.add({
|
|
42
|
+
name: 'test',
|
|
43
|
+
builder: '@blotch/angular-lynx-build:unit-test',
|
|
44
|
+
});
|
|
45
|
+
});
|
|
46
|
+
const updatePackageScripts = () => {
|
|
47
|
+
return (tree) => {
|
|
48
|
+
// `ng test` watches by default when run in a terminal, so the separate
|
|
49
|
+
// watch script this schematic used to add is gone with Vitest's.
|
|
50
|
+
const pkg = new json_file_1.JSONFile(tree, '/package.json');
|
|
51
|
+
pkg.modify(['scripts', 'test'], 'ng test');
|
|
52
|
+
if (pkg.get(['scripts', 'test:watch']) === 'vitest') {
|
|
53
|
+
pkg.remove(['scripts', 'test:watch']);
|
|
54
|
+
}
|
|
55
|
+
};
|
|
56
|
+
};
|
|
57
|
+
const addTestingDependencies = () => {
|
|
58
|
+
return (0, schematics_1.chain)([
|
|
59
|
+
// At this package's own release, like the builders `ng add` installs.
|
|
60
|
+
(0, dependency_1.addDependency)('@blotch/angular-lynx-testing-library', (0, versions_1.ownVersionRange)(), {
|
|
61
|
+
type: dependency_1.DependencyType.Dev,
|
|
62
|
+
install: dependency_1.InstallBehavior.Auto,
|
|
63
|
+
}),
|
|
64
|
+
// `ng new` already adds vitest and jsdom: keep the project's versions
|
|
65
|
+
// rather than replacing them (the default), which silently downgraded
|
|
66
|
+
// every new project. The testing library works with Vitest 4 and 5 and
|
|
67
|
+
// jsdom 25 to 30. The E2E tests run `npm test` in a fresh `ng new`
|
|
68
|
+
// project to keep it that way.
|
|
69
|
+
(0, dependency_1.addDependency)('vitest', versions_1.VERSIONS.vitest, {
|
|
70
|
+
type: dependency_1.DependencyType.Dev,
|
|
71
|
+
install: dependency_1.InstallBehavior.Auto,
|
|
72
|
+
existing: dependency_1.ExistingBehavior.Skip,
|
|
73
|
+
}),
|
|
74
|
+
(0, dependency_1.addDependency)('jsdom', versions_1.VERSIONS.jsdom, {
|
|
75
|
+
type: dependency_1.DependencyType.Dev,
|
|
76
|
+
install: dependency_1.InstallBehavior.Auto,
|
|
77
|
+
existing: dependency_1.ExistingBehavior.Skip,
|
|
78
|
+
}),
|
|
79
|
+
]);
|
|
80
|
+
};
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
"schema": "./add-tailwind/schema.json"
|
|
12
12
|
},
|
|
13
13
|
"add-testing": {
|
|
14
|
-
"description": "Adds AngularLynx Testing Library
|
|
14
|
+
"description": "Adds the AngularLynx Testing Library, which `ng test` sets up for rendering components on Lynx",
|
|
15
15
|
"factory": "./add-testing/index",
|
|
16
16
|
"schema": "./add-testing/schema.json"
|
|
17
17
|
},
|
|
@@ -21,10 +21,15 @@
|
|
|
21
21
|
"schema": "./add-i18n/schema.json"
|
|
22
22
|
},
|
|
23
23
|
"component": {
|
|
24
|
-
"description": "Generates a
|
|
24
|
+
"description": "Generates a component with Angular's component schematic, with a Lynx element template and LYNX_ELEMENTS imported",
|
|
25
25
|
"factory": "./component/index",
|
|
26
26
|
"schema": "./component/schema.json",
|
|
27
27
|
"aliases": ["c"]
|
|
28
|
+
},
|
|
29
|
+
"eject": {
|
|
30
|
+
"description": "Writes the generated iOS and Android projects into ios/ and android/ for native changes (one-way)",
|
|
31
|
+
"factory": "./eject/index",
|
|
32
|
+
"schema": "./eject/schema.json"
|
|
28
33
|
}
|
|
29
34
|
}
|
|
30
35
|
}
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const core_1 = require("@angular-devkit/core");
|
|
4
|
+
const schematics_1 = require("@angular-devkit/schematics");
|
|
5
|
+
/**
|
|
6
|
+
* `ng generate component` for Lynx (`ng add` makes it the one
|
|
7
|
+
* `ng generate component` runs). Angular's own component schematic generates
|
|
8
|
+
* the files, so every option, file name and angular.json default works as it
|
|
9
|
+
* does in any Angular app. Then the component becomes a Lynx one:
|
|
10
|
+
*
|
|
11
|
+
* 1. **Its template uses Lynx elements**: `<view><text>…</text></view>`
|
|
12
|
+
* instead of `<p>…</p>`, which Lynx has no element for.
|
|
13
|
+
* 2. **It imports LYNX_ELEMENTS**, the directives that declare Lynx's
|
|
14
|
+
* elements to Angular, so the template type-checks in the editor and
|
|
15
|
+
* under `ng test`, as it does in the app build.
|
|
16
|
+
* 3. **Its test renders it on Lynx** when the project has
|
|
17
|
+
* @blotch/angular-lynx-testing-library: a `*.test.ts` that renders the
|
|
18
|
+
* component through the Lynx renderer, in place of Angular's TestBed
|
|
19
|
+
* spec, which renders it with the DOM renderer. Without the library,
|
|
20
|
+
* Angular's spec stays: `ng test` runs both.
|
|
21
|
+
*/
|
|
22
|
+
exports.default = (options) => async (tree) => {
|
|
23
|
+
const before = createdFiles(tree);
|
|
24
|
+
return (0, schematics_1.chain)([
|
|
25
|
+
(0, schematics_1.externalSchematic)('@schematics/angular', 'component', withoutUnset(options)),
|
|
26
|
+
(tree) => {
|
|
27
|
+
const created = [...createdFiles(tree)].filter((file) => !before.has(file));
|
|
28
|
+
const componentFile = created.find((file) => file.endsWith('.ts') && !file.endsWith('.spec.ts'));
|
|
29
|
+
if (!componentFile) {
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
for (const file of created) {
|
|
33
|
+
if (file.endsWith('.html')) {
|
|
34
|
+
tree.overwrite(file, lynxTemplate(tree.readText(file)));
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
tree.overwrite(componentFile, lynxComponent(tree.readText(componentFile)));
|
|
38
|
+
const specFile = created.find((file) => file.endsWith('.spec.ts'));
|
|
39
|
+
if (specFile && hasTestingLibrary(tree)) {
|
|
40
|
+
tree.delete(specFile);
|
|
41
|
+
tree.create(specFile.replace(/\.spec\.ts$/, '.test.ts'), lynxTest(tree.readText(componentFile), componentFile, options.name));
|
|
42
|
+
}
|
|
43
|
+
},
|
|
44
|
+
]);
|
|
45
|
+
};
|
|
46
|
+
/**
|
|
47
|
+
* The options the user (or angular.json's defaults for this schematic) set.
|
|
48
|
+
* Leaving the rest out lets Angular CLI apply angular.json's defaults for
|
|
49
|
+
* `@schematics/angular:component` (e.g. `style: scss` from
|
|
50
|
+
* `ng new --style scss`), then Angular's own, as `ng generate component`
|
|
51
|
+
* does without AngularLynx.
|
|
52
|
+
*/
|
|
53
|
+
const withoutUnset = (options) => Object.fromEntries(Object.entries(options).filter(([, value]) => value !== undefined));
|
|
54
|
+
/**
|
|
55
|
+
* The files created so far in this run.
|
|
56
|
+
*/
|
|
57
|
+
const createdFiles = (tree) => new Set(tree.actions
|
|
58
|
+
.filter((action) => action.kind === 'c')
|
|
59
|
+
.map((action) => action.path));
|
|
60
|
+
/**
|
|
61
|
+
* Angular's placeholder paragraph (`<p>name works!</p>`, in a template file
|
|
62
|
+
* or inline), as Lynx elements. Lynx has no `<p>`: text goes in `<text>`,
|
|
63
|
+
* inside a `<view>` for layout.
|
|
64
|
+
*/
|
|
65
|
+
const lynxTemplate = (source) => source.replace(/^([ \t]*)<p>\s*([\s\S]*?)\s*<\/p>/m, (_match, indent, text) => `${indent}<view>\n${indent} <text>${text}</text>\n${indent}</view>`);
|
|
66
|
+
/**
|
|
67
|
+
* The generated component, with a Lynx template if it's inline, and
|
|
68
|
+
* LYNX_ELEMENTS among its imports. A component declared in an NgModule
|
|
69
|
+
* (`--standalone false`) has no `imports` of its own; its module would import
|
|
70
|
+
* them, so it's left as Angular generates it.
|
|
71
|
+
*/
|
|
72
|
+
const lynxComponent = (source) => {
|
|
73
|
+
const component = lynxTemplate(source);
|
|
74
|
+
if (!/^\s*imports: \[\],$/m.test(component)) {
|
|
75
|
+
return component;
|
|
76
|
+
}
|
|
77
|
+
return component
|
|
78
|
+
.replace(/^(\s*)imports: \[\],$/m, '$1imports: [LYNX_ELEMENTS],')
|
|
79
|
+
.replace(/^(import .* from '@angular\/core';)$/m, "$1\nimport { LYNX_ELEMENTS } from '@blotch/angular-lynx';");
|
|
80
|
+
};
|
|
81
|
+
const hasTestingLibrary = (tree) => {
|
|
82
|
+
if (!tree.exists('/package.json')) {
|
|
83
|
+
return false;
|
|
84
|
+
}
|
|
85
|
+
const packageJson = tree.readJson('/package.json');
|
|
86
|
+
return [packageJson.dependencies, packageJson.devDependencies].some((dependencies) => dependencies?.['@blotch/angular-lynx-testing-library'] !== undefined);
|
|
87
|
+
};
|
|
88
|
+
/**
|
|
89
|
+
* A test that renders the component on Lynx with the testing library and
|
|
90
|
+
* finds its placeholder text, in place of Angular's TestBed spec.
|
|
91
|
+
*/
|
|
92
|
+
const lynxTest = (component, componentFile, name) => {
|
|
93
|
+
const className = /export (?:default )?class (\w+)/.exec(component)[1];
|
|
94
|
+
const isDefault = /export default class/.test(component);
|
|
95
|
+
const importPath = `./${componentFile.split('/').pop().replace(/\.ts$/, '')}`;
|
|
96
|
+
// The text Angular's template says: the name's last segment, dasherized.
|
|
97
|
+
const text = `${core_1.strings.dasherize(name.split('/').pop())} works!`;
|
|
98
|
+
return `import { render } from '@blotch/angular-lynx-testing-library';
|
|
99
|
+
import { describe, expect, it } from 'vitest';
|
|
100
|
+
|
|
101
|
+
import ${isDefault ? className : `{ ${className} }`} from '${importPath}';
|
|
102
|
+
|
|
103
|
+
describe('${className}', () => {
|
|
104
|
+
it('should render', async () => {
|
|
105
|
+
const { getByText } = await render(${className});
|
|
106
|
+
expect(getByText('${text}')).toBeTruthy();
|
|
107
|
+
});
|
|
108
|
+
});
|
|
109
|
+
`;
|
|
110
|
+
};
|