@sdk-usage/core 0.0.0-next-9cce549 → 0.0.0-next-aa7d62e

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