@lsst/pik-plugin-select 0.6.7 → 0.8.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/README.md CHANGED
@@ -43,7 +43,9 @@ pik select set <name> <option> # Set directly
43
43
  ## Marker Syntax
44
44
 
45
45
  - `@pik:select <name>` - Defines a selector group
46
- - `@pik:option <name>` - Marks an option within a selector
46
+ - `@pik:option <name>` - Marks a single-line option within a selector
47
+ - `@pik:block-start <name>` - Starts a multi-line block option
48
+ - `@pik:block-end` - Ends a multi-line block option
47
49
 
48
50
  ### Inline Style
49
51
 
@@ -67,6 +69,28 @@ Marker on its own line, content on the next line (useful for HTML):
67
69
  <!-- <script src="http://localhost:3000/viewer.js"></script> -->
68
70
  ```
69
71
 
72
+ ### Block Style
73
+
74
+ For multi-line options where you need to switch entire blocks of configuration:
75
+
76
+ ```bash
77
+ # @pik:select Environment
78
+ # @pik:block-start Development
79
+ API_URL=http://localhost:3000
80
+ DEBUG=true
81
+ LOG_LEVEL=debug
82
+ # @pik:block-end
83
+ # @pik:block-start Production
84
+ # API_URL=https://api.example.com
85
+ # DEBUG=false
86
+ # LOG_LEVEL=error
87
+ # @pik:block-end
88
+ ```
89
+
90
+ When switching blocks:
91
+ - The selected block's content lines are uncommented
92
+ - All other blocks' content lines are commented out
93
+
70
94
  ## Supported File Types
71
95
 
72
96
  | Extensions | Comment Style |
package/dist/index.d.ts CHANGED
@@ -1,4 +1,6 @@
1
1
  export { selectPlugin } from './lib/plugin.js';
2
- export type { SelectConfig } from './lib/types.js';
2
+ export type { SelectConfig, ProfileMapping, ProfilesConfig } from './lib/types.js';
3
+ export type { ProfileStatus, SelectorMappingStatus } from './lib/types/profile-status.js';
3
4
  export { Scanner, type FileResult } from './lib/scanner.js';
5
+ export { Profile, type FoundSelector } from './lib/profile/index.js';
4
6
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAG/C,YAAY,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAGnD,OAAO,EAAE,OAAO,EAAE,KAAK,UAAU,EAAE,MAAM,kBAAkB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAG/C,YAAY,EAAE,YAAY,EAAE,cAAc,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AACnF,YAAY,EAAE,aAAa,EAAE,qBAAqB,EAAE,MAAM,+BAA+B,CAAC;AAG1F,OAAO,EAAE,OAAO,EAAE,KAAK,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAG5D,OAAO,EAAE,OAAO,EAAE,KAAK,aAAa,EAAE,MAAM,wBAAwB,CAAC"}
package/dist/index.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import { Command } from "commander";
2
+ import { Parser, loadConfig, BlockSelector } from "@lsst/pik-core";
2
3
  import pc from "picocolors";
3
4
  import { relative } from "path";
4
- import { Parser, loadConfig, SingleSwitcher } from "@lsst/pik-core";
5
5
  import { readFile, writeFile } from "fs/promises";
6
6
  import { glob } from "glob";
7
7
  import { select, Separator } from "@inquirer/prompts";
@@ -27,17 +27,22 @@ class Scanner {
27
27
  return results;
28
28
  }
29
29
  }
30
- const listCommand = new Command("list").alias("ls").description("List all selectors and their current state").option("--json", "Output in JSON format").action(async (options) => {
31
- const config = await loadConfig();
30
+ function requireSelectConfig(config, options) {
32
31
  if (!config?.select) {
33
- if (options.json) {
34
- console.log(JSON.stringify({ error: 'No pik config found or missing "select" section' }));
32
+ const message = 'No pik config found or missing "select" section';
33
+ if (options?.json) {
34
+ console.log(JSON.stringify({ error: message }));
35
35
  } else {
36
- console.error(pc.red('No pik config found or missing "select" section'));
36
+ console.error(pc.red(message));
37
37
  }
38
38
  process.exit(1);
39
39
  }
40
- const scanner = new Scanner(config.select);
40
+ return config.select;
41
+ }
42
+ const listCommand = new Command("list").alias("ls").description("List all selectors and their current state").option("--json", "Output in JSON format").action(async (options) => {
43
+ const config = await loadConfig();
44
+ const selectConfig = requireSelectConfig(config, options);
45
+ const scanner = new Scanner(selectConfig);
41
46
  const results = await scanner.scan();
42
47
  if (options.json) {
43
48
  const jsonOutput = results.flatMap(
@@ -45,11 +50,9 @@ const listCommand = new Command("list").alias("ls").description("List all select
45
50
  name: selector.name,
46
51
  file: relative(process.cwd(), file.path),
47
52
  line: selector.line,
48
- activeOption: selector.options.find((o) => o.isActive)?.name ?? null,
49
- options: selector.options.map((o) => ({
50
- name: o.name,
51
- isActive: o.isActive
52
- }))
53
+ activeOption: selector.getActiveOptionName(),
54
+ isBlock: selector instanceof BlockSelector,
55
+ options: selector.options.map((o) => ({ name: o.name, isActive: o.isActive }))
53
56
  }))
54
57
  );
55
58
  console.log(JSON.stringify(jsonOutput, null, 2));
@@ -63,9 +66,10 @@ const listCommand = new Command("list").alias("ls").description("List all select
63
66
  const relativePath = relative(process.cwd(), file.path);
64
67
  console.log(pc.cyan(relativePath));
65
68
  for (const selector of file.selectors) {
66
- const activeOption = selector.options.find((o) => o.isActive);
67
- const activeLabel = activeOption ? pc.green(activeOption.name) : pc.yellow("none");
68
- console.log(` ${pc.bold(selector.name)}: ${activeLabel}`);
69
+ const activeOptionName = selector.getActiveOptionName();
70
+ const activeLabel = activeOptionName ? pc.green(activeOptionName) : pc.yellow("none");
71
+ const blockIndicator = selector instanceof BlockSelector ? pc.dim(" [block]") : "";
72
+ console.log(` ${pc.bold(selector.name)}${blockIndicator}: ${activeLabel}`);
69
73
  for (const option of selector.options) {
70
74
  const marker = option.isActive ? pc.green("●") : pc.dim("○");
71
75
  console.log(` ${marker} ${option.name}`);
@@ -76,20 +80,16 @@ const listCommand = new Command("list").alias("ls").description("List all select
76
80
  });
77
81
  const setCommand = new Command("set").description("Set a specific option for a selector").argument("<selector>", "Selector name").argument("<option>", "Option to activate").action(async (selectorName, optionName) => {
78
82
  const config = await loadConfig();
79
- if (!config?.select) {
80
- console.error(pc.red('No pik config found or missing "select" section'));
81
- process.exit(1);
82
- }
83
- const scanner = new Scanner(config.select);
83
+ const selectConfig = requireSelectConfig(config);
84
+ const scanner = new Scanner(selectConfig);
84
85
  const results = await scanner.scan();
85
86
  let found = false;
86
87
  for (const file of results) {
87
88
  const selector = file.selectors.find((s) => s.name === selectorName);
88
89
  if (selector) {
89
90
  found = true;
90
- const switcher = SingleSwitcher.forFilePath(file.path);
91
91
  try {
92
- const newContent = switcher.switch(file.content, selector, optionName);
92
+ const newContent = selector.switchTo(file.content, optionName, file.path);
93
93
  await writeFile(file.path, newContent);
94
94
  const relativePath = relative(process.cwd(), file.path);
95
95
  console.log(
@@ -109,16 +109,13 @@ const setCommand = new Command("set").description("Set a specific option for a s
109
109
  }
110
110
  });
111
111
  const BACK_VALUE = /* @__PURE__ */ Symbol("back");
112
- function isExitPromptError(error) {
112
+ function isExitPromptError$1(error) {
113
113
  return error instanceof Error && error.name === "ExitPromptError";
114
114
  }
115
115
  const switchCommand = new Command("switch").alias("sw").description("Interactively switch options").action(async () => {
116
116
  const config = await loadConfig();
117
- if (!config?.select) {
118
- console.error(pc.red('No pik config found or missing "select" section'));
119
- process.exit(1);
120
- }
121
- const scanner = new Scanner(config.select);
117
+ const selectConfig = requireSelectConfig(config);
118
+ const scanner = new Scanner(selectConfig);
122
119
  const results = await scanner.scan();
123
120
  if (results.length === 0) {
124
121
  console.log(pc.yellow("No selectors found"));
@@ -138,10 +135,11 @@ const switchCommand = new Command("switch").alias("sw").description("Interactive
138
135
  choices: [
139
136
  ...choices.map((choice) => {
140
137
  const relativePath2 = relative(process.cwd(), choice.file.path);
141
- const activeOption = choice.selector.options.find((o) => o.isActive);
142
- const current = activeOption ? pc.dim(` (${activeOption.name})`) : "";
138
+ const activeOptionName = choice.selector.getActiveOptionName();
139
+ const current = activeOptionName ? pc.dim(` (${activeOptionName})`) : "";
140
+ const blockIndicator = choice.selector instanceof BlockSelector ? pc.dim(" [block]") : "";
143
141
  return {
144
- name: `${choice.selector.name}${current} ${pc.dim(`- ${relativePath2}`)}`,
142
+ name: `${choice.selector.name}${blockIndicator}${current} ${pc.dim(`- ${relativePath2}`)}`,
145
143
  value: choice
146
144
  };
147
145
  }),
@@ -150,7 +148,7 @@ const switchCommand = new Command("switch").alias("sw").description("Interactive
150
148
  ]
151
149
  });
152
150
  } catch (error) {
153
- if (isExitPromptError(error)) {
151
+ if (isExitPromptError$1(error)) {
154
152
  process.exit(0);
155
153
  }
156
154
  throw error;
@@ -172,7 +170,7 @@ const switchCommand = new Command("switch").alias("sw").description("Interactive
172
170
  ]
173
171
  });
174
172
  } catch (error) {
175
- if (isExitPromptError(error)) {
173
+ if (isExitPromptError$1(error)) {
176
174
  process.exit(0);
177
175
  }
178
176
  throw error;
@@ -180,11 +178,10 @@ const switchCommand = new Command("switch").alias("sw").description("Interactive
180
178
  if (selectedOption === BACK_VALUE) {
181
179
  continue;
182
180
  }
183
- const switcher = SingleSwitcher.forFilePath(selectedChoice.file.path);
184
- const newContent = switcher.switch(
181
+ const newContent = selectedChoice.selector.switchTo(
185
182
  selectedChoice.file.content,
186
- selectedChoice.selector,
187
- selectedOption
183
+ selectedOption,
184
+ selectedChoice.file.path
188
185
  );
189
186
  await writeFile(selectedChoice.file.path, newContent);
190
187
  const relativePath = relative(process.cwd(), selectedChoice.file.path);
@@ -196,6 +193,310 @@ const switchCommand = new Command("switch").alias("sw").description("Interactive
196
193
  return;
197
194
  }
198
195
  });
196
+ class Profile {
197
+ constructor(name, mapping) {
198
+ this.name = name;
199
+ this.mapping = mapping;
200
+ }
201
+ /**
202
+ * Find a selector by name across scan results
203
+ */
204
+ static findSelector(results, selectorName) {
205
+ for (const file of results) {
206
+ const selector = file.selectors.find((s) => s.name === selectorName);
207
+ if (selector) {
208
+ return { file, selector };
209
+ }
210
+ }
211
+ return null;
212
+ }
213
+ /**
214
+ * Compute the status of this profile against scan results
215
+ */
216
+ computeStatus(results) {
217
+ const mappings = [];
218
+ for (const [selectorName, expectedOption] of Object.entries(this.mapping)) {
219
+ const found = Profile.findSelector(results, selectorName);
220
+ if (!found) {
221
+ mappings.push({
222
+ selectorName,
223
+ expectedOption,
224
+ currentOption: null,
225
+ filePath: "",
226
+ isMatched: false,
227
+ error: `Selector "${selectorName}" not found`
228
+ });
229
+ continue;
230
+ }
231
+ const currentOption = found.selector.getActiveOptionName();
232
+ const optionExists = found.selector.optionExists(expectedOption);
233
+ if (!optionExists) {
234
+ mappings.push({
235
+ selectorName,
236
+ expectedOption,
237
+ currentOption,
238
+ filePath: found.file.path,
239
+ isMatched: false,
240
+ error: `Option "${expectedOption}" not found in selector "${selectorName}"`
241
+ });
242
+ continue;
243
+ }
244
+ mappings.push({
245
+ selectorName,
246
+ expectedOption,
247
+ currentOption,
248
+ filePath: found.file.path,
249
+ isMatched: currentOption === expectedOption
250
+ });
251
+ }
252
+ const matchedCount = mappings.filter((m) => m.isMatched).length;
253
+ const totalCount = mappings.length;
254
+ return {
255
+ name: this.name,
256
+ mappings,
257
+ isFullyActive: matchedCount === totalCount && totalCount > 0,
258
+ isPartiallyActive: matchedCount > 0 && matchedCount < totalCount,
259
+ matchedCount,
260
+ totalCount
261
+ };
262
+ }
263
+ /**
264
+ * Apply this profile to the scanned files
265
+ */
266
+ async apply(results) {
267
+ const applyResults = [];
268
+ const changesByFile = /* @__PURE__ */ new Map();
269
+ for (const [selectorName, optionName] of Object.entries(this.mapping)) {
270
+ const found = Profile.findSelector(results, selectorName);
271
+ if (!found) {
272
+ applyResults.push({
273
+ selectorName,
274
+ optionName,
275
+ filePath: "",
276
+ success: false,
277
+ error: `Selector "${selectorName}" not found`
278
+ });
279
+ continue;
280
+ }
281
+ const optionExists = found.selector.optionExists(optionName);
282
+ if (!optionExists) {
283
+ const availableOptions = found.selector.options.map((o) => o.name).join(", ");
284
+ applyResults.push({
285
+ selectorName,
286
+ optionName,
287
+ filePath: found.file.path,
288
+ success: false,
289
+ error: `Option "${optionName}" not found. Available: ${availableOptions}`
290
+ });
291
+ continue;
292
+ }
293
+ const existing = changesByFile.get(found.file.path);
294
+ if (existing) {
295
+ existing.push({
296
+ selectorName,
297
+ optionName,
298
+ selector: found.selector
299
+ });
300
+ } else {
301
+ changesByFile.set(found.file.path, [
302
+ {
303
+ selectorName,
304
+ optionName,
305
+ selector: found.selector
306
+ }
307
+ ]);
308
+ }
309
+ }
310
+ for (const [filePath, changes] of changesByFile) {
311
+ let content = await readFile(filePath, "utf-8");
312
+ for (const change of changes) {
313
+ try {
314
+ const parser = Parser.forFilePath(filePath);
315
+ const { selectors } = parser.parse(content);
316
+ const freshSelector = selectors.find((s) => s.name === change.selectorName);
317
+ if (!freshSelector) {
318
+ applyResults.push({
319
+ selectorName: change.selectorName,
320
+ optionName: change.optionName,
321
+ filePath,
322
+ success: false,
323
+ error: `Selector "${change.selectorName}" not found after previous changes`
324
+ });
325
+ continue;
326
+ }
327
+ content = freshSelector.switchTo(content, change.optionName, filePath);
328
+ applyResults.push({
329
+ selectorName: change.selectorName,
330
+ optionName: change.optionName,
331
+ filePath,
332
+ success: true
333
+ });
334
+ } catch (error) {
335
+ applyResults.push({
336
+ selectorName: change.selectorName,
337
+ optionName: change.optionName,
338
+ filePath,
339
+ success: false,
340
+ error: error instanceof Error ? error.message : String(error)
341
+ });
342
+ }
343
+ }
344
+ await writeFile(filePath, content);
345
+ }
346
+ return applyResults;
347
+ }
348
+ /**
349
+ * Compute statuses for multiple profiles
350
+ */
351
+ static computeAllStatuses(profiles, results) {
352
+ return Object.entries(profiles).map(
353
+ ([name, mapping]) => new Profile(name, mapping).computeStatus(results)
354
+ );
355
+ }
356
+ }
357
+ function isExitPromptError(error) {
358
+ return error instanceof Error && error.name === "ExitPromptError";
359
+ }
360
+ const profileCommand = new Command("profile").description("Apply a profile to switch multiple selectors at once").argument("[name]", "Profile name to apply").action(async (profileName) => {
361
+ const config = await loadConfig();
362
+ const selectConfig = requireSelectConfig(config);
363
+ const profiles = selectConfig.profiles;
364
+ if (!profiles || Object.keys(profiles).length === 0) {
365
+ console.error(pc.red("No profiles configured"));
366
+ process.exit(1);
367
+ }
368
+ const scanner = new Scanner(selectConfig);
369
+ const results = await scanner.scan();
370
+ if (!profileName) {
371
+ const statuses = Profile.computeAllStatuses(profiles, results);
372
+ try {
373
+ profileName = await select({
374
+ message: "Select a profile to apply",
375
+ choices: [
376
+ ...statuses.map((status) => {
377
+ let statusIndicator;
378
+ if (status.isFullyActive) {
379
+ statusIndicator = pc.green("●");
380
+ } else if (status.isPartiallyActive) {
381
+ statusIndicator = pc.yellow("●");
382
+ } else {
383
+ statusIndicator = pc.dim("○");
384
+ }
385
+ const countInfo = pc.dim(`(${status.matchedCount}/${status.totalCount})`);
386
+ return {
387
+ name: `${statusIndicator} ${status.name} ${countInfo}`,
388
+ value: status.name
389
+ };
390
+ }),
391
+ new Separator(),
392
+ { name: pc.dim("← Cancel"), value: "" }
393
+ ]
394
+ });
395
+ } catch (error) {
396
+ if (isExitPromptError(error)) {
397
+ process.exit(0);
398
+ }
399
+ throw error;
400
+ }
401
+ if (!profileName) {
402
+ return;
403
+ }
404
+ }
405
+ const profileMapping = profiles[profileName];
406
+ if (!profileMapping) {
407
+ const availableProfiles = Object.keys(profiles).join(", ");
408
+ console.error(pc.red(`Profile "${profileName}" not found`));
409
+ console.error(pc.dim(`Available profiles: ${availableProfiles}`));
410
+ process.exit(1);
411
+ }
412
+ const profile = new Profile(profileName, profileMapping);
413
+ const applyResults = await profile.apply(results);
414
+ const successes = applyResults.filter((r) => r.success);
415
+ const failures = applyResults.filter((r) => !r.success);
416
+ for (const result of successes) {
417
+ const relativePath = relative(process.cwd(), result.filePath);
418
+ console.log(
419
+ pc.green(
420
+ `✓ Set ${pc.bold(result.selectorName)} to ${pc.bold(result.optionName)} in ${relativePath}`
421
+ )
422
+ );
423
+ }
424
+ for (const result of failures) {
425
+ console.error(pc.red(`✗ ${result.selectorName}: ${result.error}`));
426
+ }
427
+ if (failures.length > 0) {
428
+ console.log();
429
+ console.log(
430
+ pc.yellow(`Applied ${successes.length}/${applyResults.length} selector(s) from profile "${profileName}"`)
431
+ );
432
+ process.exit(1);
433
+ } else {
434
+ console.log();
435
+ console.log(pc.green(`✓ Applied profile "${profileName}" (${successes.length} selector(s))`));
436
+ }
437
+ });
438
+ const profilesCommand = new Command("profiles").description("List all profiles and their status").option("--json", "Output in JSON format").action(async (options) => {
439
+ const config = await loadConfig();
440
+ const selectConfig = requireSelectConfig(config, options);
441
+ const profiles = selectConfig.profiles;
442
+ if (!profiles || Object.keys(profiles).length === 0) {
443
+ if (options.json) {
444
+ console.log(JSON.stringify({ error: "No profiles configured" }));
445
+ } else {
446
+ console.log(pc.yellow("No profiles configured"));
447
+ }
448
+ return;
449
+ }
450
+ const scanner = new Scanner(selectConfig);
451
+ const results = await scanner.scan();
452
+ const statuses = Profile.computeAllStatuses(profiles, results);
453
+ if (options.json) {
454
+ const jsonOutput = statuses.map((status) => ({
455
+ name: status.name,
456
+ isFullyActive: status.isFullyActive,
457
+ isPartiallyActive: status.isPartiallyActive,
458
+ matchedCount: status.matchedCount,
459
+ totalCount: status.totalCount,
460
+ mappings: status.mappings.map((m) => ({
461
+ selectorName: m.selectorName,
462
+ expectedOption: m.expectedOption,
463
+ currentOption: m.currentOption,
464
+ filePath: m.filePath ? relative(process.cwd(), m.filePath) : null,
465
+ isMatched: m.isMatched,
466
+ error: m.error
467
+ }))
468
+ }));
469
+ console.log(JSON.stringify(jsonOutput, null, 2));
470
+ return;
471
+ }
472
+ for (const status of statuses) {
473
+ let statusIndicator;
474
+ if (status.isFullyActive) {
475
+ statusIndicator = pc.green("●");
476
+ } else if (status.isPartiallyActive) {
477
+ statusIndicator = pc.yellow("●");
478
+ } else {
479
+ statusIndicator = pc.dim("○");
480
+ }
481
+ const countInfo = pc.dim(`(${status.matchedCount}/${status.totalCount})`);
482
+ console.log(`${statusIndicator} ${pc.bold(status.name)} ${countInfo}`);
483
+ for (const mapping of status.mappings) {
484
+ const mappingIndicator = mapping.isMatched ? pc.green("✓") : pc.dim("○");
485
+ const currentValue = mapping.currentOption ?? pc.dim("none");
486
+ const expectedValue = mapping.expectedOption;
487
+ if (mapping.error) {
488
+ console.log(` ${pc.red("✗")} ${mapping.selectorName}: ${pc.red(mapping.error)}`);
489
+ } else if (mapping.isMatched) {
490
+ console.log(` ${mappingIndicator} ${mapping.selectorName}: ${pc.green(currentValue)}`);
491
+ } else {
492
+ console.log(
493
+ ` ${mappingIndicator} ${mapping.selectorName}: ${currentValue} ${pc.dim(`→ ${expectedValue}`)}`
494
+ );
495
+ }
496
+ }
497
+ console.log();
498
+ }
499
+ });
199
500
  const selectPlugin = {
200
501
  name: "Select",
201
502
  description: "Switch config options using @pik markers",
@@ -206,6 +507,8 @@ const selectPlugin = {
206
507
  selectCmd.addCommand(listCommand);
207
508
  selectCmd.addCommand(setCommand);
208
509
  selectCmd.addCommand(switchCommand);
510
+ selectCmd.addCommand(profileCommand);
511
+ selectCmd.addCommand(profilesCommand);
209
512
  selectCmd.action(async () => {
210
513
  await switchCommand.parseAsync([], { from: "user" });
211
514
  });
@@ -215,6 +518,7 @@ const selectPlugin = {
215
518
  }
216
519
  };
217
520
  export {
521
+ Profile,
218
522
  Scanner,
219
523
  selectPlugin
220
524
  };
@@ -1,4 +1,3 @@
1
1
  import { Command } from 'commander';
2
- import '../types.js';
3
2
  export declare const listCommand: Command;
4
3
  //# sourceMappingURL=list.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"list.d.ts","sourceRoot":"","sources":["../../../src/lib/commands/list.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAKpC,OAAO,aAAa,CAAC;AAMrB,eAAO,MAAM,WAAW,SA6DpB,CAAC"}
1
+ {"version":3,"file":"list.d.ts","sourceRoot":"","sources":["../../../src/lib/commands/list.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAOpC,OAAO,aAAa,CAAC;AAMrB,eAAO,MAAM,WAAW,SAoDpB,CAAC"}
@@ -0,0 +1,3 @@
1
+ import { Command } from 'commander';
2
+ export declare const profileCommand: Command;
3
+ //# sourceMappingURL=profile.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"profile.d.ts","sourceRoot":"","sources":["../../../src/lib/commands/profile.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAQpC,OAAO,aAAa,CAAC;AAMrB,eAAO,MAAM,cAAc,SAkGvB,CAAC"}
@@ -0,0 +1,3 @@
1
+ import { Command } from 'commander';
2
+ export declare const profilesCommand: Command;
3
+ //# sourceMappingURL=profiles.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"profiles.d.ts","sourceRoot":"","sources":["../../../src/lib/commands/profiles.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAOpC,OAAO,aAAa,CAAC;AAMrB,eAAO,MAAM,eAAe,SA0ExB,CAAC"}
@@ -1,4 +1,3 @@
1
1
  import { Command } from 'commander';
2
- import '../types.js';
3
2
  export declare const setCommand: Command;
4
3
  //# sourceMappingURL=set.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"set.d.ts","sourceRoot":"","sources":["../../../src/lib/commands/set.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAMpC,OAAO,aAAa,CAAC;AAErB,eAAO,MAAM,UAAU,SA6CnB,CAAC"}
1
+ {"version":3,"file":"set.d.ts","sourceRoot":"","sources":["../../../src/lib/commands/set.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAOpC,OAAO,aAAa,CAAC;AAErB,eAAO,MAAM,UAAU,SAwCnB,CAAC"}
@@ -1,4 +1,3 @@
1
1
  import { Command } from 'commander';
2
- import '../types.js';
3
2
  export declare const switchCommand: Command;
4
3
  //# sourceMappingURL=switch.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"switch.d.ts","sourceRoot":"","sources":["../../../src/lib/commands/switch.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAOpC,OAAO,aAAa,CAAC;AAarB,eAAO,MAAM,aAAa,SAyGtB,CAAC"}
1
+ {"version":3,"file":"switch.d.ts","sourceRoot":"","sources":["../../../src/lib/commands/switch.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAQpC,OAAO,aAAa,CAAC;AAarB,eAAO,MAAM,aAAa,SAqGtB,CAAC"}
@@ -1,3 +1,3 @@
1
- import type { PikPlugin } from '@lsst/pik-core';
1
+ import { PikPlugin } from '@lsst/pik-core';
2
2
  export declare const selectPlugin: PikPlugin;
3
3
  //# sourceMappingURL=plugin.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"plugin.d.ts","sourceRoot":"","sources":["../../src/lib/plugin.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAKhD,eAAO,MAAM,YAAY,EAAE,SA4B1B,CAAC"}
1
+ {"version":3,"file":"plugin.d.ts","sourceRoot":"","sources":["../../src/lib/plugin.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAOhD,eAAO,MAAM,YAAY,EAAE,SA8B1B,CAAC"}
@@ -0,0 +1,2 @@
1
+ export { Profile, type FoundSelector, type ApplyResult } from './profile.js';
2
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/lib/profile/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,KAAK,aAAa,EAAE,KAAK,WAAW,EAAE,MAAM,cAAc,CAAC"}
@@ -0,0 +1,46 @@
1
+ import { BaseSelector } from '@lsst/pik-core';
2
+ import { FileResult } from '../scanner.js';
3
+ import { ProfileMapping } from '../types.js';
4
+ import { ProfileStatus } from '../types/profile-status.js';
5
+ /**
6
+ * Result of finding a selector by name
7
+ */
8
+ export interface FoundSelector {
9
+ file: FileResult;
10
+ selector: BaseSelector;
11
+ }
12
+ /**
13
+ * Result of applying a single selector change
14
+ */
15
+ export interface ApplyResult {
16
+ selectorName: string;
17
+ optionName: string;
18
+ filePath: string;
19
+ success: boolean;
20
+ error?: string;
21
+ }
22
+ /**
23
+ * Represents a profile that maps selector names to option names
24
+ */
25
+ export declare class Profile {
26
+ readonly name: string;
27
+ readonly mapping: ProfileMapping;
28
+ constructor(name: string, mapping: ProfileMapping);
29
+ /**
30
+ * Find a selector by name across scan results
31
+ */
32
+ static findSelector(results: FileResult[], selectorName: string): FoundSelector | null;
33
+ /**
34
+ * Compute the status of this profile against scan results
35
+ */
36
+ computeStatus(results: FileResult[]): ProfileStatus;
37
+ /**
38
+ * Apply this profile to the scanned files
39
+ */
40
+ apply(results: FileResult[]): Promise<ApplyResult[]>;
41
+ /**
42
+ * Compute statuses for multiple profiles
43
+ */
44
+ static computeAllStatuses(profiles: Record<string, ProfileMapping>, results: FileResult[]): ProfileStatus[];
45
+ }
46
+ //# sourceMappingURL=profile.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"profile.d.ts","sourceRoot":"","sources":["../../../src/lib/profile/profile.ts"],"names":[],"mappings":"AACA,OAAO,EAAU,KAAK,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAC3D,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AAChD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAClD,OAAO,KAAK,EAAE,aAAa,EAAyB,MAAM,4BAA4B,CAAC;AAEvF;;GAEG;AACH,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,UAAU,CAAC;IACjB,QAAQ,EAAE,YAAY,CAAC;CACxB;AAED;;GAEG;AACH,MAAM,WAAW,WAAW;IAC1B,YAAY,EAAE,MAAM,CAAC;IACrB,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,OAAO,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED;;GAEG;AACH,qBAAa,OAAO;aAEA,IAAI,EAAE,MAAM;aACZ,OAAO,EAAE,cAAc;gBADvB,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,cAAc;IAGzC;;OAEG;IACH,MAAM,CAAC,YAAY,CAAC,OAAO,EAAE,UAAU,EAAE,EAAE,YAAY,EAAE,MAAM,GAAG,aAAa,GAAG,IAAI;IAUtF;;OAEG;IACH,aAAa,CAAC,OAAO,EAAE,UAAU,EAAE,GAAG,aAAa;IAuDnD;;OAEG;IACG,KAAK,CAAC,OAAO,EAAE,UAAU,EAAE,GAAG,OAAO,CAAC,WAAW,EAAE,CAAC;IAsG1D;;OAEG;IACH,MAAM,CAAC,kBAAkB,CACvB,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,cAAc,CAAC,EACxC,OAAO,EAAE,UAAU,EAAE,GACpB,aAAa,EAAE;CAKnB"}
@@ -1,8 +1,8 @@
1
- import { type Selector } from '@lsst/pik-core';
2
- import type { SelectConfig } from './types.js';
1
+ import { BaseSelector } from '@lsst/pik-core';
2
+ import { SelectConfig } from './types.js';
3
3
  export interface FileResult {
4
4
  path: string;
5
- selectors: Selector[];
5
+ selectors: BaseSelector[];
6
6
  content: string;
7
7
  }
8
8
  export declare class Scanner {
@@ -1 +1 @@
1
- {"version":3,"file":"scanner.d.ts","sourceRoot":"","sources":["../../src/lib/scanner.ts"],"names":[],"mappings":"AAEA,OAAO,EAAU,KAAK,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AACvD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAE/C,MAAM,WAAW,UAAU;IACzB,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,QAAQ,EAAE,CAAC;IACtB,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,qBAAa,OAAO;IACN,OAAO,CAAC,QAAQ,CAAC,MAAM;gBAAN,MAAM,EAAE,YAAY;IAE3C,IAAI,CAAC,GAAG,GAAE,MAAsB,GAAG,OAAO,CAAC,UAAU,EAAE,CAAC;CAqB/D"}
1
+ {"version":3,"file":"scanner.d.ts","sourceRoot":"","sources":["../../src/lib/scanner.ts"],"names":[],"mappings":"AAEA,OAAO,EAAU,KAAK,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAC3D,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAE/C,MAAM,WAAW,UAAU;IACzB,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,YAAY,EAAE,CAAC;IAC1B,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,qBAAa,OAAO;IACN,OAAO,CAAC,QAAQ,CAAC,MAAM;gBAAN,MAAM,EAAE,YAAY;IAE3C,IAAI,CAAC,GAAG,GAAE,MAAsB,GAAG,OAAO,CAAC,UAAU,EAAE,CAAC;CAqB/D"}
@@ -0,0 +1,35 @@
1
+ /**
2
+ * Status of a single selector mapping within a profile
3
+ */
4
+ export interface SelectorMappingStatus {
5
+ /** Name of the selector */
6
+ selectorName: string;
7
+ /** Expected option from the profile */
8
+ expectedOption: string;
9
+ /** Current active option in the file */
10
+ currentOption: string | null;
11
+ /** Path to the file containing the selector */
12
+ filePath: string;
13
+ /** Whether the current option matches the expected option */
14
+ isMatched: boolean;
15
+ /** Error message if selector or option not found */
16
+ error?: string;
17
+ }
18
+ /**
19
+ * Status of a profile
20
+ */
21
+ export interface ProfileStatus {
22
+ /** Profile name */
23
+ name: string;
24
+ /** Status of each selector mapping */
25
+ mappings: SelectorMappingStatus[];
26
+ /** All mappings match their expected options */
27
+ isFullyActive: boolean;
28
+ /** Some mappings match their expected options */
29
+ isPartiallyActive: boolean;
30
+ /** Number of matched mappings */
31
+ matchedCount: number;
32
+ /** Total number of mappings in the profile */
33
+ totalCount: number;
34
+ }
35
+ //# sourceMappingURL=profile-status.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"profile-status.d.ts","sourceRoot":"","sources":["../../../src/lib/types/profile-status.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,MAAM,WAAW,qBAAqB;IACpC,2BAA2B;IAC3B,YAAY,EAAE,MAAM,CAAC;IACrB,uCAAuC;IACvC,cAAc,EAAE,MAAM,CAAC;IACvB,wCAAwC;IACxC,aAAa,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,+CAA+C;IAC/C,QAAQ,EAAE,MAAM,CAAC;IACjB,6DAA6D;IAC7D,SAAS,EAAE,OAAO,CAAC;IACnB,oDAAoD;IACpD,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED;;GAEG;AACH,MAAM,WAAW,aAAa;IAC5B,mBAAmB;IACnB,IAAI,EAAE,MAAM,CAAC;IACb,sCAAsC;IACtC,QAAQ,EAAE,qBAAqB,EAAE,CAAC;IAClC,gDAAgD;IAChD,aAAa,EAAE,OAAO,CAAC;IACvB,iDAAiD;IACjD,iBAAiB,EAAE,OAAO,CAAC;IAC3B,iCAAiC;IACjC,YAAY,EAAE,MAAM,CAAC;IACrB,8CAA8C;IAC9C,UAAU,EAAE,MAAM,CAAC;CACpB"}
@@ -1,9 +1,23 @@
1
+ /**
2
+ * Maps selector names to option names within a profile
3
+ */
4
+ export interface ProfileMapping {
5
+ [selectorName: string]: string;
6
+ }
7
+ /**
8
+ * Collection of named profiles
9
+ */
10
+ export interface ProfilesConfig {
11
+ [profileName: string]: ProfileMapping;
12
+ }
1
13
  /**
2
14
  * Configuration for the select plugin
3
15
  */
4
16
  export interface SelectConfig {
5
17
  /** File patterns to scan for @pik markers */
6
18
  include: string[];
19
+ /** Named profiles that apply multiple selector options at once */
20
+ profiles?: ProfilesConfig;
7
21
  }
8
22
  /**
9
23
  * Extend PikConfig to include select plugin config
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/lib/types.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,MAAM,WAAW,YAAY;IAC3B,6CAA6C;IAC7C,OAAO,EAAE,MAAM,EAAE,CAAC;CACnB;AAED;;GAEG;AACH,OAAO,QAAQ,gBAAgB,CAAC;IAC9B,UAAU,SAAS;QACjB,MAAM,CAAC,EAAE,YAAY,CAAC;KACvB;CACF"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/lib/types.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,MAAM,WAAW,cAAc;IAC7B,CAAC,YAAY,EAAE,MAAM,GAAG,MAAM,CAAC;CAChC;AAED;;GAEG;AACH,MAAM,WAAW,cAAc;IAC7B,CAAC,WAAW,EAAE,MAAM,GAAG,cAAc,CAAC;CACvC;AAED;;GAEG;AACH,MAAM,WAAW,YAAY;IAC3B,6CAA6C;IAC7C,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,kEAAkE;IAClE,QAAQ,CAAC,EAAE,cAAc,CAAC;CAC3B;AAED;;GAEG;AACH,OAAO,QAAQ,gBAAgB,CAAC;IAC9B,UAAU,SAAS;QACjB,MAAM,CAAC,EAAE,YAAY,CAAC;KACvB;CACF"}
@@ -0,0 +1,12 @@
1
+ import { PikConfig } from '@lsst/pik-core';
2
+ import { SelectConfig } from '../types.js';
3
+ /**
4
+ * Validates that config has a select section.
5
+ * Exits with error if not found.
6
+ *
7
+ * @returns The select config (type-narrowed)
8
+ */
9
+ export declare function requireSelectConfig(config: PikConfig | null, options?: {
10
+ json?: boolean;
11
+ }): SelectConfig;
12
+ //# sourceMappingURL=requireSelectConfig.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"requireSelectConfig.d.ts","sourceRoot":"","sources":["../../../src/lib/validation/requireSelectConfig.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAChD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAEhD;;;;;GAKG;AACH,wBAAgB,mBAAmB,CACjC,MAAM,EAAE,SAAS,GAAG,IAAI,EACxB,OAAO,CAAC,EAAE;IAAE,IAAI,CAAC,EAAE,OAAO,CAAA;CAAE,GAC3B,YAAY,CAWd"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lsst/pik-plugin-select",
3
- "version": "0.6.7",
3
+ "version": "0.8.0",
4
4
  "description": "Config selector plugin for pik CLI",
5
5
  "type": "module",
6
6
  "license": "MIT",