semmet-angular 0.29.0 → 0.31.0

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.en.md CHANGED
@@ -20,6 +20,7 @@ Brazilian Portuguese documentation is available in [README.md](./README.md).
20
20
  ng add semmet-angular
21
21
  ng generate semmet-angular:accordion my-faq
22
22
  ng generate semmet-angular:ci
23
+ ng generate semmet-angular:docker
23
24
  ```
24
25
 
25
26
  `ng add semmet-angular` installs the package and configures the delivery workflow once for the target application, including Playwright e2e support and npm scripts. Each generated component is dropped into your project exactly like `ng generate component` would (respecting your `angular.json` project, source root, and selector prefix), and has **zero runtime dependency** on this package — `semmet-angular` is only needed at generation time.
@@ -58,6 +59,16 @@ This command generates `.github/workflows/ci.yml` using the package scripts that
58
59
 
59
60
  By default, the schematic does not overwrite an existing workflow. Use `--force` when you want to replace `.github/workflows/ci.yml`.
60
61
 
62
+ ## Docker Packaging
63
+
64
+ ```bash
65
+ ng generate semmet-angular:docker
66
+ ```
67
+
68
+ This command generates `Dockerfile`, `.dockerignore`, and `nginx.conf` to package the Angular application as an image served by Nginx. The Dockerfile uses Node 24 in the build stage, detects the package manager by lockfile, runs the build, and copies the output configured in `angular.json` to `/usr/share/nginx/html`.
69
+
70
+ The `nginx.conf` includes SPA fallback with `try_files ... /index.html`. By default, the schematic does not overwrite existing files; use `--force` to replace the current Docker configuration.
71
+
61
72
  ## Projected content containers
62
73
 
63
74
  Some generated components are containers: they own the ARIA wiring, keyboard behavior, focus management, and visual shell, while your app owns the content rendered inside them.
package/README.md CHANGED
@@ -20,6 +20,7 @@ English documentation is available in [README.en.md](./README.en.md).
20
20
  ng add semmet-angular
21
21
  ng generate semmet-angular:accordion minha-faq
22
22
  ng generate semmet-angular:ci
23
+ ng generate semmet-angular:docker
23
24
  ```
24
25
 
25
26
  `ng add semmet-angular` instala o pacote e configura uma vez o fluxo de entrega da aplicação alvo, incluindo suporte a e2e com Playwright e scripts npm. Cada componente gerado entra no seu projeto do mesmo jeito que entraria com `ng generate component` (respeitando `angular.json`, `sourceRoot` e prefixo de seletor), e tem **zero dependência em tempo de execução** deste pacote — `semmet-angular` só é necessário na geração.
@@ -58,6 +59,16 @@ Esse comando gera `.github/workflows/ci.yml` usando os scripts já existentes no
58
59
 
59
60
  Por padrão, o schematic não sobrescreve um workflow existente. Use `--force` quando quiser substituir `.github/workflows/ci.yml`.
60
61
 
62
+ ## Empacotamento com Docker
63
+
64
+ ```bash
65
+ ng generate semmet-angular:docker
66
+ ```
67
+
68
+ Esse comando gera `Dockerfile`, `.dockerignore` e `nginx.conf` para empacotar a aplicação Angular como uma imagem servida por Nginx. O Dockerfile usa Node 24 no estágio de build, detecta o gerenciador de pacotes por lockfile, executa o build e copia o output configurado no `angular.json` para `/usr/share/nginx/html`.
69
+
70
+ O `nginx.conf` inclui fallback de SPA com `try_files ... /index.html`. Por padrão, o schematic não sobrescreve arquivos existentes; use `--force` para substituir a configuração Docker atual.
71
+
61
72
  ## Testes Gerados
62
73
 
63
74
  Todo schematic de componente gera arquivos de teste ao lado do componente:
package/README.pt-BR.md CHANGED
@@ -18,6 +18,7 @@ Leia a direção geral do produto em [PRODUCT_VISION.md](./PRODUCT_VISION.md).
18
18
  ng add semmet-angular
19
19
  ng generate semmet-angular:accordion minha-faq
20
20
  ng generate semmet-angular:ci
21
+ ng generate semmet-angular:docker
21
22
  ```
22
23
 
23
24
  `ng add semmet-angular` instala o pacote e configura uma vez o fluxo de entrega da aplicação alvo, incluindo suporte a e2e com Playwright e scripts npm. Cada componente gerado entra no seu projeto do mesmo jeito que entraria com `ng generate component` (respeitando `angular.json`, `sourceRoot` e prefixo de seletor), e tem **zero dependência em tempo de execução** deste pacote — `semmet-angular` só é necessário na geração.
@@ -56,6 +57,16 @@ Esse comando gera `.github/workflows/ci.yml` usando os scripts já existentes no
56
57
 
57
58
  Por padrão, o schematic não sobrescreve um workflow existente. Use `--force` quando quiser substituir `.github/workflows/ci.yml`.
58
59
 
60
+ ## Empacotamento com Docker
61
+
62
+ ```bash
63
+ ng generate semmet-angular:docker
64
+ ```
65
+
66
+ Esse comando gera `Dockerfile`, `.dockerignore` e `nginx.conf` para empacotar a aplicação Angular como uma imagem servida por Nginx. O Dockerfile usa Node 24 no estágio de build, detecta o gerenciador de pacotes por lockfile, executa o build e copia o output configurado no `angular.json` para `/usr/share/nginx/html`.
67
+
68
+ O `nginx.conf` inclui fallback de SPA com `try_files ... /index.html`. Por padrão, o schematic não sobrescreve arquivos existentes; use `--force` para substituir a configuração Docker atual.
69
+
59
70
  ## Testes Gerados
60
71
 
61
72
  Todo schematic de componente gera arquivos de teste ao lado do componente:
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "semmet-angular",
3
- "version": "0.29.0",
3
+ "version": "0.31.0",
4
4
  "description": "Accessible UI Delivery Kit for Angular: schematics that generate ARIA-conformant standalone components with styling, keyboard behavior, unit tests, and Playwright e2e smoke tests.",
5
5
  "publisher": "danilodevsilva",
6
6
  "license": "MIT",
@@ -11,6 +11,11 @@
11
11
  "factory": "./ci/index#ci",
12
12
  "schema": "./ci/schema.json"
13
13
  },
14
+ "docker": {
15
+ "description": "Generates Docker packaging files for an Angular application served by Nginx.",
16
+ "factory": "./docker/index#docker",
17
+ "schema": "./docker/schema.json"
18
+ },
14
19
  "accordion": {
15
20
  "description": "Generates a standalone Angular Accordion component following the W3C ARIA Authoring Practices Guide.",
16
21
  "factory": "./accordion/index#accordion",
@@ -0,0 +1,3 @@
1
+ import { Rule } from '@angular-devkit/schematics';
2
+ import { Schema } from './schema';
3
+ export declare function docker(options: Schema): Rule;
@@ -0,0 +1,154 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.docker = docker;
4
+ const schematics_1 = require("@angular-devkit/schematics");
5
+ const DOCKERFILE_PATH = '/Dockerfile';
6
+ const DOCKERIGNORE_PATH = '/.dockerignore';
7
+ const NGINX_CONFIG_PATH = '/nginx.conf';
8
+ function docker(options) {
9
+ return (tree) => {
10
+ const files = [DOCKERFILE_PATH, DOCKERIGNORE_PATH, NGINX_CONFIG_PATH];
11
+ const existingFiles = files.filter((file) => tree.exists(file));
12
+ if (existingFiles.length > 0 && !options.force) {
13
+ throw new schematics_1.SchematicsException(`${existingFiles.join(', ')} already exist. Re-run with --force to overwrite them.`);
14
+ }
15
+ const workspace = readJson(tree, '/angular.json');
16
+ const projectName = resolveProjectName(workspace, options.project);
17
+ const outputPath = resolveBuildOutputPath(workspace, projectName);
18
+ const packageManager = detectPackageManager(tree);
19
+ const commands = getPackageManagerCommands(packageManager, tree.exists('/package-lock.json'));
20
+ writeFile(tree, DOCKERFILE_PATH, buildDockerfile(outputPath, commands));
21
+ writeFile(tree, DOCKERIGNORE_PATH, buildDockerignore());
22
+ writeFile(tree, NGINX_CONFIG_PATH, buildNginxConfig());
23
+ };
24
+ }
25
+ function resolveProjectName(workspace, projectName) {
26
+ if (projectName) {
27
+ if (!workspace.projects?.[projectName]) {
28
+ throw new schematics_1.SchematicsException(`Project "${projectName}" was not found in angular.json.`);
29
+ }
30
+ return projectName;
31
+ }
32
+ if (workspace.defaultProject && workspace.projects?.[workspace.defaultProject]) {
33
+ return workspace.defaultProject;
34
+ }
35
+ const projects = Object.keys(workspace.projects ?? {});
36
+ if (projects.length === 0) {
37
+ throw new schematics_1.SchematicsException('No Angular project was found in angular.json.');
38
+ }
39
+ return projects[0];
40
+ }
41
+ function resolveBuildOutputPath(workspace, projectName) {
42
+ const project = workspace.projects?.[projectName] ?? {};
43
+ const buildTarget = project.architect?.build ?? project.targets?.build;
44
+ const outputPath = buildTarget?.options?.outputPath;
45
+ if (typeof outputPath === 'string') {
46
+ return trimSlashes(outputPath);
47
+ }
48
+ if (outputPath?.base) {
49
+ return trimSlashes([outputPath.base, outputPath.browser].filter(Boolean).join('/'));
50
+ }
51
+ return `dist/${projectName}/browser`;
52
+ }
53
+ function detectPackageManager(tree) {
54
+ if (tree.exists('/pnpm-lock.yaml')) {
55
+ return 'pnpm';
56
+ }
57
+ if (tree.exists('/yarn.lock')) {
58
+ return 'yarn';
59
+ }
60
+ return 'npm';
61
+ }
62
+ function getPackageManagerCommands(packageManager, hasNpmLockfile) {
63
+ if (packageManager === 'pnpm') {
64
+ return {
65
+ prepare: 'RUN corepack enable',
66
+ install: 'pnpm install --frozen-lockfile',
67
+ build: 'pnpm run build',
68
+ };
69
+ }
70
+ if (packageManager === 'yarn') {
71
+ return {
72
+ prepare: 'RUN corepack enable',
73
+ install: 'yarn install --frozen-lockfile',
74
+ build: 'yarn build',
75
+ };
76
+ }
77
+ return {
78
+ install: hasNpmLockfile ? 'npm ci' : 'npm install',
79
+ build: 'npm run build',
80
+ };
81
+ }
82
+ function buildDockerfile(outputPath, commands) {
83
+ const prepareStep = commands.prepare ? `${commands.prepare}\n` : '';
84
+ return `FROM node:24-alpine AS build
85
+ WORKDIR /app
86
+
87
+ COPY package*.json ./
88
+ COPY pnpm-lock.yaml* yarn.lock* ./
89
+ ${prepareStep}RUN ${commands.install}
90
+
91
+ COPY . .
92
+ RUN ${commands.build}
93
+
94
+ FROM nginx:1.27-alpine
95
+ COPY nginx.conf /etc/nginx/conf.d/default.conf
96
+ COPY --from=build /app/${outputPath} /usr/share/nginx/html
97
+
98
+ EXPOSE 80
99
+
100
+ CMD ["nginx", "-g", "daemon off;"]
101
+ `;
102
+ }
103
+ function buildDockerignore() {
104
+ return `node_modules
105
+ dist
106
+ .angular
107
+ .git
108
+ .github
109
+ coverage
110
+ playwright-report
111
+ test-results
112
+ .DS_Store
113
+ npm-debug.log*
114
+ yarn-debug.log*
115
+ yarn-error.log*
116
+ `;
117
+ }
118
+ function buildNginxConfig() {
119
+ return `server {
120
+ listen 80;
121
+ server_name _;
122
+
123
+ root /usr/share/nginx/html;
124
+ index index.html;
125
+
126
+ location / {
127
+ try_files $uri $uri/ /index.html;
128
+ }
129
+
130
+ location ~* \\.(?:css|js|mjs|map|ico|png|jpg|jpeg|gif|svg|webp|woff2?)$ {
131
+ try_files $uri =404;
132
+ access_log off;
133
+ expires 1y;
134
+ add_header Cache-Control "public, immutable";
135
+ }
136
+ }
137
+ `;
138
+ }
139
+ function writeFile(tree, path, content) {
140
+ if (tree.exists(path)) {
141
+ tree.overwrite(path, content);
142
+ }
143
+ else {
144
+ tree.create(path, content);
145
+ }
146
+ }
147
+ function trimSlashes(value) {
148
+ return value.replace(/^\/+|\/+$/g, '');
149
+ }
150
+ function readJson(tree, path) {
151
+ const content = tree.readText(path);
152
+ return JSON.parse(content);
153
+ }
154
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["index.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,186 @@
1
+ import { Rule, SchematicsException, Tree } from '@angular-devkit/schematics';
2
+ import { Schema } from './schema';
3
+
4
+ type PackageManager = 'npm' | 'pnpm' | 'yarn';
5
+
6
+ interface PackageManagerCommands {
7
+ install: string;
8
+ build: string;
9
+ prepare?: string;
10
+ }
11
+
12
+ const DOCKERFILE_PATH = '/Dockerfile';
13
+ const DOCKERIGNORE_PATH = '/.dockerignore';
14
+ const NGINX_CONFIG_PATH = '/nginx.conf';
15
+
16
+ export function docker(options: Schema): Rule {
17
+ return (tree: Tree) => {
18
+ const files = [DOCKERFILE_PATH, DOCKERIGNORE_PATH, NGINX_CONFIG_PATH];
19
+ const existingFiles = files.filter((file) => tree.exists(file));
20
+
21
+ if (existingFiles.length > 0 && !options.force) {
22
+ throw new SchematicsException(`${existingFiles.join(', ')} already exist. Re-run with --force to overwrite them.`);
23
+ }
24
+
25
+ const workspace = readJson(tree, '/angular.json');
26
+ const projectName = resolveProjectName(workspace, options.project);
27
+ const outputPath = resolveBuildOutputPath(workspace, projectName);
28
+ const packageManager = detectPackageManager(tree);
29
+ const commands = getPackageManagerCommands(packageManager, tree.exists('/package-lock.json'));
30
+
31
+ writeFile(tree, DOCKERFILE_PATH, buildDockerfile(outputPath, commands));
32
+ writeFile(tree, DOCKERIGNORE_PATH, buildDockerignore());
33
+ writeFile(tree, NGINX_CONFIG_PATH, buildNginxConfig());
34
+ };
35
+ }
36
+
37
+ function resolveProjectName(workspace: Record<string, any>, projectName?: string): string {
38
+ if (projectName) {
39
+ if (!workspace.projects?.[projectName]) {
40
+ throw new SchematicsException(`Project "${projectName}" was not found in angular.json.`);
41
+ }
42
+
43
+ return projectName;
44
+ }
45
+
46
+ if (workspace.defaultProject && workspace.projects?.[workspace.defaultProject]) {
47
+ return workspace.defaultProject as string;
48
+ }
49
+
50
+ const projects = Object.keys(workspace.projects ?? {});
51
+
52
+ if (projects.length === 0) {
53
+ throw new SchematicsException('No Angular project was found in angular.json.');
54
+ }
55
+
56
+ return projects[0];
57
+ }
58
+
59
+ function resolveBuildOutputPath(workspace: Record<string, any>, projectName: string): string {
60
+ const project = workspace.projects?.[projectName] ?? {};
61
+ const buildTarget = project.architect?.build ?? project.targets?.build;
62
+ const outputPath = buildTarget?.options?.outputPath;
63
+
64
+ if (typeof outputPath === 'string') {
65
+ return trimSlashes(outputPath);
66
+ }
67
+
68
+ if (outputPath?.base) {
69
+ return trimSlashes([outputPath.base, outputPath.browser].filter(Boolean).join('/'));
70
+ }
71
+
72
+ return `dist/${projectName}/browser`;
73
+ }
74
+
75
+ function detectPackageManager(tree: Tree): PackageManager {
76
+ if (tree.exists('/pnpm-lock.yaml')) {
77
+ return 'pnpm';
78
+ }
79
+
80
+ if (tree.exists('/yarn.lock')) {
81
+ return 'yarn';
82
+ }
83
+
84
+ return 'npm';
85
+ }
86
+
87
+ function getPackageManagerCommands(packageManager: PackageManager, hasNpmLockfile: boolean): PackageManagerCommands {
88
+ if (packageManager === 'pnpm') {
89
+ return {
90
+ prepare: 'RUN corepack enable',
91
+ install: 'pnpm install --frozen-lockfile',
92
+ build: 'pnpm run build',
93
+ };
94
+ }
95
+
96
+ if (packageManager === 'yarn') {
97
+ return {
98
+ prepare: 'RUN corepack enable',
99
+ install: 'yarn install --frozen-lockfile',
100
+ build: 'yarn build',
101
+ };
102
+ }
103
+
104
+ return {
105
+ install: hasNpmLockfile ? 'npm ci' : 'npm install',
106
+ build: 'npm run build',
107
+ };
108
+ }
109
+
110
+ function buildDockerfile(outputPath: string, commands: PackageManagerCommands): string {
111
+ const prepareStep = commands.prepare ? `${commands.prepare}\n` : '';
112
+
113
+ return `FROM node:24-alpine AS build
114
+ WORKDIR /app
115
+
116
+ COPY package*.json ./
117
+ COPY pnpm-lock.yaml* yarn.lock* ./
118
+ ${prepareStep}RUN ${commands.install}
119
+
120
+ COPY . .
121
+ RUN ${commands.build}
122
+
123
+ FROM nginx:1.27-alpine
124
+ COPY nginx.conf /etc/nginx/conf.d/default.conf
125
+ COPY --from=build /app/${outputPath} /usr/share/nginx/html
126
+
127
+ EXPOSE 80
128
+
129
+ CMD ["nginx", "-g", "daemon off;"]
130
+ `;
131
+ }
132
+
133
+ function buildDockerignore(): string {
134
+ return `node_modules
135
+ dist
136
+ .angular
137
+ .git
138
+ .github
139
+ coverage
140
+ playwright-report
141
+ test-results
142
+ .DS_Store
143
+ npm-debug.log*
144
+ yarn-debug.log*
145
+ yarn-error.log*
146
+ `;
147
+ }
148
+
149
+ function buildNginxConfig(): string {
150
+ return `server {
151
+ listen 80;
152
+ server_name _;
153
+
154
+ root /usr/share/nginx/html;
155
+ index index.html;
156
+
157
+ location / {
158
+ try_files $uri $uri/ /index.html;
159
+ }
160
+
161
+ location ~* \\.(?:css|js|mjs|map|ico|png|jpg|jpeg|gif|svg|webp|woff2?)$ {
162
+ try_files $uri =404;
163
+ access_log off;
164
+ expires 1y;
165
+ add_header Cache-Control "public, immutable";
166
+ }
167
+ }
168
+ `;
169
+ }
170
+
171
+ function writeFile(tree: Tree, path: string, content: string): void {
172
+ if (tree.exists(path)) {
173
+ tree.overwrite(path, content);
174
+ } else {
175
+ tree.create(path, content);
176
+ }
177
+ }
178
+
179
+ function trimSlashes(value: string): string {
180
+ return value.replace(/^\/+|\/+$/g, '');
181
+ }
182
+
183
+ function readJson(tree: Tree, path: string): Record<string, any> {
184
+ const content = tree.readText(path);
185
+ return JSON.parse(content) as Record<string, any>;
186
+ }
@@ -0,0 +1,4 @@
1
+ export interface Schema {
2
+ force?: boolean;
3
+ project?: string;
4
+ }
@@ -0,0 +1,3 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ //# sourceMappingURL=schema.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"schema.js","sourceRoot":"","sources":["schema.ts"],"names":[],"mappings":""}
@@ -0,0 +1,20 @@
1
+ {
2
+ "$schema": "http://json-schema.org/draft-07/schema",
3
+ "$id": "SemmetAngularDocker",
4
+ "title": "Semmet Angular Docker Options Schema",
5
+ "type": "object",
6
+ "description": "Generates Docker packaging files for an Angular application served by Nginx.",
7
+ "additionalProperties": false,
8
+ "properties": {
9
+ "force": {
10
+ "type": "boolean",
11
+ "default": false,
12
+ "description": "Overwrite existing Docker packaging files."
13
+ },
14
+ "project": {
15
+ "type": "string",
16
+ "description": "The Angular project to package.",
17
+ "$default": { "$source": "projectName" }
18
+ }
19
+ }
20
+ }
@@ -0,0 +1,4 @@
1
+ export interface Schema {
2
+ force?: boolean;
3
+ project?: string;
4
+ }