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
29 changes: 17 additions & 12 deletions .github/workflows/cd.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,34 +5,39 @@ on:
branches:
- stable

permissions:
contents: read
id-token: write

jobs:
build:
name: Build, Test, and Deploy
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
with:
fetch-depth: 0
- uses: actions/setup-node@v4
- uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6.0.0
with:
node-version: 18
- name: Configure Identity
node-version: 20
registry-url: 'https://registry.npmjs.org'
scope: '@ionic'
- name: 🔒 Configure Identity
run: |
git config user.name github-actions
git config user.email github-actions@github.com
- name: Prepare NPM Token
run: echo //registry.npmjs.org/:_authToken=${NPM_TOKEN} > .npmrc
- name: 🟢 Ensure Latest npm
run: npm install -g npm@latest
shell: bash
env:
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
- name: Install Dependencies
- name: 📦 Install Dependencies
run: npm ci --no-package-lock
shell: bash
- name: Bootstrap
- name: 🔄 Bootstrap
run: npm run bootstrap -- --ignore-scripts
shell: bash
- name: Release
run: npm run publish:ci
- name: 🚀 Release
run: npm run publish:ci -- --provenance
shell: bash
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}

11 changes: 6 additions & 5 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,23 +10,24 @@ on:

jobs:
build-and-test:
name: Build and Test (Node ${{ matrix.node }})
name: 🏗️ Build and Test (Node ${{ matrix.node }})
runs-on: ubuntu-latest
timeout-minutes: 30
strategy:
matrix:
node:
- 18.x
- 20.x
steps:
- uses: actions/setup-node@v4
- uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6.0.0
with:
node-version: ${{ matrix.node }}
- uses: actions/checkout@v4
- name: Restore Dependency Cache
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
- name: 🔄 Restore Dependency Cache
uses: actions/cache@v4
with:
path: ~/.npm
key: ${{ runner.OS }}-dependency-cache-${{ hashFiles('**/package.json') }}
- run: npm ci
- run: npm run bootstrap
- run: npm run lint

2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Angular Toolkit monorepo

This is a monorepo with the follow packages
This is a monorepo with the following packages

| Package | Source | Version |
|------------------------------------------------------------------------------------|--------------------------------------------------------------|---------------------------------------------------------------------------------------------------------------------------|
Expand Down
6 changes: 6 additions & 0 deletions packages/cordova-builders/builders.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,12 @@
"implementation": "./cordova-serve/index",
"schema": "./cordova-serve/schema.json",
"description": "Run the dev-server with Cordova assets."
},

"cordova-serve-esbuild": {
"implementation": "./cordova-serve-esbuild/index",
"schema": "./cordova-serve-esbuild/schema.json",
"description": "Run the esbuild dev-server with Cordova assets."
}
}
}
171 changes: 171 additions & 0 deletions packages/cordova-builders/cordova-serve-esbuild/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,171 @@
import { createBuilder, targetFromTargetString } from '@angular-devkit/architect';
import type { BuilderContext } from '@angular-devkit/architect';
import { getSystemPath, join, normalize } from '@angular-devkit/core';
import { executeDevServerBuilder } from '@angular/build';
import type { DevServerBuilderOptions, DevServerBuilderOutput } from '@angular/build';
import type { json } from '@angular-devkit/core';
import { existsSync, readFileSync, statSync, writeFileSync } from 'fs';
import type * as http from 'http';
import { extname, resolve } from 'path';
import { from } from 'rxjs';
import type { Observable } from 'rxjs';
import { switchMap } from 'rxjs/operators';

import { GlobalScriptsByBundleName } from '../utils';
import { augmentIndexHtml } from '../utils/append-scripts';
import { createConsoleLogServer } from '../utils/log-server';
import type { CordovaServeBuilderSchema } from './schema';

export type CordovaDevServerBuilderOptions = CordovaServeBuilderSchema & json.JsonObject;

const MIME_TYPES: Record<string, string> = {
'.js': 'application/javascript',
'.map': 'text/plain',
'.css': 'text/css',
'.html': 'text/html',
'.json': 'application/json',
'.png': 'image/png',
'.jpg': 'image/jpeg',
'.svg': 'image/svg+xml',
'.woff': 'font/woff',
'.woff2': 'font/woff2',
};

export function serveCordova(options: CordovaServeBuilderSchema, context: BuilderContext): Observable<DevServerBuilderOutput> {
const { devServerTarget, port, host, ssl } = options;
const root = context.workspaceRoot;
const devServerTargetSpec = targetFromTargetString(devServerTarget);

async function setup() {
const devServerTargetOptions = await context.getTargetOptions(devServerTargetSpec);
const devServerName = await context.getBuilderNameForTarget(devServerTargetSpec);
console.log(devServerTargetOptions, devServerName);

// console.log(devServerTargetOptions);

devServerTargetOptions.port = port;
devServerTargetOptions.host = host;
devServerTargetOptions.ssl = ssl;


// delete devServerTargetOptions.serviceWorker;


const formattedOptions = await context.validateOptions<DevServerBuilderOptions & json.JsonObject>(devServerTargetOptions, devServerName);
const serverAssets = prepareEsbuildServerConfig(options, root);

if (options.consolelogs && options.consolelogsPort) {
await createConsoleLogServer(host, options.consolelogsPort);
}

return { formattedOptions, serverAssets };
}

return from(setup()).pipe(
switchMap(({ formattedOptions, serverAssets }) =>
from(
executeDevServerBuilder(formattedOptions as unknown as DevServerBuilderOptions, context as any, {
middleware: buildMiddleware(serverAssets),
indexHtmlTransformer: indexHtmlTransformFactory(serverAssets),
})
)
)
);
}

export default createBuilder<CordovaDevServerBuilderOptions, any>(serveCordova);

function buildMiddleware(
serverAssets: EsbuildServerAssets
): ((req: http.IncomingMessage, res: http.ServerResponse, next: (err?: unknown) => void) => void)[] {
const handlers: ((req: http.IncomingMessage, res: http.ServerResponse, next: (err?: unknown) => void) => void)[] = [];

// Serve each script bundle at /{bundleName}.js by concatenating its source files
for (const script of serverAssets.scripts) {
const bundlePath = `/${script.bundleName}.js`;
handlers.push((req, res, next) => {
const reqPath = (req.url ?? '/').split('?')[0];
if (reqPath === bundlePath) {
const content = script.paths.map((p) => readFileSync(p, 'utf-8')).join('\n');
res.writeHead(200, { 'Content-Type': 'application/javascript' });
res.end(content);
return;
}
next();
});
}

// Serve static asset directories (e.g. cordova platform_www)
for (const dir of serverAssets.staticDirs) {
handlers.push((req, res, next) => {
const urlPath = (req.url ?? '/').split('?')[0];
const filePath = resolve(dir, urlPath.replace(/^\//, ''));
if (existsSync(filePath) && statSync(filePath).isFile()) {
const contentType = MIME_TYPES[extname(filePath).toLowerCase()] ?? 'application/octet-stream';
res.writeHead(200, { 'Content-Type': contentType });
res.end(readFileSync(filePath));
return;
}
next();
});
}

return handlers;
}

export const indexHtmlTransformFactory: (serverAssets: EsbuildServerAssets) => (content: string) => Promise<string> =
({ scripts }) =>
(indexHtml: string) =>
Promise.resolve(augmentIndexHtml(indexHtml, scripts));


export interface EsbuildServerAssets {
scripts: GlobalScriptsByBundleName[];
staticDirs: string[];
}

export function prepareEsbuildServerConfig(options: CordovaServeBuilderSchema, root: string): EsbuildServerAssets {
const rawScripts: { input: string; bundleName: string }[] = [];
const staticDirs: string[] = [];
const cordovaBasePath = normalize(options.cordovaBasePath ? options.cordovaBasePath : '.');

if (options.consolelogs) {
const configPath = getSystemPath(join(normalize(__dirname), '../assets', normalize('consolelog-config.js')));
writeFileSync(
configPath,
`window.Ionic = window.Ionic || {}; Ionic.ConsoleLogServerConfig = { wsPort: ${options.consolelogsPort} }`
);
rawScripts.push({ input: configPath, bundleName: 'consolelogs' });
rawScripts.push({
input: getSystemPath(join(normalize(__dirname), '../assets', normalize('consolelogs.js'))),
bundleName: 'consolelogs',
});
}

if (options.cordovaMock) {
rawScripts.push({
input: getSystemPath(join(normalize(__dirname), '../assets', normalize('cordova.js'))),
bundleName: 'cordova',
});
} else if (options.cordovaAssets) {
const platformWWWPath = join(cordovaBasePath, normalize(`platforms/${options.platform}/platform_www`));
staticDirs.push(getSystemPath(platformWWWPath));
rawScripts.push({
input: getSystemPath(join(platformWWWPath, normalize('cordova.js'))),
bundleName: 'cordova',
});
}

const scripts = rawScripts.reduce((prev: GlobalScriptsByBundleName[], curr) => {
const resolvedPath = resolve(root, curr.input);
const existingEntry = prev.find((el) => el.bundleName === curr.bundleName);
if (existingEntry) {
existingEntry.paths.push(resolvedPath);
} else {
prev.push({ bundleName: curr.bundleName, inject: true, paths: [resolvedPath] });
}
return prev;
}, []);

return { scripts, staticDirs };
}
14 changes: 14 additions & 0 deletions packages/cordova-builders/cordova-serve-esbuild/schema.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
export interface CordovaServeBuilderSchema {
cordovaBuildTarget: string;
devServerTarget: string;
platform?: string;
port: number;
host: string;
ssl: boolean;
cordovaBasePath?: string;
sourceMap?: boolean;
cordovaAssets?: boolean;
cordovaMock?: boolean;
consolelogs?: boolean;
consolelogsPort?: number;
}
63 changes: 63 additions & 0 deletions packages/cordova-builders/cordova-serve-esbuild/schema.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
{
"title": "Cordova serve for Ionic",
"description": "Options for Cordova serve for Ionic.",
"type": "object",
"properties": {
"cordovaBuildTarget": {
"type": "string",
"description": "Target to use for build."
},
"consolelogs": {
"type": "boolean",
"description": "Print console logs to the terminal."
},
"consolelogsPort": {
"type": "number",
"description": "Port for console log server.",
"default": 53703
},
"devServerTarget": {
"type": "string",
"description": "Target to use for serve."
},
"platform": {
"type": "string",
"description": "Cordova platform to use during serve."
},
"ssl": {
"type": "boolean",
"description": "Serve using HTTPS.",
"default": false
},
"port": {
"type": "number",
"description": "Port to listen on.",
"default": 4200
},
"host": {
"type": "string",
"description": "Host to listen on.",
"default": "localhost"
},
"cordovaBasePath": {
"type": "string",
"description": "Path to cordova directory"
},
"sourceMap": {
"type": "boolean",
"description": "Create source-map file"
},
"cordovaAssets": {
"type": "boolean",
"description": "Bundle Cordova assets with build",
"default": true
},
"cordovaMock": {
"type": "boolean",
"description": "Bundle empty cordova.js with build",
"default": false
}
},
"additionalProperties": false,
"required": ["cordovaBuildTarget", "devServerTarget"]
}
2 changes: 1 addition & 1 deletion packages/cordova-builders/cordova-serve/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ import { prepareServerConfig } from '../utils';
import type { FormattedAssets } from '../utils';
import { augmentIndexHtml } from '../utils/append-scripts';

import { createConsoleLogServer } from './log-server';
import { createConsoleLogServer } from '../utils/log-server';
import type { CordovaServeBuilderSchema } from './schema';

export type CordovaDevServerBuilderOptions = CordovaServeBuilderSchema & json.JsonObject;
Expand Down
Loading