@lenne.tech/cli 1.50.6 → 1.51.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.
Files changed (44) hide show
  1. package/build/commands/claude/plugins.js +29 -16
  2. package/build/commands/cli/create.js +11 -3
  3. package/build/commands/cli/rename.js +10 -2
  4. package/build/commands/dev/up.js +9 -0
  5. package/build/commands/directus/docker-setup.js +2 -2
  6. package/build/commands/directus/remove.js +1 -1
  7. package/build/commands/directus/typegen.js +31 -4
  8. package/build/commands/frontend/convert-mode.js +16 -0
  9. package/build/commands/frontend/nuxt.js +1 -1
  10. package/build/commands/fullstack/add-api.js +1 -2
  11. package/build/commands/fullstack/add-app.js +1 -1
  12. package/build/commands/fullstack/convert-mode.js +20 -0
  13. package/build/commands/fullstack/init.js +9 -6
  14. package/build/commands/git/clean.js +3 -2
  15. package/build/commands/git/create.js +7 -2
  16. package/build/commands/git/force-pull.js +4 -2
  17. package/build/commands/git/get.js +19 -6
  18. package/build/commands/git/rebase.js +13 -3
  19. package/build/commands/git/rename.js +5 -4
  20. package/build/commands/git/reset.js +18 -10
  21. package/build/commands/git/squash.js +4 -2
  22. package/build/commands/git/update.js +2 -1
  23. package/build/commands/npm/reinit.js +3 -3
  24. package/build/commands/server/add-property.js +10 -0
  25. package/build/commands/server/convert-mode.js +16 -0
  26. package/build/commands/server/permissions.js +10 -10
  27. package/build/commands/starter/chrome-extension.js +8 -2
  28. package/build/commands/typescript/create.js +3 -2
  29. package/build/extensions/api-mode.js +1 -1
  30. package/build/extensions/frontend-helper.js +25 -13
  31. package/build/extensions/git.js +25 -14
  32. package/build/extensions/server.js +14 -5
  33. package/build/extensions/template.js +8 -5
  34. package/build/extensions/typescript.js +4 -3
  35. package/build/lib/caddy.js +72 -2
  36. package/build/lib/claude-cli.js +17 -2
  37. package/build/lib/exec-args.js +117 -0
  38. package/build/lib/plugin-permissions.js +201 -0
  39. package/build/lib/plugin-utils.js +96 -36
  40. package/build/lib/safe-paths.js +57 -0
  41. package/build/lib/unknown-options.js +53 -0
  42. package/build/lib/workspace-integration.js +4 -2
  43. package/docs/commands.md +12 -0
  44. package/package.json +6 -6
@@ -22,11 +22,11 @@ exports.setupPermissions = setupPermissions;
22
22
  * Handles reading plugin contents, permissions, and post-installation setup
23
23
  */
24
24
  const fs_1 = require("fs");
25
- const os_1 = require("os");
26
25
  const path_1 = require("path");
27
26
  const claude_cli_1 = require("./claude-cli");
28
27
  const json_utils_1 = require("./json-utils");
29
28
  const platform_1 = require("./platform");
29
+ const plugin_permissions_1 = require("./plugin-permissions");
30
30
  const shell_config_1 = require("./shell-config");
31
31
  /**
32
32
  * Empty plugin contents constant (used for failed installations)
@@ -34,6 +34,7 @@ const shell_config_1 = require("./shell-config");
34
34
  exports.EMPTY_PLUGIN_CONTENTS = {
35
35
  agents: [],
36
36
  commands: [],
37
+ formerPermissions: [],
37
38
  hooks: 0,
38
39
  mcpServers: [],
39
40
  permissions: [],
@@ -312,10 +313,11 @@ function processPostInstall(pluginName, toolbox) {
312
313
  * @returns Plugin contents with skills, commands, agents, hooks, etc.
313
314
  */
314
315
  function readPluginContents(marketplaceName, pluginName) {
315
- const pluginDir = (0, path_1.join)((0, os_1.homedir)(), '.claude', 'plugins', 'marketplaces', marketplaceName, 'plugins', pluginName);
316
+ const pluginDir = (0, path_1.join)((0, claude_cli_1.getClaudeConfigDir)(), 'plugins', 'marketplaces', marketplaceName, 'plugins', pluginName);
316
317
  const result = {
317
318
  agents: [],
318
319
  commands: [],
320
+ formerPermissions: [],
319
321
  hooks: 0,
320
322
  mcpServers: [],
321
323
  permissions: [],
@@ -375,6 +377,9 @@ function readPluginContents(marketplaceName, pluginName) {
375
377
  if (pluginPerms === null || pluginPerms === void 0 ? void 0 : pluginPerms.permissions) {
376
378
  result.permissions = pluginPerms.permissions.map((p) => p.pattern);
377
379
  }
380
+ if (Array.isArray(pluginPerms === null || pluginPerms === void 0 ? void 0 : pluginPerms.formerPatterns)) {
381
+ result.formerPermissions = pluginPerms.formerPatterns.filter((p) => typeof p === 'string');
382
+ }
378
383
  return result;
379
384
  }
380
385
  /**
@@ -392,57 +397,112 @@ function safeExecCommand(command) {
392
397
  }
393
398
  }
394
399
  /**
395
- * Setup permissions in settings.json
396
- * Only adds new permissions, does not remove existing ones
397
- * (User may have customized permissions that should be preserved)
398
- * @param requiredPermissions - Array of permission patterns to add
400
+ * Sync a plugin's permission rules into Claude Code's user settings.json.
401
+ *
402
+ * Adds the rules the plugin ships and removes the rules lt wrote for the plugin that it no
403
+ * longer ships. Rules the user wrote stay untouched. How the two are told apart:
404
+ * plugin-permissions.ts. Rules with `${CLAUDE_PLUGIN_ROOT}` are never written.
405
+ * @param plugin - Marketplace and name of the plugin
406
+ * @param rules - `permissions` and `formerPermissions` from the plugin's permissions.json, may be empty
399
407
  * @param error - Error print function from toolbox
400
- * @returns Result with added and existing permissions
408
+ * @param options - Overrides for the Claude config directory and the ledger (tests)
409
+ * @returns Added, existing and removed rules
401
410
  */
402
- function setupPermissions(requiredPermissions, error) {
403
- const settingsPath = (0, path_1.join)((0, os_1.homedir)(), '.claude', 'settings.json');
411
+ function setupPermissions(plugin, rules, error, options = {}) {
412
+ var _a, _b, _c, _d, _e, _f, _g;
413
+ const claudeConfigDir = (_a = options.claudeConfigDir) !== null && _a !== void 0 ? _a : (0, claude_cli_1.getClaudeConfigDir)();
414
+ const ledgerPath = (_b = options.ledgerPath) !== null && _b !== void 0 ? _b : (0, plugin_permissions_1.getPermissionLedgerPath)();
415
+ const settingsPath = (0, path_1.join)(claudeConfigDir, 'settings.json');
416
+ const pluginKey = (0, plugin_permissions_1.pluginPermissionKey)(plugin.marketplaceName, plugin.pluginName);
417
+ const pluginRoot = (0, plugin_permissions_1.pluginCacheRootPattern)(claudeConfigDir, plugin.marketplaceName, plugin.pluginName);
418
+ const resolve = (rule) => (0, plugin_permissions_1.resolvePluginRootVariable)(rule, pluginRoot);
419
+ const skipped = [...new Set(rules.permissions.filter(plugin_permissions_1.hasPluginRootVariable))];
420
+ const written = [...new Set(rules.permissions.filter((rule) => !(0, plugin_permissions_1.hasPluginRootVariable)(rule)))];
421
+ const failed = {
422
+ added: [],
423
+ existing: [],
424
+ removed: [],
425
+ settingsPath,
426
+ skipped,
427
+ success: false,
428
+ written,
429
+ };
404
430
  try {
405
- // Read existing settings
406
431
  let settings = {};
407
432
  if ((0, fs_1.existsSync)(settingsPath)) {
408
- const content = (0, fs_1.readFileSync)(settingsPath, 'utf-8');
409
- const parsed = (0, json_utils_1.safeJsonParse)(content);
410
- if (parsed) {
411
- settings = parsed;
433
+ const parsed = (0, json_utils_1.safeJsonParse)((0, fs_1.readFileSync)(settingsPath, 'utf-8'));
434
+ if (!isPlainObject(parsed)) {
435
+ // Writing now would replace every other setting in the file with an empty object.
436
+ error(`Could not configure permissions: ${settingsPath} is not a valid JSON object, left unchanged`);
437
+ return failed;
412
438
  }
439
+ settings = parsed;
413
440
  }
414
- // Ensure permissions.allow exists
415
- if (!settings.permissions) {
416
- settings.permissions = {};
441
+ const perms = (_c = settings.permissions) !== null && _c !== void 0 ? _c : {};
442
+ if (!isPlainObject(perms)) {
443
+ error(`Could not configure permissions: "permissions" in ${settingsPath} is not an object, left unchanged`);
444
+ return failed;
417
445
  }
418
- const perms = settings.permissions;
419
- if (!perms.allow) {
420
- perms.allow = [];
446
+ const allow = (_d = perms.allow) !== null && _d !== void 0 ? _d : [];
447
+ if (!Array.isArray(allow) || !allow.every((rule) => typeof rule === 'string')) {
448
+ error(`Could not configure permissions: "permissions.allow" in ${settingsPath} is not a list of rules, left unchanged`);
449
+ return failed;
421
450
  }
422
- const currentAllowList = perms.allow;
423
- // Determine what to add
424
- const added = [];
425
- const existing = [];
426
- requiredPermissions.forEach((perm) => {
427
- if (currentAllowList.includes(perm)) {
428
- existing.push(perm);
429
- }
430
- else {
431
- added.push(perm);
432
- }
451
+ const ledgerEntries = (0, plugin_permissions_1.readLedgerEntries)(ledgerPath, settingsPath);
452
+ const entry = (_e = ledgerEntries[pluginKey]) !== null && _e !== void 0 ? _e : null;
453
+ const plan = (0, plugin_permissions_1.planPermissionSync)({
454
+ allow,
455
+ formerApplied: (_f = entry === null || entry === void 0 ? void 0 : entry.formerApplied) !== null && _f !== void 0 ? _f : [],
456
+ // A former rule with the variable may sit in settings.json in either spelling.
457
+ formerRules: [...new Set([...rules.formerPermissions, ...rules.formerPermissions.map(resolve)])],
458
+ listed: written,
459
+ owned: (_g = entry === null || entry === void 0 ? void 0 : entry.owned) !== null && _g !== void 0 ? _g : null,
460
+ ownedByOthers: Object.entries(ledgerEntries)
461
+ .filter(([key]) => key !== pluginKey)
462
+ .flatMap(([, other]) => other.owned),
463
+ withdrawnRules: [...skipped, ...skipped.map(resolve)],
433
464
  });
434
- // Add new permissions
435
- if (added.length > 0) {
436
- perms.allow = [...currentAllowList, ...added];
465
+ if (plan.added.length > 0 || plan.removed.length > 0) {
466
+ perms.allow = plan.allow;
467
+ settings.permissions = perms;
468
+ (0, fs_1.mkdirSync)((0, path_1.dirname)(settingsPath), { recursive: true });
437
469
  (0, fs_1.writeFileSync)(settingsPath, JSON.stringify(settings, null, 2));
438
470
  }
439
- return { added, existing, success: true };
471
+ // Only after settings.json: a ledger entry must never claim a write that did not happen.
472
+ // If this write fails, the rules added in this run stay unrecorded, and a later run
473
+ // will not remove them when the plugin drops them. The message says so.
474
+ if (JSON.stringify(entry) !== JSON.stringify(plan.ledger)) {
475
+ try {
476
+ (0, plugin_permissions_1.recordLedgerEntry)(ledgerPath, settingsPath, pluginKey, plan.ledger);
477
+ }
478
+ catch (err) {
479
+ error(`Could not record lt's permission rules in ${ledgerPath}: ${err.message}. ` +
480
+ 'Rules added in this run will not be removed automatically when the plugin drops them.');
481
+ }
482
+ }
483
+ return {
484
+ added: plan.added,
485
+ existing: plan.existing,
486
+ removed: plan.removed,
487
+ settingsPath,
488
+ skipped,
489
+ success: true,
490
+ written,
491
+ };
440
492
  }
441
493
  catch (err) {
442
494
  error(`Could not configure permissions: ${err.message}`);
443
- return { added: [], existing: [], success: false };
495
+ return failed;
444
496
  }
445
497
  }
498
+ /**
499
+ * Whether a parsed JSON value is an object (not an array, not null)
500
+ * @param value - Parsed JSON value
501
+ * @returns true for a plain object
502
+ */
503
+ function isPlainObject(value) {
504
+ return typeof value === 'object' && value !== null && !Array.isArray(value);
505
+ }
446
506
  /**
447
507
  * Safely read and parse a JSON file
448
508
  * @param filePath - Path to the JSON file
@@ -0,0 +1,57 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.fileOpener = fileOpener;
4
+ exports.isPathInside = isPathInside;
5
+ exports.isSafePathSegment = isSafePathSegment;
6
+ exports.openFileWithDefaultApp = openFileWithDefaultApp;
7
+ /**
8
+ * Path checks for values that arrive from CLI flags or `lt.config` files. Those values can come
9
+ * from a repository the user cloned (a config file in a parent directory counts too) or from an
10
+ * AI agent running `lt` under a global permission rule, so they must not reach outside the
11
+ * project the command was asked to work on, and must not be handed to a shell.
12
+ */
13
+ const child_process_1 = require("child_process");
14
+ const path_1 = require("path");
15
+ /**
16
+ * The program and arguments that open a file with its default application, without a shell.
17
+ * Windows uses `explorer.exe`: `start` is a `cmd.exe` builtin, and `cmd.exe` re-parses its
18
+ * command line, where `&` in a file name (legal on Windows) would start a second command.
19
+ * @param platform - `process.platform`
20
+ * @param filePath - File to open
21
+ * @returns Program and argument list
22
+ */
23
+ function fileOpener(platform, filePath) {
24
+ if (platform === 'darwin')
25
+ return { args: [filePath], command: 'open' };
26
+ if (platform === 'win32')
27
+ return { args: [filePath], command: 'explorer.exe' };
28
+ return { args: [filePath], command: 'xdg-open' };
29
+ }
30
+ /**
31
+ * Whether `target` lies inside `base` (or is `base` itself), after resolving `..` segments.
32
+ * @param base - Directory the target must stay in
33
+ * @param target - Path to check
34
+ * @returns true if the target does not escape the base
35
+ */
36
+ function isPathInside(base, target) {
37
+ const rel = (0, path_1.relative)((0, path_1.resolve)(base), (0, path_1.resolve)(target));
38
+ return rel === '' || (!rel.startsWith('..') && !(0, path_1.isAbsolute)(rel));
39
+ }
40
+ /**
41
+ * Whether a name can be used as a single directory segment (a module or object name): no
42
+ * separators, not `.` or `..`, no NUL. `join(dir, name)` with anything else leaves `dir`.
43
+ * @param name - Name from a flag or a prompt
44
+ * @returns true if the name stays one segment below its parent
45
+ */
46
+ function isSafePathSegment(name) {
47
+ return Boolean(name) && name !== '.' && name !== '..' && !/[/\\\0]/.test(name);
48
+ }
49
+ /**
50
+ * Open a file with its default application. Fire and forget, like before; `explorer.exe` exits
51
+ * with 1 even on success, so errors are ignored.
52
+ * @param filePath - File to open
53
+ */
54
+ function openFileWithDefaultApp(filePath) {
55
+ const { args, command } = fileOpener(process.platform, filePath);
56
+ (0, child_process_1.execFile)(command, args, () => undefined);
57
+ }
@@ -0,0 +1,53 @@
1
+ "use strict";
2
+ /**
3
+ * Flags a command did not declare.
4
+ *
5
+ * gluegun parses argv with yargs-parser and no schema, so a misspelt flag is silently ignored.
6
+ * For `lt fullstack convert-mode` that converted more than asked: the plugin docs said
7
+ * `--skip-app`, the CLI only knows `--skip-frontend`, and with `--noConfirm` the frontend was
8
+ * converted although the caller had asked to skip it. A command that changes a project should
9
+ * refuse a flag it does not know rather than guess.
10
+ */
11
+ Object.defineProperty(exports, "__esModule", { value: true });
12
+ exports.findUnknownOptions = findUnknownOptions;
13
+ exports.formatUnknownOptions = formatUnknownOptions;
14
+ /** Flags every command accepts: gluegun's menu, `bin/lt`, the help interceptor, confirmations. */
15
+ const GLOBAL_OPTIONS = ['compiled-build', 'fromGluegunMenu', 'h', 'help', 'help-json', 'noConfirm'];
16
+ /**
17
+ * Options in `options` that are neither declared nor global. yargs-parser reports a kebab-case
18
+ * flag twice (`skip-app` and `skipApp`); it is reported once, in the spelling that was typed.
19
+ * @param options - `parameters.options` of the command
20
+ * @param declaredFlags - The command's flags, with or without leading dashes (e.g. from its `help`)
21
+ * @returns Unknown option names, without dashes
22
+ */
23
+ function findUnknownOptions(options, declaredFlags) {
24
+ const accepted = new Set([...declaredFlags.map((flag) => flag.replace(/^-+/, '')), ...GLOBAL_OPTIONS].map(toCamel));
25
+ const unknown = new Map();
26
+ for (const key of Object.keys(options)) {
27
+ const camel = toCamel(key);
28
+ if (!accepted.has(camel) && !unknown.has(camel)) {
29
+ unknown.set(camel, key);
30
+ }
31
+ }
32
+ return [...unknown.values()];
33
+ }
34
+ /**
35
+ * Error text for unknown options, with the right name where a known misspelling has one.
36
+ * @param unknown - Result of {@link findUnknownOptions}
37
+ * @param declaredFlags - The command's flags
38
+ * @param renamed - Known wrong name → right flag, without dashes (e.g. `skip-app` → `skip-frontend`)
39
+ * @returns Message naming each unknown flag and listing the known ones
40
+ */
41
+ function formatUnknownOptions(unknown, declaredFlags, renamed = {}) {
42
+ const listed = unknown.map((name) => (renamed[name] ? `--${name} (use --${renamed[name]})` : `--${name}`));
43
+ const known = declaredFlags.map((flag) => `--${flag.replace(/^-+/, '')}`).join(', ');
44
+ return `Unknown option${unknown.length > 1 ? 's' : ''}: ${listed.join(', ')}. Nothing was changed.\nKnown options: ${known}`;
45
+ }
46
+ /**
47
+ * `skip-app` → `skipApp`, as yargs-parser expands it
48
+ * @param name - Option name
49
+ * @returns camelCase form
50
+ */
51
+ function toCamel(name) {
52
+ return name.replace(/-([a-z0-9])/g, (_, char) => char.toUpperCase());
53
+ }
@@ -83,6 +83,7 @@ exports.writeApiConfig = writeApiConfig;
83
83
  const yaml = __importStar(require("js-yaml"));
84
84
  const path_1 = require("path");
85
85
  const ensure_root_dockerignore_1 = require("./ensure-root-dockerignore");
86
+ const exec_args_1 = require("./exec-args");
86
87
  const hoist_workspace_pnpm_config_1 = require("./hoist-workspace-pnpm-config");
87
88
  const remove_nested_lockfiles_1 = require("./remove-nested-lockfiles");
88
89
  /**
@@ -321,7 +322,7 @@ function reportWorkspaceConflicts(conflicts, warn) {
321
322
  */
322
323
  function runExperimentalNestBaseRename(options) {
323
324
  return __awaiter(this, void 0, void 0, function* () {
324
- const { apiDir, patching, projectDir, system } = options;
325
+ const { apiDir, patching, projectDir, run = exec_args_1.spawnArgs } = options;
325
326
  // setupServerForFullstack already patched package.json to set
326
327
  // `name = projectDir`. The rename planner reads that name as the
327
328
  // "old" slug, which would short-circuit the rest of the rewrites
@@ -333,7 +334,8 @@ function runExperimentalNestBaseRename(options) {
333
334
  return config;
334
335
  });
335
336
  try {
336
- yield system.run(`cd ${apiDir} && bun run rename ${projectDir}`);
337
+ // No shell: projectDir can come from --name or the directory name (see exec-args.ts).
338
+ yield run('bun', ['run', 'rename', projectDir], { cwd: apiDir });
337
339
  return { attempted: true };
338
340
  }
339
341
  catch (err) {
package/docs/commands.md CHANGED
@@ -426,6 +426,8 @@ lt dev init --skip-install # do NOT auto-run `lt dev install` first
426
426
 
427
427
  Start API + App behind Caddy. Allocates internal ports (4000+), spawns processes detached, persists PIDs to `<root>/.lt-dev/state.json`.
428
428
 
429
+ Caddy listens on the loopback interfaces only (`default_bind 127.0.0.1 [::1]` in the global options block of `~/.lenneTech/Caddyfile`), so a running stack is not reachable from the local network. `lt dev up` adds the block to a Caddyfile written by an older CLI and reloads Caddy. A `default_bind` you set yourself is left as it is.
430
+
429
431
  **Usage:**
430
432
  ```bash
431
433
  lt dev up
@@ -1283,6 +1285,8 @@ lt fullstack convert-mode --to <vendor|npm> [options]
1283
1285
  | `--dry-run` | Show the resolved plan without making any changes |
1284
1286
  | `--noConfirm` | Skip confirmation prompt |
1285
1287
 
1288
+ Any other flag is refused with exit code 1 before anything is converted, and a known wrong name gets the right one in the message (`--skip-app` → `--skip-frontend`, `--api-version` → `--framework-version`, …). Before, an unknown flag was ignored, so a misspelt `--skip-*` together with `--noConfirm` converted the side it was meant to skip. `lt server convert-mode` and `lt frontend convert-mode` refuse unknown flags the same way.
1289
+
1286
1290
  **Subproject Detection:**
1287
1291
 
1288
1292
  The command searches for subprojects in this order:
@@ -1994,6 +1998,14 @@ To list or uninstall installed plugins, use Claude Code's own `/plugin` menu.
1994
1998
  **Default Behavior:**
1995
1999
  When run without a plugin name, all lenne.Tech plugins plus recommended external plugins (like `typescript-lsp`) are installed automatically. Plugins from configured `autoInstall` marketplaces are added as well, but only for users who can access the underlying repository — everyone else silently skips them.
1996
2000
 
2001
+ **Permission rules:**
2002
+ A plugin's `permissions.json` is synced into Claude Code's user `settings.json` (`$CLAUDE_CONFIG_DIR/settings.json`, default `~/.claude/settings.json`). These rules apply in every project, so the sync goes both ways:
2003
+
2004
+ - Rules the plugin ships are added, except rules that use `${CLAUDE_PLUGIN_ROOT}`. Claude Code does not expand that variable in permission rules, so such a rule never matches, and replacing it with the plugin's cache directory plus a wildcard for the version folder would allow any file on disk (a wildcard also matches `../`). lt therefore writes neither form and removes both if present; Claude asks before running those plugin scripts.
2005
+ - Rules lt wrote for a plugin that the plugin no longer ships are removed, and listed as `removed:` in the output. lt records the rules it wrote in `~/.lenneTech/claude-plugin-permissions.json`. Rules you added yourself are never removed.
2006
+ - Rules older lt versions installed before that record existed are named by the plugin itself, in an optional `formerPatterns` list of strings in its `permissions.json` (e.g. `"formerPatterns": ["Bash(curl:*)", "Bash(rm:*)"]`). lt removes each of them once, unless the plugin still ships it. Add one back by hand if you want it; lt leaves it alone after that.
2007
+ - If `settings.json` is not valid JSON, lt leaves it unchanged and reports an error.
2008
+
1997
2009
  **Examples:**
1998
2010
  ```bash
1999
2011
  # Install all recommended plugins (lenne.Tech + recommended external)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lenne.tech/cli",
3
- "version": "1.50.6",
3
+ "version": "1.51.0",
4
4
  "description": "lenne.Tech CLI: lt",
5
5
  "keywords": [
6
6
  "lenne.Tech",
@@ -58,13 +58,13 @@
58
58
  "bin"
59
59
  ],
60
60
  "dependencies": {
61
- "@aws-sdk/client-s3": "3.1149.0",
61
+ "@aws-sdk/client-s3": "3.1152.0",
62
62
  "@lenne.tech/cli-plugin-helper": "0.0.14",
63
63
  "axios": "1.20.0",
64
64
  "bcrypt": "6.0.0",
65
65
  "cross-spawn": "7.0.6",
66
66
  "defuddle": "0.19.5",
67
- "ejs": "7.0.1",
67
+ "ejs": "7.0.2",
68
68
  "glob": "13.0.6",
69
69
  "gluegun": "5.2.2",
70
70
  "js-sha256": "1.0.0",
@@ -73,7 +73,7 @@
73
73
  "jsonc-parser": "3.3.1",
74
74
  "lodash": "4.18.1",
75
75
  "open": "11.0.4",
76
- "playwright-core": "1.64.0",
76
+ "playwright-core": "1.64.1",
77
77
  "ts-morph": "28.0.0",
78
78
  "ts-node": "10.9.2",
79
79
  "turndown": "7.2.4",
@@ -97,13 +97,13 @@
97
97
  "@types/lodash": "4.17.26",
98
98
  "@types/node": "26.6.5",
99
99
  "@types/turndown": "5.0.6",
100
- "eslint": "10.12.0",
100
+ "eslint": "10.13.0",
101
101
  "husky": "9.1.7",
102
102
  "jest": "30.5.2",
103
103
  "minimatch": "10.2.6",
104
104
  "prettier": "3.9.10",
105
105
  "rimraf": "6.1.3",
106
- "ts-jest": "29.4.14"
106
+ "ts-jest": "29.4.15"
107
107
  },
108
108
  "//overrides": {
109
109
  "semver@*": "Force latest semver 7.x across all sub-deps; gluegun@5.2.2 pins semver@7.7.0 which is stale - remove once gluegun updates its dep.",