pkg-scaffold 3.1.3 → 3.2.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pkg-scaffold",
3
- "version": "3.1.3",
3
+ "version": "3.2.0",
4
4
  "description": "The ultimate enterprise-grade codebase janitor. Faster than Knip v6 with OXC integration, type-aware analysis, and self-healing capabilities.",
5
5
  "type": "module",
6
6
  "main": "index.js",
@@ -13,7 +13,7 @@
13
13
  "test": "echo \"Error: no test specified\" && exit 0",
14
14
  "test:stability": "npm run test",
15
15
  "docs:dev": "vitepress dev docs",
16
- "docs:build": "vitepress build docs",
16
+ "docs:build": "vitepress build docs && cp docs/sitemap.xml docs/.vitepress/dist/ && cp docs/robots.txt docs/.vitepress/dist",
17
17
  "docs:preview": "vitepress preview docs"
18
18
  },
19
19
  "keywords": [
@@ -20,6 +20,7 @@
20
20
  "nuxt",
21
21
  "remix",
22
22
  "sveltekit",
23
- "astro"
23
+ "astro",
24
+ "typescript"
24
25
  ]
25
26
  }
@@ -1,53 +1,71 @@
1
+ import fs from 'fs/promises';
2
+ import path from 'path';
3
+
1
4
  /**
2
5
  * Base class for all pkg-scaffold plugins.
3
6
  * Defines the contract for ecosystem detection and entry point mapping.
7
+ * Version 3.2.0: Added support for dynamic custom getters.
4
8
  */
5
9
  export class BasePlugin {
6
- constructor(context) {
7
- this.context = context;
8
- }
10
+ constructor(context) {
11
+ this.context = context;
12
+ this.customGetters = new Map();
13
+ }
14
+
15
+ /**
16
+ * Unique identifier for the plugin (e.g., 'nextjs').
17
+ */
18
+ get name() {
19
+ throw new Error('Plugin must implement name getter');
20
+ }
9
21
 
10
- /**
11
- * Unique identifier for the plugin (e.g., 'nextjs').
12
- */
13
- get name() {
14
- throw new Error('Plugin must implement name getter');
15
- }
22
+ /**
23
+ * Returns a list of configuration files that indicate this ecosystem is active.
24
+ */
25
+ getConfigFiles() {
26
+ return [];
27
+ }
16
28
 
17
- /**
18
- * Returns a list of configuration files that indicate this ecosystem is active.
19
- */
20
- getConfigFiles() {
21
- return [];
22
- }
29
+ /**
30
+ * Returns regex patterns for files that should be treated as entry points.
31
+ */
32
+ getRoutePatterns() {
33
+ return [];
34
+ }
23
35
 
24
- /**
25
- * Returns regex patterns for files that should be treated as entry points.
26
- */
27
- getRoutePatterns() {
28
- return [];
29
- }
36
+ /**
37
+ * Returns symbols that are implicitly required/exported by the framework.
38
+ */
39
+ getRequiredSystemContracts() {
40
+ return ['default'];
41
+ }
30
42
 
31
- /**
32
- * Returns symbols that are implicitly required/exported by the framework.
33
- */
34
- getRequiredSystemContracts() {
35
- return ['default'];
36
- }
43
+ /**
44
+ * Version 3.2.0: Dynamic getter for custom plugin properties.
45
+ * @param {string} key - The property key to retrieve
46
+ * @returns {any} The value of the custom property
47
+ */
48
+ get(key) {
49
+ const methodName = `get${key.charAt(0).toUpperCase() + key.slice(1)}`;
50
+ if (typeof this[methodName] === 'function') {
51
+ return this[methodName]();
52
+ }
53
+ return this.customGetters.get(key);
54
+ }
37
55
 
38
- /**
39
- * Optional: Logic to detect if the plugin should be active in the given directory.
40
- */
41
- async isActive(baseDir) {
42
- const configFiles = this.getConfigFiles();
43
- for (const file of configFiles) {
44
- try {
45
- await fs.access(path.join(baseDir, file));
46
- return true;
47
- } catch {
48
- continue;
49
- }
56
+ /**
57
+ * Optional: Logic to detect if the plugin should be active in the given directory.
58
+ */
59
+ async isActive(baseDir) {
60
+ const configFiles = this.getConfigFiles();
61
+ for (const file of configFiles) {
62
+ try {
63
+ await fs.access(path.join(baseDir, file));
64
+ return true;
65
+ } catch {
66
+ continue;
67
+ }
68
+ }
69
+ return false;
50
70
  }
51
- return false;
52
- }
53
71
  }
@@ -4,92 +4,107 @@ import { pathToFileURL } from 'url';
4
4
 
5
5
  /**
6
6
  * Advanced Plugin Registry supporting Builtin, Custom, and Knip-style plugins.
7
+ * Version 3.2.0: Enhanced with support for dynamic custom getters.
7
8
  */
8
9
  export class PluginRegistry {
9
- constructor(context) {
10
- this.context = context;
11
- this.plugins = new Map();
12
- this.config = null;
13
- }
14
-
15
- async init(projectRoot) {
16
- const configPath = path.join(projectRoot, 'pkg-scaffold', 'config.json');
17
- try {
18
- const configRaw = await fs.readFile(configPath, 'utf8');
19
- this.config = JSON.parse(configRaw);
20
- } catch (e) {
21
- this.config = { useBuiltinPlugins: true, useCustomPlugins: true, supportKnipPlugins: true };
10
+ constructor(context) {
11
+ this.context = context;
12
+ this.plugins = new Map();
13
+ this.config = null;
22
14
  }
23
15
 
24
- if (this.config.useBuiltinPlugins) {
25
- await this.loadBuiltinPlugins();
26
- }
16
+ async init(projectRoot) {
17
+ const configPath = path.join(projectRoot, 'pkg-scaffold', 'config.json');
18
+ try {
19
+ const configRaw = await fs.readFile(configPath, 'utf8');
20
+ this.config = JSON.parse(configRaw);
21
+ } catch (e) {
22
+ this.config = {
23
+ useBuiltinPlugins: true,
24
+ useCustomPlugins: true,
25
+ supportKnipPlugins: true
26
+ };
27
+ }
27
28
 
28
- if (this.config.useCustomPlugins) {
29
- await this.loadCustomPlugins(projectRoot);
30
- }
29
+ if (this.config.useBuiltinPlugins) {
30
+ await this.loadBuiltinPlugins();
31
+ }
32
+
33
+ if (this.config.useCustomPlugins) {
34
+ await this.loadCustomPlugins(projectRoot);
35
+ }
31
36
 
32
- if (this.config.supportKnipPlugins) {
33
- await this.initKnipAdapter();
37
+ if (this.config.supportKnipPlugins) {
38
+ await this.initKnipAdapter();
39
+ }
34
40
  }
35
- }
36
41
 
37
- async loadBuiltinPlugins() {
38
- const { NextJsPlugin } = await import('./ecosystems/NextJsPlugin.js');
39
- const { NuxtPlugin, RemixPlugin, SvelteKitPlugin, AstroPlugin } = await import('./ecosystems/GenericPlugins.js');
42
+ async loadBuiltinPlugins() {
43
+ const { NextJsPlugin } = await import('./ecosystems/NextJsPlugin.js');
44
+ const { NuxtPlugin, RemixPlugin, SvelteKitPlugin, AstroPlugin } = await import('./ecosystems/GenericPlugins.js');
45
+ const { TypeScriptPlugin } = await import('./ecosystems/TypeScriptPlugin.js');
40
46
 
41
- const builtins = [
42
- new NextJsPlugin(this.context),
43
- new NuxtPlugin(this.context),
44
- new RemixPlugin(this.context),
45
- new SvelteKitPlugin(this.context),
46
- new AstroPlugin(this.context)
47
- ];
47
+ const builtins = [
48
+ new NextJsPlugin(this.context),
49
+ new NuxtPlugin(this.context),
50
+ new RemixPlugin(this.context),
51
+ new SvelteKitPlugin(this.context),
52
+ new AstroPlugin(this.context),
53
+ new TypeScriptPlugin(this.context)
54
+ ];
48
55
 
49
- builtins.forEach(p => {
50
- if (!this.config.enabledPlugins || this.config.enabledPlugins.includes(p.name)) {
51
- this.register(p);
52
- }
53
- });
54
- }
56
+ builtins.forEach(p => {
57
+ if (!this.config.enabledPlugins || this.config.enabledPlugins.includes(p.name)) {
58
+ this.register(p);
59
+ }
60
+ });
61
+ }
62
+
63
+ async loadCustomPlugins(projectRoot) {
64
+ const pluginsDir = path.join(projectRoot, 'pkg-scaffold', 'plugins');
65
+ try {
66
+ const files = await fs.readdir(pluginsDir);
67
+ for (const file of files) {
68
+ if (file.endsWith('.js') || file.endsWith('.mjs')) {
69
+ const pluginModule = await import(pathToFileURL(path.join(pluginsDir, file)).href);
70
+ const PluginClass = pluginModule.default || pluginModule;
71
+ const pluginInstance = new PluginClass(this.context);
55
72
 
56
- async loadCustomPlugins(projectRoot) {
57
- const pluginsDir = path.join(projectRoot, 'pkg-scaffold', 'plugins');
58
- try {
59
- const files = await fs.readdir(pluginsDir);
60
- for (const file of files) {
61
- if (file.endsWith('.js') || file.endsWith('.mjs')) {
62
- const pluginModule = await import(pathToFileURL(path.join(pluginsDir, file)).href);
63
- const PluginClass = pluginModule.default || pluginModule;
64
- this.register(new PluginClass(this.context));
73
+ const version = pluginInstance.get('version');
74
+ if (version && this.context.verbose) {
75
+ console.log(`[PluginRegistry] Loaded ${pluginInstance.name} v${version}`);
76
+ }
77
+ this.register(pluginInstance);
78
+ }
79
+ }
80
+ } catch (e) {
81
+ // No custom plugins or dir missing
65
82
  }
66
- }
67
- } catch (e) {
68
- // No custom plugins or dir missing
69
83
  }
70
- }
71
84
 
72
- async initKnipAdapter() {
73
- // This adapter allows running Knip-style plugins by wrapping them
74
- // In a real scenario, this would interface with knip's plugin API
75
- this.context.knipCompatible = true;
76
- }
85
+ async initKnipAdapter() {
86
+ this.context.knipCompatible = true;
87
+ }
88
+
89
+ register(plugin) {
90
+ this.plugins.set(plugin.name, plugin);
91
+ }
77
92
 
78
- register(plugin) {
79
- this.plugins.set(plugin.name, plugin);
80
- }
93
+ getPlugins() {
94
+ return Array.from(this.plugins.values());
95
+ }
81
96
 
82
- getPlugins() {
83
- return Array.from(this.plugins.values());
84
- }
97
+ getPlugin(name) {
98
+ return this.plugins.get(name);
99
+ }
85
100
 
86
- async getActivePlugins(baseDir) {
87
- const active = [];
88
- for (const plugin of this.plugins.values()) {
89
- if (await plugin.isActive(baseDir)) {
90
- active.push(plugin);
91
- }
101
+ async getActivePlugins(baseDir) {
102
+ const active = [];
103
+ for (const plugin of this.plugins.values()) {
104
+ if (await plugin.isActive(baseDir)) {
105
+ active.push(plugin);
106
+ }
107
+ }
108
+ return active;
92
109
  }
93
- return active;
94
- }
95
110
  }
@@ -0,0 +1,56 @@
1
+ import path from 'path';
2
+ import fs from 'fs/promises';
3
+ import { BasePlugin } from '../BasePlugin.js';
4
+
5
+ /**
6
+ * TypeScript Plugin for pkg-scaffold.
7
+ * Handles tsconfig.json detection and TypeScript-specific entry points.
8
+ */
9
+ export class TypeScriptPlugin extends BasePlugin {
10
+ get name() {
11
+ return 'typescript';
12
+ }
13
+
14
+ getConfigFiles() {
15
+ return ['tsconfig.json', 'tsconfig.base.json', 'tsconfig.eslint.json'];
16
+ }
17
+
18
+ getRoutePatterns() {
19
+ // Common TypeScript entry points and declaration files
20
+ return [
21
+ /src\/index\.ts$/,
22
+ /src\/main\.ts$/,
23
+ /src\/lib\.ts$/,
24
+ /.*\.d\.ts$/
25
+ ];
26
+ }
27
+
28
+ getRequiredSystemContracts() {
29
+ // TypeScript specific implicit exports or requirements
30
+ return ['default'];
31
+ }
32
+
33
+ /**
34
+ * Custom Getter for v3.2.0: Get the compiler version from the project.
35
+ */
36
+ async getCompilerVersion() {
37
+ try {
38
+ const packageJsonPath = path.join(this.context.cwd, 'package.json');
39
+ const content = await fs.readFile(packageJsonPath, 'utf8');
40
+ const pkg = JSON.parse(content);
41
+ return pkg.devDependencies?.typescript || pkg.dependencies?.typescript || 'unknown';
42
+ } catch {
43
+ return 'not installed';
44
+ }
45
+ }
46
+
47
+ async isActive(baseDir) {
48
+ for (const file of this.getConfigFiles()) {
49
+ try {
50
+ await fs.access(path.join(baseDir, file));
51
+ return true;
52
+ } catch {}
53
+ }
54
+ return false;
55
+ }
56
+ }