@richapps/ong 0.1.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.md ADDED
@@ -0,0 +1,166 @@
1
+ <p align="center">
2
+ <img src="ong-logo.png" alt="ong" width="200">
3
+ </p>
4
+
5
+ <h1 align="center">@richapps/ong</h1>
6
+
7
+ <p align="center">
8
+ Angular CLI powered by <a href="https://vite.dev">Vite</a> + <a href="https://github.com/nicoss/oxc-angular-compiler">OXC</a><br>
9
+ <em>Blazing fast drop-in replacement for <code>ng serve</code> and <code>ng build</code></em>
10
+ </p>
11
+
12
+ ---
13
+
14
+ ## What is ong?
15
+
16
+ **ong** uses the Rust-based [OXC Angular compiler](https://github.com/nicoss/oxc-angular-compiler) and [Vite](https://vite.dev) to compile and serve Angular applications — no Webpack, no esbuild, just raw speed.
17
+
18
+ It reads your existing `angular.json` (or Nx `project.json`) and works with a standard Angular project out of the box. No config files to write, no migration steps.
19
+
20
+ ## Install
21
+
22
+ ```bash
23
+ npm install -D @richapps/ong
24
+ ```
25
+
26
+ If you already have `vite` installed, ong will use your version. Otherwise it will be auto-installed as a peer dependency.
27
+
28
+ For global usage:
29
+
30
+ ```bash
31
+ npm install -g @richapps/ong
32
+ ```
33
+
34
+ ## Usage
35
+
36
+ ```bash
37
+ # Start dev server
38
+ ong serve
39
+
40
+ # Production build
41
+ ong build
42
+
43
+ # With options
44
+ ong serve --port 3000 --open
45
+ ong build -c production
46
+ ong serve -p my-app # specific project
47
+ ong build -p apps/my-app # nx project path
48
+ ```
49
+
50
+ Or via npm scripts in `package.json`:
51
+
52
+ ```json
53
+ {
54
+ "scripts": {
55
+ "start": "ong serve",
56
+ "build": "ong build"
57
+ }
58
+ }
59
+ ```
60
+
61
+ ## CLI Options
62
+
63
+ | Option | Alias | Description |
64
+ |---|---|---|
65
+ | `--configuration <name>` | `-c` | Build configuration (default: per-target default) |
66
+ | `--project <name\|path>` | `-p` | Project name (angular.json) or path (nx project.json) |
67
+ | `--port <number>` | | Dev server port (default: 4200) |
68
+ | `--open` | `-o` | Open browser on start |
69
+ | `--host <host>` | | Dev server host |
70
+ | `--watch` | `-w` | Rebuild on file changes (build mode) |
71
+ | `--help` | `-h` | Show help |
72
+
73
+ ## Workspace Support
74
+
75
+ ong auto-detects your workspace type:
76
+
77
+ - **angular.json** — Standard Angular CLI workspace
78
+ - **workspace.json** — Older Nx workspace format
79
+ - **project.json** — Per-project Nx monorepo setup
80
+
81
+ Configurations, file replacements, global styles/scripts, and assets are all read from your existing workspace config.
82
+
83
+ ## Supported angular.json / project.json Properties
84
+
85
+ ### Build target options
86
+
87
+ | Property | Status | Notes |
88
+ |---|---|---|
89
+ | `browser` / `main` | Supported | Entry point |
90
+ | `tsConfig` | Supported | |
91
+ | `outputPath` | Supported | |
92
+ | `assets` | Supported | String and object (`glob`/`input`/`output`) forms |
93
+ | `styles` | Supported | Global stylesheets, string and `{ input }` forms |
94
+ | `scripts` | Supported | Global scripts |
95
+ | `fileReplacements` | Supported | `replace`/`with` pairs |
96
+ | `sourceMap` | Supported | |
97
+ | `optimization` | Supported | Uses OXC minifier |
98
+ | `outputHashing` | Supported | `none`, `all`, `media`, `bundles` |
99
+ | `configurations` | Supported | Merges base + per-config overrides |
100
+ | `defaultConfiguration` | Supported | |
101
+ | `baseHref` | Supported | Injected into `<base href="...">` and used as Vite `base` |
102
+ | `deployUrl` | Supported | Maps to Vite `base` (takes precedence over `baseHref`) |
103
+ | `polyfills` | Supported | Injected as `<script>` tags (string or array) |
104
+ | `define` | Supported | Maps to Vite `define` for global constant replacements |
105
+ | `externalDependencies` | Supported | Maps to Rollup `external` |
106
+ | `preserveSymlinks` | Supported | Maps to Vite `resolve.preserveSymlinks` |
107
+ | `namedChunks` | Supported | Disables hash-based chunk naming |
108
+ | `crossOrigin` | Supported | Sets attribute on injected `<script>`/`<link>` tags |
109
+ | `stylePreprocessorOptions` | Supported | `includePaths` mapped to Vite `css.preprocessorOptions` |
110
+ | `watch` | Supported | Maps to Vite `build.watch` (also via `--watch` CLI flag) |
111
+ | `poll` | Supported | Maps to Vite `server.watch.usePolling` with interval |
112
+
113
+ ### Serve target options
114
+
115
+ | Property | Status | Notes |
116
+ |---|---|---|
117
+ | `port` | Supported | |
118
+ | `open` | Supported | |
119
+ | `host` | Supported | |
120
+ | `poll` | Supported | File-watching poll interval |
121
+ | `buildTarget` | Ignored | Configuration is resolved from the build target directly |
122
+
123
+ ### Not yet supported
124
+
125
+ These `@angular/build:application` properties are **not currently handled** — they are silently ignored:
126
+
127
+ | Property | Description |
128
+ |---|---|
129
+ | `budgets` | Bundle size budgets and warnings |
130
+ | `extractLicenses` | Extract third-party licenses to a separate file |
131
+ | `subresourceIntegrity` | SRI hashes on `<script>`/`<link>` tags |
132
+ | `allowedCommonJsDependencies` | Suppress CommonJS import warnings |
133
+ | `serviceWorker` / `ngswConfigPath` | PWA service worker generation |
134
+ | `webWorkerTsConfig` | Separate tsconfig for web workers |
135
+ | `i18nMissingTranslation` | i18n missing translation handling |
136
+ | `localize` | i18n locale builds |
137
+ | `inlineStyleLanguage` | Default preprocessor for inline component styles |
138
+ | `loader` | Custom file extension loaders |
139
+ | `progress` | Show build progress indicator |
140
+ | `ssr` / `server` / `prerender` | Server-side rendering / prerendering |
141
+ | `security` | Content Security Policy settings |
142
+
143
+ > SSR support is not planned — ong focuses on client-side Angular applications.
144
+
145
+ ## Programmatic API
146
+
147
+ ```typescript
148
+ import { resolveWorkspace, createViteConfig } from '@richapps/ong'
149
+
150
+ const opts = resolveWorkspace(process.cwd(), {
151
+ command: 'build',
152
+ configuration: 'production',
153
+ })
154
+
155
+ const viteConfig = createViteConfig(opts)
156
+ ```
157
+
158
+ ## Requirements
159
+
160
+ - Node.js 18+
161
+ - Angular 17+ (standalone components recommended)
162
+ - Vite 6+
163
+
164
+ ## License
165
+
166
+ MIT
package/bin/ong.js ADDED
@@ -0,0 +1,5 @@
1
+ #!/usr/bin/env node
2
+ import('../dist/cli.js').catch(err => {
3
+ console.error(err.message)
4
+ process.exit(1)
5
+ })
package/dist/cli.d.ts ADDED
@@ -0,0 +1 @@
1
+ export {};
package/dist/cli.js ADDED
@@ -0,0 +1,83 @@
1
+ import { createServer, build } from 'vite';
2
+ import { resolveWorkspace } from './workspace.js';
3
+ import { createViteConfig } from './config.js';
4
+ import { banner, printBuildStats, printHelp, error } from './log.js';
5
+ function parseArgs(argv) {
6
+ const args = argv.slice(2);
7
+ const command = args[0];
8
+ if (!command || !['serve', 'build'].includes(command)) {
9
+ return { command: 'help' };
10
+ }
11
+ const result = { command };
12
+ for (let i = 1; i < args.length; i++) {
13
+ const arg = args[i];
14
+ const next = args[i + 1];
15
+ if (arg === '--project' || arg === '-p') {
16
+ result.project = next;
17
+ i++;
18
+ }
19
+ else if (arg === '--configuration' || arg === '-c') {
20
+ result.configuration = next;
21
+ i++;
22
+ }
23
+ else if (arg.startsWith('--configuration=')) {
24
+ result.configuration = arg.split('=')[1];
25
+ }
26
+ else if (arg === '--port') {
27
+ result.port = parseInt(next);
28
+ i++;
29
+ }
30
+ else if (arg === '--open' || arg === '-o') {
31
+ result.open = true;
32
+ }
33
+ else if (arg === '--host') {
34
+ result.host = next;
35
+ i++;
36
+ }
37
+ else if (arg === '--watch' || arg === '-w') {
38
+ result.watch = true;
39
+ }
40
+ else if (arg === '--help' || arg === '-h') {
41
+ return { command: 'help' };
42
+ }
43
+ }
44
+ return result;
45
+ }
46
+ // ═══════════════════════════════════════════════════════════════
47
+ // Main
48
+ // ═══════════════════════════════════════════════════════════════
49
+ async function main() {
50
+ const args = parseArgs(process.argv);
51
+ if (args.command === 'help') {
52
+ printHelp();
53
+ process.exit(0);
54
+ }
55
+ const cwd = process.cwd();
56
+ const resolveOpts = {
57
+ command: args.command,
58
+ project: args.project,
59
+ configuration: args.configuration,
60
+ port: args.port,
61
+ open: args.open,
62
+ host: args.host,
63
+ watch: args.watch,
64
+ };
65
+ const buildOpts = resolveWorkspace(cwd, resolveOpts);
66
+ banner(args.command, buildOpts.projectName, buildOpts.configName);
67
+ const viteConfig = createViteConfig(buildOpts);
68
+ if (args.command === 'serve') {
69
+ const server = await createServer(viteConfig);
70
+ await server.listen();
71
+ server.printUrls();
72
+ console.log();
73
+ }
74
+ if (args.command === 'build') {
75
+ const start = Date.now();
76
+ await build(viteConfig);
77
+ const duration = Date.now() - start;
78
+ printBuildStats(buildOpts.outputPath, duration);
79
+ }
80
+ }
81
+ main().catch(err => {
82
+ error(err.message);
83
+ });
@@ -0,0 +1,6 @@
1
+ import type { InlineConfig } from 'vite';
2
+ import type { ResolvedBuildOptions } from './workspace.js';
3
+ /**
4
+ * Creates a Vite InlineConfig from resolved Angular build options.
5
+ */
6
+ export declare function createViteConfig(opts: ResolvedBuildOptions): InlineConfig;
package/dist/config.js ADDED
@@ -0,0 +1,89 @@
1
+ import { resolve } from 'node:path';
2
+ import { angular } from '@oxc-angular/vite';
3
+ import { htmlInjectPlugin, assetCopyPlugin } from './plugins.js';
4
+ /**
5
+ * Creates a Vite InlineConfig from resolved Angular build options.
6
+ */
7
+ export function createViteConfig(opts) {
8
+ const { workspaceRoot, sourceRoot } = opts;
9
+ // Output naming based on hashing config
10
+ const hash = (scope) => {
11
+ if (opts.outputHashing === 'all')
12
+ return true;
13
+ if (opts.outputHashing === 'none')
14
+ return false;
15
+ if (opts.outputHashing === 'media' && scope === 'asset')
16
+ return true;
17
+ if (opts.outputHashing === 'bundles' && scope === 'chunk')
18
+ return true;
19
+ return false;
20
+ };
21
+ // namedChunks overrides hash-based naming for JS chunks
22
+ const useChunkHash = !opts.namedChunks && hash('chunk');
23
+ const assetNames = hash('asset') ? 'assets/[name]-[hash].[ext]' : 'assets/[name].[ext]';
24
+ const chunkNames = useChunkHash ? 'assets/[name]-[hash].js' : 'assets/[name].js';
25
+ const entryNames = useChunkHash ? '[name]-[hash].js' : '[name].js';
26
+ // deployUrl takes precedence over baseHref for Vite's base
27
+ const base = opts.deployUrl || opts.baseHref || '/';
28
+ // Sass/Less includePaths → Vite css.preprocessorOptions
29
+ const cssPreprocessorOptions = {};
30
+ if (opts.stylePreprocessorOptions.includePaths.length) {
31
+ const paths = opts.stylePreprocessorOptions.includePaths;
32
+ cssPreprocessorOptions.scss = { includePaths: paths };
33
+ cssPreprocessorOptions.sass = { includePaths: paths };
34
+ cssPreprocessorOptions.less = { paths };
35
+ }
36
+ return {
37
+ root: resolve(workspaceRoot, sourceRoot),
38
+ base,
39
+ publicDir: resolve(workspaceRoot, 'public'),
40
+ logLevel: 'info',
41
+ plugins: [
42
+ htmlInjectPlugin(opts),
43
+ ...angular({
44
+ tsconfig: opts.tsconfig,
45
+ sourceMap: opts.sourceMap,
46
+ liveReload: !opts.optimization,
47
+ workspaceRoot,
48
+ ...(opts.fileReplacements.length ? { fileReplacements: opts.fileReplacements } : {}),
49
+ }),
50
+ assetCopyPlugin(opts.assets, workspaceRoot, sourceRoot),
51
+ ],
52
+ // Global constant replacements (angular.json "define")
53
+ ...(Object.keys(opts.define).length ? { define: opts.define } : {}),
54
+ resolve: {
55
+ preserveSymlinks: opts.preserveSymlinks,
56
+ },
57
+ css: Object.keys(cssPreprocessorOptions).length
58
+ ? { preprocessorOptions: cssPreprocessorOptions }
59
+ : undefined,
60
+ build: {
61
+ outDir: opts.outputPath,
62
+ emptyOutDir: true,
63
+ sourcemap: opts.sourceMap,
64
+ minify: opts.optimization ? 'oxc' : false,
65
+ modulePreload: { polyfill: false },
66
+ watch: opts.watch ? {} : null,
67
+ rollupOptions: {
68
+ // Use index.html as entry so Vite processes HTML transforms (script/style injection)
69
+ input: resolve(workspaceRoot, sourceRoot, 'index.html'),
70
+ external: opts.externalDependencies.length ? opts.externalDependencies : undefined,
71
+ },
72
+ assetsInlineLimit: 0,
73
+ rolldownOptions: {
74
+ tsconfig: opts.tsconfig,
75
+ output: {
76
+ assetFileNames: assetNames,
77
+ chunkFileNames: chunkNames,
78
+ entryFileNames: entryNames,
79
+ },
80
+ },
81
+ },
82
+ server: {
83
+ port: opts.serve.port,
84
+ open: opts.serve.open,
85
+ host: opts.serve.host ?? false,
86
+ watch: opts.poll ? { usePolling: true, interval: opts.poll } : undefined,
87
+ },
88
+ };
89
+ }
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Programmatic API for @richapps/ong
3
+ *
4
+ * Usage:
5
+ * import { resolveWorkspace, createViteConfig } from '@richapps/ong'
6
+ *
7
+ * const opts = resolveWorkspace(process.cwd(), { command: 'build', configuration: 'production' })
8
+ * const viteConfig = createViteConfig(opts)
9
+ */
10
+ export { resolveWorkspace, detectWorkspace } from './workspace.js';
11
+ export type { ResolvedBuildOptions, ResolveOptions, AssetConfig } from './workspace.js';
12
+ export { createViteConfig } from './config.js';
13
+ export { htmlInjectPlugin, assetCopyPlugin } from './plugins.js';
package/dist/index.js ADDED
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Programmatic API for @richapps/ong
3
+ *
4
+ * Usage:
5
+ * import { resolveWorkspace, createViteConfig } from '@richapps/ong'
6
+ *
7
+ * const opts = resolveWorkspace(process.cwd(), { command: 'build', configuration: 'production' })
8
+ * const viteConfig = createViteConfig(opts)
9
+ */
10
+ export { resolveWorkspace, detectWorkspace } from './workspace.js';
11
+ export { createViteConfig } from './config.js';
12
+ export { htmlInjectPlugin, assetCopyPlugin } from './plugins.js';
package/dist/log.d.ts ADDED
@@ -0,0 +1,16 @@
1
+ declare const c: {
2
+ reset: string;
3
+ bold: string;
4
+ dim: string;
5
+ green: string;
6
+ cyan: string;
7
+ yellow: string;
8
+ red: string;
9
+ gray: string;
10
+ };
11
+ export declare function banner(command: string, projectName: string, configName: string): void;
12
+ export declare function error(msg: string): never;
13
+ export declare function formatBytes(bytes: number): string;
14
+ export declare function printBuildStats(outDir: string, durationMs: number): void;
15
+ export declare function printHelp(): void;
16
+ export { c };
package/dist/log.js ADDED
@@ -0,0 +1,96 @@
1
+ import { readdirSync, statSync } from 'node:fs';
2
+ import { join, relative } from 'node:path';
3
+ const c = {
4
+ reset: '\x1b[0m',
5
+ bold: '\x1b[1m',
6
+ dim: '\x1b[2m',
7
+ green: '\x1b[32m',
8
+ cyan: '\x1b[36m',
9
+ yellow: '\x1b[33m',
10
+ red: '\x1b[31m',
11
+ gray: '\x1b[90m',
12
+ };
13
+ export function banner(command, projectName, configName) {
14
+ console.log();
15
+ console.log(` ${c.green}${c.bold}ong${c.reset} ${c.dim}— Angular + OXC + Vite${c.reset}`);
16
+ console.log();
17
+ console.log(` ${c.dim}Command:${c.reset} ${command}`);
18
+ console.log(` ${c.dim}Project:${c.reset} ${projectName}`);
19
+ console.log(` ${c.dim}Configuration:${c.reset} ${configName}`);
20
+ console.log();
21
+ }
22
+ export function error(msg) {
23
+ console.error(`\n ${c.red}${c.bold}Error:${c.reset} ${msg}\n`);
24
+ process.exit(1);
25
+ }
26
+ export function formatBytes(bytes) {
27
+ if (bytes < 1024)
28
+ return bytes + ' B';
29
+ if (bytes < 1024 * 1024)
30
+ return (bytes / 1024).toFixed(1) + ' kB';
31
+ return (bytes / (1024 * 1024)).toFixed(2) + ' MB';
32
+ }
33
+ export function printBuildStats(outDir, durationMs) {
34
+ console.log();
35
+ console.log(` ${c.green}${c.bold}Build complete${c.reset} in ${c.bold}${durationMs}ms${c.reset}`);
36
+ console.log(` ${c.dim}Output:${c.reset} ${relative(process.cwd(), outDir)}`);
37
+ console.log();
38
+ try {
39
+ const files = readdirSync(outDir, { recursive: true, withFileTypes: true });
40
+ const entries = [];
41
+ for (const f of files) {
42
+ if (f.isFile()) {
43
+ const fullPath = join(f.parentPath ?? f.path, f.name);
44
+ const stat = statSync(fullPath);
45
+ entries.push({ path: relative(outDir, fullPath), size: stat.size });
46
+ }
47
+ }
48
+ entries.sort((a, b) => b.size - a.size);
49
+ const totalSize = entries.reduce((sum, e) => sum + e.size, 0);
50
+ for (const entry of entries.slice(0, 15)) {
51
+ const sizeStr = formatBytes(entry.size).padStart(10);
52
+ const isJs = entry.path.endsWith('.js');
53
+ const isCss = entry.path.endsWith('.css');
54
+ const color = isJs ? c.yellow : isCss ? c.cyan : c.dim;
55
+ console.log(` ${color}${sizeStr}${c.reset} ${entry.path}`);
56
+ }
57
+ if (entries.length > 15) {
58
+ console.log(` ${c.dim}... and ${entries.length - 15} more files${c.reset}`);
59
+ }
60
+ console.log();
61
+ console.log(` ${c.bold}Total: ${formatBytes(totalSize)}${c.reset}`);
62
+ }
63
+ catch {
64
+ // ignore
65
+ }
66
+ console.log();
67
+ }
68
+ export function printHelp() {
69
+ console.log(`
70
+ ${c.green}${c.bold}ong${c.reset} ${c.dim}— Angular CLI powered by Vite + OXC${c.reset}
71
+
72
+ ${c.bold}Commands:${c.reset}
73
+ serve Start dev server
74
+ build Build for production
75
+
76
+ ${c.bold}Options:${c.reset}
77
+ -c, --configuration <name> Build configuration (default: per-target default)
78
+ -p, --project <name> Project name (angular.json) or path (nx project.json)
79
+ --port <number> Dev server port (default: 4200)
80
+ -o, --open Open browser on start
81
+ --host <host> Dev server host
82
+ -w, --watch Rebuild on file changes (build mode)
83
+
84
+ ${c.bold}Workspace support:${c.reset}
85
+ angular.json Standard Angular CLI workspace
86
+ project.json Nx monorepo (auto-detected)
87
+
88
+ ${c.bold}Examples:${c.reset}
89
+ ong serve
90
+ ong build -c production
91
+ ong serve --port 3000 --open
92
+ ong build -p my-app -c production
93
+ ong serve -p apps/my-app ${c.dim}# nx project path${c.reset}
94
+ `);
95
+ }
96
+ export { c };
@@ -0,0 +1,11 @@
1
+ import type { Plugin } from 'vite';
2
+ import type { ResolvedBuildOptions, AssetConfig } from './workspace.js';
3
+ /**
4
+ * Injects global styles, scripts, polyfills, and the entry point into index.html.
5
+ * This makes a standard Angular index.html work with Vite without modifications.
6
+ */
7
+ export declare function htmlInjectPlugin(opts: ResolvedBuildOptions): Plugin;
8
+ /**
9
+ * Copies static assets from angular.json config to build output.
10
+ */
11
+ export declare function assetCopyPlugin(assets: AssetConfig[], workspaceRoot: string, sourceRoot: string): Plugin;
@@ -0,0 +1,97 @@
1
+ import { existsSync, cpSync, mkdirSync } from 'node:fs';
2
+ import { resolve, relative, join } from 'node:path';
3
+ /**
4
+ * Injects global styles, scripts, polyfills, and the entry point into index.html.
5
+ * This makes a standard Angular index.html work with Vite without modifications.
6
+ */
7
+ export function htmlInjectPlugin(opts) {
8
+ const viteRoot = resolve(opts.workspaceRoot, opts.sourceRoot);
9
+ const browserRelative = '/' + relative(viteRoot, opts.browser);
10
+ const styleRefs = opts.styles.map(s => '/' + relative(viteRoot, s));
11
+ const scriptRefs = opts.scripts.map(s => '/' + relative(viteRoot, s));
12
+ const crossOriginAttr = opts.crossOrigin !== 'none'
13
+ ? ` crossorigin="${opts.crossOrigin}"`
14
+ : '';
15
+ return {
16
+ name: 'ong:html-inject',
17
+ transformIndexHtml: {
18
+ order: 'pre',
19
+ handler(html) {
20
+ let result = html;
21
+ // Inject/update base href if not default
22
+ if (opts.baseHref && opts.baseHref !== '/') {
23
+ if (result.includes('<base href=')) {
24
+ result = result.replace(/<base href="[^"]*">/, `<base href="${opts.baseHref}">`);
25
+ }
26
+ else {
27
+ result = result.replace('<head>', `<head>\n <base href="${opts.baseHref}">`);
28
+ }
29
+ }
30
+ // Inject global stylesheets
31
+ if (styleRefs.length) {
32
+ const tags = styleRefs
33
+ .map(s => ` <link rel="stylesheet" href="${s}"${crossOriginAttr}>`)
34
+ .join('\n');
35
+ result = result.replace('</head>', `${tags}\n</head>`);
36
+ }
37
+ // Inject global scripts
38
+ if (scriptRefs.length) {
39
+ const tags = scriptRefs
40
+ .map(s => ` <script type="module" src="${s}"${crossOriginAttr}></script>`)
41
+ .join('\n');
42
+ result = result.replace('</head>', `${tags}\n</head>`);
43
+ }
44
+ // Inject polyfills before the entry point
45
+ if (opts.polyfills.length) {
46
+ const tags = opts.polyfills
47
+ .map(p => {
48
+ // Bare specifiers (e.g. "zone.js") become imports, paths become src refs
49
+ if (p.startsWith('.') || p.startsWith('/')) {
50
+ const ref = '/' + relative(viteRoot, resolve(opts.workspaceRoot, p));
51
+ return ` <script type="module" src="${ref}"${crossOriginAttr}></script>`;
52
+ }
53
+ return ` <script type="module">import '${p}';</script>`;
54
+ })
55
+ .join('\n');
56
+ result = result.replace('</body>', `${tags}\n</body>`);
57
+ }
58
+ // Inject entry point if not already present
59
+ const hasEntry = html.includes(`src="${browserRelative}"`) ||
60
+ html.includes('src="/main.ts"') ||
61
+ html.includes('src="main.ts"');
62
+ if (!hasEntry) {
63
+ result = result.replace('</body>', ` <script type="module" src="${browserRelative}"${crossOriginAttr}></script>\n</body>`);
64
+ }
65
+ return result;
66
+ },
67
+ },
68
+ };
69
+ }
70
+ /**
71
+ * Copies static assets from angular.json config to build output.
72
+ */
73
+ export function assetCopyPlugin(assets, workspaceRoot, sourceRoot) {
74
+ return {
75
+ name: 'ong:assets',
76
+ writeBundle(bundleOpts) {
77
+ const outDir = bundleOpts.dir || resolve(workspaceRoot, 'dist');
78
+ for (const asset of assets) {
79
+ if (typeof asset === 'string') {
80
+ const src = resolve(workspaceRoot, asset);
81
+ if (existsSync(src)) {
82
+ const dest = join(outDir, asset.replace(sourceRoot + '/', ''));
83
+ cpSync(src, dest, { recursive: true });
84
+ }
85
+ }
86
+ else if (asset?.input) {
87
+ const inputDir = resolve(workspaceRoot, asset.input);
88
+ if (existsSync(inputDir)) {
89
+ const outputDir = join(outDir, asset.output || '');
90
+ mkdirSync(outputDir, { recursive: true });
91
+ cpSync(inputDir, outputDir, { recursive: true });
92
+ }
93
+ }
94
+ }
95
+ },
96
+ };
97
+ }
@@ -0,0 +1,125 @@
1
+ export interface ResolvedBuildOptions {
2
+ /** Absolute path to workspace root */
3
+ workspaceRoot: string;
4
+ /** Project name */
5
+ projectName: string;
6
+ /** Configuration name */
7
+ configName: string;
8
+ /** Source root relative to workspace (e.g. "src" or "apps/my-app/src") */
9
+ sourceRoot: string;
10
+ /** Absolute path to tsconfig */
11
+ tsconfig: string;
12
+ /** Absolute path to browser entry (e.g. src/main.ts) */
13
+ browser: string;
14
+ /** Absolute paths to global style files */
15
+ styles: string[];
16
+ /** Absolute paths to global script files */
17
+ scripts: string[];
18
+ /** Asset configs from angular.json */
19
+ assets: AssetConfig[];
20
+ /** File replacement pairs */
21
+ fileReplacements: {
22
+ replace: string;
23
+ with: string;
24
+ }[];
25
+ /** Whether to generate source maps */
26
+ sourceMap: boolean;
27
+ /** Whether to optimize (minify, tree-shake) */
28
+ optimization: boolean;
29
+ /** Output hashing mode */
30
+ outputHashing: 'none' | 'all' | 'media' | 'bundles';
31
+ /** Absolute path to output directory */
32
+ outputPath: string;
33
+ /** Base href for the application */
34
+ baseHref: string;
35
+ /** Deploy URL prefix for assets */
36
+ deployUrl: string;
37
+ /** Polyfill entries (e.g. ["zone.js"]) */
38
+ polyfills: string[];
39
+ /** Global constant replacements */
40
+ define: Record<string, string>;
41
+ /** Dependencies to treat as external */
42
+ externalDependencies: string[];
43
+ /** Preserve symlinks in module resolution */
44
+ preserveSymlinks: boolean;
45
+ /** Use human-readable chunk names */
46
+ namedChunks: boolean;
47
+ /** Cross-origin attribute for script/link tags */
48
+ crossOrigin: 'none' | 'anonymous' | 'use-credentials';
49
+ /** Sass/Less preprocessor options */
50
+ stylePreprocessorOptions: {
51
+ includePaths: string[];
52
+ };
53
+ /** Watch mode for build */
54
+ watch: boolean;
55
+ /** File-watching poll interval in ms (0 = no polling) */
56
+ poll: number;
57
+ /** Serve options */
58
+ serve: {
59
+ port?: number;
60
+ open?: boolean;
61
+ host?: string;
62
+ };
63
+ }
64
+ export type AssetConfig = string | {
65
+ glob: string;
66
+ input: string;
67
+ output?: string;
68
+ };
69
+ export interface ResolveOptions {
70
+ /** Project name or path */
71
+ project?: string;
72
+ /** Configuration name */
73
+ configuration?: string;
74
+ /** Command being run */
75
+ command: 'serve' | 'build';
76
+ /** Dev server port override */
77
+ port?: number;
78
+ /** Open browser on start */
79
+ open?: boolean;
80
+ /** Dev server host override */
81
+ host?: string;
82
+ /** Watch mode override for build */
83
+ watch?: boolean;
84
+ }
85
+ interface Target {
86
+ builder?: string;
87
+ executor?: string;
88
+ options?: Record<string, any>;
89
+ configurations?: Record<string, Record<string, any>>;
90
+ defaultConfiguration?: string;
91
+ }
92
+ interface Project {
93
+ name?: string;
94
+ root?: string;
95
+ sourceRoot?: string;
96
+ prefix?: string;
97
+ architect?: Record<string, Target>;
98
+ targets?: Record<string, Target>;
99
+ }
100
+ interface WorkspaceJson {
101
+ projects?: Record<string, Project>;
102
+ defaultProject?: string;
103
+ }
104
+ type WorkspaceType = 'angular' | 'nx-project';
105
+ interface DetectedWorkspace {
106
+ type: WorkspaceType;
107
+ root: string;
108
+ data: WorkspaceJson | Project;
109
+ projectJsonPath?: string;
110
+ }
111
+ /**
112
+ * Detect and read the workspace configuration.
113
+ *
114
+ * Supports:
115
+ * - angular.json (standard Angular CLI)
116
+ * - workspace.json (older Nx format)
117
+ * - project.json (per-project Nx)
118
+ * - Auto-detection via --project pointing to an Nx app directory
119
+ */
120
+ export declare function detectWorkspace(cwd: string, projectHint?: string): DetectedWorkspace;
121
+ /**
122
+ * Main entry: detect workspace type and resolve all build options.
123
+ */
124
+ export declare function resolveWorkspace(cwd: string, opts: ResolveOptions): ResolvedBuildOptions;
125
+ export {};
@@ -0,0 +1,232 @@
1
+ import { readFileSync, existsSync } from 'node:fs';
2
+ import { resolve, join, dirname, basename } from 'node:path';
3
+ // ═══════════════════════════════════════════════════════════════
4
+ // Detection
5
+ // ═══════════════════════════════════════════════════════════════
6
+ /**
7
+ * Detect and read the workspace configuration.
8
+ *
9
+ * Supports:
10
+ * - angular.json (standard Angular CLI)
11
+ * - workspace.json (older Nx format)
12
+ * - project.json (per-project Nx)
13
+ * - Auto-detection via --project pointing to an Nx app directory
14
+ */
15
+ export function detectWorkspace(cwd, projectHint) {
16
+ // If projectHint points to a specific project.json or directory
17
+ if (projectHint) {
18
+ const hintPath = resolve(cwd, projectHint);
19
+ // Direct path to project.json
20
+ if (hintPath.endsWith('project.json') && existsSync(hintPath)) {
21
+ return {
22
+ type: 'nx-project',
23
+ root: findWorkspaceRoot(dirname(hintPath)) ?? cwd,
24
+ data: JSON.parse(readFileSync(hintPath, 'utf-8')),
25
+ projectJsonPath: hintPath,
26
+ };
27
+ }
28
+ // Directory containing project.json
29
+ const dirProjectJson = join(hintPath, 'project.json');
30
+ if (existsSync(dirProjectJson)) {
31
+ return {
32
+ type: 'nx-project',
33
+ root: findWorkspaceRoot(hintPath) ?? cwd,
34
+ data: JSON.parse(readFileSync(dirProjectJson, 'utf-8')),
35
+ projectJsonPath: dirProjectJson,
36
+ };
37
+ }
38
+ }
39
+ // angular.json in cwd
40
+ const angularJson = resolve(cwd, 'angular.json');
41
+ if (existsSync(angularJson)) {
42
+ return {
43
+ type: 'angular',
44
+ root: cwd,
45
+ data: JSON.parse(readFileSync(angularJson, 'utf-8')),
46
+ };
47
+ }
48
+ // workspace.json (some Nx workspaces)
49
+ const workspaceJson = resolve(cwd, 'workspace.json');
50
+ if (existsSync(workspaceJson)) {
51
+ return {
52
+ type: 'angular',
53
+ root: cwd,
54
+ data: JSON.parse(readFileSync(workspaceJson, 'utf-8')),
55
+ };
56
+ }
57
+ // project.json in cwd (standalone Nx project or running from project dir)
58
+ const projectJson = resolve(cwd, 'project.json');
59
+ if (existsSync(projectJson)) {
60
+ return {
61
+ type: 'nx-project',
62
+ root: findWorkspaceRoot(cwd) ?? cwd,
63
+ data: JSON.parse(readFileSync(projectJson, 'utf-8')),
64
+ projectJsonPath: projectJson,
65
+ };
66
+ }
67
+ throw new Error('No angular.json or project.json found.\n' +
68
+ ' Run from an Angular/Nx workspace root, or use --project <path>.');
69
+ }
70
+ /**
71
+ * Walk up directories to find the workspace root (has nx.json or angular.json).
72
+ */
73
+ function findWorkspaceRoot(from) {
74
+ let dir = resolve(from);
75
+ while (true) {
76
+ if (existsSync(join(dir, 'nx.json')))
77
+ return dir;
78
+ if (existsSync(join(dir, 'angular.json')))
79
+ return dir;
80
+ const parent = dirname(dir);
81
+ if (parent === dir)
82
+ return null;
83
+ dir = parent;
84
+ }
85
+ }
86
+ // ═══════════════════════════════════════════════════════════════
87
+ // Resolve
88
+ // ═══════════════════════════════════════════════════════════════
89
+ /**
90
+ * Main entry: detect workspace type and resolve all build options.
91
+ */
92
+ export function resolveWorkspace(cwd, opts) {
93
+ const ws = detectWorkspace(cwd, opts.project);
94
+ if (ws.type === 'nx-project') {
95
+ return resolveNxProject(ws, opts);
96
+ }
97
+ return resolveAngularWorkspace(ws, opts);
98
+ }
99
+ function resolveAngularWorkspace(ws, opts) {
100
+ const workspace = ws.data;
101
+ const projects = workspace.projects ?? {};
102
+ let projectName;
103
+ let project;
104
+ if (opts.project && projects[opts.project]) {
105
+ projectName = opts.project;
106
+ project = projects[opts.project];
107
+ }
108
+ else if (workspace.defaultProject && projects[workspace.defaultProject]) {
109
+ projectName = workspace.defaultProject;
110
+ project = projects[workspace.defaultProject];
111
+ }
112
+ else {
113
+ const entries = Object.entries(projects);
114
+ if (entries.length === 0)
115
+ throw new Error('No projects found in angular.json');
116
+ [projectName, project] = entries[0];
117
+ }
118
+ return resolveProjectOptions(ws.root, projectName, project, opts);
119
+ }
120
+ function resolveNxProject(ws, opts) {
121
+ const project = ws.data;
122
+ const projectName = project.name ?? basename(dirname(ws.projectJsonPath));
123
+ const projectDir = dirname(ws.projectJsonPath);
124
+ // Resolve root relative to workspace
125
+ const projectRoot = project.root ?? relPath(ws.root, projectDir);
126
+ const resolved = {
127
+ ...project,
128
+ root: projectRoot,
129
+ sourceRoot: project.sourceRoot ?? join(projectRoot, 'src'),
130
+ };
131
+ return resolveProjectOptions(ws.root, projectName, resolved, opts);
132
+ }
133
+ function relPath(base, target) {
134
+ const resolved = resolve(target);
135
+ const baseResolved = resolve(base);
136
+ if (resolved.startsWith(baseResolved)) {
137
+ return resolved.slice(baseResolved.length + 1) || '.';
138
+ }
139
+ return resolved;
140
+ }
141
+ function resolveProjectOptions(workspaceRoot, projectName, project, opts) {
142
+ // Nx uses "targets", Angular CLI uses "architect"
143
+ const targets = project.targets ?? project.architect ?? {};
144
+ const buildTarget = targets['build'];
145
+ if (!buildTarget) {
146
+ throw new Error(`No "build" target found for project "${projectName}"`);
147
+ }
148
+ const serveTarget = targets['serve'];
149
+ // Resolve configuration name
150
+ let configName;
151
+ if (opts.configuration) {
152
+ configName = opts.configuration;
153
+ }
154
+ else if (opts.command === 'serve') {
155
+ configName = serveTarget?.defaultConfiguration ?? 'development';
156
+ }
157
+ else {
158
+ configName = buildTarget.defaultConfiguration ?? 'production';
159
+ }
160
+ // Merge base + config overrides
161
+ const base = buildTarget.options ?? {};
162
+ const overrides = buildTarget.configurations?.[configName];
163
+ const merged = overrides ? { ...base, ...overrides } : { ...base };
164
+ // Serve target options
165
+ const serveBase = serveTarget?.options ?? {};
166
+ const serveOverrides = serveTarget?.configurations?.[configName];
167
+ const serveOpts = serveOverrides ? { ...serveBase, ...serveOverrides } : { ...serveBase };
168
+ const projectRoot = project.root ?? '';
169
+ const sourceRoot = project.sourceRoot ?? join(projectRoot, 'src');
170
+ const abs = (p) => resolve(workspaceRoot, p);
171
+ const tsconfig = abs(merged.tsConfig ?? join(projectRoot, 'tsconfig.app.json'));
172
+ const browser = abs(merged.browser ?? merged.main ?? join(sourceRoot, 'main.ts'));
173
+ const styles = (merged.styles ?? []).map((s) => abs(typeof s === 'string' ? s : s.input));
174
+ const scripts = (merged.scripts ?? []).map((s) => abs(typeof s === 'string' ? s : s.input));
175
+ const assets = merged.assets ?? [];
176
+ const fileReplacements = (merged.fileReplacements ?? []).map((fr) => ({
177
+ replace: abs(fr.replace),
178
+ with: abs(fr.with),
179
+ }));
180
+ const sourceMap = merged.sourceMap ?? (configName !== 'production');
181
+ const optimization = merged.optimization ?? (configName === 'production');
182
+ const outputHashing = merged.outputHashing ?? (configName === 'production' ? 'all' : 'none');
183
+ const outputPath = abs(merged.outputPath ?? join('dist', projectRoot || projectName));
184
+ const baseHref = merged.baseHref ?? '/';
185
+ const deployUrl = merged.deployUrl ?? '';
186
+ const crossOrigin = merged.crossOrigin ?? 'none';
187
+ const namedChunks = merged.namedChunks ?? false;
188
+ const preserveSymlinks = merged.preserveSymlinks ?? false;
189
+ const watch = opts.watch ?? merged.watch ?? false;
190
+ const poll = serveOpts.poll ?? merged.poll ?? 0;
191
+ const polyfills = Array.isArray(merged.polyfills)
192
+ ? merged.polyfills
193
+ : merged.polyfills ? [merged.polyfills] : [];
194
+ const define = merged.define ?? {};
195
+ const externalDependencies = merged.externalDependencies ?? [];
196
+ const rawIncludePaths = merged.stylePreprocessorOptions?.includePaths ?? [];
197
+ const stylePreprocessorOptions = {
198
+ includePaths: rawIncludePaths.map((p) => abs(p)),
199
+ };
200
+ return {
201
+ workspaceRoot,
202
+ projectName,
203
+ configName,
204
+ sourceRoot,
205
+ tsconfig,
206
+ browser,
207
+ styles,
208
+ scripts,
209
+ assets,
210
+ fileReplacements,
211
+ sourceMap,
212
+ optimization,
213
+ outputHashing,
214
+ outputPath,
215
+ baseHref,
216
+ deployUrl,
217
+ polyfills,
218
+ define,
219
+ externalDependencies,
220
+ preserveSymlinks,
221
+ namedChunks,
222
+ crossOrigin,
223
+ stylePreprocessorOptions,
224
+ watch,
225
+ poll,
226
+ serve: {
227
+ port: opts.port ?? serveOpts.port ?? merged.port ?? 4200,
228
+ open: opts.open ?? serveOpts.open ?? merged.open ?? false,
229
+ host: opts.host ?? serveOpts.host,
230
+ },
231
+ };
232
+ }
package/package.json ADDED
@@ -0,0 +1,52 @@
1
+ {
2
+ "name": "@richapps/ong",
3
+ "version": "0.1.0",
4
+ "description": "Angular CLI powered by Vite + OXC — blazing fast drop-in replacement for ng serve/build",
5
+ "keywords": [
6
+ "angular",
7
+ "vite",
8
+ "oxc",
9
+ "cli",
10
+ "compiler",
11
+ "nx",
12
+ "build",
13
+ "dev-server"
14
+ ],
15
+ "license": "MIT",
16
+ "type": "module",
17
+ "bin": {
18
+ "ong": "./bin/ong.js"
19
+ },
20
+ "main": "./dist/index.js",
21
+ "types": "./dist/index.d.ts",
22
+ "exports": {
23
+ ".": {
24
+ "types": "./dist/index.d.ts",
25
+ "default": "./dist/index.js"
26
+ }
27
+ },
28
+ "files": [
29
+ "bin",
30
+ "dist"
31
+ ],
32
+ "scripts": {
33
+ "build": "tsc",
34
+ "dev": "tsc --watch",
35
+ "prepublishOnly": "npm run build"
36
+ },
37
+ "dependencies": {
38
+ "@oxc-angular/vite": "^0.0.15"
39
+ },
40
+ "peerDependencies": {
41
+ "vite": ">=6.0.0"
42
+ },
43
+ "devDependencies": {
44
+ "@types/node": "^22.0.0",
45
+ "typescript": "~5.9.2",
46
+ "vite": "^8.0.0-beta.16"
47
+ },
48
+ "repository": {
49
+ "type": "git",
50
+ "url": "https://github.com/nicoss/ong"
51
+ }
52
+ }