semmet-angular 0.30.0 → 0.32.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
@@ -21,6 +21,7 @@ ng add semmet-angular
21
21
  ng generate semmet-angular:accordion my-faq
22
22
  ng generate semmet-angular:ci
23
23
  ng generate semmet-angular:docker
24
+ ng generate semmet-angular:deploy
24
25
  ```
25
26
 
26
27
  `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.
@@ -65,10 +66,28 @@ By default, the schematic does not overwrite an existing workflow. Use `--force`
65
66
  ng generate semmet-angular:docker
66
67
  ```
67
68
 
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
+ 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
 
70
71
  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
72
 
73
+ ## Guided Deploy
74
+
75
+ ```bash
76
+ ng generate semmet-angular:deploy
77
+ ```
78
+
79
+ The command asks which platform you want to target and generates the minimal configuration for that destination. You can also pass the platform directly:
80
+
81
+ ```bash
82
+ ng generate semmet-angular:deploy --platform=vercel
83
+ ng generate semmet-angular:deploy --platform=netlify
84
+ ng generate semmet-angular:deploy --platform=firebase
85
+ ng generate semmet-angular:deploy --platform=github-pages
86
+ ng generate semmet-angular:deploy --platform=docker
87
+ ```
88
+
89
+ Supported targets: Vercel (`vercel.json`), Netlify (`netlify.toml`), Firebase Hosting (`firebase.json` and `.firebaserc`), GitHub Pages (`.github/workflows/deploy.yml`), and Docker-based deployment (`compose.yml` and `deploy/docker.README.md`). The schematic detects the build output from `angular.json` when possible and does not overwrite existing files without `--force`.
90
+
72
91
  ## Projected content containers
73
92
 
74
93
  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
@@ -21,6 +21,7 @@ ng add semmet-angular
21
21
  ng generate semmet-angular:accordion minha-faq
22
22
  ng generate semmet-angular:ci
23
23
  ng generate semmet-angular:docker
24
+ ng generate semmet-angular:deploy
24
25
  ```
25
26
 
26
27
  `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.
@@ -65,10 +66,28 @@ Por padrão, o schematic não sobrescreve um workflow existente. Use `--force` q
65
66
  ng generate semmet-angular:docker
66
67
  ```
67
68
 
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
+ 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
 
70
71
  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
72
 
73
+ ## Deploy Guiado
74
+
75
+ ```bash
76
+ ng generate semmet-angular:deploy
77
+ ```
78
+
79
+ O comando pergunta qual plataforma você quer usar e gera a configuração mínima para o destino escolhido. Também dá para passar a plataforma direto:
80
+
81
+ ```bash
82
+ ng generate semmet-angular:deploy --platform=vercel
83
+ ng generate semmet-angular:deploy --platform=netlify
84
+ ng generate semmet-angular:deploy --platform=firebase
85
+ ng generate semmet-angular:deploy --platform=github-pages
86
+ ng generate semmet-angular:deploy --platform=docker
87
+ ```
88
+
89
+ Destinos suportados: Vercel (`vercel.json`), Netlify (`netlify.toml`), Firebase Hosting (`firebase.json` e `.firebaserc`), GitHub Pages (`.github/workflows/deploy.yml`) e deploy baseado em Docker (`compose.yml` e `deploy/docker.README.md`). O schematic detecta o output do build no `angular.json` quando possível e não sobrescreve arquivos existentes sem `--force`.
90
+
72
91
  ## Testes Gerados
73
92
 
74
93
  Todo schematic de componente gera arquivos de teste ao lado do componente:
package/README.pt-BR.md CHANGED
@@ -19,6 +19,7 @@ ng add semmet-angular
19
19
  ng generate semmet-angular:accordion minha-faq
20
20
  ng generate semmet-angular:ci
21
21
  ng generate semmet-angular:docker
22
+ ng generate semmet-angular:deploy
22
23
  ```
23
24
 
24
25
  `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.
@@ -63,10 +64,28 @@ Por padrão, o schematic não sobrescreve um workflow existente. Use `--force` q
63
64
  ng generate semmet-angular:docker
64
65
  ```
65
66
 
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
+ 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
 
68
69
  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
70
 
71
+ ## Deploy Guiado
72
+
73
+ ```bash
74
+ ng generate semmet-angular:deploy
75
+ ```
76
+
77
+ O comando pergunta qual plataforma você quer usar e gera a configuração mínima para o destino escolhido. Também dá para passar a plataforma direto:
78
+
79
+ ```bash
80
+ ng generate semmet-angular:deploy --platform=vercel
81
+ ng generate semmet-angular:deploy --platform=netlify
82
+ ng generate semmet-angular:deploy --platform=firebase
83
+ ng generate semmet-angular:deploy --platform=github-pages
84
+ ng generate semmet-angular:deploy --platform=docker
85
+ ```
86
+
87
+ Destinos suportados: Vercel (`vercel.json`), Netlify (`netlify.toml`), Firebase Hosting (`firebase.json` e `.firebaserc`), GitHub Pages (`.github/workflows/deploy.yml`) e deploy baseado em Docker (`compose.yml` e `deploy/docker.README.md`). O schematic detecta o output do build no `angular.json` quando possível e não sobrescreve arquivos existentes sem `--force`.
88
+
70
89
  ## Testes Gerados
71
90
 
72
91
  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.30.0",
3
+ "version": "0.32.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",
@@ -16,6 +16,11 @@
16
16
  "factory": "./docker/index#docker",
17
17
  "schema": "./docker/schema.json"
18
18
  },
19
+ "deploy": {
20
+ "description": "Generates deploy scaffolding for a selected platform.",
21
+ "factory": "./deploy/index#deploy",
22
+ "schema": "./deploy/schema.json"
23
+ },
19
24
  "accordion": {
20
25
  "description": "Generates a standalone Angular Accordion component following the W3C ARIA Authoring Practices Guide.",
21
26
  "factory": "./accordion/index#accordion",
@@ -0,0 +1,3 @@
1
+ import { Rule } from '@angular-devkit/schematics';
2
+ import { Schema } from './schema';
3
+ export declare function deploy(options: Schema): Rule;
@@ -0,0 +1,245 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.deploy = deploy;
4
+ const schematics_1 = require("@angular-devkit/schematics");
5
+ function deploy(options) {
6
+ return (tree) => {
7
+ const workspace = readJson(tree, '/angular.json');
8
+ const project = resolveProjectInfo(workspace, options.project);
9
+ const packageManager = detectPackageManager(tree);
10
+ const files = buildDeployFiles(options.platform, project, packageManager);
11
+ const existingFiles = files.map((file) => file.path).filter((path) => tree.exists(path));
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
+ for (const file of files) {
16
+ writeFile(tree, file.path, file.content);
17
+ }
18
+ };
19
+ }
20
+ function buildDeployFiles(platform, project, packageManager) {
21
+ if (platform === 'vercel') {
22
+ return [
23
+ {
24
+ path: '/vercel.json',
25
+ content: `${JSON.stringify({
26
+ buildCommand: getBuildCommand(packageManager),
27
+ outputDirectory: project.outputPath,
28
+ rewrites: [{ source: '/(.*)', destination: '/index.html' }],
29
+ }, null, 2)}\n`,
30
+ },
31
+ ];
32
+ }
33
+ if (platform === 'netlify') {
34
+ return [
35
+ {
36
+ path: '/netlify.toml',
37
+ content: `[build]
38
+ command = "${getBuildCommand(packageManager)}"
39
+ publish = "${project.outputPath}"
40
+
41
+ [[redirects]]
42
+ from = "/*"
43
+ to = "/index.html"
44
+ status = 200
45
+ `,
46
+ },
47
+ ];
48
+ }
49
+ if (platform === 'firebase') {
50
+ return [
51
+ {
52
+ path: '/firebase.json',
53
+ content: `${JSON.stringify({
54
+ hosting: {
55
+ public: project.outputPath,
56
+ ignore: ['firebase.json', '**/.*', '**/node_modules/**'],
57
+ rewrites: [{ source: '**', destination: '/index.html' }],
58
+ },
59
+ }, null, 2)}\n`,
60
+ },
61
+ {
62
+ path: '/.firebaserc',
63
+ content: `${JSON.stringify({ projects: { default: project.name } }, null, 2)}\n`,
64
+ },
65
+ ];
66
+ }
67
+ if (platform === 'github-pages') {
68
+ return [
69
+ {
70
+ path: '/.github/workflows/deploy.yml',
71
+ content: buildGitHubPagesWorkflow(project.outputPath, packageManager),
72
+ },
73
+ ];
74
+ }
75
+ return [
76
+ {
77
+ path: '/compose.yml',
78
+ content: `services:
79
+ web:
80
+ build: .
81
+ ports:
82
+ - "8080:80"
83
+ restart: unless-stopped
84
+ `,
85
+ },
86
+ {
87
+ path: '/deploy/docker.README.md',
88
+ content: `# Docker Deployment
89
+
90
+ Build and run the generated Docker image:
91
+
92
+ \`\`\`bash
93
+ docker compose up --build
94
+ \`\`\`
95
+
96
+ The application will be available at http://localhost:8080.
97
+ `,
98
+ },
99
+ ];
100
+ }
101
+ function buildGitHubPagesWorkflow(outputPath, packageManager) {
102
+ const setupPackageManagerStep = packageManager === 'pnpm'
103
+ ? ` - name: Setup pnpm
104
+ uses: pnpm/action-setup@v4
105
+ with:
106
+ version: 9
107
+
108
+ `
109
+ : packageManager === 'yarn'
110
+ ? ` - name: Enable Corepack
111
+ run: corepack enable
112
+
113
+ `
114
+ : '';
115
+ return `name: Deploy to GitHub Pages
116
+
117
+ on:
118
+ push:
119
+ branches: [main]
120
+ workflow_dispatch:
121
+
122
+ permissions:
123
+ contents: read
124
+ pages: write
125
+ id-token: write
126
+
127
+ concurrency:
128
+ group: pages
129
+ cancel-in-progress: false
130
+
131
+ jobs:
132
+ build:
133
+ runs-on: ubuntu-latest
134
+
135
+ steps:
136
+ - name: Checkout
137
+ uses: actions/checkout@v4
138
+
139
+ ${setupPackageManagerStep} - name: Setup Node
140
+ uses: actions/setup-node@v4
141
+ with:
142
+ node-version: 24
143
+
144
+ - name: Install dependencies
145
+ run: ${getInstallCommand(packageManager)}
146
+
147
+ - name: Build
148
+ run: ${getBuildCommand(packageManager)}
149
+
150
+ - name: Upload artifact
151
+ uses: actions/upload-pages-artifact@v3
152
+ with:
153
+ path: ${outputPath}
154
+
155
+ deploy:
156
+ environment:
157
+ name: github-pages
158
+ url: \${{ steps.deployment.outputs.page_url }}
159
+ runs-on: ubuntu-latest
160
+ needs: build
161
+
162
+ steps:
163
+ - name: Deploy
164
+ id: deployment
165
+ uses: actions/deploy-pages@v4
166
+ `;
167
+ }
168
+ function resolveProjectInfo(workspace, projectName) {
169
+ const name = resolveProjectName(workspace, projectName);
170
+ return {
171
+ name,
172
+ outputPath: resolveBuildOutputPath(workspace, name),
173
+ };
174
+ }
175
+ function resolveProjectName(workspace, projectName) {
176
+ if (projectName) {
177
+ if (!workspace.projects?.[projectName]) {
178
+ throw new schematics_1.SchematicsException(`Project "${projectName}" was not found in angular.json.`);
179
+ }
180
+ return projectName;
181
+ }
182
+ if (workspace.defaultProject && workspace.projects?.[workspace.defaultProject]) {
183
+ return workspace.defaultProject;
184
+ }
185
+ const projects = Object.keys(workspace.projects ?? {});
186
+ if (projects.length === 0) {
187
+ throw new schematics_1.SchematicsException('No Angular project was found in angular.json.');
188
+ }
189
+ return projects[0];
190
+ }
191
+ function resolveBuildOutputPath(workspace, projectName) {
192
+ const project = workspace.projects?.[projectName] ?? {};
193
+ const buildTarget = project.architect?.build ?? project.targets?.build;
194
+ const outputPath = buildTarget?.options?.outputPath;
195
+ if (typeof outputPath === 'string') {
196
+ return trimSlashes(outputPath);
197
+ }
198
+ if (outputPath?.base) {
199
+ return trimSlashes([outputPath.base, outputPath.browser].filter(Boolean).join('/'));
200
+ }
201
+ return `dist/${projectName}/browser`;
202
+ }
203
+ function detectPackageManager(tree) {
204
+ if (tree.exists('/pnpm-lock.yaml')) {
205
+ return 'pnpm';
206
+ }
207
+ if (tree.exists('/yarn.lock')) {
208
+ return 'yarn';
209
+ }
210
+ return 'npm';
211
+ }
212
+ function getInstallCommand(packageManager) {
213
+ if (packageManager === 'pnpm') {
214
+ return 'pnpm install --frozen-lockfile';
215
+ }
216
+ if (packageManager === 'yarn') {
217
+ return 'yarn install --frozen-lockfile';
218
+ }
219
+ return 'npm ci';
220
+ }
221
+ function getBuildCommand(packageManager) {
222
+ if (packageManager === 'pnpm') {
223
+ return 'pnpm run build';
224
+ }
225
+ if (packageManager === 'yarn') {
226
+ return 'yarn build';
227
+ }
228
+ return 'npm run build';
229
+ }
230
+ function writeFile(tree, path, content) {
231
+ if (tree.exists(path)) {
232
+ tree.overwrite(path, content);
233
+ }
234
+ else {
235
+ tree.create(path, content);
236
+ }
237
+ }
238
+ function trimSlashes(value) {
239
+ return value.replace(/^\/+|\/+$/g, '');
240
+ }
241
+ function readJson(tree, path) {
242
+ const content = tree.readText(path);
243
+ return JSON.parse(content);
244
+ }
245
+ //# 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,296 @@
1
+ import { Rule, SchematicsException, Tree } from '@angular-devkit/schematics';
2
+ import { DeployPlatform, Schema } from './schema';
3
+
4
+ type PackageManager = 'npm' | 'pnpm' | 'yarn';
5
+
6
+ interface DeployFile {
7
+ path: string;
8
+ content: string;
9
+ }
10
+
11
+ interface ProjectInfo {
12
+ name: string;
13
+ outputPath: string;
14
+ }
15
+
16
+ export function deploy(options: Schema): Rule {
17
+ return (tree: Tree) => {
18
+ const workspace = readJson(tree, '/angular.json');
19
+ const project = resolveProjectInfo(workspace, options.project);
20
+ const packageManager = detectPackageManager(tree);
21
+ const files = buildDeployFiles(options.platform, project, packageManager);
22
+ const existingFiles = files.map((file) => file.path).filter((path) => tree.exists(path));
23
+
24
+ if (existingFiles.length > 0 && !options.force) {
25
+ throw new SchematicsException(`${existingFiles.join(', ')} already exist. Re-run with --force to overwrite them.`);
26
+ }
27
+
28
+ for (const file of files) {
29
+ writeFile(tree, file.path, file.content);
30
+ }
31
+ };
32
+ }
33
+
34
+ function buildDeployFiles(platform: DeployPlatform, project: ProjectInfo, packageManager: PackageManager): DeployFile[] {
35
+ if (platform === 'vercel') {
36
+ return [
37
+ {
38
+ path: '/vercel.json',
39
+ content: `${JSON.stringify(
40
+ {
41
+ buildCommand: getBuildCommand(packageManager),
42
+ outputDirectory: project.outputPath,
43
+ rewrites: [{ source: '/(.*)', destination: '/index.html' }],
44
+ },
45
+ null,
46
+ 2
47
+ )}\n`,
48
+ },
49
+ ];
50
+ }
51
+
52
+ if (platform === 'netlify') {
53
+ return [
54
+ {
55
+ path: '/netlify.toml',
56
+ content: `[build]
57
+ command = "${getBuildCommand(packageManager)}"
58
+ publish = "${project.outputPath}"
59
+
60
+ [[redirects]]
61
+ from = "/*"
62
+ to = "/index.html"
63
+ status = 200
64
+ `,
65
+ },
66
+ ];
67
+ }
68
+
69
+ if (platform === 'firebase') {
70
+ return [
71
+ {
72
+ path: '/firebase.json',
73
+ content: `${JSON.stringify(
74
+ {
75
+ hosting: {
76
+ public: project.outputPath,
77
+ ignore: ['firebase.json', '**/.*', '**/node_modules/**'],
78
+ rewrites: [{ source: '**', destination: '/index.html' }],
79
+ },
80
+ },
81
+ null,
82
+ 2
83
+ )}\n`,
84
+ },
85
+ {
86
+ path: '/.firebaserc',
87
+ content: `${JSON.stringify({ projects: { default: project.name } }, null, 2)}\n`,
88
+ },
89
+ ];
90
+ }
91
+
92
+ if (platform === 'github-pages') {
93
+ return [
94
+ {
95
+ path: '/.github/workflows/deploy.yml',
96
+ content: buildGitHubPagesWorkflow(project.outputPath, packageManager),
97
+ },
98
+ ];
99
+ }
100
+
101
+ return [
102
+ {
103
+ path: '/compose.yml',
104
+ content: `services:
105
+ web:
106
+ build: .
107
+ ports:
108
+ - "8080:80"
109
+ restart: unless-stopped
110
+ `,
111
+ },
112
+ {
113
+ path: '/deploy/docker.README.md',
114
+ content: `# Docker Deployment
115
+
116
+ Build and run the generated Docker image:
117
+
118
+ \`\`\`bash
119
+ docker compose up --build
120
+ \`\`\`
121
+
122
+ The application will be available at http://localhost:8080.
123
+ `,
124
+ },
125
+ ];
126
+ }
127
+
128
+ function buildGitHubPagesWorkflow(outputPath: string, packageManager: PackageManager): string {
129
+ const setupPackageManagerStep =
130
+ packageManager === 'pnpm'
131
+ ? ` - name: Setup pnpm
132
+ uses: pnpm/action-setup@v4
133
+ with:
134
+ version: 9
135
+
136
+ `
137
+ : packageManager === 'yarn'
138
+ ? ` - name: Enable Corepack
139
+ run: corepack enable
140
+
141
+ `
142
+ : '';
143
+
144
+ return `name: Deploy to GitHub Pages
145
+
146
+ on:
147
+ push:
148
+ branches: [main]
149
+ workflow_dispatch:
150
+
151
+ permissions:
152
+ contents: read
153
+ pages: write
154
+ id-token: write
155
+
156
+ concurrency:
157
+ group: pages
158
+ cancel-in-progress: false
159
+
160
+ jobs:
161
+ build:
162
+ runs-on: ubuntu-latest
163
+
164
+ steps:
165
+ - name: Checkout
166
+ uses: actions/checkout@v4
167
+
168
+ ${setupPackageManagerStep} - name: Setup Node
169
+ uses: actions/setup-node@v4
170
+ with:
171
+ node-version: 24
172
+
173
+ - name: Install dependencies
174
+ run: ${getInstallCommand(packageManager)}
175
+
176
+ - name: Build
177
+ run: ${getBuildCommand(packageManager)}
178
+
179
+ - name: Upload artifact
180
+ uses: actions/upload-pages-artifact@v3
181
+ with:
182
+ path: ${outputPath}
183
+
184
+ deploy:
185
+ environment:
186
+ name: github-pages
187
+ url: \${{ steps.deployment.outputs.page_url }}
188
+ runs-on: ubuntu-latest
189
+ needs: build
190
+
191
+ steps:
192
+ - name: Deploy
193
+ id: deployment
194
+ uses: actions/deploy-pages@v4
195
+ `;
196
+ }
197
+
198
+ function resolveProjectInfo(workspace: Record<string, any>, projectName?: string): ProjectInfo {
199
+ const name = resolveProjectName(workspace, projectName);
200
+
201
+ return {
202
+ name,
203
+ outputPath: resolveBuildOutputPath(workspace, name),
204
+ };
205
+ }
206
+
207
+ function resolveProjectName(workspace: Record<string, any>, projectName?: string): string {
208
+ if (projectName) {
209
+ if (!workspace.projects?.[projectName]) {
210
+ throw new SchematicsException(`Project "${projectName}" was not found in angular.json.`);
211
+ }
212
+
213
+ return projectName;
214
+ }
215
+
216
+ if (workspace.defaultProject && workspace.projects?.[workspace.defaultProject]) {
217
+ return workspace.defaultProject as string;
218
+ }
219
+
220
+ const projects = Object.keys(workspace.projects ?? {});
221
+
222
+ if (projects.length === 0) {
223
+ throw new SchematicsException('No Angular project was found in angular.json.');
224
+ }
225
+
226
+ return projects[0];
227
+ }
228
+
229
+ function resolveBuildOutputPath(workspace: Record<string, any>, projectName: string): string {
230
+ const project = workspace.projects?.[projectName] ?? {};
231
+ const buildTarget = project.architect?.build ?? project.targets?.build;
232
+ const outputPath = buildTarget?.options?.outputPath;
233
+
234
+ if (typeof outputPath === 'string') {
235
+ return trimSlashes(outputPath);
236
+ }
237
+
238
+ if (outputPath?.base) {
239
+ return trimSlashes([outputPath.base, outputPath.browser].filter(Boolean).join('/'));
240
+ }
241
+
242
+ return `dist/${projectName}/browser`;
243
+ }
244
+
245
+ function detectPackageManager(tree: Tree): PackageManager {
246
+ if (tree.exists('/pnpm-lock.yaml')) {
247
+ return 'pnpm';
248
+ }
249
+
250
+ if (tree.exists('/yarn.lock')) {
251
+ return 'yarn';
252
+ }
253
+
254
+ return 'npm';
255
+ }
256
+
257
+ function getInstallCommand(packageManager: PackageManager): string {
258
+ if (packageManager === 'pnpm') {
259
+ return 'pnpm install --frozen-lockfile';
260
+ }
261
+
262
+ if (packageManager === 'yarn') {
263
+ return 'yarn install --frozen-lockfile';
264
+ }
265
+
266
+ return 'npm ci';
267
+ }
268
+
269
+ function getBuildCommand(packageManager: PackageManager): string {
270
+ if (packageManager === 'pnpm') {
271
+ return 'pnpm run build';
272
+ }
273
+
274
+ if (packageManager === 'yarn') {
275
+ return 'yarn build';
276
+ }
277
+
278
+ return 'npm run build';
279
+ }
280
+
281
+ function writeFile(tree: Tree, path: string, content: string): void {
282
+ if (tree.exists(path)) {
283
+ tree.overwrite(path, content);
284
+ } else {
285
+ tree.create(path, content);
286
+ }
287
+ }
288
+
289
+ function trimSlashes(value: string): string {
290
+ return value.replace(/^\/+|\/+$/g, '');
291
+ }
292
+
293
+ function readJson(tree: Tree, path: string): Record<string, any> {
294
+ const content = tree.readText(path);
295
+ return JSON.parse(content) as Record<string, any>;
296
+ }
@@ -0,0 +1,6 @@
1
+ export type DeployPlatform = 'vercel' | 'netlify' | 'firebase' | 'github-pages' | 'docker';
2
+ export interface Schema {
3
+ force?: boolean;
4
+ platform: DeployPlatform;
5
+ project?: string;
6
+ }
@@ -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,37 @@
1
+ {
2
+ "$schema": "http://json-schema.org/draft-07/schema",
3
+ "$id": "SemmetAngularDeploy",
4
+ "title": "Semmet Angular Deploy Options Schema",
5
+ "type": "object",
6
+ "description": "Generates deploy scaffolding for a selected platform.",
7
+ "additionalProperties": false,
8
+ "properties": {
9
+ "platform": {
10
+ "type": "string",
11
+ "enum": ["vercel", "netlify", "firebase", "github-pages", "docker"],
12
+ "description": "The deployment platform to scaffold.",
13
+ "x-prompt": {
14
+ "message": "Which deployment platform do you want to target?",
15
+ "type": "list",
16
+ "items": [
17
+ { "value": "vercel", "label": "Vercel" },
18
+ { "value": "netlify", "label": "Netlify" },
19
+ { "value": "firebase", "label": "Firebase Hosting" },
20
+ { "value": "github-pages", "label": "GitHub Pages" },
21
+ { "value": "docker", "label": "Docker-based deployment" }
22
+ ]
23
+ }
24
+ },
25
+ "force": {
26
+ "type": "boolean",
27
+ "default": false,
28
+ "description": "Overwrite existing deployment files."
29
+ },
30
+ "project": {
31
+ "type": "string",
32
+ "description": "The Angular project to deploy.",
33
+ "$default": { "$source": "projectName" }
34
+ }
35
+ },
36
+ "required": ["platform"]
37
+ }
@@ -0,0 +1,7 @@
1
+ export type DeployPlatform = 'vercel' | 'netlify' | 'firebase' | 'github-pages' | 'docker';
2
+
3
+ export interface Schema {
4
+ force?: boolean;
5
+ platform: DeployPlatform;
6
+ project?: string;
7
+ }
@@ -81,7 +81,7 @@ function getPackageManagerCommands(packageManager, hasNpmLockfile) {
81
81
  }
82
82
  function buildDockerfile(outputPath, commands) {
83
83
  const prepareStep = commands.prepare ? `${commands.prepare}\n` : '';
84
- return `FROM node:20-alpine AS build
84
+ return `FROM node:24-alpine AS build
85
85
  WORKDIR /app
86
86
 
87
87
  COPY package*.json ./
@@ -110,7 +110,7 @@ function getPackageManagerCommands(packageManager: PackageManager, hasNpmLockfil
110
110
  function buildDockerfile(outputPath: string, commands: PackageManagerCommands): string {
111
111
  const prepareStep = commands.prepare ? `${commands.prepare}\n` : '';
112
112
 
113
- return `FROM node:20-alpine AS build
113
+ return `FROM node:24-alpine AS build
114
114
  WORKDIR /app
115
115
 
116
116
  COPY package*.json ./