@ionify/ionify 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/LICENSE +21 -0
- package/README.md +205 -0
- package/dist/cli/index.cjs +219 -0
- package/dist/cli/index.d.cts +2 -0
- package/dist/cli/index.d.ts +2 -0
- package/dist/cli/index.js +192 -0
- package/dist/cli/worker.cjs +217 -0
- package/dist/client/hmr.js +124 -0
- package/dist/client/overlay.js +43 -0
- package/dist/client/react-refresh-runtime.js +72 -0
- package/dist/core/worker/worker.cjs +217 -0
- package/dist/index.cjs +38 -0
- package/dist/index.d.cts +123 -0
- package/dist/index.d.ts +123 -0
- package/dist/index.js +12 -0
- package/dist/ionify_core.node +0 -0
- package/package.json +81 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Khaled Salem
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
# ⚡ Ionify
|
|
2
|
+
|
|
3
|
+
**A web infrastructure intelligence engine**
|
|
4
|
+
|
|
5
|
+
Ionify unifies development and production workflows into one persistent pipeline: dependency graph + content-addressable cache + hybrid transforms + analysis-ready architecture.
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## What is Ionify?
|
|
10
|
+
|
|
11
|
+
Ionify is a web infrastructure intelligence engine.
|
|
12
|
+
|
|
13
|
+
Instead of treating development and production as separate tools, Ionify runs the entire lifecycle through a single persistent engine — from file watching and resolution to transformation, caching, and bundling.
|
|
14
|
+
|
|
15
|
+
At its core, Ionify maintains a **long-lived dependency graph** and a **content-addressable cache** that survive across runs. This allows the engine to understand how projects evolve over time, not just how they build once.
|
|
16
|
+
|
|
17
|
+
Ionify combines a high-performance native core with a hybrid transformation strategy:
|
|
18
|
+
- **OXC** as the primary engine for parsing and transformation
|
|
19
|
+
- **SWC** as a fallback to ensure compatibility and resilience
|
|
20
|
+
|
|
21
|
+
This unified and persistent design enables something traditional tooling cannot:
|
|
22
|
+
**infrastructure-level insight into the build process itself** — opening the door to analysis, optimization, and future AI-assisted recommendations.
|
|
23
|
+
|
|
24
|
+
---
|
|
25
|
+
|
|
26
|
+
## Why This Matters
|
|
27
|
+
|
|
28
|
+
### What Ionify Unifies
|
|
29
|
+
|
|
30
|
+
Ionify unifies what is traditionally fragmented across multiple tools:
|
|
31
|
+
|
|
32
|
+
- Development server and production bundling
|
|
33
|
+
- Resolution logic and dependency semantics
|
|
34
|
+
- Transformation and caching strategies
|
|
35
|
+
- Performance characteristics across environments
|
|
36
|
+
|
|
37
|
+
By running everything through the same engine, Ionify eliminates an entire class of inconsistencies that appear when dev and build pipelines diverge.
|
|
38
|
+
|
|
39
|
+
### For Developers
|
|
40
|
+
|
|
41
|
+
- **Fewer "works in dev, breaks in build" surprises** — same pipeline in both modes
|
|
42
|
+
- **Faster iteration** — persistent graph and cache reuse across runs
|
|
43
|
+
- **Deterministic behavior** — consistent across environments
|
|
44
|
+
- **Foundation for intelligent tooling** — infrastructure that can reason about builds, not just execute them
|
|
45
|
+
|
|
46
|
+
Ionify is designed to be the layer *below* frameworks and plugins — the infrastructure they can rely on.
|
|
47
|
+
|
|
48
|
+
---
|
|
49
|
+
|
|
50
|
+
## Architecture
|
|
51
|
+
|
|
52
|
+
### Pipeline Overview
|
|
53
|
+
|
|
54
|
+
```
|
|
55
|
+
Source Files
|
|
56
|
+
↓
|
|
57
|
+
Resolver
|
|
58
|
+
↓
|
|
59
|
+
Persistent Dependency Graph (native)
|
|
60
|
+
↓
|
|
61
|
+
Transform Engine (OXC/SWC hybrid)
|
|
62
|
+
↓
|
|
63
|
+
Content-Addressable Store (.ionify/cas/versionHash/moduleHash/...)
|
|
64
|
+
↓
|
|
65
|
+
Dev Server / Bundler
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
### Hybrid Transformation Engine
|
|
69
|
+
|
|
70
|
+
Ionify uses a hybrid transformation strategy by design.
|
|
71
|
+
|
|
72
|
+
**OXC** is used as the primary engine for parsing and transformation, optimized for performance and modern JavaScript syntax. **SWC** acts as a fallback layer to ensure robustness and compatibility across edge cases and evolving ecosystems.
|
|
73
|
+
|
|
74
|
+
This approach allows Ionify to remain framework-agnostic while balancing speed, correctness, and long-term maintainability.
|
|
75
|
+
|
|
76
|
+
### Storage
|
|
77
|
+
|
|
78
|
+
- **Graph persistence** — native Rust implementation
|
|
79
|
+
- **Transformed outputs** — stored in version-isolated CAS
|
|
80
|
+
- **Automatic invalidation** — via configuration hash
|
|
81
|
+
|
|
82
|
+
### Why This Enables Intelligence
|
|
83
|
+
|
|
84
|
+
Because Ionify persists the dependency graph and transformed outputs, the engine can observe patterns over time:
|
|
85
|
+
- Which modules change frequently
|
|
86
|
+
- Which transformations are expensive
|
|
87
|
+
- How dependency structure affects rebuild cost
|
|
88
|
+
|
|
89
|
+
This data is the basis for future analyzer tooling and AI-assisted optimization.
|
|
90
|
+
|
|
91
|
+
---
|
|
92
|
+
|
|
93
|
+
## Quick Start
|
|
94
|
+
|
|
95
|
+
### Installation
|
|
96
|
+
|
|
97
|
+
```bash
|
|
98
|
+
pnpm add -D ionify
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
### Minimal Configuration
|
|
102
|
+
|
|
103
|
+
Create `ionify.config.ts`:
|
|
104
|
+
|
|
105
|
+
```typescript
|
|
106
|
+
export default {
|
|
107
|
+
entry: "/src/main.ts",
|
|
108
|
+
outDir: "dist",
|
|
109
|
+
};
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
### Development Server
|
|
113
|
+
|
|
114
|
+
```bash
|
|
115
|
+
pnpm ionify dev
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
### Production Build
|
|
119
|
+
|
|
120
|
+
```bash
|
|
121
|
+
pnpm ionify build
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
---
|
|
125
|
+
|
|
126
|
+
## Project Status
|
|
127
|
+
|
|
128
|
+
**Core engine:** Stable and production-ready
|
|
129
|
+
**Unified dev + build pipeline:** Implemented
|
|
130
|
+
**Persistent graph and CAS:** In place
|
|
131
|
+
**Dependency pipeline:** Stabilization in progress
|
|
132
|
+
**Plugin system:** Temporarily paused
|
|
133
|
+
**Analyzer and AI layers:** Planned on top of the unified engine
|
|
134
|
+
|
|
135
|
+
---
|
|
136
|
+
|
|
137
|
+
## Key Features
|
|
138
|
+
|
|
139
|
+
### Current
|
|
140
|
+
|
|
141
|
+
- **Persistent Graph Engine** — dependency graph saved to disk and reused across runs
|
|
142
|
+
- **Unified Pipeline** — dev and production share the same core logic
|
|
143
|
+
- **Content Addressable Cache (CAS)** — version-isolated, deterministic caching
|
|
144
|
+
- **Rust-Powered Performance** — native core for parsing, transformation, and bundling
|
|
145
|
+
- **Hybrid Transform Strategy** — OXC primary, SWC fallback
|
|
146
|
+
- **Graph-based HMR** — intelligent hot module replacement based on dependency structure
|
|
147
|
+
|
|
148
|
+
### Planned
|
|
149
|
+
|
|
150
|
+
- **Analysis Dashboard** — visualize builds, cache hits, and dependency hot paths
|
|
151
|
+
- **AI-Assisted Optimization** — auto-tune splits, targets, and bundle strategies
|
|
152
|
+
- **Monorepo Support** — native workspace handling
|
|
153
|
+
- **Remote Build Cache** — team-level caching infrastructure
|
|
154
|
+
|
|
155
|
+
---
|
|
156
|
+
|
|
157
|
+
## Language Stack
|
|
158
|
+
|
|
159
|
+
| Component | Technology |
|
|
160
|
+
| ----------------------- | ------------------- |
|
|
161
|
+
| Core Engine | Rust |
|
|
162
|
+
| CLI / SDK / Plugin API | TypeScript |
|
|
163
|
+
| Graph Persistence | Native (sled/SQLite)|
|
|
164
|
+
| Primary Parser | OXC |
|
|
165
|
+
| Fallback Parser | SWC |
|
|
166
|
+
| Future Analyzer UI | React + TypeScript |
|
|
167
|
+
|
|
168
|
+
---
|
|
169
|
+
|
|
170
|
+
## Roadmap
|
|
171
|
+
|
|
172
|
+
1. ✅ **Core Engine** — parser, graph, CAS, dev server
|
|
173
|
+
2. ✅ **Unified Pipeline** — same engine for dev and production
|
|
174
|
+
3. ✅ **Persistent Graph + Cache** — version-isolated storage
|
|
175
|
+
4. 🔄 **Dependency Pipeline Stabilization** — robust node_modules handling
|
|
176
|
+
5. ⏸️ **Plugin System** — paused for pipeline stabilization
|
|
177
|
+
6. 📋 **Analyzer UI + Insights** — build visualization and metrics
|
|
178
|
+
7. 📋 **AI Optimization Engine** — intelligent build recommendations
|
|
179
|
+
8. 📋 **Monorepo / Remote Cache** — team collaboration features
|
|
180
|
+
|
|
181
|
+
---
|
|
182
|
+
|
|
183
|
+
## Philosophy
|
|
184
|
+
|
|
185
|
+
Ionify is designed to be the infrastructure layer that frameworks and plugins rely on — not another framework itself.
|
|
186
|
+
|
|
187
|
+
By unifying the build pipeline and persisting the dependency graph, Ionify creates a foundation for:
|
|
188
|
+
- Smarter tooling that understands your project over time
|
|
189
|
+
- Analysis and optimization based on real build patterns
|
|
190
|
+
- Future AI-assisted recommendations grounded in actual data
|
|
191
|
+
|
|
192
|
+
---
|
|
193
|
+
|
|
194
|
+
## Links
|
|
195
|
+
|
|
196
|
+
- **Website:** [ionify.cloud](https://ionify.cloud)
|
|
197
|
+
- **GitHub:** [github.com/khaledM-salem/Ionify](https://github.com/khaledM-salem/Ionify)
|
|
198
|
+
- **Issues:** [github.com/khaledM-salem/Ionify/issues](https://github.com/khaledM-salem/Ionify/issues)
|
|
199
|
+
- **Contact:** contact@ionify.cloud
|
|
200
|
+
|
|
201
|
+
---
|
|
202
|
+
|
|
203
|
+
## License
|
|
204
|
+
|
|
205
|
+
MIT © Khaled Salem
|
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
var __create = Object.create;
|
|
4
|
+
var __defProp = Object.defineProperty;
|
|
5
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
6
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
7
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
8
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
9
|
+
var __copyProps = (to, from, except, desc) => {
|
|
10
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
11
|
+
for (let key of __getOwnPropNames(from))
|
|
12
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
13
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
14
|
+
}
|
|
15
|
+
return to;
|
|
16
|
+
};
|
|
17
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
18
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
19
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
20
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
21
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
22
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
23
|
+
mod
|
|
24
|
+
));
|
|
25
|
+
|
|
26
|
+
// node_modules/.pnpm/tsup@8.5.1_@swc+core@1.15.4_postcss@8.5.6_tsx@4.21.0_typescript@5.9.3_yaml@2.8.2/node_modules/tsup/assets/cjs_shims.js
|
|
27
|
+
var getImportMetaUrl = () => typeof document === "undefined" ? new URL(`file:${__filename}`).href : document.currentScript && document.currentScript.tagName.toUpperCase() === "SCRIPT" ? document.currentScript.src : new URL("main.js", document.baseURI).href;
|
|
28
|
+
var importMetaUrl = /* @__PURE__ */ getImportMetaUrl();
|
|
29
|
+
|
|
30
|
+
// src/cli/index.ts
|
|
31
|
+
var import_commander = require("commander");
|
|
32
|
+
|
|
33
|
+
// src/cli/utils/logger.ts
|
|
34
|
+
var import_chalk = __toESM(require("chalk"), 1);
|
|
35
|
+
function logInfo(message) {
|
|
36
|
+
console.log(import_chalk.default.cyan(`[Ionify] ${message}`));
|
|
37
|
+
}
|
|
38
|
+
function logError(message, err) {
|
|
39
|
+
console.error(import_chalk.default.red(`[Ionify] ${message}`));
|
|
40
|
+
if (err) console.error(err);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// src/cli/commands/analyze.ts
|
|
44
|
+
var import_fs2 = __toESM(require("fs"), 1);
|
|
45
|
+
var import_path2 = __toESM(require("path"), 1);
|
|
46
|
+
|
|
47
|
+
// src/native/index.ts
|
|
48
|
+
var import_fs = __toESM(require("fs"), 1);
|
|
49
|
+
var import_path = __toESM(require("path"), 1);
|
|
50
|
+
var import_module = require("module");
|
|
51
|
+
|
|
52
|
+
// src/core/version.ts
|
|
53
|
+
var import_node_crypto = require("crypto");
|
|
54
|
+
|
|
55
|
+
// src/native/index.ts
|
|
56
|
+
function resolveCandidates() {
|
|
57
|
+
const cwd = process.cwd();
|
|
58
|
+
const releaseDir = import_path.default.resolve(cwd, "target", "release");
|
|
59
|
+
const debugDir = import_path.default.resolve(cwd, "target", "debug");
|
|
60
|
+
const nativeDir = import_path.default.resolve(cwd, "native");
|
|
61
|
+
const moduleDir = import_path.default.dirname(new URL(importMetaUrl).pathname);
|
|
62
|
+
const packageNativeDir = import_path.default.resolve(moduleDir, "..", "native");
|
|
63
|
+
const packageDistDir = import_path.default.resolve(moduleDir, "..");
|
|
64
|
+
const platformFile = process.platform === "win32" ? "ionify_core.dll" : process.platform === "darwin" ? "libionify_core.dylib" : "libionify_core.so";
|
|
65
|
+
const candidates = [
|
|
66
|
+
// Installed package locations (checked first)
|
|
67
|
+
import_path.default.join(packageDistDir, "ionify_core.node"),
|
|
68
|
+
import_path.default.join(packageNativeDir, "ionify_core.node"),
|
|
69
|
+
// Development locations
|
|
70
|
+
import_path.default.join(nativeDir, "ionify_core.node"),
|
|
71
|
+
import_path.default.join(releaseDir, "ionify_core.node"),
|
|
72
|
+
import_path.default.join(releaseDir, platformFile),
|
|
73
|
+
import_path.default.join(debugDir, "ionify_core.node"),
|
|
74
|
+
import_path.default.join(debugDir, platformFile)
|
|
75
|
+
];
|
|
76
|
+
return candidates.filter((candidate) => {
|
|
77
|
+
try {
|
|
78
|
+
return import_fs.default.existsSync(candidate) && import_fs.default.statSync(candidate).isFile();
|
|
79
|
+
} catch {
|
|
80
|
+
return false;
|
|
81
|
+
}
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
var nativeBinding = null;
|
|
85
|
+
(() => {
|
|
86
|
+
const require2 = (0, import_module.createRequire)(importMetaUrl);
|
|
87
|
+
for (const candidate of resolveCandidates()) {
|
|
88
|
+
try {
|
|
89
|
+
const mod = require2(candidate);
|
|
90
|
+
if (mod) {
|
|
91
|
+
nativeBinding = mod;
|
|
92
|
+
break;
|
|
93
|
+
}
|
|
94
|
+
} catch {
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
})();
|
|
98
|
+
var native = nativeBinding;
|
|
99
|
+
|
|
100
|
+
// src/cli/commands/analyze.ts
|
|
101
|
+
function readGraphFromDisk(root) {
|
|
102
|
+
const file = import_path2.default.join(root, ".ionify", "graph.json");
|
|
103
|
+
if (!import_fs2.default.existsSync(file)) return null;
|
|
104
|
+
try {
|
|
105
|
+
const raw = import_fs2.default.readFileSync(file, "utf8");
|
|
106
|
+
const snapshot = JSON.parse(raw);
|
|
107
|
+
if (snapshot?.version !== 1 || !snapshot?.nodes) return null;
|
|
108
|
+
return Object.entries(snapshot.nodes).map(([id, node]) => ({
|
|
109
|
+
id,
|
|
110
|
+
hash: node.hash ?? null,
|
|
111
|
+
deps: Array.isArray(node.deps) ? node.deps : []
|
|
112
|
+
}));
|
|
113
|
+
} catch (err) {
|
|
114
|
+
logError("Failed to read graph snapshot", err);
|
|
115
|
+
return null;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
function computeSummary(nodes, limit = 10) {
|
|
119
|
+
const modules = nodes.length;
|
|
120
|
+
let edgeCount = 0;
|
|
121
|
+
const dependentCounts = /* @__PURE__ */ new Map();
|
|
122
|
+
for (const node of nodes) {
|
|
123
|
+
for (const dep of node.deps) {
|
|
124
|
+
edgeCount += 1;
|
|
125
|
+
dependentCounts.set(dep, (dependentCounts.get(dep) ?? 0) + 1);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
const densest = [...nodes].sort((a, b) => b.deps.length - a.deps.length).slice(0, limit).map((node) => ({ id: node.id, deps: node.deps.length }));
|
|
129
|
+
const mostDepended = [...dependentCounts.entries()].sort((a, b) => b[1] - a[1]).slice(0, limit).map(([id, count]) => ({ id, dependents: count }));
|
|
130
|
+
const orphanSet = new Set(nodes.map((n) => n.id));
|
|
131
|
+
for (const node of nodes) {
|
|
132
|
+
for (const dep of node.deps) {
|
|
133
|
+
orphanSet.delete(dep);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
return {
|
|
137
|
+
modules,
|
|
138
|
+
edges: edgeCount,
|
|
139
|
+
averageDeps: modules === 0 ? 0 : edgeCount / modules,
|
|
140
|
+
densest,
|
|
141
|
+
mostDepended,
|
|
142
|
+
orphans: Array.from(orphanSet)
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
async function loadGraphSnapshot() {
|
|
146
|
+
if (native?.graphLoad) {
|
|
147
|
+
try {
|
|
148
|
+
const nodes = native.graphLoad();
|
|
149
|
+
if (Array.isArray(nodes)) {
|
|
150
|
+
return nodes.map((node) => ({
|
|
151
|
+
id: node.id,
|
|
152
|
+
hash: node.hash ?? null,
|
|
153
|
+
deps: Array.isArray(node.deps) ? node.deps : []
|
|
154
|
+
}));
|
|
155
|
+
}
|
|
156
|
+
} catch (err) {
|
|
157
|
+
logError("Failed to load native graph snapshot", err);
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
return readGraphFromDisk(process.cwd());
|
|
161
|
+
}
|
|
162
|
+
async function runAnalyzeCommand(options = {}) {
|
|
163
|
+
const nodes = await loadGraphSnapshot();
|
|
164
|
+
if (!nodes || nodes.length === 0) {
|
|
165
|
+
logInfo("No cached graph found. Run `ionify dev` to generate dependency data.");
|
|
166
|
+
return;
|
|
167
|
+
}
|
|
168
|
+
const summary = computeSummary(nodes, options.limit ?? 10);
|
|
169
|
+
if (options.json) {
|
|
170
|
+
console.log(JSON.stringify(summary, null, 2));
|
|
171
|
+
return;
|
|
172
|
+
}
|
|
173
|
+
logInfo("Ionify Graph Summary");
|
|
174
|
+
console.log(` Modules: ${summary.modules}`);
|
|
175
|
+
console.log(` Dependencies: ${summary.edges}`);
|
|
176
|
+
console.log(` Avg deps / module: ${summary.averageDeps.toFixed(2)}`);
|
|
177
|
+
if (summary.densest.length > 0) {
|
|
178
|
+
console.log("\n Top modules by dependency count:");
|
|
179
|
+
for (const entry of summary.densest) {
|
|
180
|
+
console.log(` \u2022 ${entry.id} (${entry.deps})`);
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
if (summary.mostDepended.length > 0) {
|
|
184
|
+
console.log("\n Top modules by inbound dependents:");
|
|
185
|
+
for (const entry of summary.mostDepended) {
|
|
186
|
+
console.log(` \u2022 ${entry.id} (${entry.dependents})`);
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
if (summary.orphans.length) {
|
|
190
|
+
console.log("\n Orphan modules (no dependents):");
|
|
191
|
+
for (const file of summary.orphans.slice(0, options.limit ?? 10)) {
|
|
192
|
+
console.log(` \u2022 ${file}`);
|
|
193
|
+
}
|
|
194
|
+
if (summary.orphans.length > (options.limit ?? 10)) {
|
|
195
|
+
console.log(` \u2022 \u2026and ${summary.orphans.length - (options.limit ?? 10)} more`);
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
// src/cli/index.ts
|
|
201
|
+
var program = new import_commander.Command();
|
|
202
|
+
program.name("ionify").description("Ionify \u2013 Instant, Intelligent, Unified Build Engine").version("0.1.0");
|
|
203
|
+
program.command("dev").description("Start Ionify development server (coming in full release)").option("-p, --port <port>", "Port to run the server on", "5173").action(async () => {
|
|
204
|
+
logError("Dev server is not available in this release. Stay tuned!");
|
|
205
|
+
process.exit(1);
|
|
206
|
+
});
|
|
207
|
+
program.command("build").description("Build for production (coming in full release)").option("-o, --outDir <dir>", "Output directory", "dist").option("-l, --level <level>", "Optimization level (0-4)", "3").action(async () => {
|
|
208
|
+
logError("Build command is not available in this release. Stay tuned!");
|
|
209
|
+
process.exit(1);
|
|
210
|
+
});
|
|
211
|
+
program.command("analyze").description("Analyze bundle and performance").option("-f, --format <format>", "Output format (json|text)", "text").action(async (options) => {
|
|
212
|
+
try {
|
|
213
|
+
await runAnalyzeCommand(options);
|
|
214
|
+
} catch (err) {
|
|
215
|
+
logError(`Analyzer failed: ${err.message}`);
|
|
216
|
+
process.exit(1);
|
|
217
|
+
}
|
|
218
|
+
});
|
|
219
|
+
program.parse();
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/cli/index.ts
|
|
4
|
+
import { Command } from "commander";
|
|
5
|
+
|
|
6
|
+
// src/cli/utils/logger.ts
|
|
7
|
+
import chalk from "chalk";
|
|
8
|
+
function logInfo(message) {
|
|
9
|
+
console.log(chalk.cyan(`[Ionify] ${message}`));
|
|
10
|
+
}
|
|
11
|
+
function logError(message, err) {
|
|
12
|
+
console.error(chalk.red(`[Ionify] ${message}`));
|
|
13
|
+
if (err) console.error(err);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
// src/cli/commands/analyze.ts
|
|
17
|
+
import fs2 from "fs";
|
|
18
|
+
import path2 from "path";
|
|
19
|
+
|
|
20
|
+
// src/native/index.ts
|
|
21
|
+
import fs from "fs";
|
|
22
|
+
import path from "path";
|
|
23
|
+
import { createRequire } from "module";
|
|
24
|
+
|
|
25
|
+
// src/core/version.ts
|
|
26
|
+
import { createHash } from "crypto";
|
|
27
|
+
|
|
28
|
+
// src/native/index.ts
|
|
29
|
+
function resolveCandidates() {
|
|
30
|
+
const cwd = process.cwd();
|
|
31
|
+
const releaseDir = path.resolve(cwd, "target", "release");
|
|
32
|
+
const debugDir = path.resolve(cwd, "target", "debug");
|
|
33
|
+
const nativeDir = path.resolve(cwd, "native");
|
|
34
|
+
const moduleDir = path.dirname(new URL(import.meta.url).pathname);
|
|
35
|
+
const packageNativeDir = path.resolve(moduleDir, "..", "native");
|
|
36
|
+
const packageDistDir = path.resolve(moduleDir, "..");
|
|
37
|
+
const platformFile = process.platform === "win32" ? "ionify_core.dll" : process.platform === "darwin" ? "libionify_core.dylib" : "libionify_core.so";
|
|
38
|
+
const candidates = [
|
|
39
|
+
// Installed package locations (checked first)
|
|
40
|
+
path.join(packageDistDir, "ionify_core.node"),
|
|
41
|
+
path.join(packageNativeDir, "ionify_core.node"),
|
|
42
|
+
// Development locations
|
|
43
|
+
path.join(nativeDir, "ionify_core.node"),
|
|
44
|
+
path.join(releaseDir, "ionify_core.node"),
|
|
45
|
+
path.join(releaseDir, platformFile),
|
|
46
|
+
path.join(debugDir, "ionify_core.node"),
|
|
47
|
+
path.join(debugDir, platformFile)
|
|
48
|
+
];
|
|
49
|
+
return candidates.filter((candidate) => {
|
|
50
|
+
try {
|
|
51
|
+
return fs.existsSync(candidate) && fs.statSync(candidate).isFile();
|
|
52
|
+
} catch {
|
|
53
|
+
return false;
|
|
54
|
+
}
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
var nativeBinding = null;
|
|
58
|
+
(() => {
|
|
59
|
+
const require2 = createRequire(import.meta.url);
|
|
60
|
+
for (const candidate of resolveCandidates()) {
|
|
61
|
+
try {
|
|
62
|
+
const mod = require2(candidate);
|
|
63
|
+
if (mod) {
|
|
64
|
+
nativeBinding = mod;
|
|
65
|
+
break;
|
|
66
|
+
}
|
|
67
|
+
} catch {
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
})();
|
|
71
|
+
var native = nativeBinding;
|
|
72
|
+
|
|
73
|
+
// src/cli/commands/analyze.ts
|
|
74
|
+
function readGraphFromDisk(root) {
|
|
75
|
+
const file = path2.join(root, ".ionify", "graph.json");
|
|
76
|
+
if (!fs2.existsSync(file)) return null;
|
|
77
|
+
try {
|
|
78
|
+
const raw = fs2.readFileSync(file, "utf8");
|
|
79
|
+
const snapshot = JSON.parse(raw);
|
|
80
|
+
if (snapshot?.version !== 1 || !snapshot?.nodes) return null;
|
|
81
|
+
return Object.entries(snapshot.nodes).map(([id, node]) => ({
|
|
82
|
+
id,
|
|
83
|
+
hash: node.hash ?? null,
|
|
84
|
+
deps: Array.isArray(node.deps) ? node.deps : []
|
|
85
|
+
}));
|
|
86
|
+
} catch (err) {
|
|
87
|
+
logError("Failed to read graph snapshot", err);
|
|
88
|
+
return null;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
function computeSummary(nodes, limit = 10) {
|
|
92
|
+
const modules = nodes.length;
|
|
93
|
+
let edgeCount = 0;
|
|
94
|
+
const dependentCounts = /* @__PURE__ */ new Map();
|
|
95
|
+
for (const node of nodes) {
|
|
96
|
+
for (const dep of node.deps) {
|
|
97
|
+
edgeCount += 1;
|
|
98
|
+
dependentCounts.set(dep, (dependentCounts.get(dep) ?? 0) + 1);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
const densest = [...nodes].sort((a, b) => b.deps.length - a.deps.length).slice(0, limit).map((node) => ({ id: node.id, deps: node.deps.length }));
|
|
102
|
+
const mostDepended = [...dependentCounts.entries()].sort((a, b) => b[1] - a[1]).slice(0, limit).map(([id, count]) => ({ id, dependents: count }));
|
|
103
|
+
const orphanSet = new Set(nodes.map((n) => n.id));
|
|
104
|
+
for (const node of nodes) {
|
|
105
|
+
for (const dep of node.deps) {
|
|
106
|
+
orphanSet.delete(dep);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
return {
|
|
110
|
+
modules,
|
|
111
|
+
edges: edgeCount,
|
|
112
|
+
averageDeps: modules === 0 ? 0 : edgeCount / modules,
|
|
113
|
+
densest,
|
|
114
|
+
mostDepended,
|
|
115
|
+
orphans: Array.from(orphanSet)
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
async function loadGraphSnapshot() {
|
|
119
|
+
if (native?.graphLoad) {
|
|
120
|
+
try {
|
|
121
|
+
const nodes = native.graphLoad();
|
|
122
|
+
if (Array.isArray(nodes)) {
|
|
123
|
+
return nodes.map((node) => ({
|
|
124
|
+
id: node.id,
|
|
125
|
+
hash: node.hash ?? null,
|
|
126
|
+
deps: Array.isArray(node.deps) ? node.deps : []
|
|
127
|
+
}));
|
|
128
|
+
}
|
|
129
|
+
} catch (err) {
|
|
130
|
+
logError("Failed to load native graph snapshot", err);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
return readGraphFromDisk(process.cwd());
|
|
134
|
+
}
|
|
135
|
+
async function runAnalyzeCommand(options = {}) {
|
|
136
|
+
const nodes = await loadGraphSnapshot();
|
|
137
|
+
if (!nodes || nodes.length === 0) {
|
|
138
|
+
logInfo("No cached graph found. Run `ionify dev` to generate dependency data.");
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
141
|
+
const summary = computeSummary(nodes, options.limit ?? 10);
|
|
142
|
+
if (options.json) {
|
|
143
|
+
console.log(JSON.stringify(summary, null, 2));
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
146
|
+
logInfo("Ionify Graph Summary");
|
|
147
|
+
console.log(` Modules: ${summary.modules}`);
|
|
148
|
+
console.log(` Dependencies: ${summary.edges}`);
|
|
149
|
+
console.log(` Avg deps / module: ${summary.averageDeps.toFixed(2)}`);
|
|
150
|
+
if (summary.densest.length > 0) {
|
|
151
|
+
console.log("\n Top modules by dependency count:");
|
|
152
|
+
for (const entry of summary.densest) {
|
|
153
|
+
console.log(` \u2022 ${entry.id} (${entry.deps})`);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
if (summary.mostDepended.length > 0) {
|
|
157
|
+
console.log("\n Top modules by inbound dependents:");
|
|
158
|
+
for (const entry of summary.mostDepended) {
|
|
159
|
+
console.log(` \u2022 ${entry.id} (${entry.dependents})`);
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
if (summary.orphans.length) {
|
|
163
|
+
console.log("\n Orphan modules (no dependents):");
|
|
164
|
+
for (const file of summary.orphans.slice(0, options.limit ?? 10)) {
|
|
165
|
+
console.log(` \u2022 ${file}`);
|
|
166
|
+
}
|
|
167
|
+
if (summary.orphans.length > (options.limit ?? 10)) {
|
|
168
|
+
console.log(` \u2022 \u2026and ${summary.orphans.length - (options.limit ?? 10)} more`);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// src/cli/index.ts
|
|
174
|
+
var program = new Command();
|
|
175
|
+
program.name("ionify").description("Ionify \u2013 Instant, Intelligent, Unified Build Engine").version("0.1.0");
|
|
176
|
+
program.command("dev").description("Start Ionify development server (coming in full release)").option("-p, --port <port>", "Port to run the server on", "5173").action(async () => {
|
|
177
|
+
logError("Dev server is not available in this release. Stay tuned!");
|
|
178
|
+
process.exit(1);
|
|
179
|
+
});
|
|
180
|
+
program.command("build").description("Build for production (coming in full release)").option("-o, --outDir <dir>", "Output directory", "dist").option("-l, --level <level>", "Optimization level (0-4)", "3").action(async () => {
|
|
181
|
+
logError("Build command is not available in this release. Stay tuned!");
|
|
182
|
+
process.exit(1);
|
|
183
|
+
});
|
|
184
|
+
program.command("analyze").description("Analyze bundle and performance").option("-f, --format <format>", "Output format (json|text)", "text").action(async (options) => {
|
|
185
|
+
try {
|
|
186
|
+
await runAnalyzeCommand(options);
|
|
187
|
+
} catch (err) {
|
|
188
|
+
logError(`Analyzer failed: ${err.message}`);
|
|
189
|
+
process.exit(1);
|
|
190
|
+
}
|
|
191
|
+
});
|
|
192
|
+
program.parse();
|