@sdk-usage/core 0.0.0-next-3c1e8df → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs ADDED
@@ -0,0 +1,246 @@
1
+ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
2
+ let node_fs = require("node:fs");
3
+ let node_path = require("node:path");
4
+ let node_child_process = require("node:child_process");
5
+ let node_module = require("node:module");
6
+ let _open_vanilla_visitor = require("@open-vanilla/visitor");
7
+ let _swc_core = require("@swc/core");
8
+ let fdir = require("fdir");
9
+
10
+ //#region src/entities/item/createLocation.ts
11
+ const createLocation = ({ code, file, link, module, offset, path }) => {
12
+ const linesTillOffset = code.slice(0, Math.max(0, offset)).split(/\n/);
13
+ const line = linesTillOffset.length;
14
+ return {
15
+ column: linesTillOffset[line - 1].length,
16
+ file: `./${(0, node_path.relative)(path, file)}`,
17
+ line,
18
+ link,
19
+ module
20
+ };
21
+ };
22
+
23
+ //#endregion
24
+ //#region src/entities/item/createItem.ts
25
+ /**
26
+ * Aggregate factory that creates an item.
27
+ * @param input - Factory variables.
28
+ * @param input.name - Name.
29
+ * @param input.type - Component type.
30
+ * @param input.module - Source module.
31
+ * @param input.version - Module version.
32
+ * @param input.location - Location.
33
+ * @param input.input - Parameter list and metadata describing the way it's passed.
34
+ * @returns Created item.
35
+ * @example
36
+ * createItem({ ... });
37
+ */
38
+ const createItem = ({ input, location, module, name, type, version }) => {
39
+ return {
40
+ createdAt: (/* @__PURE__ */ new Date()).toISOString(),
41
+ input: {
42
+ data: input?.data ?? {},
43
+ metadata: { withSpreading: input?.metadata.withSpreading ?? false }
44
+ },
45
+ location: createLocation(location),
46
+ module,
47
+ name,
48
+ type,
49
+ version
50
+ };
51
+ };
52
+
53
+ //#endregion
54
+ //#region src/helpers.ts
55
+ const require$1 = (0, node_module.createRequire)(require("url").pathToFileURL(__filename).href);
56
+ const resolvePackageJson = (fromPath) => {
57
+ const filepath = (0, node_path.join)(fromPath, "./package.json");
58
+ if ((0, node_fs.existsSync)(filepath)) return filepath;
59
+ return resolvePackageJson((0, node_path.join)(fromPath, "../"));
60
+ };
61
+ /**
62
+ * Execute an external command.
63
+ * @param command - The command to execute.
64
+ * @param options - Options including current working directory configuration.
65
+ * @param options.cwd - Configure the current working directory.
66
+ * @returns The output (either the command output or error).
67
+ * @example
68
+ * exec("ls");
69
+ */
70
+ const exec = async (command, options = {}) => {
71
+ return new Promise((resolve, reject) => {
72
+ let stdout = "";
73
+ let stderr = "";
74
+ const childProcess = (0, node_child_process.spawn)(command, {
75
+ cwd: options.cwd,
76
+ shell: true,
77
+ stdio: "pipe"
78
+ });
79
+ childProcess.stdout.on("data", (chunk) => {
80
+ stdout += chunk;
81
+ });
82
+ childProcess.stderr.on("data", (chunk) => {
83
+ stderr += chunk;
84
+ });
85
+ childProcess.on("close", (exitCode) => {
86
+ if (exitCode === 0) resolve(stdout.trim());
87
+ else {
88
+ const output = `${stderr}${stdout}`;
89
+ reject(new Error(output.trim()));
90
+ }
91
+ });
92
+ });
93
+ };
94
+
95
+ //#endregion
96
+ //#region src/modules/parser/parse.ts
97
+ const parse = async (code, { onAdd, plugins }) => {
98
+ const context = { imports: /* @__PURE__ */ new Map() };
99
+ let ast;
100
+ try {
101
+ ast = await (0, _swc_core.parse)(code, {
102
+ syntax: "typescript",
103
+ tsx: true
104
+ });
105
+ } catch {
106
+ ast = void 0;
107
+ }
108
+ if (ast === void 0) return;
109
+ const visitor = { ImportDeclaration(node) {
110
+ const module = node.source.value;
111
+ node.specifiers.forEach((specifier) => {
112
+ const specifierValue = specifier.local.value;
113
+ context.imports.set(specifierValue, {
114
+ alias: specifierValue,
115
+ module,
116
+ name: specifier.imported?.value ?? specifierValue
117
+ });
118
+ });
119
+ } };
120
+ for (const plugin of plugins) {
121
+ const pluginOutput = plugin(context, { getJSXAttributeValue });
122
+ const nodeKeys = Object.keys(pluginOutput);
123
+ for (const nodeKey of nodeKeys) {
124
+ const currentVisitorFunction = visitor[nodeKey];
125
+ visitor[nodeKey] = (node) => {
126
+ if (typeof currentVisitorFunction === "function") currentVisitorFunction(node);
127
+ const output = pluginOutput[nodeKey]?.(node);
128
+ if (output) onAdd(output);
129
+ };
130
+ }
131
+ }
132
+ (0, _open_vanilla_visitor.visit)(ast, visitor);
133
+ };
134
+ const getJSXAttributeValue = (node) => {
135
+ if (!node) return true;
136
+ switch (node.type) {
137
+ case "BigIntLiteral":
138
+ case "BooleanLiteral":
139
+ case "JSXText":
140
+ case "NumericLiteral":
141
+ case "StringLiteral": return node.value;
142
+ case "JSXExpressionContainer": return getJSXAttributeValue(node.expression);
143
+ case "NullLiteral": return null;
144
+ default: return createUnknownToken(node.type);
145
+ }
146
+ };
147
+ /**
148
+ * Helper to unify the way unknown AST token are managed.
149
+ * @param token - AST token value.
150
+ * @returns Formatted AST token.
151
+ * @example
152
+ * createUnknownToken("VariableDeclaration");
153
+ */
154
+ const createUnknownToken = (token) => `#${token}`;
155
+
156
+ //#endregion
157
+ //#region src/modules/scanner/scan.ts
158
+ const scan = async (path, options = {}) => {
159
+ const excludedFolders = options.excludeFolders ?? DEFAULT_EXCLUDED_FOLDERS;
160
+ const includedFiles = options.includeFiles ?? DEFAULT_INCLUDED_FILES;
161
+ const projectPaths = new fdir.fdir().withBasePath().glob("**/package.json").exclude((directoryName) => excludedFolders.includes(directoryName)).crawl(path).sync();
162
+ const projects = [];
163
+ for (const projectPath of projectPaths) {
164
+ const metadata = require$1(projectPath);
165
+ const folder = (0, node_path.dirname)(projectPath);
166
+ let link;
167
+ try {
168
+ link = await exec("git config --get remote.origin.url", { cwd: folder });
169
+ } catch {
170
+ link = "";
171
+ }
172
+ projects.push({
173
+ folder,
174
+ link,
175
+ metadata
176
+ });
177
+ }
178
+ return projects.map((project) => {
179
+ const files = new fdir.fdir().withBasePath().glob(...includedFiles).exclude((directoryName) => excludedFolders.includes(directoryName)).crawl(project.folder).sync();
180
+ return {
181
+ ...project,
182
+ files
183
+ };
184
+ });
185
+ };
186
+ const DEFAULT_EXCLUDED_FOLDERS = [
187
+ ".git",
188
+ "node_modules",
189
+ "dist",
190
+ "out"
191
+ ];
192
+ const DEFAULT_INCLUDED_FILES = ["**/*/!(test|*.test|stories|*.stories).?(m){j,t}s?(x)"];
193
+
194
+ //#endregion
195
+ //#region src/createInstance.ts
196
+ const createInstance = (path, options) => {
197
+ return { async getItems() {
198
+ const projects = await scan(path);
199
+ const items = [];
200
+ for (const project of projects) {
201
+ const module = project.metadata.name;
202
+ const dependencies = {
203
+ ...project.metadata.devDependencies,
204
+ ...project.metadata.optionalDependencies,
205
+ ...project.metadata.dependencies
206
+ };
207
+ const link = project.link;
208
+ for (const file of project.files) {
209
+ const code = (0, node_fs.readFileSync)(file, "utf8");
210
+ await parse(code, {
211
+ onAdd(item) {
212
+ if (options.includeModules && options.includeModules.length > 0 && !options.includeModules.includes(item.module)) return;
213
+ let version = dependencies[item.module] ?? "";
214
+ if (options.resolveInstalledVersions) try {
215
+ version = require$1(resolvePackageJson(require$1.resolve(item.module, { paths: [file] }))).version;
216
+ } catch {}
217
+ items.push(createItem({
218
+ ...item,
219
+ location: {
220
+ code,
221
+ file,
222
+ link,
223
+ module,
224
+ offset: item.offset,
225
+ path
226
+ },
227
+ version
228
+ }));
229
+ },
230
+ plugins: options.plugins ?? []
231
+ });
232
+ }
233
+ }
234
+ return items;
235
+ } };
236
+ };
237
+
238
+ //#endregion
239
+ //#region src/modules/plugin/createPlugin.ts
240
+ const createPlugin = (input) => {
241
+ return input;
242
+ };
243
+
244
+ //#endregion
245
+ exports.createInstance = createInstance;
246
+ exports.createPlugin = createPlugin;
@@ -0,0 +1,101 @@
1
+ import { ImportDeclaration, JSXAttrValue, JSXExpression, JSXOpeningElement, TsType } from "@swc/core";
2
+
3
+ //#region src/types.d.ts
4
+ /**
5
+ * Import entity to model an import statement.
6
+ */
7
+ type Import = {
8
+ alias: string;
9
+ module: string;
10
+ name: string;
11
+ };
12
+ type Nodes = {
13
+ ImportDeclaration: ImportDeclaration;
14
+ JSXAttrValue: JSXAttrValue;
15
+ JSXExpression: JSXExpression;
16
+ JSXOpeningElement: JSXOpeningElement;
17
+ TsType: TsType;
18
+ };
19
+ /**
20
+ * Package entity to model `package.json` metadata.
21
+ */
22
+ type Package = {
23
+ dependencies?: Record<string, string>;
24
+ description: string;
25
+ devDependencies?: Record<string, string>;
26
+ name: string;
27
+ optionalDependencies?: Record<string, string>;
28
+ peerDependencies?: Record<string, string>;
29
+ version: string;
30
+ };
31
+ type Primitive = bigint | boolean | null | number | string | undefined;
32
+ //#endregion
33
+ //#region src/entities/item/createLocation.d.ts
34
+ type Location = {
35
+ column: number;
36
+ file: string;
37
+ line: number;
38
+ link: string;
39
+ module: string;
40
+ };
41
+ //#endregion
42
+ //#region src/entities/item/createItem.d.ts
43
+ type Item = {
44
+ createdAt: string;
45
+ input: {
46
+ data: Record<string, unknown>;
47
+ metadata: {
48
+ withSpreading: boolean;
49
+ };
50
+ };
51
+ location: Location;
52
+ module: Package["name"];
53
+ name: string;
54
+ type: string;
55
+ version: Package["version"];
56
+ };
57
+ type ItemDTO = {
58
+ offset: number;
59
+ } & Partial<Pick<Item, "input">> & Pick<Item, "module" | "name" | "type">;
60
+ //#endregion
61
+ //#region src/modules/plugin/createPlugin.d.ts
62
+ declare const createPlugin: (input: Plugin) => Plugin;
63
+ type Plugin = (context: {
64
+ imports: Map<Import["alias"], Import>;
65
+ }, helpers: {
66
+ getJSXAttributeValue: (node: Nodes["JSXAttrValue"] | undefined) => Primitive;
67
+ }) => { [Key in keyof Nodes]?: (node: Nodes[Key]) => ItemDTO | undefined };
68
+ //#endregion
69
+ //#region src/modules/scanner/scan.d.ts
70
+ type ScanOptions = {
71
+ /**
72
+ * A list of folders to ignore.
73
+ */
74
+ excludeFolders?: string[];
75
+ /**
76
+ * A list of files to include (following glob matcher).
77
+ */
78
+ includeFiles?: string[];
79
+ };
80
+ //#endregion
81
+ //#region src/createInstance.d.ts
82
+ type Options = Partial<{
83
+ /**
84
+ * Only analyze components imported from the specificied module list.
85
+ */
86
+ includeModules: string[];
87
+ /**
88
+ * A list of plugins to enable.
89
+ */
90
+ plugins: Plugin[];
91
+ /**
92
+ * Attempt to resolve installed versions of modules. If false or not possible, the specified version from the package.json will be used.
93
+ * @default false
94
+ */
95
+ resolveInstalledVersions: boolean;
96
+ } & Pick<ScanOptions, "excludeFolders" | "includeFiles">>;
97
+ declare const createInstance: (path: string, options: Options) => {
98
+ getItems(): Promise<Item[]>;
99
+ };
100
+ //#endregion
101
+ export { type Plugin, createInstance, createPlugin };
package/dist/index.mjs ADDED
@@ -0,0 +1,244 @@
1
+ import { existsSync, readFileSync } from "node:fs";
2
+ import { dirname, join, relative } from "node:path";
3
+ import { spawn } from "node:child_process";
4
+ import { createRequire } from "node:module";
5
+ import { visit } from "@open-vanilla/visitor";
6
+ import { parse } from "@swc/core";
7
+ import { fdir } from "fdir";
8
+
9
+ //#region src/entities/item/createLocation.ts
10
+ const createLocation = ({ code, file, link, module, offset, path }) => {
11
+ const linesTillOffset = code.slice(0, Math.max(0, offset)).split(/\n/);
12
+ const line = linesTillOffset.length;
13
+ return {
14
+ column: linesTillOffset[line - 1].length,
15
+ file: `./${relative(path, file)}`,
16
+ line,
17
+ link,
18
+ module
19
+ };
20
+ };
21
+
22
+ //#endregion
23
+ //#region src/entities/item/createItem.ts
24
+ /**
25
+ * Aggregate factory that creates an item.
26
+ * @param input - Factory variables.
27
+ * @param input.name - Name.
28
+ * @param input.type - Component type.
29
+ * @param input.module - Source module.
30
+ * @param input.version - Module version.
31
+ * @param input.location - Location.
32
+ * @param input.input - Parameter list and metadata describing the way it's passed.
33
+ * @returns Created item.
34
+ * @example
35
+ * createItem({ ... });
36
+ */
37
+ const createItem = ({ input, location, module, name, type, version }) => {
38
+ return {
39
+ createdAt: (/* @__PURE__ */ new Date()).toISOString(),
40
+ input: {
41
+ data: input?.data ?? {},
42
+ metadata: { withSpreading: input?.metadata.withSpreading ?? false }
43
+ },
44
+ location: createLocation(location),
45
+ module,
46
+ name,
47
+ type,
48
+ version
49
+ };
50
+ };
51
+
52
+ //#endregion
53
+ //#region src/helpers.ts
54
+ const require = createRequire(import.meta.url);
55
+ const resolvePackageJson = (fromPath) => {
56
+ const filepath = join(fromPath, "./package.json");
57
+ if (existsSync(filepath)) return filepath;
58
+ return resolvePackageJson(join(fromPath, "../"));
59
+ };
60
+ /**
61
+ * Execute an external command.
62
+ * @param command - The command to execute.
63
+ * @param options - Options including current working directory configuration.
64
+ * @param options.cwd - Configure the current working directory.
65
+ * @returns The output (either the command output or error).
66
+ * @example
67
+ * exec("ls");
68
+ */
69
+ const exec = async (command, options = {}) => {
70
+ return new Promise((resolve, reject) => {
71
+ let stdout = "";
72
+ let stderr = "";
73
+ const childProcess = spawn(command, {
74
+ cwd: options.cwd,
75
+ shell: true,
76
+ stdio: "pipe"
77
+ });
78
+ childProcess.stdout.on("data", (chunk) => {
79
+ stdout += chunk;
80
+ });
81
+ childProcess.stderr.on("data", (chunk) => {
82
+ stderr += chunk;
83
+ });
84
+ childProcess.on("close", (exitCode) => {
85
+ if (exitCode === 0) resolve(stdout.trim());
86
+ else {
87
+ const output = `${stderr}${stdout}`;
88
+ reject(new Error(output.trim()));
89
+ }
90
+ });
91
+ });
92
+ };
93
+
94
+ //#endregion
95
+ //#region src/modules/parser/parse.ts
96
+ const parse$1 = async (code, { onAdd, plugins }) => {
97
+ const context = { imports: /* @__PURE__ */ new Map() };
98
+ let ast;
99
+ try {
100
+ ast = await parse(code, {
101
+ syntax: "typescript",
102
+ tsx: true
103
+ });
104
+ } catch {
105
+ ast = void 0;
106
+ }
107
+ if (ast === void 0) return;
108
+ const visitor = { ImportDeclaration(node) {
109
+ const module = node.source.value;
110
+ node.specifiers.forEach((specifier) => {
111
+ const specifierValue = specifier.local.value;
112
+ context.imports.set(specifierValue, {
113
+ alias: specifierValue,
114
+ module,
115
+ name: specifier.imported?.value ?? specifierValue
116
+ });
117
+ });
118
+ } };
119
+ for (const plugin of plugins) {
120
+ const pluginOutput = plugin(context, { getJSXAttributeValue });
121
+ const nodeKeys = Object.keys(pluginOutput);
122
+ for (const nodeKey of nodeKeys) {
123
+ const currentVisitorFunction = visitor[nodeKey];
124
+ visitor[nodeKey] = (node) => {
125
+ if (typeof currentVisitorFunction === "function") currentVisitorFunction(node);
126
+ const output = pluginOutput[nodeKey]?.(node);
127
+ if (output) onAdd(output);
128
+ };
129
+ }
130
+ }
131
+ visit(ast, visitor);
132
+ };
133
+ const getJSXAttributeValue = (node) => {
134
+ if (!node) return true;
135
+ switch (node.type) {
136
+ case "BigIntLiteral":
137
+ case "BooleanLiteral":
138
+ case "JSXText":
139
+ case "NumericLiteral":
140
+ case "StringLiteral": return node.value;
141
+ case "JSXExpressionContainer": return getJSXAttributeValue(node.expression);
142
+ case "NullLiteral": return null;
143
+ default: return createUnknownToken(node.type);
144
+ }
145
+ };
146
+ /**
147
+ * Helper to unify the way unknown AST token are managed.
148
+ * @param token - AST token value.
149
+ * @returns Formatted AST token.
150
+ * @example
151
+ * createUnknownToken("VariableDeclaration");
152
+ */
153
+ const createUnknownToken = (token) => `#${token}`;
154
+
155
+ //#endregion
156
+ //#region src/modules/scanner/scan.ts
157
+ const scan = async (path, options = {}) => {
158
+ const excludedFolders = options.excludeFolders ?? DEFAULT_EXCLUDED_FOLDERS;
159
+ const includedFiles = options.includeFiles ?? DEFAULT_INCLUDED_FILES;
160
+ const projectPaths = new fdir().withBasePath().glob("**/package.json").exclude((directoryName) => excludedFolders.includes(directoryName)).crawl(path).sync();
161
+ const projects = [];
162
+ for (const projectPath of projectPaths) {
163
+ const metadata = require(projectPath);
164
+ const folder = dirname(projectPath);
165
+ let link;
166
+ try {
167
+ link = await exec("git config --get remote.origin.url", { cwd: folder });
168
+ } catch {
169
+ link = "";
170
+ }
171
+ projects.push({
172
+ folder,
173
+ link,
174
+ metadata
175
+ });
176
+ }
177
+ return projects.map((project) => {
178
+ const files = new fdir().withBasePath().glob(...includedFiles).exclude((directoryName) => excludedFolders.includes(directoryName)).crawl(project.folder).sync();
179
+ return {
180
+ ...project,
181
+ files
182
+ };
183
+ });
184
+ };
185
+ const DEFAULT_EXCLUDED_FOLDERS = [
186
+ ".git",
187
+ "node_modules",
188
+ "dist",
189
+ "out"
190
+ ];
191
+ const DEFAULT_INCLUDED_FILES = ["**/*/!(test|*.test|stories|*.stories).?(m){j,t}s?(x)"];
192
+
193
+ //#endregion
194
+ //#region src/createInstance.ts
195
+ const createInstance = (path, options) => {
196
+ return { async getItems() {
197
+ const projects = await scan(path);
198
+ const items = [];
199
+ for (const project of projects) {
200
+ const module = project.metadata.name;
201
+ const dependencies = {
202
+ ...project.metadata.devDependencies,
203
+ ...project.metadata.optionalDependencies,
204
+ ...project.metadata.dependencies
205
+ };
206
+ const link = project.link;
207
+ for (const file of project.files) {
208
+ const code = readFileSync(file, "utf8");
209
+ await parse$1(code, {
210
+ onAdd(item) {
211
+ if (options.includeModules && options.includeModules.length > 0 && !options.includeModules.includes(item.module)) return;
212
+ let version = dependencies[item.module] ?? "";
213
+ if (options.resolveInstalledVersions) try {
214
+ version = require(resolvePackageJson(require.resolve(item.module, { paths: [file] }))).version;
215
+ } catch {}
216
+ items.push(createItem({
217
+ ...item,
218
+ location: {
219
+ code,
220
+ file,
221
+ link,
222
+ module,
223
+ offset: item.offset,
224
+ path
225
+ },
226
+ version
227
+ }));
228
+ },
229
+ plugins: options.plugins ?? []
230
+ });
231
+ }
232
+ }
233
+ return items;
234
+ } };
235
+ };
236
+
237
+ //#endregion
238
+ //#region src/modules/plugin/createPlugin.ts
239
+ const createPlugin = (input) => {
240
+ return input;
241
+ };
242
+
243
+ //#endregion
244
+ export { createInstance, createPlugin };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sdk-usage/core",
3
- "version": "0.0.0-next-3c1e8df",
3
+ "version": "0.1.0",
4
4
  "description": "sdk-usage core functionalities",
5
5
  "keywords": [
6
6
  "analyze",