@blotch/angular-lynx 22.2.1 → 22.2.2
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 +2 -0
- package/fesm2022/blotch-angular-lynx.mjs +6772 -0
- package/fesm2022/blotch-angular-lynx.mjs.map +1 -0
- package/package.json +11 -6
- package/schematics/add-i18n/index.js +151 -0
- package/schematics/add-i18n/schema.js +2 -0
- package/schematics/add-tailwind/index.js +101 -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.json +4 -0
- package/schematics/ng-add/index.js +472 -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/types/blotch-angular-lynx.d.ts +4349 -0
- package/schematics/files/lynx.config.ts.template +0 -17
|
@@ -0,0 +1,472 @@
|
|
|
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("./versions");
|
|
8
|
+
/**
|
|
9
|
+
* Transforms a standard `ng new` Angular project into a Lynx-native application.
|
|
10
|
+
* 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.
|
|
15
|
+
* This is a one-shot destructive transform — the old browser-Angular files
|
|
16
|
+
* are deleted because they reference DOM APIs and HTML elements that don't
|
|
17
|
+
* exist on the Lynx runtime.
|
|
18
|
+
*/
|
|
19
|
+
exports.default = (options) => async (tree) => {
|
|
20
|
+
const workspace = await (0, workspace_1.getWorkspace)(tree);
|
|
21
|
+
const projectName = options.project ??
|
|
22
|
+
workspace.extensions['defaultProject'] ??
|
|
23
|
+
[...workspace.projects.keys()][0];
|
|
24
|
+
if (!projectName) {
|
|
25
|
+
throw new schematics_1.SchematicsException('No project found. Run this schematic inside an Angular workspace.');
|
|
26
|
+
}
|
|
27
|
+
const project = workspace.projects.get(projectName);
|
|
28
|
+
if (!project) {
|
|
29
|
+
throw new schematics_1.SchematicsException(`Project "${projectName}" not found in angular.json.`);
|
|
30
|
+
}
|
|
31
|
+
const sourceRoot = (project.sourceRoot ?? `${project.root}/src`);
|
|
32
|
+
const projectRoot = project.root;
|
|
33
|
+
const prefix = project.extensions['prefix'] ?? 'app';
|
|
34
|
+
// The tsconfig the app is built with. `ng new` points the build target's
|
|
35
|
+
// `tsConfig` at tsconfig.app.json.
|
|
36
|
+
const buildTsConfig = project.targets.get('build')?.options?.['tsConfig'];
|
|
37
|
+
const appTsconfig = typeof buildTsConfig === 'string'
|
|
38
|
+
? buildTsConfig
|
|
39
|
+
: `${projectRoot ? projectRoot + '/' : ''}tsconfig.app.json`;
|
|
40
|
+
const rules = [
|
|
41
|
+
addTemplateFiles(projectRoot, sourceRoot),
|
|
42
|
+
replaceMainTs(sourceRoot),
|
|
43
|
+
replaceAppConfig(sourceRoot),
|
|
44
|
+
replaceApp(sourceRoot, prefix, projectName),
|
|
45
|
+
deleteOldAppFiles(sourceRoot),
|
|
46
|
+
excludeTestFilesFromAppBuild(appTsconfig),
|
|
47
|
+
includeTestFilesInSpecTsconfig(`${projectRoot ? projectRoot + '/' : ''}tsconfig.spec.json`),
|
|
48
|
+
useLynxBuilders(projectName),
|
|
49
|
+
useLynxSchematics(),
|
|
50
|
+
updatePackageScripts(),
|
|
51
|
+
addCoreDependencies(),
|
|
52
|
+
];
|
|
53
|
+
// `"false"` is how `ng add ... --tailwind=false` arrives (see schema.ts).
|
|
54
|
+
if (options.tailwind !== false && options.tailwind !== 'false') {
|
|
55
|
+
rules.push(addTailwindConfig(projectRoot), updateStylesCss(sourceRoot), addTailwindDependencies());
|
|
56
|
+
}
|
|
57
|
+
return (0, schematics_1.chain)(rules);
|
|
58
|
+
};
|
|
59
|
+
const addTemplateFiles = (projectRoot, _sourceRoot) => (0, schematics_1.mergeWith)(
|
|
60
|
+
// MergeStrategy.Overwrite is deliberate — `ng add` is meant to be
|
|
61
|
+
// idempotent so re-running it should refresh the template files
|
|
62
|
+
// (src/rspeedy-env.d.ts) to the package's current version. Default merge
|
|
63
|
+
// strategy would error on file collisions.
|
|
64
|
+
(0, schematics_1.apply)((0, schematics_1.url)('../files'), [(0, schematics_1.applyTemplates)({}), (0, schematics_1.move)(projectRoot || '/')]), schematics_1.MergeStrategy.Overwrite);
|
|
65
|
+
const replaceMainTs = (sourceRoot) => {
|
|
66
|
+
return (tree) => {
|
|
67
|
+
// `ng new` generates a main.ts that calls bootstrapApplication from
|
|
68
|
+
// @angular/platform-browser. That platform implementation reaches into
|
|
69
|
+
// browser globals (document, history, navigator) which don't exist on
|
|
70
|
+
// Lynx, so we replace the bootstrap entrypoint with the AngularLynx
|
|
71
|
+
// equivalent that wires the Lynx renderer instead.
|
|
72
|
+
const mainPath = `${sourceRoot}/main.ts`;
|
|
73
|
+
const content = `import { bootstrapApplication } from '@blotch/angular-lynx';
|
|
74
|
+
import { App } from './app/app';
|
|
75
|
+
import { appConfig } from './app/app.config';
|
|
76
|
+
|
|
77
|
+
bootstrapApplication(App, appConfig);
|
|
78
|
+
`;
|
|
79
|
+
tree.overwrite(mainPath, content);
|
|
80
|
+
};
|
|
81
|
+
};
|
|
82
|
+
const replaceAppConfig = (sourceRoot) => {
|
|
83
|
+
return (tree) => {
|
|
84
|
+
// Two providers are required for any AngularLynx app:
|
|
85
|
+
// 1. provideZonelessChangeDetection() — Lynx doesn't carry zone.js, so
|
|
86
|
+
// Angular must run in zoneless mode (signals + manual change detection
|
|
87
|
+
// triggers are the only reactivity path).
|
|
88
|
+
// 2. provideRenderer() — registers the Lynx RendererFactory2 over
|
|
89
|
+
// Angular's default DOM renderer so element creation calls go to
|
|
90
|
+
// __CreateElement/__SetAttribute instead of document.createElement.
|
|
91
|
+
// provideRouter is included by default so the generated App has a working
|
|
92
|
+
// <router-outlet> for the user to hang their routes off.
|
|
93
|
+
const configPath = `${sourceRoot}/app/app.config.ts`;
|
|
94
|
+
const content = `import {
|
|
95
|
+
type ApplicationConfig,
|
|
96
|
+
provideZonelessChangeDetection,
|
|
97
|
+
} from '@angular/core';
|
|
98
|
+
import { provideRenderer, provideRouter } from '@blotch/angular-lynx';
|
|
99
|
+
|
|
100
|
+
import { routes } from './app.routes';
|
|
101
|
+
|
|
102
|
+
export const appConfig: ApplicationConfig = {
|
|
103
|
+
providers: [
|
|
104
|
+
provideZonelessChangeDetection(),
|
|
105
|
+
provideRenderer(),
|
|
106
|
+
provideRouter(routes),
|
|
107
|
+
],
|
|
108
|
+
};
|
|
109
|
+
`;
|
|
110
|
+
tree.overwrite(configPath, content);
|
|
111
|
+
};
|
|
112
|
+
};
|
|
113
|
+
const replaceApp = (sourceRoot, prefix, projectName) => {
|
|
114
|
+
return (tree) => {
|
|
115
|
+
// `ng new` before Angular 20 generated app.component.ts — delete it and
|
|
116
|
+
// create app.ts. The Angular 2025 style guide drops the `.component`
|
|
117
|
+
// suffix, and AngularLynx's component schematic follows the same
|
|
118
|
+
// convention so the bootstrap component should too.
|
|
119
|
+
const oldPath = `${sourceRoot}/app/app.component.ts`;
|
|
120
|
+
if (tree.exists(oldPath)) {
|
|
121
|
+
tree.delete(oldPath);
|
|
122
|
+
}
|
|
123
|
+
// LYNX_ELEMENTS is the directive bundle that registers <view>, <text>,
|
|
124
|
+
// <scroll-view>, etc. as Angular elements. Without it, the template would
|
|
125
|
+
// fail compilation because those tags aren't known HTML elements and the
|
|
126
|
+
// user hasn't yet added CUSTOM_ELEMENTS_SCHEMA (the build plugin injects
|
|
127
|
+
// it automatically but the type-checker still surfaces template errors).
|
|
128
|
+
const newPath = `${sourceRoot}/app/app.ts`;
|
|
129
|
+
const content = `import { Component, signal } from '@angular/core';
|
|
130
|
+
import { RouterOutlet } from '@angular/router';
|
|
131
|
+
import { LYNX_ELEMENTS } from '@blotch/angular-lynx';
|
|
132
|
+
|
|
133
|
+
@Component({
|
|
134
|
+
selector: '${prefix}-root',
|
|
135
|
+
imports: [RouterOutlet, LYNX_ELEMENTS],
|
|
136
|
+
template: \`
|
|
137
|
+
<scroll-view scroll-orientation="vertical">
|
|
138
|
+
<view style="padding: 24px; align-items: center;">
|
|
139
|
+
<text style="font-size: 24px; font-weight: bold;">{{ title() }}</text>
|
|
140
|
+
<text style="font-size: 14px; margin-top: 8px; color: #666;">
|
|
141
|
+
Edit src/app/app.ts to get started
|
|
142
|
+
</text>
|
|
143
|
+
<router-outlet />
|
|
144
|
+
</view>
|
|
145
|
+
</scroll-view>
|
|
146
|
+
\`,
|
|
147
|
+
})
|
|
148
|
+
export class App {
|
|
149
|
+
title = signal('${projectName}');
|
|
150
|
+
}
|
|
151
|
+
`;
|
|
152
|
+
if (tree.exists(newPath)) {
|
|
153
|
+
tree.overwrite(newPath, content);
|
|
154
|
+
}
|
|
155
|
+
else {
|
|
156
|
+
tree.create(newPath, content);
|
|
157
|
+
}
|
|
158
|
+
};
|
|
159
|
+
};
|
|
160
|
+
const deleteOldAppFiles = (sourceRoot) => {
|
|
161
|
+
return (tree) => {
|
|
162
|
+
// Remove files generated by `ng new` that we don't need.
|
|
163
|
+
// The root component's template and styles are dead once we inline the
|
|
164
|
+
// template above, and its spec tests the browser template we replaced, so
|
|
165
|
+
// it would fail if left behind. Angular 20+ names them app.html, app.css
|
|
166
|
+
// and app.spec.ts; earlier versions used an `app.component.` prefix. We
|
|
167
|
+
// don't try to migrate the user's existing template/styles because this
|
|
168
|
+
// schematic is intended for fresh `ng new` projects — running it on an
|
|
169
|
+
// established app is unsupported.
|
|
170
|
+
for (const file of [
|
|
171
|
+
`${sourceRoot}/app/app.html`,
|
|
172
|
+
`${sourceRoot}/app/app.spec.ts`,
|
|
173
|
+
`${sourceRoot}/app/app.css`,
|
|
174
|
+
`${sourceRoot}/app/app.scss`,
|
|
175
|
+
`${sourceRoot}/app/app.component.html`,
|
|
176
|
+
`${sourceRoot}/app/app.component.spec.ts`,
|
|
177
|
+
`${sourceRoot}/app/app.component.css`,
|
|
178
|
+
`${sourceRoot}/app/app.component.scss`,
|
|
179
|
+
]) {
|
|
180
|
+
if (tree.exists(file)) {
|
|
181
|
+
tree.delete(file);
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
};
|
|
185
|
+
};
|
|
186
|
+
const excludeTestFilesFromAppBuild = (tsconfigPath) => {
|
|
187
|
+
return (tree) => {
|
|
188
|
+
// `ng new` excludes `*.spec.ts` from the app build, but AngularLynx's
|
|
189
|
+
// component and add-testing schematics name tests `*.test.ts`. Left
|
|
190
|
+
// in, the Rsbuild plugin compiles every test file into the app, and a
|
|
191
|
+
// test importing the testing library (a devDependency the app bundle
|
|
192
|
+
// can't resolve) produces build errors.
|
|
193
|
+
if (!tree.exists(tsconfigPath)) {
|
|
194
|
+
return;
|
|
195
|
+
}
|
|
196
|
+
const tsconfig = new json_file_1.JSONFile(tree, tsconfigPath);
|
|
197
|
+
const exclude = tsconfig.get(['exclude']);
|
|
198
|
+
const patterns = Array.isArray(exclude) ? exclude : [];
|
|
199
|
+
if (!patterns.includes('src/**/*.test.ts')) {
|
|
200
|
+
tsconfig.modify(['exclude'], [...patterns, 'src/**/*.test.ts']);
|
|
201
|
+
}
|
|
202
|
+
};
|
|
203
|
+
};
|
|
204
|
+
const includeTestFilesInSpecTsconfig = (tsconfigPath) => {
|
|
205
|
+
return (tree) => {
|
|
206
|
+
// `ng test` compiles `*.test.ts` files alongside `ng new`'s `*.spec.ts`
|
|
207
|
+
// ones, and AngularLynx's schematics name tests `*.test.ts`. Listed in
|
|
208
|
+
// tsconfig.spec.json, editors type-check them with the test types
|
|
209
|
+
// (vitest/globals) instead of treating them as files of no project.
|
|
210
|
+
if (!tree.exists(tsconfigPath)) {
|
|
211
|
+
return;
|
|
212
|
+
}
|
|
213
|
+
const tsconfig = new json_file_1.JSONFile(tree, tsconfigPath);
|
|
214
|
+
const include = tsconfig.get(['include']);
|
|
215
|
+
if (Array.isArray(include) && !include.includes('src/**/*.test.ts')) {
|
|
216
|
+
tsconfig.modify(['include'], [...include, 'src/**/*.test.ts']);
|
|
217
|
+
}
|
|
218
|
+
};
|
|
219
|
+
};
|
|
220
|
+
/**
|
|
221
|
+
* Angular compiler options from `ng new`'s build target that the Lynx builder
|
|
222
|
+
* accepts (see @blotch/angular-lynx-build's builders/application/schema.json).
|
|
223
|
+
* @blotch/rsbuild-plugin-angular-lynx and `ng test` read them from
|
|
224
|
+
* angular.json; anything else, such as options only the browser builder
|
|
225
|
+
* knows, would fail the builder's option validation. `optimization` also picks
|
|
226
|
+
* the bundle's production or development mode.
|
|
227
|
+
*/
|
|
228
|
+
const ANGULAR_BUILD_OPTIONS = new Set([
|
|
229
|
+
'browser',
|
|
230
|
+
'tsConfig',
|
|
231
|
+
'polyfills',
|
|
232
|
+
'styles',
|
|
233
|
+
'assets',
|
|
234
|
+
'scripts',
|
|
235
|
+
'inlineStyleLanguage',
|
|
236
|
+
'fileReplacements',
|
|
237
|
+
'aot',
|
|
238
|
+
'optimization',
|
|
239
|
+
'sourceMap',
|
|
240
|
+
'outputHashing',
|
|
241
|
+
'i18nMissingTranslation',
|
|
242
|
+
'budgets',
|
|
243
|
+
'extractLicenses',
|
|
244
|
+
'outputPath',
|
|
245
|
+
'index',
|
|
246
|
+
]);
|
|
247
|
+
const keepAngularOptions = (options) => Object.fromEntries(Object.entries(options ?? {}).filter(([key]) => ANGULAR_BUILD_OPTIONS.has(key)));
|
|
248
|
+
/**
|
|
249
|
+
* Options of Angular's dev server that the Lynx dev server accepts too (see
|
|
250
|
+
* @blotch/angular-lynx-build's builders/dev-server/schema.json).
|
|
251
|
+
*/
|
|
252
|
+
const DEV_SERVER_OPTIONS = new Set(['buildTarget', 'port', 'host']);
|
|
253
|
+
const keepDevServerOptions = (options) => Object.fromEntries(Object.entries(options ?? {}).filter(([key]) => DEV_SERVER_OPTIONS.has(key)));
|
|
254
|
+
/**
|
|
255
|
+
* `ng new`'s initial-bundle budget, sized for a web app's first download.
|
|
256
|
+
*/
|
|
257
|
+
const NG_NEW_INITIAL_BUDGET = { maximumWarning: '500kB', maximumError: '1MB' };
|
|
258
|
+
/**
|
|
259
|
+
* The initial-bundle budget for a Lynx bundle. It carries more than a web
|
|
260
|
+
* app's first download: Angular runs on both of Lynx's threads, so the
|
|
261
|
+
* framework is in it twice, and a new app's bundle is already about 1 MB.
|
|
262
|
+
* Kept at roughly the same multiples of a new app as Angular's budget.
|
|
263
|
+
*/
|
|
264
|
+
const LYNX_INITIAL_BUDGET = { maximumWarning: '2MB', maximumError: '5MB' };
|
|
265
|
+
/**
|
|
266
|
+
* A configuration's options with `ng new`'s initial-bundle budget resized for
|
|
267
|
+
* a Lynx bundle. Budgets the user set differently stay as they are.
|
|
268
|
+
*/
|
|
269
|
+
const withLynxBudgets = (options) => {
|
|
270
|
+
if (!Array.isArray(options['budgets'])) {
|
|
271
|
+
return options;
|
|
272
|
+
}
|
|
273
|
+
return {
|
|
274
|
+
...options,
|
|
275
|
+
budgets: options['budgets'].map((budget) => budget['type'] === 'initial' &&
|
|
276
|
+
budget['maximumWarning'] === NG_NEW_INITIAL_BUDGET.maximumWarning &&
|
|
277
|
+
budget['maximumError'] === NG_NEW_INITIAL_BUDGET.maximumError
|
|
278
|
+
? { ...budget, ...LYNX_INITIAL_BUDGET }
|
|
279
|
+
: budget),
|
|
280
|
+
};
|
|
281
|
+
};
|
|
282
|
+
/**
|
|
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
|
+
*/
|
|
290
|
+
const useLynxBuilders = (projectName) => (0, workspace_1.updateWorkspace)((workspace) => {
|
|
291
|
+
const project = workspace.projects.get(projectName);
|
|
292
|
+
const build = project.targets.get('build');
|
|
293
|
+
const configurations = Object.fromEntries(Object.entries(build?.configurations ?? {}).map(([name, options]) => [
|
|
294
|
+
name,
|
|
295
|
+
withLynxBudgets(keepAngularOptions(options)),
|
|
296
|
+
]));
|
|
297
|
+
const buildDefinition = {
|
|
298
|
+
builder: '@blotch/angular-lynx-build:application',
|
|
299
|
+
options: {
|
|
300
|
+
...keepAngularOptions(build?.options),
|
|
301
|
+
appName: projectName,
|
|
302
|
+
// A placeholder the user replaces before publishing: the stores
|
|
303
|
+
// identify the app by it, and it can't change after the first upload.
|
|
304
|
+
appId: `com.example.${projectName.toLowerCase().replace(/[^a-z0-9]/g, '') || 'app'}`,
|
|
305
|
+
},
|
|
306
|
+
configurations,
|
|
307
|
+
defaultConfiguration: build?.defaultConfiguration ?? 'production',
|
|
308
|
+
};
|
|
309
|
+
if (build) {
|
|
310
|
+
project.targets.delete('build');
|
|
311
|
+
}
|
|
312
|
+
project.targets.add({ name: 'build', ...buildDefinition });
|
|
313
|
+
// The Lynx dev server takes `buildTarget` like Angular's, so `ng new`'s
|
|
314
|
+
// configurations (production and development, each naming its build
|
|
315
|
+
// configuration) carry over, and `ng serve -c production` keeps working.
|
|
316
|
+
// Options only the browser dev server knows (ssl, proxyConfig, ...) are
|
|
317
|
+
// dropped: they would fail the builder's option validation.
|
|
318
|
+
// Without a serve target to keep, write the one `ng new` would have, for
|
|
319
|
+
// the build configurations that exist. Without a development one, serve
|
|
320
|
+
// the build target's default configuration rather than the dev server's
|
|
321
|
+
// default (`::development`), which would point at nothing.
|
|
322
|
+
const serve = project.targets.get('serve');
|
|
323
|
+
const serveConfigurations = Object.fromEntries(Object.entries(serve?.configurations ??
|
|
324
|
+
Object.fromEntries(['production', 'development']
|
|
325
|
+
.filter((name) => name in configurations)
|
|
326
|
+
.map((name) => [
|
|
327
|
+
name,
|
|
328
|
+
{ buildTarget: `${projectName}:build:${name}` },
|
|
329
|
+
]))).map(([name, options]) => [name, keepDevServerOptions(options)]));
|
|
330
|
+
const serveDefinition = serve
|
|
331
|
+
? {
|
|
332
|
+
builder: '@blotch/angular-lynx-build:dev-server',
|
|
333
|
+
options: keepDevServerOptions(serve.options),
|
|
334
|
+
configurations: serveConfigurations,
|
|
335
|
+
defaultConfiguration: serve.defaultConfiguration,
|
|
336
|
+
}
|
|
337
|
+
: {
|
|
338
|
+
builder: '@blotch/angular-lynx-build:dev-server',
|
|
339
|
+
options: 'development' in serveConfigurations
|
|
340
|
+
? {}
|
|
341
|
+
: { buildTarget: `${projectName}:build` },
|
|
342
|
+
configurations: serveConfigurations,
|
|
343
|
+
defaultConfiguration: 'development' in serveConfigurations ? 'development' : undefined,
|
|
344
|
+
};
|
|
345
|
+
if (serve) {
|
|
346
|
+
project.targets.delete('serve');
|
|
347
|
+
}
|
|
348
|
+
project.targets.add({ name: 'serve', ...serveDefinition });
|
|
349
|
+
// The Lynx unit-test builder is Angular's, set up for Lynx, so it takes
|
|
350
|
+
// the same options and `ng new`'s carry over (as do its own, when `ng add`
|
|
351
|
+
// runs again). Any other test builder (Karma, from older `ng new`
|
|
352
|
+
// versions) can't run Lynx code; its options don't apply.
|
|
353
|
+
const test = project.targets.get('test');
|
|
354
|
+
const keepTest = test?.builder === '@angular/build:unit-test' ||
|
|
355
|
+
test?.builder === '@blotch/angular-lynx-build:unit-test';
|
|
356
|
+
if (test) {
|
|
357
|
+
project.targets.delete('test');
|
|
358
|
+
}
|
|
359
|
+
project.targets.add({
|
|
360
|
+
name: 'test',
|
|
361
|
+
builder: '@blotch/angular-lynx-build:unit-test',
|
|
362
|
+
...(keepTest && {
|
|
363
|
+
options: test.options,
|
|
364
|
+
configurations: test.configurations,
|
|
365
|
+
defaultConfiguration: test.defaultConfiguration,
|
|
366
|
+
}),
|
|
367
|
+
});
|
|
368
|
+
});
|
|
369
|
+
/**
|
|
370
|
+
* Makes `ng generate` reach AngularLynx's schematics first, so
|
|
371
|
+
* `ng generate component` makes a component with Lynx elements. Angular's own
|
|
372
|
+
* collection stays after it: AngularLynx only has the schematics that differ
|
|
373
|
+
* on Lynx, and `ng generate service`, `pipe`, `guard` and the rest come from
|
|
374
|
+
* Angular's as before.
|
|
375
|
+
*/
|
|
376
|
+
const useLynxSchematics = () => (0, workspace_1.updateWorkspace)((workspace) => {
|
|
377
|
+
const cli = (workspace.extensions['cli'] ?? {});
|
|
378
|
+
const collections = Array.isArray(cli['schematicCollections'])
|
|
379
|
+
? cli['schematicCollections']
|
|
380
|
+
: // What Angular CLI uses when the list isn't set.
|
|
381
|
+
['@schematics/angular'];
|
|
382
|
+
workspace.extensions['cli'] = {
|
|
383
|
+
...cli,
|
|
384
|
+
schematicCollections: [
|
|
385
|
+
'@blotch/angular-lynx',
|
|
386
|
+
...collections.filter((collection) => collection !== '@blotch/angular-lynx'),
|
|
387
|
+
],
|
|
388
|
+
};
|
|
389
|
+
});
|
|
390
|
+
const updatePackageScripts = () => {
|
|
391
|
+
return (tree) => {
|
|
392
|
+
// `npm start` and `npm run build` go through the Angular CLI, which runs
|
|
393
|
+
// the AngularLynx builders set up by useLynxBuilders. `ng new` already
|
|
394
|
+
// writes these (and `npm test` as `ng test`); this keeps them for
|
|
395
|
+
// projects whose scripts called rspeedy directly.
|
|
396
|
+
const pkg = new json_file_1.JSONFile(tree, '/package.json');
|
|
397
|
+
pkg.modify(['scripts', 'start'], 'ng serve');
|
|
398
|
+
pkg.modify(['scripts', 'build'], 'ng build');
|
|
399
|
+
};
|
|
400
|
+
};
|
|
401
|
+
const addCoreDependencies = () => {
|
|
402
|
+
return (0, schematics_1.chain)([
|
|
403
|
+
// The `ng build`, `ng serve` and `ng test` builders, the one build
|
|
404
|
+
// dependency an AngularLynx app needs, as `@angular/build` is for a web
|
|
405
|
+
// app. They bring rspeedy, the rsbuild plugin that compiles Angular, the
|
|
406
|
+
// QR code plugin and the iOS and Android app builder with them. Taken at
|
|
407
|
+
// this package's own release, which they were tested with.
|
|
408
|
+
(0, dependency_1.addDependency)('@blotch/angular-lynx-build', (0, versions_1.ownVersionRange)(), {
|
|
409
|
+
type: dependency_1.DependencyType.Dev,
|
|
410
|
+
install: dependency_1.InstallBehavior.Auto,
|
|
411
|
+
}),
|
|
412
|
+
(0, dependency_1.addDependency)('@lynx-js/types', versions_1.VERSIONS.lynxTypes, {
|
|
413
|
+
type: dependency_1.DependencyType.Dev,
|
|
414
|
+
install: dependency_1.InstallBehavior.Auto,
|
|
415
|
+
}),
|
|
416
|
+
]);
|
|
417
|
+
};
|
|
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
|
+
};
|
|
@@ -14,10 +14,14 @@
|
|
|
14
14
|
}
|
|
15
15
|
},
|
|
16
16
|
"tailwind": {
|
|
17
|
-
"type": "boolean",
|
|
17
|
+
"type": ["boolean", "string"],
|
|
18
|
+
"pattern": "^(true|false)$",
|
|
18
19
|
"description": "Whether to add Tailwind CSS with @lynx-js/tailwind-preset.",
|
|
19
20
|
"default": true,
|
|
20
|
-
"x-prompt":
|
|
21
|
+
"x-prompt": {
|
|
22
|
+
"message": "Would you like to add Tailwind CSS with Lynx preset?",
|
|
23
|
+
"type": "confirmation"
|
|
24
|
+
}
|
|
21
25
|
}
|
|
22
26
|
},
|
|
23
27
|
"required": []
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.ownVersionRange = exports.VERSIONS = void 0;
|
|
4
|
+
const tslib_1 = require("tslib");
|
|
5
|
+
const node_fs_1 = require("node:fs");
|
|
6
|
+
const node_path_1 = tslib_1.__importDefault(require("node:path"));
|
|
7
|
+
/**
|
|
8
|
+
* Versions of the third-party packages the `ng add @blotch/angular-lynx`
|
|
9
|
+
* schematics install: the ones AngularLynx is tested with.
|
|
10
|
+
*
|
|
11
|
+
* Keep this map in sync when upgrading any Lynx or build-tooling dependency.
|
|
12
|
+
* It lists none of our own packages or Angular's: those come from
|
|
13
|
+
* {@link ownVersionRange} and the app's own `@angular/core`, so they can't go
|
|
14
|
+
* stale.
|
|
15
|
+
*/
|
|
16
|
+
exports.VERSIONS = {
|
|
17
|
+
/**
|
|
18
|
+
* @lynx-js/types — TypeScript type definitions for the Lynx runtime APIs
|
|
19
|
+
*/
|
|
20
|
+
lynxTypes: '^4.3.0',
|
|
21
|
+
/**
|
|
22
|
+
* tailwindcss — utility-first CSS framework (optional, controlled by the `tailwind` schema option)
|
|
23
|
+
*/
|
|
24
|
+
tailwindcss: '^3.4.19',
|
|
25
|
+
/**
|
|
26
|
+
* @lynx-js/tailwind-preset — Lynx-specific Tailwind preset with native design tokens
|
|
27
|
+
*/
|
|
28
|
+
tailwindPreset: '^0.6.0',
|
|
29
|
+
/**
|
|
30
|
+
* vitest — fast unit test runner powered by Vite. Only added when the
|
|
31
|
+
* project has none; `ng new` already adds it.
|
|
32
|
+
*/
|
|
33
|
+
vitest: '^5.0.0',
|
|
34
|
+
/**
|
|
35
|
+
* jsdom — DOM environment for running component tests. Only added when the
|
|
36
|
+
* project has none; `ng new` already adds it.
|
|
37
|
+
*/
|
|
38
|
+
jsdom: '^30.0.0',
|
|
39
|
+
};
|
|
40
|
+
/**
|
|
41
|
+
* The range the schematics install our other packages at
|
|
42
|
+
* (@blotch/angular-lynx-build, the testing library): the version of
|
|
43
|
+
* @blotch/angular-lynx they shipped in. Every package is released at one
|
|
44
|
+
* version (see packages/release), so a range written out here would go stale
|
|
45
|
+
* at each release unless someone remembered to move it, as `^22.2.1` nearly
|
|
46
|
+
* shipped in 22.2.2. The E2E tests check that a new app gets this range.
|
|
47
|
+
*
|
|
48
|
+
* `../../package.json` is the runtime's package.json both in the repository
|
|
49
|
+
* and in the published package, where ng-packagr copies it into dist/ beside
|
|
50
|
+
* the schematics. (`../package.json` is schematics/package.json, which only
|
|
51
|
+
* marks the schematics as CommonJS.)
|
|
52
|
+
*/
|
|
53
|
+
const ownVersionRange = () => {
|
|
54
|
+
const { version } = JSON.parse((0, node_fs_1.readFileSync)(node_path_1.default.join(__dirname, '../../package.json'), 'utf8'));
|
|
55
|
+
return `^${version}`;
|
|
56
|
+
};
|
|
57
|
+
exports.ownVersionRange = ownVersionRange;
|