@lsst/pik 0.5.2 → 0.5.3
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 +61 -20
- package/dist/lib/program.d.ts.map +1 -1
- package/dist/lib/program.js +26 -6
- package/dist/lib/program.spec.d.ts +2 -0
- package/dist/lib/program.spec.d.ts.map +1 -0
- package/dist/lib/program.spec.js +169 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -8,7 +8,29 @@ A developer toolkit with extensible plugins for common development tasks.
|
|
|
8
8
|
npm install -g @lsst/pik
|
|
9
9
|
```
|
|
10
10
|
|
|
11
|
-
##
|
|
11
|
+
## Configuration
|
|
12
|
+
|
|
13
|
+
Create `pik.config.ts` in your project root:
|
|
14
|
+
|
|
15
|
+
```typescript
|
|
16
|
+
export default {
|
|
17
|
+
// Enable select plugin
|
|
18
|
+
select: {
|
|
19
|
+
include: ['src/**/*.ts', '.env'],
|
|
20
|
+
},
|
|
21
|
+
|
|
22
|
+
// Enable worktree plugin
|
|
23
|
+
worktree: {
|
|
24
|
+
baseDir: '../',
|
|
25
|
+
copyFiles: ['.env.local'],
|
|
26
|
+
postCreate: 'npm install',
|
|
27
|
+
},
|
|
28
|
+
};
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
Plugins are only available when their configuration key is present.
|
|
32
|
+
|
|
33
|
+
## Built-in Plugins
|
|
12
34
|
|
|
13
35
|
### Select Plugin
|
|
14
36
|
|
|
@@ -22,41 +44,59 @@ Switch config options in source files using `@pik` markers.
|
|
|
22
44
|
const env = 'LOCAL'; // @pik:option LOCAL
|
|
23
45
|
```
|
|
24
46
|
|
|
25
|
-
#### 2.
|
|
47
|
+
#### 2. Run commands
|
|
26
48
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
export default defineConfig({
|
|
33
|
-
select: {
|
|
34
|
-
include: ['src/**/*.ts', '.env'],
|
|
35
|
-
},
|
|
36
|
-
});
|
|
49
|
+
```bash
|
|
50
|
+
pik select # Interactive mode
|
|
51
|
+
pik select list # List all selectors
|
|
52
|
+
pik select set Environment DEV # Set directly
|
|
37
53
|
```
|
|
38
54
|
|
|
39
|
-
|
|
55
|
+
### Worktree Plugin
|
|
56
|
+
|
|
57
|
+
Manage git worktrees with automatic setup.
|
|
40
58
|
|
|
41
59
|
```bash
|
|
42
|
-
# Interactive mode
|
|
43
|
-
pik
|
|
60
|
+
pik worktree # Interactive mode
|
|
61
|
+
pik worktree create # Create a new worktree
|
|
62
|
+
pik worktree list # List all worktrees
|
|
63
|
+
pik worktree remove # Remove a worktree
|
|
64
|
+
```
|
|
44
65
|
|
|
45
|
-
|
|
46
|
-
pik select list
|
|
66
|
+
## External Plugins
|
|
47
67
|
|
|
48
|
-
|
|
49
|
-
|
|
68
|
+
Add third-party or custom plugins:
|
|
69
|
+
|
|
70
|
+
```typescript
|
|
71
|
+
import { myPlugin } from 'pik-plugin-my';
|
|
72
|
+
|
|
73
|
+
export default {
|
|
74
|
+
plugins: [
|
|
75
|
+
myPlugin({ apiKey: 'xxx' }),
|
|
76
|
+
],
|
|
77
|
+
select: { include: ['src/**/*.ts'] },
|
|
78
|
+
};
|
|
50
79
|
```
|
|
51
80
|
|
|
52
81
|
## Commands
|
|
53
82
|
|
|
83
|
+
### Select
|
|
84
|
+
|
|
54
85
|
| Command | Alias | Description |
|
|
55
86
|
|---------|-------|-------------|
|
|
56
87
|
| `pik select` | `sel` | Interactive selection mode |
|
|
57
88
|
| `pik select list` | `ls` | Show all selectors and their state |
|
|
58
89
|
| `pik select set <selector> <option>` | - | Set an option directly |
|
|
59
90
|
|
|
91
|
+
### Worktree
|
|
92
|
+
|
|
93
|
+
| Command | Alias | Description |
|
|
94
|
+
|---------|-------|-------------|
|
|
95
|
+
| `pik worktree` | `wt` | Interactive worktree menu |
|
|
96
|
+
| `pik worktree create [name]` | `add` | Create a new worktree |
|
|
97
|
+
| `pik worktree list` | `ls` | List all worktrees |
|
|
98
|
+
| `pik worktree remove [path]` | `rm` | Remove a worktree |
|
|
99
|
+
|
|
60
100
|
## Marker Syntax
|
|
61
101
|
|
|
62
102
|
- `@pik:select <name>` - Defines a selector group
|
|
@@ -68,7 +108,8 @@ Commented lines are inactive, uncommented lines are active.
|
|
|
68
108
|
|
|
69
109
|
| Extensions | Comment Style |
|
|
70
110
|
|------------|---------------|
|
|
71
|
-
| `.ts`, `.js`, `.tsx`, `.jsx` | `//` |
|
|
111
|
+
| `.ts`, `.js`, `.tsx`, `.jsx`, `.mts`, `.mjs` | `//` |
|
|
112
|
+
| `.html`, `.htm` | `//` and `<!-- -->` |
|
|
72
113
|
| `.sh`, `.bash`, `.zsh`, `.py`, `.yaml`, `.yml`, `.env` | `#` |
|
|
73
114
|
|
|
74
115
|
## License
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"program.d.ts","sourceRoot":"","sources":["../../src/lib/program.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAGpC,OAAO,
|
|
1
|
+
{"version":3,"file":"program.d.ts","sourceRoot":"","sources":["../../src/lib/program.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAGpC,OAAO,EAA6B,KAAK,SAAS,EAAE,MAAM,gBAAgB,CAAC;AA6C3E,eAAO,MAAM,OAAO,SAGG,CAAC;AAExB;;GAEG;AACH,wBAAsB,iBAAiB,IAAI,OAAO,CAAC,SAAS,EAAE,CAAC,CAgE9D"}
|
package/dist/lib/program.js
CHANGED
|
@@ -1,23 +1,43 @@
|
|
|
1
1
|
import { Command } from 'commander';
|
|
2
2
|
import { select, Separator } from '@inquirer/prompts';
|
|
3
3
|
import pc from 'picocolors';
|
|
4
|
-
import { loadConfig } from '@lsst/pik-core';
|
|
4
|
+
import { loadConfig, isValidPlugin } from '@lsst/pik-core';
|
|
5
5
|
import { selectPlugin } from '@lsst/pik-plugin-select';
|
|
6
6
|
import { worktreePlugin } from '@lsst/pik-plugin-worktree';
|
|
7
7
|
import pkg from '../../package.json' with { type: 'json' };
|
|
8
|
-
//
|
|
9
|
-
const
|
|
8
|
+
// Built-in plugins
|
|
9
|
+
const builtinPlugins = [selectPlugin, worktreePlugin];
|
|
10
10
|
/**
|
|
11
11
|
* Get plugins that are enabled in the config.
|
|
12
|
-
*
|
|
12
|
+
* - Built-in plugins are enabled if their command key exists in config
|
|
13
|
+
* - External plugins from the `plugins` array are added directly
|
|
13
14
|
*/
|
|
14
15
|
async function getEnabledPlugins() {
|
|
15
16
|
const config = await loadConfig();
|
|
16
17
|
if (!config) {
|
|
17
|
-
// No config - no plugins enabled
|
|
18
|
+
// No config - no plugins enabled
|
|
18
19
|
return [];
|
|
19
20
|
}
|
|
20
|
-
|
|
21
|
+
const enabledPlugins = [];
|
|
22
|
+
// Add built-in plugins that have config keys
|
|
23
|
+
for (const plugin of builtinPlugins) {
|
|
24
|
+
if (plugin.command in config) {
|
|
25
|
+
enabledPlugins.push(plugin);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
// Add external plugins from config
|
|
29
|
+
if (config.plugins && Array.isArray(config.plugins)) {
|
|
30
|
+
for (const plugin of config.plugins) {
|
|
31
|
+
if (isValidPlugin(plugin)) {
|
|
32
|
+
enabledPlugins.push(plugin);
|
|
33
|
+
}
|
|
34
|
+
else {
|
|
35
|
+
console.error(pc.red('Invalid plugin in config.plugins array'));
|
|
36
|
+
console.error(pc.dim('Each plugin must have: name, description, command, register'));
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
return enabledPlugins;
|
|
21
41
|
}
|
|
22
42
|
export const program = new Command()
|
|
23
43
|
.name(pkg.name)
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"program.spec.d.ts","sourceRoot":"","sources":["../../src/lib/program.spec.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
|
|
2
|
+
import { Command } from 'commander';
|
|
3
|
+
// Mock the config and plugins before importing program
|
|
4
|
+
vi.mock('@lsst/pik-core', async () => {
|
|
5
|
+
const actual = await vi.importActual('@lsst/pik-core');
|
|
6
|
+
return {
|
|
7
|
+
...actual,
|
|
8
|
+
loadConfig: vi.fn(),
|
|
9
|
+
};
|
|
10
|
+
});
|
|
11
|
+
vi.mock('@lsst/pik-plugin-select', () => ({
|
|
12
|
+
selectPlugin: {
|
|
13
|
+
name: 'Select',
|
|
14
|
+
description: 'Mock select plugin',
|
|
15
|
+
command: 'select',
|
|
16
|
+
register: vi.fn(),
|
|
17
|
+
},
|
|
18
|
+
}));
|
|
19
|
+
vi.mock('@lsst/pik-plugin-worktree', () => ({
|
|
20
|
+
worktreePlugin: {
|
|
21
|
+
name: 'Worktree',
|
|
22
|
+
description: 'Mock worktree plugin',
|
|
23
|
+
command: 'worktree',
|
|
24
|
+
register: vi.fn(),
|
|
25
|
+
},
|
|
26
|
+
}));
|
|
27
|
+
describe('program', () => {
|
|
28
|
+
let loadConfigMock;
|
|
29
|
+
beforeEach(async () => {
|
|
30
|
+
vi.resetModules();
|
|
31
|
+
const pikCore = await import('@lsst/pik-core');
|
|
32
|
+
loadConfigMock = pikCore.loadConfig;
|
|
33
|
+
});
|
|
34
|
+
afterEach(() => {
|
|
35
|
+
vi.clearAllMocks();
|
|
36
|
+
});
|
|
37
|
+
describe('initializeProgram', () => {
|
|
38
|
+
it('should return empty array when no config exists', async () => {
|
|
39
|
+
loadConfigMock.mockResolvedValue(null);
|
|
40
|
+
const { initializeProgram } = await import('./program.js');
|
|
41
|
+
const plugins = await initializeProgram();
|
|
42
|
+
expect(plugins).toEqual([]);
|
|
43
|
+
});
|
|
44
|
+
it('should load built-in plugins when their config keys exist', async () => {
|
|
45
|
+
loadConfigMock.mockResolvedValue({
|
|
46
|
+
select: { include: ['*.ts'] },
|
|
47
|
+
});
|
|
48
|
+
const { initializeProgram } = await import('./program.js');
|
|
49
|
+
const plugins = await initializeProgram();
|
|
50
|
+
expect(plugins).toHaveLength(1);
|
|
51
|
+
expect(plugins[0].command).toBe('select');
|
|
52
|
+
});
|
|
53
|
+
it('should load multiple built-in plugins when configured', async () => {
|
|
54
|
+
loadConfigMock.mockResolvedValue({
|
|
55
|
+
select: { include: ['*.ts'] },
|
|
56
|
+
worktree: { baseDir: '../' },
|
|
57
|
+
});
|
|
58
|
+
const { initializeProgram } = await import('./program.js');
|
|
59
|
+
const plugins = await initializeProgram();
|
|
60
|
+
expect(plugins).toHaveLength(2);
|
|
61
|
+
expect(plugins.map((p) => p.command)).toContain('select');
|
|
62
|
+
expect(plugins.map((p) => p.command)).toContain('worktree');
|
|
63
|
+
});
|
|
64
|
+
it('should load external plugins from plugins array', async () => {
|
|
65
|
+
const externalPlugin = {
|
|
66
|
+
name: 'External Plugin',
|
|
67
|
+
description: 'Test external plugin',
|
|
68
|
+
command: 'external',
|
|
69
|
+
register: vi.fn(),
|
|
70
|
+
};
|
|
71
|
+
loadConfigMock.mockResolvedValue({
|
|
72
|
+
plugins: [externalPlugin],
|
|
73
|
+
});
|
|
74
|
+
const { initializeProgram } = await import('./program.js');
|
|
75
|
+
const plugins = await initializeProgram();
|
|
76
|
+
expect(plugins).toHaveLength(1);
|
|
77
|
+
expect(plugins[0].name).toBe('External Plugin');
|
|
78
|
+
expect(plugins[0].command).toBe('external');
|
|
79
|
+
});
|
|
80
|
+
it('should combine built-in and external plugins', async () => {
|
|
81
|
+
const externalPlugin = {
|
|
82
|
+
name: 'External Plugin',
|
|
83
|
+
description: 'Test external plugin',
|
|
84
|
+
command: 'external',
|
|
85
|
+
register: vi.fn(),
|
|
86
|
+
};
|
|
87
|
+
loadConfigMock.mockResolvedValue({
|
|
88
|
+
plugins: [externalPlugin],
|
|
89
|
+
select: { include: ['*.ts'] },
|
|
90
|
+
});
|
|
91
|
+
const { initializeProgram } = await import('./program.js');
|
|
92
|
+
const plugins = await initializeProgram();
|
|
93
|
+
expect(plugins).toHaveLength(2);
|
|
94
|
+
expect(plugins.map((p) => p.command)).toContain('select');
|
|
95
|
+
expect(plugins.map((p) => p.command)).toContain('external');
|
|
96
|
+
});
|
|
97
|
+
it('should register external plugins with the program', async () => {
|
|
98
|
+
const registerFn = vi.fn();
|
|
99
|
+
const externalPlugin = {
|
|
100
|
+
name: 'External Plugin',
|
|
101
|
+
description: 'Test external plugin',
|
|
102
|
+
command: 'external',
|
|
103
|
+
register: registerFn,
|
|
104
|
+
};
|
|
105
|
+
loadConfigMock.mockResolvedValue({
|
|
106
|
+
plugins: [externalPlugin],
|
|
107
|
+
});
|
|
108
|
+
const { initializeProgram } = await import('./program.js');
|
|
109
|
+
await initializeProgram();
|
|
110
|
+
expect(registerFn).toHaveBeenCalledTimes(1);
|
|
111
|
+
expect(registerFn).toHaveBeenCalledWith(expect.any(Command));
|
|
112
|
+
});
|
|
113
|
+
it('should skip invalid plugins and log error', async () => {
|
|
114
|
+
const consoleSpy = vi.spyOn(console, 'error').mockImplementation(() => { });
|
|
115
|
+
const invalidPlugin = {
|
|
116
|
+
name: 'Invalid',
|
|
117
|
+
// missing description, command, register
|
|
118
|
+
};
|
|
119
|
+
loadConfigMock.mockResolvedValue({
|
|
120
|
+
plugins: [invalidPlugin],
|
|
121
|
+
});
|
|
122
|
+
const { initializeProgram } = await import('./program.js');
|
|
123
|
+
const plugins = await initializeProgram();
|
|
124
|
+
expect(plugins).toHaveLength(0);
|
|
125
|
+
expect(consoleSpy).toHaveBeenCalled();
|
|
126
|
+
consoleSpy.mockRestore();
|
|
127
|
+
});
|
|
128
|
+
it('should load multiple external plugins', async () => {
|
|
129
|
+
const plugin1 = {
|
|
130
|
+
name: 'Plugin 1',
|
|
131
|
+
description: 'First plugin',
|
|
132
|
+
command: 'first',
|
|
133
|
+
register: vi.fn(),
|
|
134
|
+
};
|
|
135
|
+
const plugin2 = {
|
|
136
|
+
name: 'Plugin 2',
|
|
137
|
+
description: 'Second plugin',
|
|
138
|
+
command: 'second',
|
|
139
|
+
register: vi.fn(),
|
|
140
|
+
};
|
|
141
|
+
loadConfigMock.mockResolvedValue({
|
|
142
|
+
plugins: [plugin1, plugin2],
|
|
143
|
+
});
|
|
144
|
+
const { initializeProgram } = await import('./program.js');
|
|
145
|
+
const plugins = await initializeProgram();
|
|
146
|
+
expect(plugins).toHaveLength(2);
|
|
147
|
+
expect(plugins[0].command).toBe('first');
|
|
148
|
+
expect(plugins[1].command).toBe('second');
|
|
149
|
+
});
|
|
150
|
+
it('should handle plugin factory pattern (function returning plugin)', async () => {
|
|
151
|
+
// This simulates: myPlugin({ config: 'value' }) in the config
|
|
152
|
+
const pluginFactory = (config) => ({
|
|
153
|
+
name: 'Factory Plugin',
|
|
154
|
+
description: `Plugin with key: ${config.apiKey}`,
|
|
155
|
+
command: 'factory',
|
|
156
|
+
register: vi.fn(),
|
|
157
|
+
});
|
|
158
|
+
const factoryPlugin = pluginFactory({ apiKey: 'test-key' });
|
|
159
|
+
loadConfigMock.mockResolvedValue({
|
|
160
|
+
plugins: [factoryPlugin],
|
|
161
|
+
});
|
|
162
|
+
const { initializeProgram } = await import('./program.js');
|
|
163
|
+
const plugins = await initializeProgram();
|
|
164
|
+
expect(plugins).toHaveLength(1);
|
|
165
|
+
expect(plugins[0].name).toBe('Factory Plugin');
|
|
166
|
+
expect(plugins[0].description).toBe('Plugin with key: test-key');
|
|
167
|
+
});
|
|
168
|
+
});
|
|
169
|
+
});
|