@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.
Files changed (34) hide show
  1. package/README.md +3 -1
  2. package/fesm2022/blotch-angular-lynx-web-glass-8MFrhPz0.mjs +951 -0
  3. package/fesm2022/blotch-angular-lynx-web-glass-8MFrhPz0.mjs.map +1 -0
  4. package/fesm2022/blotch-angular-lynx.mjs +8084 -0
  5. package/fesm2022/blotch-angular-lynx.mjs.map +1 -0
  6. package/package.json +11 -6
  7. package/schematics/add-i18n/index.js +168 -0
  8. package/schematics/add-i18n/schema.js +2 -0
  9. package/schematics/add-tailwind/index.js +100 -0
  10. package/schematics/add-tailwind/schema.js +2 -0
  11. package/schematics/add-testing/index.js +80 -0
  12. package/schematics/add-testing/schema.js +2 -0
  13. package/schematics/collection.json +7 -2
  14. package/schematics/component/index.js +110 -0
  15. package/schematics/component/schema.js +2 -0
  16. package/schematics/component/schema.json +102 -25
  17. package/schematics/eject/index.js +87 -0
  18. package/schematics/eject/schema.js +2 -0
  19. package/schematics/eject/schema.json +24 -0
  20. package/schematics/files/src/rspeedy-env.d.ts.template +1 -1
  21. package/schematics/migrations/accessibility-traits/index.js +56 -0
  22. package/schematics/migrations/dev-logs-gitignore/index.js +9 -0
  23. package/schematics/migrations/extract-i18n-target/index.js +31 -0
  24. package/schematics/migrations/tailwind-html-templates/index.js +44 -0
  25. package/schematics/migrations.json +25 -0
  26. package/schematics/ng-add/index.js +480 -0
  27. package/schematics/ng-add/schema.js +2 -0
  28. package/schematics/ng-add/schema.json +6 -2
  29. package/schematics/ng-add/versions.js +57 -0
  30. package/schematics/package.json +3 -0
  31. package/schematics/utility/dev-logs-gitignore.js +25 -0
  32. package/schematics/utility/extract-i18n-target.js +58 -0
  33. package/types/blotch-angular-lynx.d.ts +4789 -0
  34. 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.1",
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,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -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,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -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
+ };
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -11,7 +11,7 @@
11
11
  "schema": "./add-tailwind/schema.json"
12
12
  },
13
13
  "add-testing": {
14
- "description": "Adds AngularLynx Testing Library with Vitest to an existing project",
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 standalone Angular component with Lynx element template",
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
+ };
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });