semmet-angular 0.29.0 → 0.30.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 +11 -0
- package/README.md +11 -0
- package/README.pt-BR.md +11 -0
- package/package.json +1 -1
- package/src/collection.json +5 -0
- package/src/docker/index.d.ts +3 -0
- package/src/docker/index.js +154 -0
- package/src/docker/index.js.map +1 -0
- package/src/docker/index.ts +186 -0
- package/src/docker/schema.d.ts +4 -0
- package/src/docker/schema.js +3 -0
- package/src/docker/schema.js.map +1 -0
- package/src/docker/schema.json +20 -0
- package/src/docker/schema.ts +4 -0
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 detects the package manager by lockfile, runs the build in the Node stage, 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 detecta o gerenciador de pacotes por lockfile, executa o build no estágio Node 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 detecta o gerenciador de pacotes por lockfile, executa o build no estágio Node 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.
|
|
3
|
+
"version": "0.30.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",
|
package/src/collection.json
CHANGED
|
@@ -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,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:20-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:20-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 @@
|
|
|
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
|
+
}
|