diff --git a/packages/schematics/scripts/copy-meta-to-dist.js b/packages/schematics/scripts/copy-meta-to-dist.js index 2948353bb1..9d9a695fd5 100644 --- a/packages/schematics/scripts/copy-meta-to-dist.js +++ b/packages/schematics/scripts/copy-meta-to-dist.js @@ -3,6 +3,16 @@ const { resolve, join } = require('path'); const { getMigrations } = require('../src/utils/migrations'); const { statSync } = require('fs'); +/** Files every migration directory has to provide; they ship next to the compiled rule. */ +const MIGRATION_META_FILES = ['schema.json', 'README.md']; + +/** + * Everything that could not be copied. Collected rather than thrown so that one missing file + * neither hides the ones after it nor leaves the remaining copies undone — an aborted run used + * to publish a truncated `dist/components/schematics`. + */ +const failures = []; + const resolvePath = (...segments) => resolve(__dirname, ...segments); const ensureDirectoryExistence = async (filePath) => { @@ -17,8 +27,20 @@ const copyFileWrapper = async (src, dest) => { try { await copyFile(src, dest); } catch (error) { - console.error(`Failed to copy file from ${src} to ${dest}: ${error.message}`); - throw error; + failures.push(`Failed to copy file from ${src} to ${dest}: ${error.message}`); + } +}; + +/** Copies the meta files of one migration, naming the migration instead of reporting a bare ENOENT path. */ +const copyMigrationMeta = async (migration, migrationPath) => { + for (const file of MIGRATION_META_FILES) { + const src = resolvePath(`../src/migrations/${migration}/${file}`); + + if (statSync(src, { throwIfNoEntry: false })) { + await copyFileWrapper(src, join(migrationPath, file)); + } else { + failures.push(`No ${file} for the ${migration} migration`); + } } }; @@ -45,14 +67,8 @@ const init = async () => { await ensureDirectoryExistence(migrationPath); - await copyFileWrapper( - resolvePath(`../src/migrations/${migration}/schema.json`), - join(migrationPath, 'schema.json') - ); - await copyFileWrapper( - resolvePath(`../src/migrations/${migration}/README.md`), - join(migrationPath, 'README.md') - ); + await copyMigrationMeta(migration, migrationPath); + await copyFileWrapper(resolvePath(`../dist/migrations/${migration}/index.js`), join(migrationPath, 'index.js')); const optionalMigrationData = resolvePath(`../dist/migrations/${migration}/data.js`); const fileExists = statSync(optionalMigrationData, { throwIfNoEntry: false }); @@ -76,6 +92,15 @@ const init = async () => { await copyFileWrapper(resolvePath('../dist/utils/typescript.js'), join(utilsPath, 'typescript.js')); await copyFileWrapper(resolvePath('../dist/utils/ast.js'), join(utilsPath, 'ast.js')); await copyFileWrapper(resolvePath('../dist/utils/angular-parsing.js'), join(utilsPath, 'angular-parsing.js')); + + if (failures.length > 0) { + throw new Error([`${failures.length} file(s) missing from the package:`, ...failures].join('\n ')); + } }; -init().catch((error) => console.error(`Failed to initialize directories and copy files: ${error.message}`)); +init().catch((error) => { + console.error(`Failed to initialize directories and copy files: ${error.message}`); + // Without this the rejection is swallowed, the build stays green, and an incomplete + // dist/components/schematics gets published. + process.exitCode = 1; +}); diff --git a/packages/schematics/src/migrations/scrollbar-deprecated-path/README.md b/packages/schematics/src/migrations/scrollbar-deprecated-path/README.md new file mode 100644 index 0000000000..ec573e061d --- /dev/null +++ b/packages/schematics/src/migrations/scrollbar-deprecated-path/README.md @@ -0,0 +1,128 @@ +# scrollbar-deprecated-path + +Migration schematic invoked automatically by `ng update @koobiq/components@20` +(registered for `20.3.0-0`). Rewrites `@koobiq/components/scrollbar` imports to +`@koobiq/components/scrollbar/deprecated`. + +## Background + +`@koobiq/components/scrollbar` now resolves to a new, dependency-free scrollbar built on +`CdkScrollable`. The previous implementation — the `overlayscrollbars`-based `KbqScrollbar` +component together with `KbqScrollbarDirective` — moved to +`@koobiq/components/scrollbar/deprecated` and will be removed in a future major version. + +Nothing about that implementation changed: `initializationTarget`, `options`, `events`, `defer`, +`scrollbarInstance`, the `onInitialize` / `onUpdate` / `onDestroy` / `onScroll` outputs and the +`kbq-scrollbar` element selector all behave exactly as before at the new path. Rewriting the +import is the whole migration — adopting the new scrollbar is a separate, manual step. + +## Behaviour change + +The two entry points share only two exported names, so most unmigrated imports break loudly: +`KbqScrollbarModule`, `KbqScrollbarDirective`, `KBQ_SCROLLBAR_CONFIG`, +`KBQ_SCROLLBAR_OPTIONS_DEFAULT_CONFIG`, `KBQ_SCROLLBAR_OPTIONS_DEFAULT_CONFIG_PROVIDER`, +`KbqScrollbarEvents`, `KbqScrollbarEventListenerArgs` and `KbqScrollbarTarget` live at the +`/deprecated` path only, so the build stops with `TS2305: … has no exported member`. + +`KbqScrollbar` and `KbqScrollbarOptions` are the silent pair — both names exist at both paths: + +- `KbqScrollbar` keeps the `kbq-scrollbar` selector and `exportAs: 'kbqScrollbar'`, so a template + that only renders `` keeps compiling and keeps rendering — with the new + component, which has a different set of inputs. The `[kbq-scrollbar]` attribute form of the + selector is gone as well, so a host marked that way quietly matches nothing. +- `KbqScrollbarOptions` is now `{ mode: KbqScrollbarMode }` instead of the `overlayscrollbars` + `PartialOptions`. + +## What it does + +The schematic walks every `.ts` file under the project root — the whole workspace when `--project` +is omitted, which is how `ng update` invokes it — skipping `node_modules` and `dist`, and rewrites +the module specifier `@koobiq/components/scrollbar` to `@koobiq/components/scrollbar/deprecated`. + +The match is quote-anchored (`(['"])@koobiq/components/scrollbar\1`), so only the exact, bare +specifier is taken, in the quote style it was written in: + +| Specifier | Result | +| ------------------------------------------------- | -------------------------------------------------------------------------------------- | +| `'@koobiq/components/scrollbar'` | rewritten, still single-quoted | +| `"@koobiq/components/scrollbar"` | rewritten, still double-quoted | +| `'@koobiq/components/scrollbar/deprecated'` | untouched — the migration is idempotent | +| `'@koobiq/components/scrollbar-x'` (hypothetical) | untouched — the closing quote is required, so a longer specifier is not a prefix match | + +It is a textual replacement rather than an AST rewrite, so an `import`, an `export … from`, a +dynamic `import()` and a specifier handed to something like `jest.mock()` are all covered by the +same pass. + +## What it does _not_ do (manual) + +| Pattern | Manual migration | +| ------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| A specifier outside a `.ts` file | Only `.ts` files are visited — the same string in a JSON config or a documentation snippet stays as written | +| `overlayscrollbars` in `package.json` | The `/deprecated` entry point still imports it. Adding it as a dependency is the `mandatory-peer-dependencies` migration's job | +| Moving off `/deprecated` | Nothing is migrated _to_ the new scrollbar: `options` / `events` / `defer` are replaced by `kbqScrollbarMode`, and the `overlayscrollbars` instance by the `scrollTo*` methods and `scrollChanges` | + +`fix` defaults to `true`. `ng update` invokes migrations with no options at all, and +`migrations.json` declares no schema, so the rule applies that default itself. With `--fix false` +every file that would change is logged instead of written, followed by the same count either way. + +[Params](schema.ts) + +Usage for Angular Cli: + +```shell +ng g @koobiq/components:scrollbar-deprecated-path --project +``` + +Usage for Nx: + +```shell +nx g @koobiq/components:scrollbar-deprecated-path --project +``` + +### Run locally + +Build package + +```shell +yarn run build:schematics +``` + +Run command (for example, for `koobiq-docs` project) + +```shell +ng g ./dist/components/schematics/collection.json:scrollbar-deprecated-path --project koobiq-docs +``` + +### Result + +#### Before + +```ts +import { Component } from '@angular/core'; +import { KbqScrollbarModule } from '@koobiq/components/scrollbar'; + +@Component({ + selector: 'my-page', + imports: [KbqScrollbarModule], + template: ` + ... + ` +}) +export class MyPage {} +``` + +#### After + +```ts +import { Component } from '@angular/core'; +import { KbqScrollbarModule } from '@koobiq/components/scrollbar/deprecated'; + +@Component({ + selector: 'my-page', + imports: [KbqScrollbarModule], + template: ` + ... + ` +}) +export class MyPage {} +```