Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 24 additions & 0 deletions .github/workflows/checks.yml
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,30 @@ jobs:
base-coverage-file: ./coverage/ui-watchtower/report.json
skip-step: all

smoke:
name: Packed packages
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v7

- name: Setup Node.js
uses: actions/setup-node@v7
with:
node-version: 22.x
cache: npm

- name: Install dependencies
run: npm ci

- name: Build both packages
run: npm run build

# Packs both packages and installs them, outside the workspace, into a
# core-only Node and TypeScript consumer and an unchanged Angular one.
- name: Smoke-test the packed packages
run: npm run smoke

lint:
name: Lint and format
runs-on: ubuntu-latest
Expand Down
15 changes: 12 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ projects/ngx-ui-watchtower/
README.md usage guide, published to npm
DESIGN.md full design
package.json the published package's manifest
scripts/ the public API snapshot
scripts/ the public API snapshot, and the packaging smoke test
```

The Angular package depends on the core, never the other way round.
Expand Down Expand Up @@ -66,11 +66,12 @@ application does. Tests and the typecheck read the core's source.
| `npm run typecheck` | Builds the core, then type-checks every tsconfig and type test |
| `npm run api:check` | Builds both packages, then compares their public APIs |
| `npm run api:update` | Builds both packages, then rewrites their snapshots |
| `npm run smoke` | Installs the built packages, packed, into throwaway consumers |
| `npm run lint` | ESLint, no warnings allowed |
| `npm run format` | Formats with Prettier |
| `npm run format:check` | Checks formatting without writing |

Three checks guard what consumers depend on:
Four checks guard what consumers depend on:

- **Type tests** (`type-tests/` in each package) compile a consumer that uses
every public export, and one that augments the name registries: registered
Expand All @@ -84,7 +85,14 @@ Three checks guard what consumers depend on:
(`projects/ngx-ui-watchtower/src/lib/sinks/uwt-broadcast/uwt-broadcast.wire.fixture.json`) records
every message a shell and a fragment exchange. Shells and fragments are
deployed separately, so it changes only with a deliberate protocol change:
`UPDATE_WIRE_FIXTURE=1 npm test` records it again.
`UPDATE_WIRE_FIXTURE=1 npm test` records it again. Every pairing of an
Angular or framework-free shell and fragment is held to it.
- **The smoke test** (`scripts/smoke/`) packs both packages and installs the
tarballs, outside this workspace, into a framework-free Node consumer (no
Angular, RxJS or `aws-rum-web` installed), a TypeScript one (the core's
type tests, and a project without the DOM typings), and an unchanged
Angular application, prerendered as a fragment without `aws-rum-web` and as
a shell with `/rum`. It installs from the registry, so it needs a network.

## Before you push

Expand All @@ -97,6 +105,7 @@ npm run lint
npm run format:check
npm run build
npm run api:check
npm run smoke
```

CI runs the same checks on every pull request and every push to `main`.
Expand Down
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@
"format:check": "prettier --check \"**/*.{ts,mjs,html}\"",
"typecheck": "tsc -p projects/ui-watchtower/tsconfig.lib.json --noEmit && tsc -p projects/ui-watchtower/rxjs/tsconfig.lib.json --noEmit && tsc -p projects/ui-watchtower/rum/tsconfig.lib.json --noEmit && tsc -p projects/ui-watchtower/tsconfig.spec.json --noEmit && tsc -p projects/ui-watchtower/type-tests/tsconfig.json && tsc -p projects/ui-watchtower/type-tests/tsconfig.augmented.json && ng build --configuration=production --project=ui-watchtower && tsc -p projects/ngx-ui-watchtower/tsconfig.lib.json --noEmit && tsc -p projects/ngx-ui-watchtower/tsconfig.spec.json --noEmit && tsc -p projects/ngx-ui-watchtower/rum/tsconfig.lib.json --noEmit && tsc -p projects/ngx-ui-watchtower/rum/tsconfig.spec.json --noEmit && ngc -p projects/ngx-ui-watchtower/type-tests/tsconfig.json && ngc -p projects/ngx-ui-watchtower/type-tests/tsconfig.augmented.json",
"api:check": "npm run build && node scripts/api-snapshot.mjs",
"api:update": "npm run build && node scripts/api-snapshot.mjs --update"
"api:update": "npm run build && node scripts/api-snapshot.mjs --update",
"smoke": "node scripts/smoke/smoke.mjs"
},
"private": true,
"dependencies": {
Expand Down
6 changes: 6 additions & 0 deletions projects/ngx-ui-watchtower/DESIGN.md
Original file line number Diff line number Diff line change
Expand Up @@ -239,6 +239,12 @@ providers: [
`@absaoss-cps/ngx-ui-watchtower/rum` can't import the main entry's
internals by relative path and carries small copies of what it needs: its
Angular helpers and the reporter.
- **What is published is tested as published.** `npm run smoke` installs
both tarballs into consumers outside the workspace: a framework-free one
with neither Angular nor `aws-rum-web` installed, and an Angular
application, prerendered as a fragment without `aws-rum-web` — which then
holds no RUM code — and as a shell with `/rum`, whose RUM client is a chunk
loaded on demand.
- **Test doubles live in the specs that use them**, declared inline, never in
a shared file and never exported. The one browser gap needing a stub is
`BroadcastChannel`, which jsdom doesn't implement.
Expand Down
2 changes: 1 addition & 1 deletion projects/ui-watchtower/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@absaoss-cps/ui-watchtower",
"version": "0.4.0",
"version": "0.1.0",
"description": "Framework-independent telemetry engine: application logs, scenario health telemetry and business/UX events, for any JavaScript or TypeScript application.",
"repository": {
"type": "git",
Expand Down
24 changes: 24 additions & 0 deletions scripts/smoke/angular/angular.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
{
"version": 1,
"cli": { "analytics": false },
"projects": {
"consumer": {
"projectType": "application",
"root": "",
"sourceRoot": "src",
"architect": {
"build": {
"builder": "@angular/build:application",
"options": {
"browser": "src/main.ts",
"server": "src/main.server.ts",
"outputMode": "static",
"index": "src/index.html",
"outputPath": "dist",
"tsConfig": "tsconfig.json"
}
}
}
}
}
}
8 changes: 8 additions & 0 deletions scripts/smoke/angular/src/app/app.config.server.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { ApplicationConfig, mergeApplicationConfig } from '@angular/core';
import { provideServerRendering, withRoutes } from '@angular/ssr';
import { appConfig } from './app.config';
import { serverRoutes } from './app.routes.server';

export const config: ApplicationConfig = mergeApplicationConfig(appConfig, {
providers: [provideServerRendering(withRoutes(serverRoutes))]
});
5 changes: 5 additions & 0 deletions scripts/smoke/angular/src/app/app.routes.server.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { RenderMode, ServerRoute } from '@angular/ssr';

export const serverRoutes: ServerRoute[] = [
{ path: '**', renderMode: RenderMode.Prerender }
];
4 changes: 4 additions & 0 deletions scripts/smoke/angular/src/app/app.routes.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import { Routes } from '@angular/router';
import { Home } from './home';

export const routes: Routes = [{ path: '', component: Home }];
9 changes: 9 additions & 0 deletions scripts/smoke/angular/src/app/app.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { Component } from '@angular/core';
import { RouterOutlet } from '@angular/router';

@Component({
selector: 'app-root',
imports: [RouterOutlet],
template: '<router-outlet />'
})
export class App {}
28 changes: 28 additions & 0 deletions scripts/smoke/angular/src/app/home.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { Component, inject } from '@angular/core';
import {
UwtBITelemetryService,
UwtLoggerService,
UwtScenarioTelemetryService
} from '@absaoss-cps/ngx-ui-watchtower';

/** Uses every service while it renders — on the server, when prerendered. */
@Component({
selector: 'app-home',
template: '<p id="scenario">scenario {{ status }}</p>'
})
export class Home {
readonly status: string;

constructor() {
const scenarios = inject(UwtScenarioTelemetryService);
const scenario = scenarios.start({ name: 'render-home' });
scenario.step('render');
inject(UwtBITelemetryService).track('home_rendered', { server: true });
inject(UwtLoggerService).getLogger('smoke').log('Home rendered');
scenario.complete();
this.status = scenario.status ?? 'running';

// @ts-expect-error not a registered scenario: the augmentation applies
scenarios.findByName('render-hom');
}
}
11 changes: 11 additions & 0 deletions scripts/smoke/angular/src/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>ui-watchtower smoke</title>
<base href="/" />
</head>
<body>
<app-root></app-root>
</body>
</html>
11 changes: 11 additions & 0 deletions scripts/smoke/angular/src/main.server.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import {
BootstrapContext,
bootstrapApplication
} from '@angular/platform-browser';
import { App } from './app/app';
import { config } from './app/app.config.server';

const bootstrap = (context: BootstrapContext) =>
bootstrapApplication(App, config, context);

export default bootstrap;
5 changes: 5 additions & 0 deletions scripts/smoke/angular/src/main.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { bootstrapApplication } from '@angular/platform-browser';
import { App } from './app/app';
import { appConfig } from './app/app.config';

bootstrapApplication(App, appConfig).catch((error) => console.error(error));
17 changes: 17 additions & 0 deletions scripts/smoke/angular/src/telemetry.schema.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
/** The application's names, as an existing consumer declares them. */
declare module '@absaoss-cps/ngx-ui-watchtower' {
interface UwtScenarioNames {
'render-home': true;
}
interface UwtScenarioSteps {
render: true;
}
interface UwtBIEventNames {
home_rendered: true;
}
interface UwtLoggerNames {
smoke: true;
}
}

export {};
18 changes: 18 additions & 0 deletions scripts/smoke/angular/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
{
"compilerOptions": {
"strict": true,
"noImplicitOverride": true,
"skipLibCheck": true,
"isolatedModules": true,
"experimentalDecorators": true,
"importHelpers": true,
"target": "ES2022",
"module": "ES2022",
"moduleResolution": "bundler",
"lib": ["ES2022", "dom"]
},
"angularCompilerOptions": {
"strictTemplates": true
},
"include": ["src/**/*.ts"]
}
24 changes: 24 additions & 0 deletions scripts/smoke/angular/variants/fragment.app.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import {
ApplicationConfig,
provideZonelessChangeDetection
} from '@angular/core';
import { provideRouter } from '@angular/router';
import {
provideUwtTelemetry,
provideUwtTelemetrySink
} from '@absaoss-cps/ngx-ui-watchtower';
import { routes } from './app.routes';

/** A fragment: no RUM, and aws-rum-web is not installed. */
export const appConfig: ApplicationConfig = {
providers: [
provideZonelessChangeDetection(),
provideRouter(routes),
provideUwtTelemetry({
application: 'smoke',
environment: 'test',
version: '1.0.0'
}),
provideUwtTelemetrySink('broadcast')
]
};
36 changes: 36 additions & 0 deletions scripts/smoke/angular/variants/shell.app.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import {
ApplicationConfig,
provideZonelessChangeDetection
} from '@angular/core';
import { provideRouter } from '@angular/router';
import {
provideUwtTelemetry,
provideUwtTelemetryBroadcastHost,
UWT_LOG_API_PROVIDER,
UwtNoopLogApiProvider
} from '@absaoss-cps/ngx-ui-watchtower';
import {
provideUwtTelemetryRumSink,
UWT_RUM_CREDENTIALS_PROVIDER
} from '@absaoss-cps/ngx-ui-watchtower/rum';
import { routes } from './app.routes';

/** A shell: AWS RUM, hosting its fragments. */
export const appConfig: ApplicationConfig = {
providers: [
provideZonelessChangeDetection(),
provideRouter(routes),
provideUwtTelemetry({
application: 'smoke',
environment: 'test',
version: '1.0.0'
}),
provideUwtTelemetryRumSink(),
{
provide: UWT_RUM_CREDENTIALS_PROVIDER,
useValue: { load: async () => null }
},
{ provide: UWT_LOG_API_PROVIDER, useClass: UwtNoopLogApiProvider },
provideUwtTelemetryBroadcastHost()
]
};
Loading
Loading