@facilis.dev/analyze 0.0.0-stage → 0.1.1
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 +18 -2
- package/dist/analyze.d.ts +17 -0
- package/dist/analyze.js +99 -0
- package/dist/ast/node.d.ts +29 -0
- package/dist/ast/node.js +85 -0
- package/dist/ast/walk.d.ts +8 -0
- package/dist/ast/walk.js +40 -0
- package/dist/diagnostics.d.ts +5 -0
- package/dist/diagnostics.js +12 -0
- package/dist/fs.d.ts +13 -0
- package/dist/fs.js +51 -0
- package/dist/graph-edges.d.ts +3 -0
- package/dist/graph-edges.js +148 -0
- package/dist/graph-ids.d.ts +6 -0
- package/dist/graph-ids.js +22 -0
- package/dist/graph.d.ts +15 -0
- package/dist/graph.js +162 -0
- package/dist/index.d.ts +24 -0
- package/dist/index.js +17 -0
- package/dist/json.d.ts +8 -0
- package/dist/json.js +31 -0
- package/dist/module-graph.d.ts +33 -0
- package/dist/module-graph.js +86 -0
- package/dist/module-info.d.ts +27 -0
- package/dist/module-info.js +143 -0
- package/dist/node.d.ts +16 -0
- package/dist/node.js +41 -0
- package/dist/parser.d.ts +16 -0
- package/dist/parser.js +1 -0
- package/dist/passes/clients.d.ts +22 -0
- package/dist/passes/clients.js +167 -0
- package/dist/passes/config.d.ts +16 -0
- package/dist/passes/config.js +229 -0
- package/dist/passes/file-facts.d.ts +13 -0
- package/dist/passes/file-facts.js +130 -0
- package/dist/passes/i18n.d.ts +14 -0
- package/dist/passes/i18n.js +55 -0
- package/dist/passes/job-relations-diagnostics.d.ts +9 -0
- package/dist/passes/job-relations-diagnostics.js +38 -0
- package/dist/passes/job-relations.d.ts +18 -0
- package/dist/passes/job-relations.js +52 -0
- package/dist/passes/jobs.d.ts +20 -0
- package/dist/passes/jobs.js +133 -0
- package/dist/passes/jsx-text.d.ts +28 -0
- package/dist/passes/jsx-text.js +121 -0
- package/dist/passes/jsx.d.ts +24 -0
- package/dist/passes/jsx.js +135 -0
- package/dist/passes/path-expression.d.ts +26 -0
- package/dist/passes/path-expression.js +265 -0
- package/dist/passes/route-config.d.ts +24 -0
- package/dist/passes/route-config.js +152 -0
- package/dist/passes/route-description.d.ts +13 -0
- package/dist/passes/route-description.js +23 -0
- package/dist/passes/route-elevation.d.ts +18 -0
- package/dist/passes/route-elevation.js +136 -0
- package/dist/passes/route-handler.d.ts +27 -0
- package/dist/passes/route-handler.js +87 -0
- package/dist/passes/route-location.d.ts +16 -0
- package/dist/passes/route-location.js +58 -0
- package/dist/passes/route-relation-resolve.d.ts +51 -0
- package/dist/passes/route-relation-resolve.js +172 -0
- package/dist/passes/route-relations-diagnostics.d.ts +15 -0
- package/dist/passes/route-relations-diagnostics.js +79 -0
- package/dist/passes/route-relations-follow.d.ts +16 -0
- package/dist/passes/route-relations-follow.js +116 -0
- package/dist/passes/route-relations-usage.d.ts +50 -0
- package/dist/passes/route-relations-usage.js +44 -0
- package/dist/passes/route-relations-walk.d.ts +25 -0
- package/dist/passes/route-relations-walk.js +199 -0
- package/dist/passes/route-relations.d.ts +17 -0
- package/dist/passes/route-relations.js +43 -0
- package/dist/passes/route-table.d.ts +21 -0
- package/dist/passes/route-table.js +104 -0
- package/dist/passes/route-tables.d.ts +10 -0
- package/dist/passes/route-tables.js +42 -0
- package/dist/passes/routes.d.ts +18 -0
- package/dist/passes/routes.js +152 -0
- package/dist/passes/schema-column-types.d.ts +9 -0
- package/dist/passes/schema-column-types.js +55 -0
- package/dist/passes/schema-columns.d.ts +35 -0
- package/dist/passes/schema-columns.js +197 -0
- package/dist/passes/schema-dsl.d.ts +24 -0
- package/dist/passes/schema-dsl.js +117 -0
- package/dist/passes/schema-file-links-ddl.d.ts +18 -0
- package/dist/passes/schema-file-links-ddl.js +106 -0
- package/dist/passes/schema-file-links.d.ts +19 -0
- package/dist/passes/schema-file-links.js +144 -0
- package/dist/passes/schema-foreign-keys.d.ts +21 -0
- package/dist/passes/schema-foreign-keys.js +80 -0
- package/dist/passes/schema-policies.d.ts +15 -0
- package/dist/passes/schema-policies.js +201 -0
- package/dist/passes/schema-re-exports.d.ts +5 -0
- package/dist/passes/schema-re-exports.js +77 -0
- package/dist/passes/schema-relations.d.ts +8 -0
- package/dist/passes/schema-relations.js +182 -0
- package/dist/passes/schema-scope.d.ts +7 -0
- package/dist/passes/schema-scope.js +20 -0
- package/dist/passes/schema-tables.d.ts +13 -0
- package/dist/passes/schema-tables.js +149 -0
- package/dist/passes/schema.d.ts +9 -0
- package/dist/passes/schema.js +55 -0
- package/dist/passes/screen-components.d.ts +22 -0
- package/dist/passes/screen-components.js +143 -0
- package/dist/passes/screen-intrinsics.d.ts +18 -0
- package/dist/passes/screen-intrinsics.js +21 -0
- package/dist/passes/screen-preview.d.ts +29 -0
- package/dist/passes/screen-preview.js +174 -0
- package/dist/passes/screens.d.ts +12 -0
- package/dist/passes/screens.js +278 -0
- package/dist/passes/static-read.d.ts +47 -0
- package/dist/passes/static-read.js +151 -0
- package/dist/passes/views.d.ts +20 -0
- package/dist/passes/views.js +101 -0
- package/dist/passes/workflow-tables.d.ts +18 -0
- package/dist/passes/workflow-tables.js +71 -0
- package/dist/passes/workflows.d.ts +8 -0
- package/dist/passes/workflows.js +160 -0
- package/dist/path-resolver.d.ts +20 -0
- package/dist/path-resolver.js +134 -0
- package/dist/revision.d.ts +2 -0
- package/dist/revision.js +23 -0
- package/dist/source.d.ts +4 -0
- package/dist/source.js +13 -0
- package/dist/static-eval.d.ts +36 -0
- package/dist/static-eval.js +215 -0
- package/dist/types-graph.d.ts +144 -0
- package/dist/types-graph.js +4 -0
- package/dist/types-jobs.d.ts +28 -0
- package/dist/types-jobs.js +14 -0
- package/dist/types-relations.d.ts +49 -0
- package/dist/types-relations.js +11 -0
- package/dist/types-workflows.d.ts +36 -0
- package/dist/types-workflows.js +3 -0
- package/dist/types.d.ts +226 -0
- package/dist/types.js +1 -0
- package/package.json +49 -4
- package/src/analyze.ts +119 -0
- package/src/ast/node.ts +102 -0
- package/src/ast/walk.ts +37 -0
- package/src/diagnostics.ts +22 -0
- package/src/fs.ts +59 -0
- package/src/graph-edges.ts +167 -0
- package/src/graph-ids.ts +31 -0
- package/src/graph.ts +194 -0
- package/src/index.ts +26 -0
- package/src/json.ts +34 -0
- package/src/module-graph.ts +117 -0
- package/src/module-info.ts +162 -0
- package/src/node.ts +54 -0
- package/src/parser.ts +19 -0
- package/src/passes/clients.ts +233 -0
- package/src/passes/config.ts +278 -0
- package/src/passes/file-facts.ts +142 -0
- package/src/passes/i18n.ts +66 -0
- package/src/passes/job-relations-diagnostics.ts +60 -0
- package/src/passes/job-relations.ts +76 -0
- package/src/passes/jobs.ts +181 -0
- package/src/passes/jsx-text.ts +126 -0
- package/src/passes/jsx.ts +142 -0
- package/src/passes/path-expression.ts +297 -0
- package/src/passes/route-config.ts +188 -0
- package/src/passes/route-description.ts +33 -0
- package/src/passes/route-elevation.ts +161 -0
- package/src/passes/route-handler.ts +105 -0
- package/src/passes/route-location.ts +62 -0
- package/src/passes/route-relation-resolve.ts +228 -0
- package/src/passes/route-relations-diagnostics.ts +115 -0
- package/src/passes/route-relations-follow.ts +146 -0
- package/src/passes/route-relations-usage.ts +98 -0
- package/src/passes/route-relations-walk.ts +255 -0
- package/src/passes/route-relations.ts +62 -0
- package/src/passes/route-table.ts +123 -0
- package/src/passes/route-tables.ts +50 -0
- package/src/passes/routes.ts +188 -0
- package/src/passes/schema-column-types.ts +62 -0
- package/src/passes/schema-columns.ts +287 -0
- package/src/passes/schema-dsl.ts +136 -0
- package/src/passes/schema-file-links-ddl.ts +125 -0
- package/src/passes/schema-file-links.ts +213 -0
- package/src/passes/schema-foreign-keys.ts +121 -0
- package/src/passes/schema-policies.ts +270 -0
- package/src/passes/schema-re-exports.ts +95 -0
- package/src/passes/schema-relations.ts +239 -0
- package/src/passes/schema-scope.ts +29 -0
- package/src/passes/schema-tables.ts +216 -0
- package/src/passes/schema.ts +67 -0
- package/src/passes/screen-components.ts +162 -0
- package/src/passes/screen-intrinsics.ts +29 -0
- package/src/passes/screen-preview.ts +295 -0
- package/src/passes/screens.ts +291 -0
- package/src/passes/static-read.ts +196 -0
- package/src/passes/views.ts +125 -0
- package/src/passes/workflow-tables.ts +87 -0
- package/src/passes/workflows.ts +213 -0
- package/src/path-resolver.ts +146 -0
- package/src/revision.ts +25 -0
- package/src/source.ts +15 -0
- package/src/static-eval.ts +242 -0
- package/src/types-graph.ts +176 -0
- package/src/types-jobs.ts +40 -0
- package/src/types-relations.ts +64 -0
- package/src/types-workflows.ts +42 -0
- package/src/types.ts +299 -0
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
// Route relation-usage and view facts (D-157), split from types.ts so the base fact model
|
|
2
|
+
// stays under the 300-line source limit. A fact that cannot be determined is explicit:
|
|
3
|
+
// an empty usage is "nothing observed", never "nothing to check".
|
|
4
|
+
/** Usage of a route the pass cannot scan (invalid wrapper): explicitly empty, not unknown. */
|
|
5
|
+
export const EMPTY_ROUTE_RELATIONS = {
|
|
6
|
+
used: [],
|
|
7
|
+
unresolved: [],
|
|
8
|
+
rawSql: [],
|
|
9
|
+
opaque: false,
|
|
10
|
+
elevations: [],
|
|
11
|
+
};
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import type { SourceRef, UnknownFact } from "./types.js";
|
|
2
|
+
/** One step of a `defineWorkflow` chain, as the static subset reads it. */
|
|
3
|
+
export interface WorkflowStepFact {
|
|
4
|
+
readonly index: number;
|
|
5
|
+
readonly kind: "job" | "approval";
|
|
6
|
+
/** Job name for a job step, approval title key for an approval step. */
|
|
7
|
+
readonly name: string | null;
|
|
8
|
+
/** Approval assignment permission, when the definition declares one. */
|
|
9
|
+
readonly permission: string | null;
|
|
10
|
+
/** Forward step index declared by `onReject.step`, when present. */
|
|
11
|
+
readonly onRejectStep: number | null;
|
|
12
|
+
readonly source: SourceRef;
|
|
13
|
+
}
|
|
14
|
+
/** One `server/workflows/**` definition. */
|
|
15
|
+
export interface WorkflowFact {
|
|
16
|
+
readonly name: string;
|
|
17
|
+
readonly sourceFile: string;
|
|
18
|
+
readonly source: SourceRef;
|
|
19
|
+
readonly steps: readonly WorkflowStepFact[];
|
|
20
|
+
readonly unknown: readonly UnknownFact[];
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Table surface of a workflow, derived from the jobs its steps reference (never declared):
|
|
24
|
+
* the sorted union of the referenced jobs' own `dbTables` (tables and views alike).
|
|
25
|
+
*/
|
|
26
|
+
export interface WorkflowTablesFact {
|
|
27
|
+
readonly name: string;
|
|
28
|
+
readonly sourceFile: string;
|
|
29
|
+
readonly source: SourceRef;
|
|
30
|
+
/** Sorted unique union of the referenced jobs' declared dbTables. */
|
|
31
|
+
readonly tables: readonly string[];
|
|
32
|
+
/** Job-step names with no definition under `server/jobs/**`; [] when all resolve. */
|
|
33
|
+
readonly unknownJobs: readonly string[];
|
|
34
|
+
/** True when a referenced job's dbTables is unreadable, so the union is incomplete. */
|
|
35
|
+
readonly unknown: boolean;
|
|
36
|
+
}
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
import type { JobFact, JobRelationsFact } from "./types-jobs.js";
|
|
2
|
+
import type { RouteRelationsFact, ViewFact } from "./types-relations.js";
|
|
3
|
+
import type { ProjectGraph } from "./types-graph.js";
|
|
4
|
+
import type { WorkflowFact, WorkflowTablesFact } from "./types-workflows.js";
|
|
5
|
+
/**
|
|
6
|
+
* Fact model of the AST analyzer (docs/findings/ast-dsl-contract.md).
|
|
7
|
+
*
|
|
8
|
+
* Facts are parser-independent and always carry their source. A fact that cannot be
|
|
9
|
+
* determined is represented explicitly (null or an unknown entry), never as an empty value:
|
|
10
|
+
* in particular "dbTables unknown" (null) is not "no DB tables" ([]).
|
|
11
|
+
*/
|
|
12
|
+
export interface SourceRef {
|
|
13
|
+
readonly file: string;
|
|
14
|
+
readonly line: number;
|
|
15
|
+
}
|
|
16
|
+
export interface UnknownFact {
|
|
17
|
+
readonly code: string;
|
|
18
|
+
readonly reason: string;
|
|
19
|
+
readonly source: SourceRef;
|
|
20
|
+
}
|
|
21
|
+
export type Severity = "error" | "warning" | "info";
|
|
22
|
+
export interface Diagnostic {
|
|
23
|
+
readonly code: string;
|
|
24
|
+
readonly severity: Severity;
|
|
25
|
+
readonly message: string;
|
|
26
|
+
readonly source: SourceRef | null;
|
|
27
|
+
}
|
|
28
|
+
export interface LocaleCatalogs {
|
|
29
|
+
readonly [locale: string]: Readonly<Record<string, string>>;
|
|
30
|
+
}
|
|
31
|
+
export interface ConfigFacts {
|
|
32
|
+
readonly parsed: boolean;
|
|
33
|
+
readonly source: SourceRef | null;
|
|
34
|
+
readonly permissions: readonly string[];
|
|
35
|
+
readonly roles: Readonly<Record<string, readonly string[]>>;
|
|
36
|
+
readonly defaultRole: string | null;
|
|
37
|
+
readonly defaultLocale: string | null;
|
|
38
|
+
readonly locales: readonly string[];
|
|
39
|
+
readonly migrationsDir: string;
|
|
40
|
+
readonly tenantColumns: readonly string[];
|
|
41
|
+
readonly ownerColumns: readonly string[];
|
|
42
|
+
readonly unknown: readonly UnknownFact[];
|
|
43
|
+
}
|
|
44
|
+
export type ColumnDefaultKind = "literal" | "now" | "random" | "unknown";
|
|
45
|
+
export interface ColumnDefaultFact {
|
|
46
|
+
readonly kind: ColumnDefaultKind;
|
|
47
|
+
readonly value: string | null;
|
|
48
|
+
}
|
|
49
|
+
export interface ForeignKeyFact {
|
|
50
|
+
readonly columns: readonly string[];
|
|
51
|
+
readonly targetTable: string;
|
|
52
|
+
readonly targetColumns: readonly string[];
|
|
53
|
+
readonly onDelete: string | null;
|
|
54
|
+
readonly onUpdate: string | null;
|
|
55
|
+
readonly source: SourceRef;
|
|
56
|
+
}
|
|
57
|
+
export interface ColumnFact {
|
|
58
|
+
/** Drizzle property key in the table object (the dbColumns domain). */
|
|
59
|
+
readonly property: string;
|
|
60
|
+
/** SQL column name (first literal argument of the builder, or the property key). */
|
|
61
|
+
readonly name: string;
|
|
62
|
+
readonly builder: string;
|
|
63
|
+
/** Display type derived from the builder and its arguments. */
|
|
64
|
+
readonly type: string;
|
|
65
|
+
readonly notNull: boolean;
|
|
66
|
+
readonly primaryKey: boolean;
|
|
67
|
+
readonly unique: boolean;
|
|
68
|
+
readonly default: ColumnDefaultFact | null;
|
|
69
|
+
readonly source: SourceRef;
|
|
70
|
+
readonly unknown: readonly UnknownFact[];
|
|
71
|
+
}
|
|
72
|
+
export type PolicyCommand = "SELECT" | "INSERT" | "UPDATE" | "DELETE" | "ALL";
|
|
73
|
+
export interface PolicyFact {
|
|
74
|
+
readonly name: string;
|
|
75
|
+
readonly command: PolicyCommand;
|
|
76
|
+
readonly restrictive: boolean;
|
|
77
|
+
readonly source: SourceRef;
|
|
78
|
+
readonly unknown: readonly UnknownFact[];
|
|
79
|
+
}
|
|
80
|
+
export interface ScopeFact {
|
|
81
|
+
readonly column: string;
|
|
82
|
+
readonly kind: "tenant" | "owner";
|
|
83
|
+
}
|
|
84
|
+
export interface TableFact {
|
|
85
|
+
readonly name: string;
|
|
86
|
+
/** TypeScript binding holding the table, when declared as a const. */
|
|
87
|
+
readonly binding: string | null;
|
|
88
|
+
readonly columns: readonly ColumnFact[];
|
|
89
|
+
readonly primaryKey: readonly string[];
|
|
90
|
+
readonly foreignKeys: readonly ForeignKeyFact[];
|
|
91
|
+
readonly policies: readonly PolicyFact[];
|
|
92
|
+
readonly rlsEnabled: boolean;
|
|
93
|
+
readonly scope: ScopeFact | null;
|
|
94
|
+
readonly source: SourceRef;
|
|
95
|
+
readonly unknown: readonly UnknownFact[];
|
|
96
|
+
}
|
|
97
|
+
export interface RelationFact {
|
|
98
|
+
readonly from: string;
|
|
99
|
+
readonly to: string;
|
|
100
|
+
readonly cardinality: "one" | "many";
|
|
101
|
+
readonly fields: readonly string[];
|
|
102
|
+
readonly references: readonly string[];
|
|
103
|
+
readonly source: SourceRef;
|
|
104
|
+
readonly unknown: readonly UnknownFact[];
|
|
105
|
+
}
|
|
106
|
+
export type HttpMethod = "GET" | "POST" | "PUT" | "PATCH" | "DELETE" | "HEAD" | "OPTIONS" | "TRACE" | "CONNECT" | "ALL";
|
|
107
|
+
export type WrapperState = "valid" | "invalid" | "unknown";
|
|
108
|
+
export interface RouteInputFact {
|
|
109
|
+
readonly body: boolean;
|
|
110
|
+
/** A multipart/form-data schema (D-053); mutually exclusive with `body`. */
|
|
111
|
+
readonly form: boolean;
|
|
112
|
+
readonly query: boolean;
|
|
113
|
+
readonly params: boolean;
|
|
114
|
+
}
|
|
115
|
+
export type ElevationKind = "sudo" | "system";
|
|
116
|
+
/** One audited elevation call in a route handler (D-099): `ctx.sudo` / `ctx.system`. */
|
|
117
|
+
export interface ElevationFact {
|
|
118
|
+
readonly kind: ElevationKind;
|
|
119
|
+
/** Tables passed to the elevation; [] when the array cannot be read statically. */
|
|
120
|
+
readonly tables: readonly string[];
|
|
121
|
+
/** The audit operation label, when statically readable. */
|
|
122
|
+
readonly operation: string | null;
|
|
123
|
+
readonly source: SourceRef;
|
|
124
|
+
readonly unknown: readonly UnknownFact[];
|
|
125
|
+
}
|
|
126
|
+
export interface RouteFact {
|
|
127
|
+
readonly method: HttpMethod;
|
|
128
|
+
readonly path: string;
|
|
129
|
+
readonly sourceFile: string;
|
|
130
|
+
readonly source: SourceRef;
|
|
131
|
+
readonly wrapper: WrapperState;
|
|
132
|
+
/** null = unknown; [] = explicitly public. */
|
|
133
|
+
readonly permission: readonly string[] | null;
|
|
134
|
+
/**
|
|
135
|
+
* True when the wrapper declares the route callable from another origin; false when absent;
|
|
136
|
+
* null when the declaration cannot be read statically (a cross-origin route is never assumed).
|
|
137
|
+
*/
|
|
138
|
+
readonly crossSite: boolean | null;
|
|
139
|
+
/** null = unknown; [] = explicitly no DB. */
|
|
140
|
+
readonly dbTables: readonly string[] | null;
|
|
141
|
+
/** null = unknown; [] = explicitly no columns. */
|
|
142
|
+
readonly dbColumns: readonly string[] | null;
|
|
143
|
+
/** rawSql: true allows `db.execute` (D-157); false when absent; null when unreadable. */
|
|
144
|
+
readonly rawSql: boolean | null;
|
|
145
|
+
/** Static db usage of the handler, project-local helper calls followed (D-157). */
|
|
146
|
+
readonly relations: RouteRelationsFact;
|
|
147
|
+
readonly description: string | null;
|
|
148
|
+
readonly input: RouteInputFact;
|
|
149
|
+
/** True when the handler body reads the request body (SPEC §9.3 rule 8). */
|
|
150
|
+
readonly handlerBodyRead: boolean;
|
|
151
|
+
/** True when the handler body reads the multipart form (SPEC §9.3 rule 8, D-053). */
|
|
152
|
+
readonly handlerFormRead: boolean;
|
|
153
|
+
/** Audited elevation calls in the handler, in source order. */
|
|
154
|
+
readonly elevations: readonly ElevationFact[];
|
|
155
|
+
readonly unknown: readonly UnknownFact[];
|
|
156
|
+
}
|
|
157
|
+
export type ImportKind = "named" | "default" | "namespace" | "side-effect" | "export-from" | "dynamic" | "require";
|
|
158
|
+
/** One module reference of a source file: static import, re-export, dynamic import or require. */
|
|
159
|
+
export interface ImportFact {
|
|
160
|
+
readonly specifier: string;
|
|
161
|
+
readonly kind: ImportKind;
|
|
162
|
+
/** Local binding the reference introduces; null for side-effect/re-export/dynamic forms. */
|
|
163
|
+
readonly local: string | null;
|
|
164
|
+
/** Imported name for named imports ("default" for a default import); null otherwise. */
|
|
165
|
+
readonly imported: string | null;
|
|
166
|
+
readonly source: SourceRef;
|
|
167
|
+
}
|
|
168
|
+
/** One literal `t("key")` call; interpolated keys are skipped because they are not static. */
|
|
169
|
+
export interface TranslationCallFact {
|
|
170
|
+
readonly key: string;
|
|
171
|
+
readonly source: SourceRef;
|
|
172
|
+
}
|
|
173
|
+
/** One call to a bare identifier (`drizzle(...)`, `t(...)`): enough for the security rules. */
|
|
174
|
+
export interface CallFact {
|
|
175
|
+
readonly name: string;
|
|
176
|
+
readonly source: SourceRef;
|
|
177
|
+
}
|
|
178
|
+
export interface FileFacts {
|
|
179
|
+
/** Project-relative path of the parsed source file. */
|
|
180
|
+
readonly path: string;
|
|
181
|
+
readonly imports: readonly ImportFact[];
|
|
182
|
+
readonly translationCalls: readonly TranslationCallFact[];
|
|
183
|
+
readonly calls: readonly CallFact[];
|
|
184
|
+
}
|
|
185
|
+
export type PageKind = "page" | "component" | "lib" | "other";
|
|
186
|
+
export type PathKind = "literal" | "concat" | "template" | "builder" | "dynamic" | "unknown";
|
|
187
|
+
export interface RouteCallFact {
|
|
188
|
+
readonly expression: string;
|
|
189
|
+
readonly kind: PathKind;
|
|
190
|
+
readonly staticPrefix: string | null;
|
|
191
|
+
readonly matchedRouteIds: readonly string[];
|
|
192
|
+
readonly source: SourceRef;
|
|
193
|
+
readonly unknown: readonly UnknownFact[];
|
|
194
|
+
}
|
|
195
|
+
export interface PageFact {
|
|
196
|
+
readonly path: string;
|
|
197
|
+
readonly kind: PageKind;
|
|
198
|
+
readonly calls: readonly RouteCallFact[];
|
|
199
|
+
readonly source: SourceRef;
|
|
200
|
+
readonly unknown: readonly UnknownFact[];
|
|
201
|
+
}
|
|
202
|
+
export type { GraphEdge, GraphEdgeKind, GraphNode, GraphNodeData, GraphNodeKind, PageLink, PageNodeData, PagePreview, PagePreviewNode, ProjectGraph, RouteNodeData, ScreenFacts, TableNodeData, ViewNodeData, WorkflowNodeData, WorkflowStepNodeData, } from "./types-graph.js";
|
|
203
|
+
export type { RouteElevationUsage, RouteRelationUse, RouteRelationsFact, ViewFact, } from "./types-relations.js";
|
|
204
|
+
export type { WorkflowFact, WorkflowStepFact, WorkflowTablesFact } from "./types-workflows.js";
|
|
205
|
+
export interface Analysis {
|
|
206
|
+
readonly revision: string;
|
|
207
|
+
readonly files: readonly string[];
|
|
208
|
+
readonly config: ConfigFacts;
|
|
209
|
+
readonly routes: readonly RouteFact[];
|
|
210
|
+
readonly tables: readonly TableFact[];
|
|
211
|
+
readonly relations: readonly RelationFact[];
|
|
212
|
+
/** Declarative views (D-157): facts, and graph nodes since D-160. */
|
|
213
|
+
readonly views: readonly ViewFact[];
|
|
214
|
+
readonly pages: readonly PageFact[];
|
|
215
|
+
readonly workflows: readonly WorkflowFact[];
|
|
216
|
+
/** Job definitions, with D-157 applied to the db client their `run` body sees. */
|
|
217
|
+
readonly jobs: readonly JobFact[];
|
|
218
|
+
readonly jobRelations: Readonly<Record<string, JobRelationsFact>>;
|
|
219
|
+
/** Each workflow's table surface, derived from the jobs its steps reference. */
|
|
220
|
+
readonly workflowTables: readonly WorkflowTablesFact[];
|
|
221
|
+
/** Module facts for every parsed source file, in path order. */
|
|
222
|
+
readonly fileFacts: readonly FileFacts[];
|
|
223
|
+
readonly locales: LocaleCatalogs;
|
|
224
|
+
readonly diagnostics: readonly Diagnostic[];
|
|
225
|
+
readonly graph: ProjectGraph;
|
|
226
|
+
}
|
package/dist/types.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/package.json
CHANGED
|
@@ -1,6 +1,51 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@facilis.dev/analyze",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"description": "
|
|
6
|
-
|
|
3
|
+
"version": "0.1.1",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"description": "Facilis AST analyzer: static facts for the project DSL (routes, schema, relations, pages) with an injected parser.",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"exports": {
|
|
8
|
+
".": "./dist/index.js",
|
|
9
|
+
"./fs": "./dist/fs.js",
|
|
10
|
+
"./node": "./dist/node.js",
|
|
11
|
+
"./package.json": "./package.json"
|
|
12
|
+
},
|
|
13
|
+
"files": [
|
|
14
|
+
"dist",
|
|
15
|
+
"src",
|
|
16
|
+
"README.md",
|
|
17
|
+
"LICENSE"
|
|
18
|
+
],
|
|
19
|
+
"engines": {
|
|
20
|
+
"node": ">=22.15.0"
|
|
21
|
+
},
|
|
22
|
+
"peerDependencies": {
|
|
23
|
+
"oxc-parser": "0.157.0"
|
|
24
|
+
},
|
|
25
|
+
"peerDependenciesMeta": {
|
|
26
|
+
"oxc-parser": {
|
|
27
|
+
"optional": true
|
|
28
|
+
}
|
|
29
|
+
},
|
|
30
|
+
"publishConfig": {
|
|
31
|
+
"access": "public"
|
|
32
|
+
},
|
|
33
|
+
"repository": {
|
|
34
|
+
"type": "git",
|
|
35
|
+
"url": "git+https://github.com/facilis-dev/facilis.git",
|
|
36
|
+
"directory": "packages/analyze"
|
|
37
|
+
},
|
|
38
|
+
"homepage": "https://github.com/facilis-dev/facilis/tree/main/packages/analyze#readme",
|
|
39
|
+
"bugs": {
|
|
40
|
+
"url": "https://github.com/facilis-dev/facilis/issues"
|
|
41
|
+
},
|
|
42
|
+
"keywords": [
|
|
43
|
+
"facilis",
|
|
44
|
+
"framework",
|
|
45
|
+
"vite",
|
|
46
|
+
"nitro",
|
|
47
|
+
"low-code"
|
|
48
|
+
],
|
|
49
|
+
"main": "./dist/index.js",
|
|
50
|
+
"types": "./dist/index.d.ts"
|
|
51
|
+
}
|
package/src/analyze.ts
ADDED
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
import { buildPageFacts, type TransportSpec } from "./passes/clients.ts";
|
|
2
|
+
import { buildConfigFacts } from "./passes/config.ts";
|
|
3
|
+
import { buildFileFacts } from "./passes/file-facts.ts";
|
|
4
|
+
import { readLocaleCatalogs } from "./passes/i18n.ts";
|
|
5
|
+
import { buildJobFacts } from "./passes/jobs.ts";
|
|
6
|
+
import { buildJobRelationFacts } from "./passes/job-relations.ts";
|
|
7
|
+
import { buildRouteFacts } from "./passes/routes.ts";
|
|
8
|
+
import { buildRouteRelationFacts } from "./passes/route-relations.ts";
|
|
9
|
+
import { routeSystemTableDiagnostics } from "./passes/route-tables.ts";
|
|
10
|
+
import { buildSchemaFacts } from "./passes/schema.ts";
|
|
11
|
+
import { buildScreens } from "./passes/screens.ts";
|
|
12
|
+
import { buildViewFacts } from "./passes/views.ts";
|
|
13
|
+
import { buildWorkflowFacts } from "./passes/workflows.ts";
|
|
14
|
+
import { buildWorkflowTableFacts } from "./passes/workflow-tables.ts";
|
|
15
|
+
import { buildProjectGraph } from "./graph.ts";
|
|
16
|
+
import { createModuleGraph, type ModuleGraph } from "./module-graph.ts";
|
|
17
|
+
import type { PathAlias } from "./path-resolver.ts";
|
|
18
|
+
import type { Parser } from "./parser.ts";
|
|
19
|
+
import { computeRevision } from "./revision.ts";
|
|
20
|
+
import { diagnostic } from "./diagnostics.ts";
|
|
21
|
+
import type { Analysis, Diagnostic } from "./types.ts";
|
|
22
|
+
|
|
23
|
+
export interface AnalyzeInput {
|
|
24
|
+
/** Project-relative path -> source. The analyzer never touches the filesystem itself. */
|
|
25
|
+
readonly files: ReadonlyMap<string, string>;
|
|
26
|
+
readonly parser: Parser;
|
|
27
|
+
/** Project root inside the file map (defaults to the map root). */
|
|
28
|
+
readonly rootDir?: string;
|
|
29
|
+
readonly aliases?: readonly PathAlias[];
|
|
30
|
+
readonly transport?: TransportSpec;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/** The sanctioned client transport (docs/findings/ast-dsl-contract.md §3.4). */
|
|
34
|
+
export const DEFAULT_TRANSPORT: TransportSpec = {
|
|
35
|
+
module: "src/lib/api.ts",
|
|
36
|
+
exportName: "api",
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
/** Analyze a project snapshot into the fact model + project graph. Pure and synchronous. */
|
|
40
|
+
export function analyzeProject(input: AnalyzeInput): Analysis {
|
|
41
|
+
const graph: ModuleGraph = createModuleGraph({
|
|
42
|
+
files: input.files,
|
|
43
|
+
parser: input.parser,
|
|
44
|
+
root: input.rootDir ?? "",
|
|
45
|
+
...(input.aliases === undefined ? {} : { aliases: input.aliases }),
|
|
46
|
+
});
|
|
47
|
+
const config = buildConfigFacts(graph);
|
|
48
|
+
const routes = buildRouteFacts(graph, config.facts);
|
|
49
|
+
const schema = buildSchemaFacts(graph, config.facts);
|
|
50
|
+
const views = buildViewFacts(graph);
|
|
51
|
+
// D-157: the declaration check runs after the schema and the views, because a route's
|
|
52
|
+
// `dbTables` and its usage are compared against both (tables and views).
|
|
53
|
+
const routeRelations = buildRouteRelationFacts(graph, routes.routes, schema.tables, views.views);
|
|
54
|
+
// D-157 applies to jobs too: their `run` sees the system-scoped db client, tables and
|
|
55
|
+
// views alike, and the relation index is the same one the route pass builds its cases on.
|
|
56
|
+
const jobs = buildJobFacts(graph);
|
|
57
|
+
const jobRelations = buildJobRelationFacts(graph, jobs.jobs, schema.tables, views.views);
|
|
58
|
+
const workflows = buildWorkflowFacts(graph);
|
|
59
|
+
// A workflow declares no tables: its surface is derived from the jobs its steps reference.
|
|
60
|
+
const workflowTables = buildWorkflowTableFacts(workflows.workflows, jobs.jobs);
|
|
61
|
+
const locales = readLocaleCatalogs(graph);
|
|
62
|
+
const pages = buildPageFacts(graph, routes.routes, input.transport ?? DEFAULT_TRANSPORT);
|
|
63
|
+
const screens = buildScreens(graph, pages.pages);
|
|
64
|
+
const fileFacts = buildFileFacts(graph);
|
|
65
|
+
|
|
66
|
+
const diagnostics: Diagnostic[] = [];
|
|
67
|
+
for (const file of graph.files.values()) {
|
|
68
|
+
for (const error of file.errors) {
|
|
69
|
+
diagnostics.push(diagnostic("parse-error", "error", error.message, error.source));
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
diagnostics.push(
|
|
73
|
+
...[
|
|
74
|
+
...config.diagnostics,
|
|
75
|
+
...routes.diagnostics,
|
|
76
|
+
...schema.diagnostics,
|
|
77
|
+
...views.diagnostics,
|
|
78
|
+
...routeRelations.diagnostics,
|
|
79
|
+
...jobs.diagnostics,
|
|
80
|
+
...jobRelations.diagnostics,
|
|
81
|
+
...locales.diagnostics,
|
|
82
|
+
...pages.diagnostics,
|
|
83
|
+
...routeSystemTableDiagnostics(routes.routes, schema.tables),
|
|
84
|
+
...workflows.diagnostics,
|
|
85
|
+
...workflowTables.diagnostics,
|
|
86
|
+
].filter(
|
|
87
|
+
// Framework sources are shipped code: their facts stay, but their lint-style
|
|
88
|
+
// diagnostics are not the project's. Project files start outside node_modules/.
|
|
89
|
+
(entry) => entry.source === null || !entry.source.file.startsWith("node_modules/"),
|
|
90
|
+
),
|
|
91
|
+
);
|
|
92
|
+
|
|
93
|
+
return {
|
|
94
|
+
revision: computeRevision(input.files),
|
|
95
|
+
files: [...graph.files.keys()].toSorted((left, right) => left.localeCompare(right)),
|
|
96
|
+
config: config.facts,
|
|
97
|
+
routes: routeRelations.routes,
|
|
98
|
+
tables: schema.tables,
|
|
99
|
+
relations: schema.relations,
|
|
100
|
+
views: views.views,
|
|
101
|
+
pages: pages.pages,
|
|
102
|
+
workflows: workflows.workflows,
|
|
103
|
+
jobs: jobs.jobs,
|
|
104
|
+
jobRelations: jobRelations.relations,
|
|
105
|
+
workflowTables: workflowTables.facts,
|
|
106
|
+
fileFacts,
|
|
107
|
+
locales: locales.locales,
|
|
108
|
+
diagnostics,
|
|
109
|
+
graph: buildProjectGraph({
|
|
110
|
+
routes: routes.routes,
|
|
111
|
+
tables: schema.tables,
|
|
112
|
+
relations: schema.relations,
|
|
113
|
+
pages: pages.pages,
|
|
114
|
+
views: views.views,
|
|
115
|
+
screens,
|
|
116
|
+
workflows: workflows.workflows,
|
|
117
|
+
}),
|
|
118
|
+
};
|
|
119
|
+
}
|
package/src/ast/node.ts
ADDED
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Minimal ESTree narrowing helpers shared by every analyzer pass.
|
|
3
|
+
*
|
|
4
|
+
* The AST shape is parser-independent: oxc-parser (Node, browser) and any future adapter
|
|
5
|
+
* produce plain ESTree / TS-ESTree nodes. Passes never import a parser package.
|
|
6
|
+
*/
|
|
7
|
+
export interface AstNode {
|
|
8
|
+
type: string;
|
|
9
|
+
start?: number;
|
|
10
|
+
end?: number;
|
|
11
|
+
[key: string]: unknown;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function isAstNode(value: unknown): value is AstNode {
|
|
15
|
+
if (typeof value !== "object" || value === null) return false;
|
|
16
|
+
return typeof (value as { type?: unknown }).type === "string";
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function asNode(value: unknown): AstNode | undefined {
|
|
20
|
+
return isAstNode(value) ? value : undefined;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function nodeArray(node: AstNode, key: string): AstNode[] {
|
|
24
|
+
const value = node[key];
|
|
25
|
+
if (!Array.isArray(value)) return [];
|
|
26
|
+
return value.filter(isAstNode);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export function identifierName(node: unknown): string | undefined {
|
|
30
|
+
const candidate = asNode(node);
|
|
31
|
+
if (!candidate || candidate.type !== "Identifier") return undefined;
|
|
32
|
+
return typeof candidate.name === "string" ? candidate.name : undefined;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export function literalString(node: unknown): string | undefined {
|
|
36
|
+
const candidate = asNode(node);
|
|
37
|
+
if (!candidate || candidate.type !== "Literal") return undefined;
|
|
38
|
+
return typeof candidate.value === "string" ? candidate.value : undefined;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/** Name of an object-literal property, or undefined for computed/spread entries. */
|
|
42
|
+
export function propertyName(property: AstNode): string | undefined {
|
|
43
|
+
if (property.type !== "Property") return undefined;
|
|
44
|
+
const key = asNode(property.key);
|
|
45
|
+
if (!key) return undefined;
|
|
46
|
+
if (key.type === "Identifier") {
|
|
47
|
+
return property.computed === true ? undefined : identifierName(key);
|
|
48
|
+
}
|
|
49
|
+
if (key.type === "Literal" && (typeof key.value === "string" || typeof key.value === "number")) {
|
|
50
|
+
return String(key.value);
|
|
51
|
+
}
|
|
52
|
+
return undefined;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export function propertyValue(property: AstNode): AstNode | undefined {
|
|
56
|
+
return asNode(property.value);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/** Object-literal entries, including spread elements. */
|
|
60
|
+
export function objectEntries(node: AstNode): AstNode[] {
|
|
61
|
+
return nodeArray(node, "properties");
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export function isCallTo(node: unknown, calleeName: string): node is AstNode {
|
|
65
|
+
const candidate = asNode(node);
|
|
66
|
+
if (!candidate || candidate.type !== "CallExpression") return false;
|
|
67
|
+
return identifierName(candidate.callee) === calleeName;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export function callArguments(node: AstNode): AstNode[] {
|
|
71
|
+
return nodeArray(node, "arguments");
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const UNWRAPPED_EXPRESSIONS = new Set([
|
|
75
|
+
"ParenthesizedExpression",
|
|
76
|
+
"TSAsExpression",
|
|
77
|
+
"TSSatisfiesExpression",
|
|
78
|
+
"TSNonNullExpression",
|
|
79
|
+
"TSInstantiationExpression",
|
|
80
|
+
"ChainExpression",
|
|
81
|
+
]);
|
|
82
|
+
|
|
83
|
+
/** Strip parentheses and TypeScript-only wrappers from an expression. */
|
|
84
|
+
export function unwrapExpression(value: unknown): AstNode | undefined {
|
|
85
|
+
let node = asNode(value);
|
|
86
|
+
while (node && UNWRAPPED_EXPRESSIONS.has(node.type)) {
|
|
87
|
+
node = asNode(node.expression);
|
|
88
|
+
}
|
|
89
|
+
return node;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/** A call expression after unwrapping parentheses/TS wrappers. */
|
|
93
|
+
export function callExpression(value: unknown): AstNode | undefined {
|
|
94
|
+
const node = unwrapExpression(value);
|
|
95
|
+
return node?.type === "CallExpression" ? node : undefined;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
export function memberName(node: AstNode): string | undefined {
|
|
99
|
+
if (node.type !== "MemberExpression") return undefined;
|
|
100
|
+
if (node.computed === true) return literalString(node.property);
|
|
101
|
+
return identifierName(node.property);
|
|
102
|
+
}
|
package/src/ast/walk.ts
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { isAstNode, type AstNode } from "./node.ts";
|
|
2
|
+
|
|
3
|
+
const SKIPPED_KEYS = new Set(["type", "start", "end", "range"]);
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Depth-first walk over an ESTree node. The AST is plain data (no cycles), so a generic
|
|
7
|
+
* recursion is enough and keeps the analyzer independent of a parser-specific visitor.
|
|
8
|
+
*/
|
|
9
|
+
export function walk(node: AstNode, visit: (node: AstNode) => void): void {
|
|
10
|
+
visit(node);
|
|
11
|
+
for (const key of Object.keys(node)) {
|
|
12
|
+
if (SKIPPED_KEYS.has(key)) continue;
|
|
13
|
+
const value = node[key];
|
|
14
|
+
if (Array.isArray(value)) {
|
|
15
|
+
for (const child of value) {
|
|
16
|
+
if (isAstNode(child)) walk(child, visit);
|
|
17
|
+
}
|
|
18
|
+
continue;
|
|
19
|
+
}
|
|
20
|
+
if (isAstNode(value)) walk(value, visit);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/** Visit descendants of a node, not the node itself. */
|
|
25
|
+
export function walkChildren(node: AstNode, visit: (node: AstNode) => void): void {
|
|
26
|
+
for (const key of Object.keys(node)) {
|
|
27
|
+
if (SKIPPED_KEYS.has(key)) continue;
|
|
28
|
+
const value = node[key];
|
|
29
|
+
if (Array.isArray(value)) {
|
|
30
|
+
for (const child of value) {
|
|
31
|
+
if (isAstNode(child)) walk(child, visit);
|
|
32
|
+
}
|
|
33
|
+
continue;
|
|
34
|
+
}
|
|
35
|
+
if (isAstNode(value)) walk(value, visit);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { Diagnostic, Severity, SourceRef } from "./types.ts";
|
|
2
|
+
|
|
3
|
+
export function diagnostic(
|
|
4
|
+
code: string,
|
|
5
|
+
severity: Severity,
|
|
6
|
+
message: string,
|
|
7
|
+
source: SourceRef | null,
|
|
8
|
+
): Diagnostic {
|
|
9
|
+
return { code, severity, message, source };
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export function error(code: string, message: string, source: SourceRef | null): Diagnostic {
|
|
13
|
+
return diagnostic(code, "error", message, source);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function warning(code: string, message: string, source: SourceRef | null): Diagnostic {
|
|
17
|
+
return diagnostic(code, "warning", message, source);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function info(code: string, message: string, source: SourceRef | null): Diagnostic {
|
|
21
|
+
return diagnostic(code, "info", message, source);
|
|
22
|
+
}
|
package/src/fs.ts
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Node filesystem loader for the analyzer (exported as `@facilis.dev/analyze/fs`).
|
|
3
|
+
*
|
|
4
|
+
* Kept apart from the `./node` parser adapter: a Vite config that runs in the browser VM
|
|
5
|
+
* needs to read a project directory but must not load a native/wasm parser. The loader only
|
|
6
|
+
* reads and filters the file map; the caller injects the parser (D-092).
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { readdirSync, readFileSync, statSync } from "node:fs";
|
|
10
|
+
import { join, sep } from "node:path";
|
|
11
|
+
|
|
12
|
+
const SOURCE_EXTENSIONS = [".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs", ".mts", ".cts", ".json"];
|
|
13
|
+
const ALWAYS_SKIPPED = new Set([".git", "dist", "coverage", ".turbo", ".facilis", ".output"]);
|
|
14
|
+
|
|
15
|
+
export interface LoadProjectOptions {
|
|
16
|
+
/** Package scopes allowed under node_modules (default: @facilis.dev). */
|
|
17
|
+
readonly nodeModuleScopes?: readonly string[];
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/** Read a project directory into the analyzer's path -> source map. */
|
|
21
|
+
export function loadProjectFiles(
|
|
22
|
+
root: string,
|
|
23
|
+
options: LoadProjectOptions = {},
|
|
24
|
+
): Map<string, string> {
|
|
25
|
+
const scopes = options.nodeModuleScopes ?? ["@facilis.dev"];
|
|
26
|
+
const files = new Map<string, string>();
|
|
27
|
+
const walk = (directory: string, relativeDirectory: string): void => {
|
|
28
|
+
for (const entry of readdirSync(directory, { withFileTypes: true })) {
|
|
29
|
+
if (ALWAYS_SKIPPED.has(entry.name)) continue;
|
|
30
|
+
const full = join(directory, entry.name);
|
|
31
|
+
const path =
|
|
32
|
+
relativeDirectory.length === 0 ? entry.name : relativeDirectory + "/" + entry.name;
|
|
33
|
+
const stat = entry.isSymbolicLink() ? statSync(full) : null;
|
|
34
|
+
const isDirectory = stat === null ? entry.isDirectory() : stat.isDirectory();
|
|
35
|
+
const isFile = stat === null ? entry.isFile() : stat.isFile();
|
|
36
|
+
if (isDirectory) {
|
|
37
|
+
if (entry.name === "node_modules") {
|
|
38
|
+
for (const scope of scopes) {
|
|
39
|
+
const scopeDirectory = join(full, scope);
|
|
40
|
+
try {
|
|
41
|
+
readdirSync(scopeDirectory);
|
|
42
|
+
} catch {
|
|
43
|
+
continue;
|
|
44
|
+
}
|
|
45
|
+
walk(scopeDirectory, path + "/" + scope);
|
|
46
|
+
}
|
|
47
|
+
continue;
|
|
48
|
+
}
|
|
49
|
+
walk(full, path);
|
|
50
|
+
continue;
|
|
51
|
+
}
|
|
52
|
+
if (!isFile) continue;
|
|
53
|
+
if (!SOURCE_EXTENSIONS.some((extension) => entry.name.endsWith(extension))) continue;
|
|
54
|
+
files.set(path.split(sep).join("/"), readFileSync(full, "utf8"));
|
|
55
|
+
}
|
|
56
|
+
};
|
|
57
|
+
walk(root, "");
|
|
58
|
+
return files;
|
|
59
|
+
}
|