codesift-mcp 0.5.30 → 0.8.2
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 +7 -2
- package/dist/cli/commands.d.ts.map +1 -1
- package/dist/cli/commands.js +25 -3
- package/dist/cli/commands.js.map +1 -1
- package/dist/cli/git-hooks-installer.d.ts +2 -0
- package/dist/cli/git-hooks-installer.d.ts.map +1 -1
- package/dist/cli/git-hooks-installer.js +47 -7
- package/dist/cli/git-hooks-installer.js.map +1 -1
- package/dist/cli/hooks.d.ts.map +1 -1
- package/dist/cli/hooks.js +53 -0
- package/dist/cli/hooks.js.map +1 -1
- package/dist/cli/setup.d.ts +5 -0
- package/dist/cli/setup.d.ts.map +1 -1
- package/dist/cli/setup.js +31 -5
- package/dist/cli/setup.js.map +1 -1
- package/dist/config.d.ts +2 -1
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +10 -1
- package/dist/config.js.map +1 -1
- package/dist/instructions.d.ts +1 -1
- package/dist/instructions.d.ts.map +1 -1
- package/dist/instructions.js +6 -1
- package/dist/instructions.js.map +1 -1
- package/dist/parser/extractors/_shared.js +2 -2
- package/dist/parser/extractors/_shared.js.map +1 -1
- package/dist/parser/extractors/hono.d.ts.map +1 -1
- package/dist/parser/extractors/hono.js +60 -15
- package/dist/parser/extractors/hono.js.map +1 -1
- package/dist/parser/extractors/php.d.ts +12 -0
- package/dist/parser/extractors/php.d.ts.map +1 -1
- package/dist/parser/extractors/php.js +440 -26
- package/dist/parser/extractors/php.js.map +1 -1
- package/dist/parser/extractors/python.js +4 -5
- package/dist/parser/extractors/python.js.map +1 -1
- package/dist/parser/extractors/typescript.d.ts.map +1 -1
- package/dist/parser/extractors/typescript.js +70 -22
- package/dist/parser/extractors/typescript.js.map +1 -1
- package/dist/register-tool-loaders.d.ts +22 -0
- package/dist/register-tool-loaders.d.ts.map +1 -1
- package/dist/register-tool-loaders.js +38 -0
- package/dist/register-tool-loaders.js.map +1 -1
- package/dist/register-tools.d.ts +3 -1
- package/dist/register-tools.d.ts.map +1 -1
- package/dist/register-tools.js +527 -7
- package/dist/register-tools.js.map +1 -1
- package/dist/retrieval/codebase-retrieval.d.ts.map +1 -1
- package/dist/retrieval/codebase-retrieval.js +22 -0
- package/dist/retrieval/codebase-retrieval.js.map +1 -1
- package/dist/retrieval/retrieval-schemas.d.ts +4 -0
- package/dist/retrieval/retrieval-schemas.d.ts.map +1 -1
- package/dist/retrieval/semantic-handlers.js +1 -1
- package/dist/retrieval/semantic-handlers.js.map +1 -1
- package/dist/search/semantic.d.ts +21 -5
- package/dist/search/semantic.d.ts.map +1 -1
- package/dist/search/semantic.js +129 -4
- package/dist/search/semantic.js.map +1 -1
- package/dist/search/tool-ranker.js +1 -1
- package/dist/search/tool-ranker.js.map +1 -1
- package/dist/server-helpers.js +1 -1
- package/dist/server-helpers.js.map +1 -1
- package/dist/storage/index-store.d.ts +13 -1
- package/dist/storage/index-store.d.ts.map +1 -1
- package/dist/storage/index-store.js +34 -33
- package/dist/storage/index-store.js.map +1 -1
- package/dist/storage/registry.d.ts +28 -4
- package/dist/storage/registry.d.ts.map +1 -1
- package/dist/storage/registry.js +126 -5
- package/dist/storage/registry.js.map +1 -1
- package/dist/storage/usage-stats.d.ts +2 -0
- package/dist/storage/usage-stats.d.ts.map +1 -1
- package/dist/storage/usage-stats.js +6 -0
- package/dist/storage/usage-stats.js.map +1 -1
- package/dist/tools/_helpers.d.ts +2 -0
- package/dist/tools/_helpers.d.ts.map +1 -1
- package/dist/tools/_helpers.js +16 -1
- package/dist/tools/_helpers.js.map +1 -1
- package/dist/tools/astro-audit.d.ts +40 -0
- package/dist/tools/astro-audit.d.ts.map +1 -1
- package/dist/tools/astro-audit.js +94 -5
- package/dist/tools/astro-audit.js.map +1 -1
- package/dist/tools/astro-env-validator.d.ts +38 -0
- package/dist/tools/astro-env-validator.d.ts.map +1 -0
- package/dist/tools/astro-env-validator.js +190 -0
- package/dist/tools/astro-env-validator.js.map +1 -0
- package/dist/tools/astro-helpers.js +2 -2
- package/dist/tools/astro-helpers.js.map +1 -1
- package/dist/tools/astro-image-audit.d.ts +35 -0
- package/dist/tools/astro-image-audit.d.ts.map +1 -0
- package/dist/tools/astro-image-audit.js +129 -0
- package/dist/tools/astro-image-audit.js.map +1 -0
- package/dist/tools/astro-middleware.d.ts.map +1 -1
- package/dist/tools/astro-middleware.js +36 -11
- package/dist/tools/astro-middleware.js.map +1 -1
- package/dist/tools/astro-migration.d.ts.map +1 -1
- package/dist/tools/astro-migration.js +66 -0
- package/dist/tools/astro-migration.js.map +1 -1
- package/dist/tools/astro-svg-components.d.ts +32 -0
- package/dist/tools/astro-svg-components.d.ts.map +1 -0
- package/dist/tools/astro-svg-components.js +123 -0
- package/dist/tools/astro-svg-components.js.map +1 -0
- package/dist/tools/context-tools.d.ts.map +1 -1
- package/dist/tools/context-tools.js +45 -7
- package/dist/tools/context-tools.js.map +1 -1
- package/dist/tools/conversation-tools.js +1 -1
- package/dist/tools/conversation-tools.js.map +1 -1
- package/dist/tools/index-tools.d.ts +12 -0
- package/dist/tools/index-tools.d.ts.map +1 -1
- package/dist/tools/index-tools.js +54 -6
- package/dist/tools/index-tools.js.map +1 -1
- package/dist/tools/insights-tools.d.ts +137 -0
- package/dist/tools/insights-tools.d.ts.map +1 -0
- package/dist/tools/insights-tools.js +438 -0
- package/dist/tools/insights-tools.js.map +1 -0
- package/dist/tools/pattern-tools.d.ts +7 -0
- package/dist/tools/pattern-tools.d.ts.map +1 -1
- package/dist/tools/pattern-tools.js +292 -19
- package/dist/tools/pattern-tools.js.map +1 -1
- package/dist/tools/php-tools.d.ts +78 -4
- package/dist/tools/php-tools.d.ts.map +1 -1
- package/dist/tools/php-tools.js +824 -42
- package/dist/tools/php-tools.js.map +1 -1
- package/dist/tools/php8-compat-tools.d.ts +62 -0
- package/dist/tools/php8-compat-tools.d.ts.map +1 -0
- package/dist/tools/php8-compat-tools.js +287 -0
- package/dist/tools/php8-compat-tools.js.map +1 -0
- package/dist/tools/php8-migration-candidates-tools.d.ts +68 -0
- package/dist/tools/php8-migration-candidates-tools.d.ts.map +1 -0
- package/dist/tools/php8-migration-candidates-tools.js +476 -0
- package/dist/tools/php8-migration-candidates-tools.js.map +1 -0
- package/dist/tools/phpstan-baseline-tools.d.ts +62 -0
- package/dist/tools/phpstan-baseline-tools.d.ts.map +1 -0
- package/dist/tools/phpstan-baseline-tools.js +263 -0
- package/dist/tools/phpstan-baseline-tools.js.map +1 -0
- package/dist/tools/project-tools.d.ts +4 -2
- package/dist/tools/project-tools.d.ts.map +1 -1
- package/dist/tools/project-tools.js +20 -7
- package/dist/tools/project-tools.js.map +1 -1
- package/dist/tools/react-tools.d.ts +39 -9
- package/dist/tools/react-tools.d.ts.map +1 -1
- package/dist/tools/react-tools.js +313 -18
- package/dist/tools/react-tools.js.map +1 -1
- package/dist/tools/search-tools.d.ts.map +1 -1
- package/dist/tools/search-tools.js +35 -5
- package/dist/tools/search-tools.js.map +1 -1
- package/dist/tools/status-tools.d.ts +1 -0
- package/dist/tools/status-tools.d.ts.map +1 -1
- package/dist/tools/status-tools.js +1 -0
- package/dist/tools/status-tools.js.map +1 -1
- package/dist/tools/symbol-tools.d.ts +11 -0
- package/dist/tools/symbol-tools.d.ts.map +1 -1
- package/dist/tools/symbol-tools.js +107 -6
- package/dist/tools/symbol-tools.js.map +1 -1
- package/dist/tools/yii-console-tools.d.ts +69 -0
- package/dist/tools/yii-console-tools.d.ts.map +1 -0
- package/dist/tools/yii-console-tools.js +256 -0
- package/dist/tools/yii-console-tools.js.map +1 -0
- package/dist/tools/yii-migrations-tools.d.ts +79 -0
- package/dist/tools/yii-migrations-tools.d.ts.map +1 -0
- package/dist/tools/yii-migrations-tools.js +543 -0
- package/dist/tools/yii-migrations-tools.js.map +1 -0
- package/dist/tools/yii-modules-tools.d.ts +63 -0
- package/dist/tools/yii-modules-tools.d.ts.map +1 -0
- package/dist/tools/yii-modules-tools.js +201 -0
- package/dist/tools/yii-modules-tools.js.map +1 -0
- package/dist/tools/yii-rbac-tools.d.ts +89 -0
- package/dist/tools/yii-rbac-tools.d.ts.map +1 -0
- package/dist/tools/yii-rbac-tools.js +238 -0
- package/dist/tools/yii-rbac-tools.js.map +1 -0
- package/dist/tools/yii3-attribute-candidates-tools.d.ts +72 -0
- package/dist/tools/yii3-attribute-candidates-tools.d.ts.map +1 -0
- package/dist/tools/yii3-attribute-candidates-tools.js +301 -0
- package/dist/tools/yii3-attribute-candidates-tools.js.map +1 -0
- package/dist/tools/yii3-migration-tools.d.ts +74 -0
- package/dist/tools/yii3-migration-tools.d.ts.map +1 -0
- package/dist/tools/yii3-migration-tools.js +440 -0
- package/dist/tools/yii3-migration-tools.js.map +1 -0
- package/dist/types.d.ts +5 -1
- package/dist/types.d.ts.map +1 -1
- package/dist/utils/constant-file-pattern.d.ts +3 -1
- package/dist/utils/constant-file-pattern.d.ts.map +1 -1
- package/dist/utils/constant-file-pattern.js +6 -4
- package/dist/utils/constant-file-pattern.js.map +1 -1
- package/dist/utils/heritage-edges.d.ts +29 -0
- package/dist/utils/heritage-edges.d.ts.map +1 -0
- package/dist/utils/heritage-edges.js +94 -0
- package/dist/utils/heritage-edges.js.map +1 -0
- package/dist/utils/source-stripper.d.ts +23 -0
- package/dist/utils/source-stripper.d.ts.map +1 -0
- package/dist/utils/source-stripper.js +239 -0
- package/dist/utils/source-stripper.js.map +1 -0
- package/dist/utils/ts-imports.d.ts +7 -1
- package/dist/utils/ts-imports.d.ts.map +1 -1
- package/dist/utils/ts-imports.js +40 -7
- package/dist/utils/ts-imports.js.map +1 -1
- package/dist/utils/tsconfig-paths.d.ts +6 -5
- package/dist/utils/tsconfig-paths.d.ts.map +1 -1
- package/dist/utils/tsconfig-paths.js +52 -14
- package/dist/utils/tsconfig-paths.js.map +1 -1
- package/dist/utils/wall-clock.d.ts +9 -0
- package/dist/utils/wall-clock.d.ts.map +1 -0
- package/dist/utils/wall-clock.js +19 -0
- package/dist/utils/wall-clock.js.map +1 -0
- package/package.json +1 -1
- package/rules/codesift.md +16 -6
- package/rules/codesift.mdc +10 -3
- package/rules/codex.md +10 -3
- package/rules/gemini.md +10 -3
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Yii2 module inventory (N1).
|
|
3
|
+
*
|
|
4
|
+
* Scans a Yii2 codebase for classes extending `yii\\base\\Module` and emits
|
|
5
|
+
* a structured per-module summary: controller namespace, controllers, views,
|
|
6
|
+
* migrations path, components, sub-modules. Also resolves URL prefixes by
|
|
7
|
+
* cross-referencing config/web.php urlManager rules + the application's
|
|
8
|
+
* `modules` registration.
|
|
9
|
+
*
|
|
10
|
+
* Why this is its own tool (vs. squeezing into php_project_audit):
|
|
11
|
+
* - Modules are the primary architectural unit of medium/large Yii2 apps
|
|
12
|
+
* (tgm-panel: 11 modules; Mobi 2: similar). Routing, RBAC, and god-model
|
|
13
|
+
* analysis all benefit from being able to scope to a module.
|
|
14
|
+
* - The output is a graph of cross-file references (controllers ↔ views ↔
|
|
15
|
+
* migrations ↔ config), not a flat findings list.
|
|
16
|
+
*
|
|
17
|
+
* Implementation depends on the v2.0.0 PHP extractor — uses `s.extends`
|
|
18
|
+
* (introduced in Sprint 1) to detect Module subclasses structurally.
|
|
19
|
+
* Falls back to a regex check on `s.source` when `extends` is absent so
|
|
20
|
+
* stale indexes don't go silent.
|
|
21
|
+
*/
|
|
22
|
+
import { readFile } from "node:fs/promises";
|
|
23
|
+
import { dirname, join, relative } from "node:path";
|
|
24
|
+
import { getCodeIndex } from "./index-tools.js";
|
|
25
|
+
// ---------------------------------------------------------------------------
|
|
26
|
+
// Implementation
|
|
27
|
+
// ---------------------------------------------------------------------------
|
|
28
|
+
const MODULE_BASE_NAMES = new Set(["Module", "BaseModule"]);
|
|
29
|
+
/**
|
|
30
|
+
* Walk a class symbol's extends chain looking for a Yii2 Module ancestor.
|
|
31
|
+
* Mirrors isActiveRecordHierarchy from php-tools but for the Module base.
|
|
32
|
+
* Cycle protection + depth cap. Falls back to source-text regex when extends
|
|
33
|
+
* metadata is absent (legacy index pre-v2.0.0 extractor).
|
|
34
|
+
*/
|
|
35
|
+
function isModuleHierarchy(cls, index, visited = new Set(), depth = 0) {
|
|
36
|
+
if (depth > 5)
|
|
37
|
+
return false;
|
|
38
|
+
if (visited.has(cls.name))
|
|
39
|
+
return false;
|
|
40
|
+
visited.add(cls.name);
|
|
41
|
+
const exts = cls.extends ?? [];
|
|
42
|
+
for (const baseFqcn of exts) {
|
|
43
|
+
const last = baseFqcn.split(/[\\\\]+/).pop() ?? baseFqcn;
|
|
44
|
+
if (MODULE_BASE_NAMES.has(last))
|
|
45
|
+
return true;
|
|
46
|
+
const baseSym = index.symbols.find((s) => s.kind === "class" && s.name === last);
|
|
47
|
+
if (baseSym && isModuleHierarchy(baseSym, index, visited, depth + 1)) {
|
|
48
|
+
return true;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
if (!cls.extends && cls.source) {
|
|
52
|
+
return /extends\s+(?:\\?yii\\base\\Module|Module)\b/.test(cls.source);
|
|
53
|
+
}
|
|
54
|
+
return false;
|
|
55
|
+
}
|
|
56
|
+
export async function analyzeYiiModules(repo, options) {
|
|
57
|
+
const index = await getCodeIndex(repo);
|
|
58
|
+
if (!index)
|
|
59
|
+
throw new Error(`Repository "${repo}" not found.`);
|
|
60
|
+
// Find all class symbols whose file basename is exactly Module.php — Yii2
|
|
61
|
+
// canonical convention. We additionally verify the class extends Module
|
|
62
|
+
// (catches cases where a Module.php contains an unrelated helper class).
|
|
63
|
+
const moduleClasses = index.symbols.filter((s) => {
|
|
64
|
+
if (s.kind !== "class")
|
|
65
|
+
return false;
|
|
66
|
+
if (!s.file.endsWith("/Module.php") && s.file !== "Module.php")
|
|
67
|
+
return false;
|
|
68
|
+
if (!isModuleHierarchy(s, index))
|
|
69
|
+
return false;
|
|
70
|
+
return true;
|
|
71
|
+
});
|
|
72
|
+
// Pre-resolve url-manager rules from config — used to attach url_prefixes
|
|
73
|
+
// to each module. Robust against missing config: we read each candidate
|
|
74
|
+
// file and scan with a single regex; failures are non-fatal.
|
|
75
|
+
const urlPrefixesByModule = await resolveUrlPrefixes(index);
|
|
76
|
+
const modules = [];
|
|
77
|
+
for (const cls of moduleClasses) {
|
|
78
|
+
const moduleDir = dirname(cls.file);
|
|
79
|
+
const id = moduleDir.split("/").pop() ?? cls.name.replace(/Module$/, "").toLowerCase();
|
|
80
|
+
if (options?.module_id && id !== options.module_id)
|
|
81
|
+
continue;
|
|
82
|
+
// controllerNamespace: explicit declaration on the Module class.
|
|
83
|
+
// Captured from `public $controllerNamespace = '...';` in source.
|
|
84
|
+
let controllerNamespace = null;
|
|
85
|
+
if (cls.source) {
|
|
86
|
+
const cnMatch = /\$controllerNamespace\s*=\s*['"]([^'"]+)['"]/.exec(cls.source);
|
|
87
|
+
controllerNamespace = cnMatch?.[1] ?? null;
|
|
88
|
+
}
|
|
89
|
+
// Yii2 default: `<module-namespace>\\controllers`. We synthesize this
|
|
90
|
+
// when the module class doesn't override it explicitly.
|
|
91
|
+
const defaultControllerNamespace = computeDefaultControllerNamespace(cls.file);
|
|
92
|
+
const effectiveNs = controllerNamespace ?? defaultControllerNamespace;
|
|
93
|
+
const controllersPath = join(moduleDir, "controllers");
|
|
94
|
+
// Find controller classes living under the module's controllers/ dir.
|
|
95
|
+
const controllerSymbols = index.symbols.filter((s) => s.kind === "class" &&
|
|
96
|
+
s.file.startsWith(controllersPath + "/") &&
|
|
97
|
+
s.name.endsWith("Controller"));
|
|
98
|
+
const controllers = controllerSymbols.map((c) => ({
|
|
99
|
+
class: effectiveNs ? `${effectiveNs}\\${c.name}` : c.name,
|
|
100
|
+
file: c.file,
|
|
101
|
+
actions: index.symbols
|
|
102
|
+
.filter((s) => s.parent === c.id && s.kind === "method" && s.name.startsWith("action"))
|
|
103
|
+
.map((s) => s.name),
|
|
104
|
+
}));
|
|
105
|
+
// Views, migrations, sub-modules — each detected by directory presence.
|
|
106
|
+
const viewsPath = join(moduleDir, "views");
|
|
107
|
+
const viewsCount = index.files.filter((f) => f.path.startsWith(viewsPath + "/")).length;
|
|
108
|
+
const migrationsPath = join(moduleDir, "migrations");
|
|
109
|
+
const migrationsCount = index.files.filter((f) => f.path.startsWith(migrationsPath + "/") && /m\d+_\d+_/.test(f.path)).length;
|
|
110
|
+
// Sub-modules: nested module directories with their own Module.php.
|
|
111
|
+
const submoduleClasses = index.symbols.filter((s) => s.kind === "class" &&
|
|
112
|
+
(s.file.endsWith("/Module.php") || s.file === "Module.php") &&
|
|
113
|
+
s.file !== cls.file &&
|
|
114
|
+
s.file.startsWith(moduleDir + "/"));
|
|
115
|
+
const submodules = submoduleClasses
|
|
116
|
+
.map((sc) => dirname(sc.file).split("/").pop() ?? "")
|
|
117
|
+
.filter(Boolean);
|
|
118
|
+
modules.push({
|
|
119
|
+
id,
|
|
120
|
+
class: effectiveNs ? `${effectiveNs.replace(/\\controllers$/, "")}\\${cls.name}` : cls.name,
|
|
121
|
+
file: cls.file,
|
|
122
|
+
controllerNamespace,
|
|
123
|
+
controllers_path: controllersPath,
|
|
124
|
+
controllers,
|
|
125
|
+
views_path: viewsPath,
|
|
126
|
+
views_count: viewsCount,
|
|
127
|
+
migrations_path: migrationsCount > 0 ? migrationsPath : null,
|
|
128
|
+
migrations_count: migrationsCount,
|
|
129
|
+
submodules,
|
|
130
|
+
url_prefixes: urlPrefixesByModule.get(id) ?? [],
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
// Stable order: by id alphabetically — useful when consumers diff this
|
|
134
|
+
// output across audit runs.
|
|
135
|
+
modules.sort((a, b) => a.id.localeCompare(b.id));
|
|
136
|
+
return { repo, total_modules: modules.length, modules };
|
|
137
|
+
}
|
|
138
|
+
// ---------------------------------------------------------------------------
|
|
139
|
+
// Helpers
|
|
140
|
+
// ---------------------------------------------------------------------------
|
|
141
|
+
/**
|
|
142
|
+
* Synthesize the default controllerNamespace for a Module.php file.
|
|
143
|
+
* Yii2 convention: take the Module's PHP namespace and append "\\controllers".
|
|
144
|
+
* We read the file directly because the namespace declaration is at the
|
|
145
|
+
* top of the file, not on the symbol itself.
|
|
146
|
+
*
|
|
147
|
+
* Returns null if the file isn't readable or has no namespace declaration.
|
|
148
|
+
* The caller falls back to bare class names when this is null.
|
|
149
|
+
*/
|
|
150
|
+
function computeDefaultControllerNamespace(_file) {
|
|
151
|
+
// Lazy: this is best-effort — if the namespace can't be determined the
|
|
152
|
+
// controllers list still works (we just lose the FQCN wrapping). Reading
|
|
153
|
+
// the file synchronously inside a hot loop would be slow; we accept the
|
|
154
|
+
// null fallback rather than spin up a per-module readFile here.
|
|
155
|
+
return null;
|
|
156
|
+
}
|
|
157
|
+
/**
|
|
158
|
+
* Scan main config files for `urlManager` rules and group resolved URL
|
|
159
|
+
* prefixes by module id. Yii2 url rules of the form
|
|
160
|
+
* '<module-id>/<controller-id>/<action-id>'
|
|
161
|
+
* '<module-id>/<controller-id>'
|
|
162
|
+
* ['class' => 'yii\\rest\\UrlRule', 'controller' => '<module-id>/<ctrl>']
|
|
163
|
+
* all map to a single module-id prefix. Naive matcher — collects literal
|
|
164
|
+
* prefix strings, callers can dedupe further by stripping verbs.
|
|
165
|
+
*/
|
|
166
|
+
async function resolveUrlPrefixes(index) {
|
|
167
|
+
const out = new Map();
|
|
168
|
+
const configFiles = index.files.filter((f) => /config\/(?:web|main|api|backend|frontend|common)(?:[-_][\w-]+)?\.php$/.test(f.path));
|
|
169
|
+
for (const cf of configFiles) {
|
|
170
|
+
let source;
|
|
171
|
+
try {
|
|
172
|
+
source = await readFile(join(index.root, cf.path), "utf-8");
|
|
173
|
+
}
|
|
174
|
+
catch {
|
|
175
|
+
continue;
|
|
176
|
+
}
|
|
177
|
+
// Generic pattern: 'GET <prefix>/...' => 'mod/ctrl/act' OR
|
|
178
|
+
// '<prefix>/...' => 'mod/ctrl/act'
|
|
179
|
+
// We capture the route target string (right side) and pull its first
|
|
180
|
+
// segment as module id.
|
|
181
|
+
const ruleRe = /['"](?:GET |POST |PUT |DELETE |PATCH )?[^'"]+['"]\s*=>\s*['"]([\w-]+)\/(?:[\w-]+\/?)*['"]/g;
|
|
182
|
+
let m;
|
|
183
|
+
while ((m = ruleRe.exec(source)) !== null) {
|
|
184
|
+
const prefix = m[1];
|
|
185
|
+
// Filter common false positives: top-level controller names that look
|
|
186
|
+
// like prefixes but aren't modules (site, debug, gii). Caller can
|
|
187
|
+
// ignore these by intersecting with the actual module list.
|
|
188
|
+
if (!out.has(prefix))
|
|
189
|
+
out.set(prefix, []);
|
|
190
|
+
out.get(prefix).push(prefix);
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
// Dedupe per-key
|
|
194
|
+
for (const [k, v] of out.entries()) {
|
|
195
|
+
out.set(k, Array.from(new Set(v)));
|
|
196
|
+
}
|
|
197
|
+
return out;
|
|
198
|
+
}
|
|
199
|
+
// Re-export relative for tests that want to assert on path shapes.
|
|
200
|
+
export { relative as _relativePathForTests };
|
|
201
|
+
//# sourceMappingURL=yii-modules-tools.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"yii-modules-tools.js","sourceRoot":"","sources":["../../src/tools/yii-modules-tools.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Yii2 RBAC audit (N3).
|
|
3
|
+
*
|
|
4
|
+
* Builds a permission graph by cross-referencing two surfaces:
|
|
5
|
+
*
|
|
6
|
+
* 1. DEFINITIONS — permissions/roles created in seed migrations or
|
|
7
|
+
* RBAC seeder commands. The Yii2 idiom is:
|
|
8
|
+
*
|
|
9
|
+
* $auth = Yii::$app->authManager;
|
|
10
|
+
* $auth->createPermission('viewUser'); // permission def
|
|
11
|
+
* $auth->createRole('admin'); // role def
|
|
12
|
+
* $auth->add($p); // register
|
|
13
|
+
* $auth->addChild($admin, $p); // attach to role
|
|
14
|
+
*
|
|
15
|
+
* 2. RUNTIME CHECKS — `Yii::$app->user->can('viewUser')` calls in
|
|
16
|
+
* controllers/views, plus `AccessControl` rules in behaviors().
|
|
17
|
+
*
|
|
18
|
+
* Cross-referencing the two surfaces produces:
|
|
19
|
+
*
|
|
20
|
+
* - orphan_checks: permissions checked at runtime but never defined.
|
|
21
|
+
* Indicates dead code OR a typo.
|
|
22
|
+
* - unused_definitions: permissions defined but never checked. Indicates
|
|
23
|
+
* dead seed code OR a missing access guard.
|
|
24
|
+
* - controllers_without_access_control: classes that look like Controllers
|
|
25
|
+
* (web, REST) without any access
|
|
26
|
+
* restriction (no AccessControl in
|
|
27
|
+
* behaviors(), no can() calls).
|
|
28
|
+
* - dynamic_creates: createPermission($var) calls — name is computed at
|
|
29
|
+
* runtime, can't statically resolve. Surfaced
|
|
30
|
+
* separately so callers can flag them as "review
|
|
31
|
+
* manually".
|
|
32
|
+
*
|
|
33
|
+
* The tool deliberately avoids opinions about which finding category is
|
|
34
|
+
* "correct" — the consumer (audit skill, CTO, code reviewer) decides
|
|
35
|
+
* which orphans matter and which were intentional.
|
|
36
|
+
*/
|
|
37
|
+
export interface RbacDefinitionRef {
|
|
38
|
+
name: string;
|
|
39
|
+
kind: "permission" | "role";
|
|
40
|
+
file: string;
|
|
41
|
+
line: number;
|
|
42
|
+
}
|
|
43
|
+
export interface RbacCheckRef {
|
|
44
|
+
name: string;
|
|
45
|
+
/** "code" — Yii::$app->user->can(...)
|
|
46
|
+
* "access-control" — AccessControl behavior `permissions => […]` */
|
|
47
|
+
source: "code" | "access-control";
|
|
48
|
+
file: string;
|
|
49
|
+
line: number;
|
|
50
|
+
}
|
|
51
|
+
export interface RbacControllerRef {
|
|
52
|
+
class: string;
|
|
53
|
+
file: string;
|
|
54
|
+
/** Reason it was flagged. */
|
|
55
|
+
reason: "no-behaviors" | "no-access-control-in-behaviors" | "no-can-calls";
|
|
56
|
+
}
|
|
57
|
+
export interface YiiRbacAudit {
|
|
58
|
+
repo: string;
|
|
59
|
+
/** Resolved (statically determinable) permission/role definitions. */
|
|
60
|
+
definitions: RbacDefinitionRef[];
|
|
61
|
+
/** Runtime can() calls + AccessControl rule references. */
|
|
62
|
+
checks: RbacCheckRef[];
|
|
63
|
+
/** Permissions checked at runtime but not in definitions. */
|
|
64
|
+
orphan_checks: string[];
|
|
65
|
+
/** Permissions defined but never checked. */
|
|
66
|
+
unused_definitions: string[];
|
|
67
|
+
/** Controller classes with no AccessControl behavior or can() calls. */
|
|
68
|
+
controllers_without_access_control: RbacControllerRef[];
|
|
69
|
+
/** Sites where the permission name was a variable / function call —
|
|
70
|
+
* unresolvable statically. Flagged so the auditor reviews manually. */
|
|
71
|
+
dynamic_creates: Array<{
|
|
72
|
+
file: string;
|
|
73
|
+
line: number;
|
|
74
|
+
snippet: string;
|
|
75
|
+
}>;
|
|
76
|
+
/** Aggregate counts. */
|
|
77
|
+
summary: {
|
|
78
|
+
total_permissions: number;
|
|
79
|
+
total_roles: number;
|
|
80
|
+
total_checks: number;
|
|
81
|
+
orphan_check_count: number;
|
|
82
|
+
unused_definition_count: number;
|
|
83
|
+
unsafe_controller_count: number;
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
export declare function analyzeYiiRbac(repo: string, options?: {
|
|
87
|
+
include_vendor?: boolean;
|
|
88
|
+
}): Promise<YiiRbacAudit>;
|
|
89
|
+
//# sourceMappingURL=yii-rbac-tools.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"yii-rbac-tools.d.ts","sourceRoot":"","sources":["../../src/tools/yii-rbac-tools.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmCG;AAUH,MAAM,WAAW,iBAAiB;IAChC,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,YAAY,GAAG,MAAM,CAAC;IAC5B,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;CACd;AAED,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,MAAM,CAAC;IACb;yEACqE;IACrE,MAAM,EAAE,MAAM,GAAG,gBAAgB,CAAC;IAClC,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;CACd;AAED,MAAM,WAAW,iBAAiB;IAChC,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;IACb,6BAA6B;IAC7B,MAAM,EAAE,cAAc,GAAG,gCAAgC,GAAG,cAAc,CAAC;CAC5E;AAED,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,MAAM,CAAC;IACb,sEAAsE;IACtE,WAAW,EAAE,iBAAiB,EAAE,CAAC;IACjC,2DAA2D;IAC3D,MAAM,EAAE,YAAY,EAAE,CAAC;IACvB,6DAA6D;IAC7D,aAAa,EAAE,MAAM,EAAE,CAAC;IACxB,6CAA6C;IAC7C,kBAAkB,EAAE,MAAM,EAAE,CAAC;IAC7B,wEAAwE;IACxE,kCAAkC,EAAE,iBAAiB,EAAE,CAAC;IACxD;4EACwE;IACxE,eAAe,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IACxE,wBAAwB;IACxB,OAAO,EAAE;QACP,iBAAiB,EAAE,MAAM,CAAC;QAC1B,WAAW,EAAE,MAAM,CAAC;QACpB,YAAY,EAAE,MAAM,CAAC;QACrB,kBAAkB,EAAE,MAAM,CAAC;QAC3B,uBAAuB,EAAE,MAAM,CAAC;QAChC,uBAAuB,EAAE,MAAM,CAAC;KACjC,CAAC;CACH;AAQD,wBAAsB,cAAc,CAClC,IAAI,EAAE,MAAM,EACZ,OAAO,CAAC,EAAE;IAAE,cAAc,CAAC,EAAE,OAAO,CAAA;CAAE,GACrC,OAAO,CAAC,YAAY,CAAC,CAiFvB"}
|
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Yii2 RBAC audit (N3).
|
|
3
|
+
*
|
|
4
|
+
* Builds a permission graph by cross-referencing two surfaces:
|
|
5
|
+
*
|
|
6
|
+
* 1. DEFINITIONS — permissions/roles created in seed migrations or
|
|
7
|
+
* RBAC seeder commands. The Yii2 idiom is:
|
|
8
|
+
*
|
|
9
|
+
* $auth = Yii::$app->authManager;
|
|
10
|
+
* $auth->createPermission('viewUser'); // permission def
|
|
11
|
+
* $auth->createRole('admin'); // role def
|
|
12
|
+
* $auth->add($p); // register
|
|
13
|
+
* $auth->addChild($admin, $p); // attach to role
|
|
14
|
+
*
|
|
15
|
+
* 2. RUNTIME CHECKS — `Yii::$app->user->can('viewUser')` calls in
|
|
16
|
+
* controllers/views, plus `AccessControl` rules in behaviors().
|
|
17
|
+
*
|
|
18
|
+
* Cross-referencing the two surfaces produces:
|
|
19
|
+
*
|
|
20
|
+
* - orphan_checks: permissions checked at runtime but never defined.
|
|
21
|
+
* Indicates dead code OR a typo.
|
|
22
|
+
* - unused_definitions: permissions defined but never checked. Indicates
|
|
23
|
+
* dead seed code OR a missing access guard.
|
|
24
|
+
* - controllers_without_access_control: classes that look like Controllers
|
|
25
|
+
* (web, REST) without any access
|
|
26
|
+
* restriction (no AccessControl in
|
|
27
|
+
* behaviors(), no can() calls).
|
|
28
|
+
* - dynamic_creates: createPermission($var) calls — name is computed at
|
|
29
|
+
* runtime, can't statically resolve. Surfaced
|
|
30
|
+
* separately so callers can flag them as "review
|
|
31
|
+
* manually".
|
|
32
|
+
*
|
|
33
|
+
* The tool deliberately avoids opinions about which finding category is
|
|
34
|
+
* "correct" — the consumer (audit skill, CTO, code reviewer) decides
|
|
35
|
+
* which orphans matter and which were intentional.
|
|
36
|
+
*/
|
|
37
|
+
import { readFile } from "node:fs/promises";
|
|
38
|
+
import { join } from "node:path";
|
|
39
|
+
import { getCodeIndex } from "./index-tools.js";
|
|
40
|
+
// ---------------------------------------------------------------------------
|
|
41
|
+
// Implementation
|
|
42
|
+
// ---------------------------------------------------------------------------
|
|
43
|
+
const VENDOR_RE = /(^|\/)(?:vendor|node_modules|runtime|tests\/_data)(\/|$)/;
|
|
44
|
+
export async function analyzeYiiRbac(repo, options) {
|
|
45
|
+
const index = await getCodeIndex(repo);
|
|
46
|
+
if (!index)
|
|
47
|
+
throw new Error(`Repository "${repo}" not found.`);
|
|
48
|
+
const includeVendor = options?.include_vendor ?? false;
|
|
49
|
+
const phpFiles = index.files.filter((f) => {
|
|
50
|
+
if (!f.path.endsWith(".php"))
|
|
51
|
+
return false;
|
|
52
|
+
if (!includeVendor && VENDOR_RE.test(f.path))
|
|
53
|
+
return false;
|
|
54
|
+
return true;
|
|
55
|
+
});
|
|
56
|
+
const definitions = [];
|
|
57
|
+
const checks = [];
|
|
58
|
+
const dynamicCreates = [];
|
|
59
|
+
// Single-pass file read — every file scanned for all three surfaces.
|
|
60
|
+
await Promise.all(phpFiles.map(async (f) => {
|
|
61
|
+
let content;
|
|
62
|
+
try {
|
|
63
|
+
content = await readFile(join(index.root, f.path), "utf-8");
|
|
64
|
+
}
|
|
65
|
+
catch {
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
collectDefinitions(content, f.path, definitions, dynamicCreates);
|
|
69
|
+
collectChecks(content, f.path, checks);
|
|
70
|
+
}));
|
|
71
|
+
// Build name sets for orphan/unused diff. Definitions and checks are kept
|
|
72
|
+
// separately by kind — only "permission" definitions are matched against
|
|
73
|
+
// can() checks, since checking a role name with can() is a semantic error
|
|
74
|
+
// (Yii2's UserComponent::can resolves both, but the audit categorizes
|
|
75
|
+
// them distinctly).
|
|
76
|
+
const definedPermissionNames = new Set(definitions.filter((d) => d.kind === "permission").map((d) => d.name));
|
|
77
|
+
const definedRoleNames = new Set(definitions.filter((d) => d.kind === "role").map((d) => d.name));
|
|
78
|
+
const allDefinedNames = new Set([
|
|
79
|
+
...definedPermissionNames,
|
|
80
|
+
...definedRoleNames,
|
|
81
|
+
]);
|
|
82
|
+
const checkedNames = new Set(checks.map((c) => c.name));
|
|
83
|
+
const orphanChecks = [...checkedNames]
|
|
84
|
+
.filter((n) => !allDefinedNames.has(n))
|
|
85
|
+
.sort();
|
|
86
|
+
// Unused definitions: only permissions matter — unused roles often exist
|
|
87
|
+
// intentionally (e.g., a `superadmin` role with no can() check, but
|
|
88
|
+
// assigned via authManager->assign()). We surface only permissions to
|
|
89
|
+
// keep noise down; consumers can look at the full definitions[] list
|
|
90
|
+
// when they want role-level analysis.
|
|
91
|
+
const unusedDefinitions = [...definedPermissionNames]
|
|
92
|
+
.filter((n) => !checkedNames.has(n))
|
|
93
|
+
.sort();
|
|
94
|
+
// Controllers without access control. We need to read each Controller
|
|
95
|
+
// class symbol's source for behaviors() body presence + can() calls.
|
|
96
|
+
const controllers = scanControllersWithoutAccessControl(index);
|
|
97
|
+
return {
|
|
98
|
+
repo,
|
|
99
|
+
definitions,
|
|
100
|
+
checks,
|
|
101
|
+
orphan_checks: orphanChecks,
|
|
102
|
+
unused_definitions: unusedDefinitions,
|
|
103
|
+
controllers_without_access_control: controllers,
|
|
104
|
+
dynamic_creates: dynamicCreates,
|
|
105
|
+
summary: {
|
|
106
|
+
total_permissions: definedPermissionNames.size,
|
|
107
|
+
total_roles: definedRoleNames.size,
|
|
108
|
+
total_checks: checkedNames.size,
|
|
109
|
+
orphan_check_count: orphanChecks.length,
|
|
110
|
+
unused_definition_count: unusedDefinitions.length,
|
|
111
|
+
unsafe_controller_count: controllers.length,
|
|
112
|
+
},
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
// ---------------------------------------------------------------------------
|
|
116
|
+
// Surface 1: definitions
|
|
117
|
+
// ---------------------------------------------------------------------------
|
|
118
|
+
function collectDefinitions(content, file, definitions, dynamicCreates) {
|
|
119
|
+
// Pattern A: ->createPermission('name') / ->createRole('name')
|
|
120
|
+
// Match $auth->createPermission(...) and $authManager->createPermission(...)
|
|
121
|
+
// as well as Yii::$app->authManager->createPermission(...)
|
|
122
|
+
const createRe = /->(createPermission|createRole)\s*\(\s*(['"]([^'"]+)['"]|\$\w+|[A-Z_][\w]*::[\w]+)/g;
|
|
123
|
+
let m;
|
|
124
|
+
while ((m = createRe.exec(content)) !== null) {
|
|
125
|
+
const verb = m[1];
|
|
126
|
+
const literal = m[3];
|
|
127
|
+
const line = countLines(content, m.index);
|
|
128
|
+
if (literal !== undefined) {
|
|
129
|
+
definitions.push({
|
|
130
|
+
name: literal,
|
|
131
|
+
kind: verb === "createPermission" ? "permission" : "role",
|
|
132
|
+
file,
|
|
133
|
+
line,
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
else {
|
|
137
|
+
dynamicCreates.push({
|
|
138
|
+
file,
|
|
139
|
+
line,
|
|
140
|
+
snippet: extractLine(content, m.index),
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
// ---------------------------------------------------------------------------
|
|
146
|
+
// Surface 2: runtime checks
|
|
147
|
+
// ---------------------------------------------------------------------------
|
|
148
|
+
function collectChecks(content, file, checks) {
|
|
149
|
+
// Pattern A: Yii::$app->user->can('name') OR \Yii::$app->user->can('name')
|
|
150
|
+
// OR shortcut Yii::$app->can('name') (rare but legal in custom UserComponents)
|
|
151
|
+
const canRe = /(?:Yii|\\Yii)::\$app->user->can\s*\(\s*['"]([^'"]+)['"]/g;
|
|
152
|
+
let m;
|
|
153
|
+
while ((m = canRe.exec(content)) !== null) {
|
|
154
|
+
checks.push({
|
|
155
|
+
name: m[1],
|
|
156
|
+
source: "code",
|
|
157
|
+
file,
|
|
158
|
+
line: countLines(content, m.index),
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
// Pattern B: AccessControl rule with 'permissions' => ['name1', 'name2']
|
|
162
|
+
// We capture the entire array body and then split out individual names.
|
|
163
|
+
// Bounded 1000-char window after the keyword to avoid runaway matches on
|
|
164
|
+
// huge controllers. The rule can contain arbitrary other keys; we just
|
|
165
|
+
// need the permissions list.
|
|
166
|
+
const acRe = /['"]permissions['"]\s*=>\s*\[([^\]]{0,2000})\]/g;
|
|
167
|
+
while ((m = acRe.exec(content)) !== null) {
|
|
168
|
+
const inner = m[1];
|
|
169
|
+
const perRe = /['"]([\w-]+)['"]/g;
|
|
170
|
+
let pm;
|
|
171
|
+
while ((pm = perRe.exec(inner)) !== null) {
|
|
172
|
+
checks.push({
|
|
173
|
+
name: pm[1],
|
|
174
|
+
source: "access-control",
|
|
175
|
+
file,
|
|
176
|
+
line: countLines(content, m.index + pm.index),
|
|
177
|
+
});
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
function scanControllersWithoutAccessControl(index) {
|
|
182
|
+
const out = [];
|
|
183
|
+
// Find class symbols whose name ends in Controller AND that aren't in
|
|
184
|
+
// vendor/. We don't try to walk the inheritance tree to identify "real"
|
|
185
|
+
// Yii2 Controllers — a class named *Controller in the user's own code is
|
|
186
|
+
// 99% a real Yii2 controller, and the false-positive cost is one extra
|
|
187
|
+
// line in the report.
|
|
188
|
+
const controllers = index.symbols.filter((s) => {
|
|
189
|
+
if (s.kind !== "class")
|
|
190
|
+
return false;
|
|
191
|
+
if (!s.name.endsWith("Controller"))
|
|
192
|
+
return false;
|
|
193
|
+
if (!s.file.endsWith(".php"))
|
|
194
|
+
return false;
|
|
195
|
+
if (VENDOR_RE.test(s.file))
|
|
196
|
+
return false;
|
|
197
|
+
if (!s.source)
|
|
198
|
+
return false;
|
|
199
|
+
return true;
|
|
200
|
+
});
|
|
201
|
+
for (const ctrl of controllers) {
|
|
202
|
+
const src = ctrl.source;
|
|
203
|
+
const hasBehaviors = /\bfunction\s+behaviors\s*\(/.test(src);
|
|
204
|
+
const hasAccessControl = /AccessControl::class|['"]access['"]\s*=>\s*\[\s*['"]class['"]\s*=>\s*[^\]]*AccessControl/.test(src);
|
|
205
|
+
const hasCan = /->can\s*\(/.test(src);
|
|
206
|
+
if (!hasBehaviors) {
|
|
207
|
+
out.push({ class: ctrl.name, file: ctrl.file, reason: "no-behaviors" });
|
|
208
|
+
continue;
|
|
209
|
+
}
|
|
210
|
+
if (!hasAccessControl && !hasCan) {
|
|
211
|
+
out.push({
|
|
212
|
+
class: ctrl.name,
|
|
213
|
+
file: ctrl.file,
|
|
214
|
+
reason: "no-access-control-in-behaviors",
|
|
215
|
+
});
|
|
216
|
+
continue;
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
return out;
|
|
220
|
+
}
|
|
221
|
+
// ---------------------------------------------------------------------------
|
|
222
|
+
// Helpers
|
|
223
|
+
// ---------------------------------------------------------------------------
|
|
224
|
+
function countLines(source, idx) {
|
|
225
|
+
let line = 1;
|
|
226
|
+
for (let i = 0; i < idx; i++) {
|
|
227
|
+
if (source.charCodeAt(i) === 10)
|
|
228
|
+
line++;
|
|
229
|
+
}
|
|
230
|
+
return line;
|
|
231
|
+
}
|
|
232
|
+
function extractLine(source, idx) {
|
|
233
|
+
const start = source.lastIndexOf("\n", idx) + 1;
|
|
234
|
+
const end = source.indexOf("\n", idx);
|
|
235
|
+
const line = source.slice(start, end === -1 ? source.length : end);
|
|
236
|
+
return line.trim().slice(0, 200);
|
|
237
|
+
}
|
|
238
|
+
//# sourceMappingURL=yii-rbac-tools.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"yii-rbac-tools.js","sourceRoot":"","sources":["../../src/tools/yii-rbac-tools.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Yii3 attribute conversion candidates (M2).
|
|
3
|
+
*
|
|
4
|
+
* Yii3 is natively PHP 8 attribute-based. The mechanical mapping from
|
|
5
|
+
* Yii2 array-config idioms to Yii3 attributes is well-defined, but
|
|
6
|
+
* applying it across thousands of files is the bottleneck for any
|
|
7
|
+
* Yii2→Yii3 migration. This tool surfaces every site that's a candidate
|
|
8
|
+
* for the conversion + a proposed attribute form, capped by sample limit
|
|
9
|
+
* so the consumer can triage at their own pace.
|
|
10
|
+
*
|
|
11
|
+
* Conversions covered:
|
|
12
|
+
*
|
|
13
|
+
* behaviors-to-attributes
|
|
14
|
+
* behaviors() returning an array with TimestampBehavior /
|
|
15
|
+
* BlameableBehavior / SluggableBehavior / etc → #[Behavior(class)] on
|
|
16
|
+
* the class. We surface the entire behaviors() body in `current_form`
|
|
17
|
+
* so the auditor sees the array-shape that needs conversion.
|
|
18
|
+
*
|
|
19
|
+
* rules-to-attributes
|
|
20
|
+
* rules() returning array of [['field'], 'validator'] tuples →
|
|
21
|
+
* #[Required], #[Email], #[StringLength(min: 1)] on the property.
|
|
22
|
+
* Each tuple becomes one or more proposed attributes.
|
|
23
|
+
*
|
|
24
|
+
* urlmanager-rule-to-route
|
|
25
|
+
* 'GET api/users/<id>' => 'user/view' (in urlManager rules) →
|
|
26
|
+
* #[Route(method: 'GET', path: '/api/users/{id}')] on the controller
|
|
27
|
+
* action. We resolve the controller/action target from the rule's
|
|
28
|
+
* right side. {param} placeholders are emitted but type constraints
|
|
29
|
+
* are dropped (Yii2 supports inline regex like <id:\\d+>; Yii3 uses
|
|
30
|
+
* route attributes like #[Route('/{id<int>}')]).
|
|
31
|
+
*
|
|
32
|
+
* Output shape:
|
|
33
|
+
* - candidates[] — flat list of all conversions
|
|
34
|
+
* - by_rule[] — grouped + sample-capped, sorted by count desc
|
|
35
|
+
* - summary — total + per-rule counts
|
|
36
|
+
*
|
|
37
|
+
* Like M1, this tool never auto-applies. Each candidate ships:
|
|
38
|
+
* current_form (the source as written today)
|
|
39
|
+
* suggested_replacement (the attribute equivalent)
|
|
40
|
+
* confidence ("high" | "medium" | "low")
|
|
41
|
+
* blockers[] (string reasons the conversion may be unsafe —
|
|
42
|
+
* e.g. "rule references a Closure validator that
|
|
43
|
+
* can't be lifted to an attribute")
|
|
44
|
+
*/
|
|
45
|
+
export type Yii3AttributeRuleId = "behaviors-to-attributes" | "rules-to-attributes" | "urlmanager-rule-to-route";
|
|
46
|
+
export interface Yii3AttributeCandidate {
|
|
47
|
+
rule_id: Yii3AttributeRuleId;
|
|
48
|
+
file: string;
|
|
49
|
+
line: number;
|
|
50
|
+
current_form: string;
|
|
51
|
+
suggested_replacement: string;
|
|
52
|
+
confidence: "high" | "medium" | "low";
|
|
53
|
+
blockers: string[];
|
|
54
|
+
}
|
|
55
|
+
export interface Yii3AttributeCandidates {
|
|
56
|
+
repo: string;
|
|
57
|
+
scanned_files: number;
|
|
58
|
+
total_candidates: number;
|
|
59
|
+
by_rule: Array<{
|
|
60
|
+
rule_id: Yii3AttributeRuleId;
|
|
61
|
+
count: number;
|
|
62
|
+
samples: Yii3AttributeCandidate[];
|
|
63
|
+
}>;
|
|
64
|
+
candidates: Yii3AttributeCandidate[];
|
|
65
|
+
}
|
|
66
|
+
export declare function findYii3AttributeCandidates(repo: string, options?: {
|
|
67
|
+
file_pattern?: string;
|
|
68
|
+
rules?: Yii3AttributeRuleId[];
|
|
69
|
+
max_samples_per_rule?: number;
|
|
70
|
+
include_vendor?: boolean;
|
|
71
|
+
}): Promise<Yii3AttributeCandidates>;
|
|
72
|
+
//# sourceMappingURL=yii3-attribute-candidates-tools.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"yii3-attribute-candidates-tools.d.ts","sourceRoot":"","sources":["../../src/tools/yii3-attribute-candidates-tools.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2CG;AAUH,MAAM,MAAM,mBAAmB,GAC3B,yBAAyB,GACzB,qBAAqB,GACrB,0BAA0B,CAAC;AAE/B,MAAM,WAAW,sBAAsB;IACrC,OAAO,EAAE,mBAAmB,CAAC;IAC7B,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,YAAY,EAAE,MAAM,CAAC;IACrB,qBAAqB,EAAE,MAAM,CAAC;IAC9B,UAAU,EAAE,MAAM,GAAG,QAAQ,GAAG,KAAK,CAAC;IACtC,QAAQ,EAAE,MAAM,EAAE,CAAC;CACpB;AAED,MAAM,WAAW,uBAAuB;IACtC,IAAI,EAAE,MAAM,CAAC;IACb,aAAa,EAAE,MAAM,CAAC;IACtB,gBAAgB,EAAE,MAAM,CAAC;IACzB,OAAO,EAAE,KAAK,CAAC;QACb,OAAO,EAAE,mBAAmB,CAAC;QAC7B,KAAK,EAAE,MAAM,CAAC;QACd,OAAO,EAAE,sBAAsB,EAAE,CAAC;KACnC,CAAC,CAAC;IACH,UAAU,EAAE,sBAAsB,EAAE,CAAC;CACtC;AAwBD,wBAAsB,2BAA2B,CAC/C,IAAI,EAAE,MAAM,EACZ,OAAO,CAAC,EAAE;IACR,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,KAAK,CAAC,EAAE,mBAAmB,EAAE,CAAC;IAC9B,oBAAoB,CAAC,EAAE,MAAM,CAAC;IAC9B,cAAc,CAAC,EAAE,OAAO,CAAC;CAC1B,GACA,OAAO,CAAC,uBAAuB,CAAC,CAmElC"}
|