@ronaldjdevfs/forge 1.0.2 → 1.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 +130 -8
- package/package.json +2 -2
- package/skills/forge/SKILL.md +86 -15
- package/skills/forge/profiles/express-drizzle.md +107 -0
- package/skills/forge/profiles/fastify-mongodb.md +103 -0
- package/skills/forge/profiles/fastify-prisma.md +81 -0
- package/skills/forge/profiles/nestjs-mongodb.md +92 -0
- package/skills/forge/profiles/nestjs-postgres.md +98 -0
- package/skills/forge/reference/api-design.md +62 -0
- package/skills/forge/reference/assay.md +82 -0
- package/skills/forge/reference/cast.md +81 -7
- package/skills/forge/reference/data-patterns.md +86 -0
- package/skills/forge/reference/di-strategies.md +50 -0
- package/skills/forge/reference/errors.md +65 -0
- package/skills/forge/reference/events.md +95 -0
- package/skills/forge/reference/help.md +40 -0
- package/skills/forge/reference/hooks.md +62 -0
- package/skills/forge/reference/observability.md +66 -0
- package/skills/forge/reference/patterns.md +52 -0
- package/skills/forge/reference/principles.md +6 -0
- package/skills/forge/reference/reforge.md +69 -5
- package/skills/forge/reference/relocate.md +15 -2
- package/skills/forge/reference/security-patterns.md +87 -0
- package/skills/forge/reference/testing-patterns.md +69 -0
- package/skills/forge/scripts/assay.mjs +481 -0
- package/skills/forge/scripts/context.mjs +147 -43
- package/skills/forge/scripts/detect.mjs +371 -22
- package/skills/forge/scripts/forge-api.mjs +373 -0
- package/skills/forge/scripts/forge-config.mjs +268 -0
- package/skills/forge/scripts/forge-signals.mjs +131 -0
- package/skills/forge/scripts/forge-state.mjs +97 -0
- package/skills/forge/scripts/formatter.mjs +133 -0
- package/skills/forge/scripts/graph.mjs +5 -21
- package/skills/forge/scripts/hook.mjs +250 -0
- package/skills/forge/scripts/inspect.mjs +171 -22
- package/skills/forge/scripts/parse-imports.mjs +249 -0
- package/skills/forge/scripts/pin.mjs +151 -0
- package/skills/forge/scripts/posttool.mjs +224 -0
- package/skills/forge/scripts/profile.mjs +124 -20
- package/skills/forge/scripts/registry/rules.mjs +344 -0
- package/skills/forge/scripts/rename.mjs +669 -0
- package/skills/forge/scripts/rollback.mjs +213 -0
- package/skills/forge/scripts/update.mjs +114 -0
- package/skills/forge/templates/feature/domain-error.ts.md +9 -0
- package/skills/forge/templates/feature/domain-event.ts.md +9 -0
- package/skills/forge/templates/feature/event-handler.ts.md +10 -0
- package/skills/forge/templates/feature/use-case.ts.md +10 -2
- package/skills/forge/tests/core.test.mjs +403 -0
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
import { readFileSync, existsSync, readdirSync, statSync } from "fs";
|
|
4
|
-
import { join, basename } from "path";
|
|
4
|
+
import { join, basename, resolve } from "path";
|
|
5
5
|
import { buildGraph } from "./graph.mjs";
|
|
6
6
|
import { buildOwnershipReport } from "./armorer.mjs";
|
|
7
7
|
|
|
@@ -9,9 +9,9 @@ const ROOT = process.cwd();
|
|
|
9
9
|
const SRC = join(ROOT, "src");
|
|
10
10
|
const FEATURES = join(SRC, "features");
|
|
11
11
|
|
|
12
|
-
const PLATFORM_KNOWN = ["config", "database", "http", "server", "logger", "cache", "security", "events", "scheduler", "observability", "di"];
|
|
13
|
-
const SHARED_KNOWN = ["errors", "contracts", "types", "utils", "helpers", "constants", "enums"];
|
|
14
|
-
const INFRA_KNOWN = ["prisma", "mongodb", "postgres", "redis", "mail", "s3", "cloudinary", "stripe", "sqs", "rabbitmq", "kafka", "smtp"];
|
|
12
|
+
export const PLATFORM_KNOWN = ["config", "database", "http", "server", "logger", "cache", "security", "events", "scheduler", "observability", "di"];
|
|
13
|
+
export const SHARED_KNOWN = ["errors", "contracts", "types", "utils", "helpers", "constants", "enums"];
|
|
14
|
+
export const INFRA_KNOWN = ["prisma", "mongodb", "postgres", "redis", "mail", "s3", "cloudinary", "stripe", "sqs", "rabbitmq", "kafka", "smtp"];
|
|
15
15
|
|
|
16
16
|
function read(path) {
|
|
17
17
|
try {
|
|
@@ -101,38 +101,10 @@ function detectLegacyFeatures() {
|
|
|
101
101
|
return legacy;
|
|
102
102
|
}
|
|
103
103
|
|
|
104
|
-
function
|
|
105
|
-
const
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
const known = components.filter((c) => PLATFORM_KNOWN.includes(c.replace(/\.(ts|js)$/, "")));
|
|
109
|
-
const unknown = components.filter((c) => !PLATFORM_KNOWN.includes(c.replace(/\.(ts|js)$/, "")));
|
|
110
|
-
return { components, exists: true, known, unknown };
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
function detectShared() {
|
|
114
|
-
const sharedDir = join(SRC, "shared");
|
|
115
|
-
if (!isDir(sharedDir)) return { components: [], exists: false };
|
|
116
|
-
const components = listDir(sharedDir).filter((d) => isDir(join(sharedDir, d)) || d.endsWith(".ts") || d.endsWith(".js"));
|
|
117
|
-
const known = components.filter((c) => SHARED_KNOWN.includes(c.replace(/\.(ts|js)$/, "")));
|
|
118
|
-
const unknown = components.filter((c) => !SHARED_KNOWN.includes(c.replace(/\.(ts|js)$/, "")));
|
|
119
|
-
return { components, exists: true, known, unknown };
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
function detectInfra() {
|
|
123
|
-
const infraDir = join(SRC, "infra");
|
|
124
|
-
const infraDir2 = join(SRC, "infrastructure");
|
|
125
|
-
const dir = isDir(infraDir) ? infraDir : isDir(infraDir2) ? infraDir2 : null;
|
|
126
|
-
if (!dir) return { components: [], exists: false };
|
|
127
|
-
const components = listDir(dir).filter((d) => isDir(join(dir, d)) || d.endsWith(".ts") || d.endsWith(".js"));
|
|
128
|
-
const known = components.filter((c) => INFRA_KNOWN.includes(c.replace(/\.(ts|js)$/, "")));
|
|
129
|
-
const unknown = components.filter((c) => !INFRA_KNOWN.includes(c.replace(/\.(ts|js)$/, "")));
|
|
130
|
-
return { components, exists: true, known, unknown };
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
function detectOrphans() {
|
|
134
|
-
const report = buildOwnershipReport();
|
|
135
|
-
return report.orphans;
|
|
104
|
+
function classifyComponents(components, knownList) {
|
|
105
|
+
const known = components.filter((c) => knownList.includes(c.replace(/\.(ts|js)$/, "")));
|
|
106
|
+
const unknown = components.filter((c) => !knownList.includes(c.replace(/\.(ts|js)$/, "")));
|
|
107
|
+
return { components, exists: components.length > 0, known, unknown };
|
|
136
108
|
}
|
|
137
109
|
|
|
138
110
|
function stripJsonComments(raw) {
|
|
@@ -163,8 +135,129 @@ function readJson(path) {
|
|
|
163
135
|
}
|
|
164
136
|
}
|
|
165
137
|
|
|
166
|
-
|
|
167
|
-
|
|
138
|
+
// ── Monorepo detection (D2) ──
|
|
139
|
+
|
|
140
|
+
function resolveGlob(pattern, base) {
|
|
141
|
+
// Simple glob: supports * (single dir) and ** (recursive)
|
|
142
|
+
// Not full glob but enough for pnpm-workspace.yaml patterns like "packages/*"
|
|
143
|
+
if (!pattern) return [];
|
|
144
|
+
const starStar = pattern.includes("**");
|
|
145
|
+
const star = pattern.includes("*") && !starStar;
|
|
146
|
+
const baseDir = join(base, pattern.split("*")[0].replace(/\/+$/, ""));
|
|
147
|
+
if (!existsSync(baseDir)) return [];
|
|
148
|
+
|
|
149
|
+
const results = [];
|
|
150
|
+
if (star) {
|
|
151
|
+
for (const entry of readdirSync(baseDir, { withFileTypes: true })) {
|
|
152
|
+
if (entry.isDirectory()) {
|
|
153
|
+
const pkgPath = join(baseDir, entry.name);
|
|
154
|
+
if (existsSync(join(pkgPath, "package.json"))) results.push(pkgPath);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
} else if (starStar) {
|
|
158
|
+
function walk(dir) {
|
|
159
|
+
for (const entry of readdirSync(dir, { withFileTypes: true })) {
|
|
160
|
+
if (entry.isDirectory() && !entry.name.startsWith(".") && entry.name !== "node_modules") {
|
|
161
|
+
const sub = join(dir, entry.name);
|
|
162
|
+
if (existsSync(join(sub, "package.json"))) results.push(sub);
|
|
163
|
+
walk(sub);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
walk(baseDir);
|
|
168
|
+
}
|
|
169
|
+
return results;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
export function detectMonorepo(projectRoot = ROOT) {
|
|
173
|
+
const configs = [];
|
|
174
|
+
|
|
175
|
+
// pnpm-workspace.yaml — must have packages: key
|
|
176
|
+
const pnpmWorkspace = join(projectRoot, "pnpm-workspace.yaml");
|
|
177
|
+
if (existsSync(pnpmWorkspace)) {
|
|
178
|
+
const content = readFileSync(pnpmWorkspace, "utf-8");
|
|
179
|
+
if (content.includes("packages:")) {
|
|
180
|
+
configs.push({ type: "pnpm", file: "pnpm-workspace.yaml" });
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
// turbo.json
|
|
185
|
+
if (existsSync(join(projectRoot, "turbo.json"))) {
|
|
186
|
+
configs.push({ type: "turbo", file: "turbo.json" });
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
// nx.json
|
|
190
|
+
if (existsSync(join(projectRoot, "nx.json"))) {
|
|
191
|
+
configs.push({ type: "nx", file: "nx.json" });
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
// package.json workspaces
|
|
195
|
+
const pkg = readJson(join(projectRoot, "package.json"));
|
|
196
|
+
if (pkg?.workspaces) {
|
|
197
|
+
const patterns = Array.isArray(pkg.workspaces) ? pkg.workspaces : pkg.workspaces.packages || [];
|
|
198
|
+
if (patterns.length > 0) {
|
|
199
|
+
configs.push({ type: "npm", file: "package.json" });
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
// Lerna
|
|
204
|
+
if (existsSync(join(projectRoot, "lerna.json"))) {
|
|
205
|
+
configs.push({ type: "lerna", file: "lerna.json" });
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
return configs;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
export function detectWorkspaces(projectRoot = ROOT) {
|
|
212
|
+
const workspaces = [];
|
|
213
|
+
const pkg = readJson(join(projectRoot, "package.json"));
|
|
214
|
+
|
|
215
|
+
// 1. pnpm-workspace.yaml
|
|
216
|
+
const pnpmYaml = join(projectRoot, "pnpm-workspace.yaml");
|
|
217
|
+
if (existsSync(pnpmYaml)) {
|
|
218
|
+
const content = readFileSync(pnpmYaml, "utf-8");
|
|
219
|
+
const lines = content.split("\n");
|
|
220
|
+
let inPackages = false;
|
|
221
|
+
for (let line of lines) {
|
|
222
|
+
line = line.trim();
|
|
223
|
+
if (line.startsWith("packages:")) { inPackages = true; continue; }
|
|
224
|
+
if (inPackages && line.startsWith("-")) {
|
|
225
|
+
const pattern = line.replace(/^-\s*['"]?|['"]?\s*$/g, "").trim();
|
|
226
|
+
if (pattern) {
|
|
227
|
+
const resolved = resolveGlob(pattern, projectRoot);
|
|
228
|
+
workspaces.push(...resolved);
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
if (inPackages && line.startsWith("#")) continue;
|
|
232
|
+
if (inPackages && !line.startsWith("-") && line !== "") inPackages = false;
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
// 2. package.json workspaces
|
|
237
|
+
if (pkg?.workspaces) {
|
|
238
|
+
const patterns = Array.isArray(pkg.workspaces) ? pkg.workspaces : pkg.workspaces.packages || [];
|
|
239
|
+
for (const pattern of patterns) {
|
|
240
|
+
const resolved = resolveGlob(pattern, projectRoot);
|
|
241
|
+
workspaces.push(...resolved);
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
// Deduplicate
|
|
246
|
+
const seen = new Set();
|
|
247
|
+
return workspaces.filter(w => {
|
|
248
|
+
if (seen.has(w)) return false;
|
|
249
|
+
seen.add(w);
|
|
250
|
+
return true;
|
|
251
|
+
}).map(w => ({
|
|
252
|
+
path: w,
|
|
253
|
+
name: basename(w),
|
|
254
|
+
hasSrc: existsSync(join(w, "src")),
|
|
255
|
+
hasFeatures: existsSync(join(w, "src", "features")),
|
|
256
|
+
}));
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
export async function buildContext(projectRoot = ROOT, workspaceScope = null) {
|
|
260
|
+
const root = workspaceScope || projectRoot;
|
|
168
261
|
const src = join(root, "src");
|
|
169
262
|
|
|
170
263
|
const pkg = readJson(join(root, "package.json")) || {};
|
|
@@ -182,18 +275,25 @@ export async function buildContext(projectRoot = ROOT) {
|
|
|
182
275
|
|
|
183
276
|
const migratedFeatures = detectFeatures();
|
|
184
277
|
const legacyFeatures = detectLegacyFeatures();
|
|
185
|
-
const platform = detectPlatform();
|
|
186
|
-
const shared = detectShared();
|
|
187
|
-
const infra = detectInfra();
|
|
188
|
-
const orphans = detectOrphans();
|
|
189
|
-
const graph = buildGraph(root);
|
|
190
278
|
const ownership = buildOwnershipReport(root);
|
|
279
|
+
const platform = classifyComponents(ownership.ownership.platform, PLATFORM_KNOWN);
|
|
280
|
+
const shared = classifyComponents(ownership.ownership.shared, SHARED_KNOWN);
|
|
281
|
+
const infra = classifyComponents(ownership.ownership.infra, INFRA_KNOWN);
|
|
282
|
+
const orphans = ownership.orphans;
|
|
283
|
+
const graph = buildGraph(root);
|
|
191
284
|
|
|
192
285
|
const hasFeatures = migratedFeatures.length > 0;
|
|
193
286
|
const hasLegacy = legacyFeatures.length > 0;
|
|
194
287
|
|
|
288
|
+
// Monorepo detection
|
|
289
|
+
const isRoot = root === projectRoot;
|
|
290
|
+
const monorepo = isRoot ? detectMonorepo(projectRoot) : null;
|
|
291
|
+
const workspaces = isRoot ? detectWorkspaces(projectRoot) : [];
|
|
292
|
+
|
|
195
293
|
return {
|
|
196
294
|
projectName: basename(root),
|
|
295
|
+
isWorkspace: !isRoot,
|
|
296
|
+
workspaceScope: isRoot ? null : basename(root),
|
|
197
297
|
framework,
|
|
198
298
|
runtime: `Node ${process.version}`,
|
|
199
299
|
database,
|
|
@@ -222,6 +322,10 @@ export async function buildContext(projectRoot = ROOT) {
|
|
|
222
322
|
isFullyMigrated: hasFeatures && !hasLegacy,
|
|
223
323
|
isLegacy: hasLegacy && !hasFeatures,
|
|
224
324
|
isGreenfield: !hasLegacy && !hasFeatures && isDir(src),
|
|
325
|
+
// Monorepo
|
|
326
|
+
monorepo,
|
|
327
|
+
workspaces,
|
|
328
|
+
isMonorepo: monorepo !== null && monorepo.length > 0,
|
|
225
329
|
graph,
|
|
226
330
|
dependencies: {},
|
|
227
331
|
};
|