safeword 0.8.6 → 0.8.9

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 (39) hide show
  1. package/dist/{check-I2J6THGQ.js → check-QMAGWUOA.js} +24 -22
  2. package/dist/check-QMAGWUOA.js.map +1 -0
  3. package/dist/{chunk-DES5CSPH.js → chunk-4URRFBUS.js} +10 -10
  4. package/dist/chunk-4URRFBUS.js.map +1 -0
  5. package/dist/{chunk-DXT6TWW4.js → chunk-CLSGXTOL.js} +232 -435
  6. package/dist/chunk-CLSGXTOL.js.map +1 -0
  7. package/dist/{chunk-W66Z3C5H.js → chunk-FJYRWU2V.js} +5 -5
  8. package/dist/chunk-FJYRWU2V.js.map +1 -0
  9. package/dist/{chunk-VXKJ5ZIV.js → chunk-KQ6BLN6W.js} +172 -155
  10. package/dist/chunk-KQ6BLN6W.js.map +1 -0
  11. package/dist/cli.js +6 -6
  12. package/dist/cli.js.map +1 -1
  13. package/dist/{diff-4YFDNEZB.js → diff-2T7UDES7.js} +12 -12
  14. package/dist/diff-2T7UDES7.js.map +1 -0
  15. package/dist/index.d.ts +2 -2
  16. package/dist/{reset-QVERBAQJ.js → reset-QRXG7KZZ.js} +8 -8
  17. package/dist/reset-QRXG7KZZ.js.map +1 -0
  18. package/dist/{setup-ZSMZ7HZG.js → setup-QUUJ7SH3.js} +8 -8
  19. package/dist/setup-QUUJ7SH3.js.map +1 -0
  20. package/dist/sync-ISBJ7X2T.js +9 -0
  21. package/dist/{upgrade-WILVVHUY.js → upgrade-FALAUUKE.js} +22 -10
  22. package/dist/upgrade-FALAUUKE.js.map +1 -0
  23. package/package.json +15 -14
  24. package/templates/SAFEWORD.md +4 -2
  25. package/templates/commands/cleanup-zombies.md +48 -0
  26. package/templates/guides/zombie-process-cleanup.md +40 -24
  27. package/templates/scripts/cleanup-zombies.sh +222 -0
  28. package/templates/scripts/lint-md.sh +0 -0
  29. package/dist/check-I2J6THGQ.js.map +0 -1
  30. package/dist/chunk-DES5CSPH.js.map +0 -1
  31. package/dist/chunk-DXT6TWW4.js.map +0 -1
  32. package/dist/chunk-VXKJ5ZIV.js.map +0 -1
  33. package/dist/chunk-W66Z3C5H.js.map +0 -1
  34. package/dist/diff-4YFDNEZB.js.map +0 -1
  35. package/dist/reset-QVERBAQJ.js.map +0 -1
  36. package/dist/setup-ZSMZ7HZG.js.map +0 -1
  37. package/dist/sync-VQW5DSTV.js +0 -9
  38. package/dist/upgrade-WILVVHUY.js.map +0 -1
  39. /package/dist/{sync-VQW5DSTV.js.map → sync-ISBJ7X2T.js.map} +0 -0
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  isNewerVersion
3
- } from "./chunk-W66Z3C5H.js";
3
+ } from "./chunk-FJYRWU2V.js";
4
4
  import {
5
5
  createProjectContext,
6
6
  header,
@@ -9,22 +9,22 @@ import {
9
9
  reconcile,
10
10
  success,
11
11
  warn
12
- } from "./chunk-VXKJ5ZIV.js";
12
+ } from "./chunk-KQ6BLN6W.js";
13
13
  import {
14
14
  SAFEWORD_SCHEMA,
15
15
  exists,
16
16
  readFileSafe
17
- } from "./chunk-DXT6TWW4.js";
17
+ } from "./chunk-CLSGXTOL.js";
18
18
  import {
19
19
  VERSION
20
20
  } from "./chunk-ORQHKDT2.js";
21
21
 
22
22
  // src/commands/check.ts
23
- import { join } from "path";
23
+ import nodePath from "path";
24
24
  function findMissingFiles(cwd, actions) {
25
25
  const issues = [];
26
26
  for (const action of actions) {
27
- if (action.type === "write" && !exists(join(cwd, action.path))) {
27
+ if (action.type === "write" && !exists(nodePath.join(cwd, action.path))) {
28
28
  issues.push(`Missing: ${action.path}`);
29
29
  }
30
30
  }
@@ -34,14 +34,14 @@ function findMissingPatches(cwd, actions) {
34
34
  const issues = [];
35
35
  for (const action of actions) {
36
36
  if (action.type !== "text-patch") continue;
37
- const fullPath = join(cwd, action.path);
38
- if (!exists(fullPath)) {
39
- issues.push(`${action.path} file missing`);
40
- } else {
37
+ const fullPath = nodePath.join(cwd, action.path);
38
+ if (exists(fullPath)) {
41
39
  const content = readFileSafe(fullPath) ?? "";
42
40
  if (action.definition && !content.includes(action.definition.marker)) {
43
41
  issues.push(`${action.path} missing safeword link`);
44
42
  }
43
+ } else {
44
+ issues.push(`${action.path} file missing`);
45
45
  }
46
46
  }
47
47
  return issues;
@@ -49,40 +49,42 @@ function findMissingPatches(cwd, actions) {
49
49
  async function checkLatestVersion(timeout = 3e3) {
50
50
  try {
51
51
  const controller = new AbortController();
52
- const timeoutId = setTimeout(() => controller.abort(), timeout);
52
+ const timeoutId = setTimeout(() => {
53
+ controller.abort();
54
+ }, timeout);
53
55
  const response = await fetch("https://registry.npmjs.org/safeword/latest", {
54
56
  signal: controller.signal
55
57
  });
56
58
  clearTimeout(timeoutId);
57
- if (!response.ok) return null;
59
+ if (!response.ok) return void 0;
58
60
  const data = await response.json();
59
- return data.version ?? null;
61
+ return data.version ?? void 0;
60
62
  } catch {
61
- return null;
63
+ return void 0;
62
64
  }
63
65
  }
64
66
  async function checkHealth(cwd) {
65
- const safewordDir = join(cwd, ".safeword");
66
- if (!exists(safewordDir)) {
67
+ const safewordDirectory = nodePath.join(cwd, ".safeword");
68
+ if (!exists(safewordDirectory)) {
67
69
  return {
68
70
  configured: false,
69
- projectVersion: null,
71
+ projectVersion: void 0,
70
72
  cliVersion: VERSION,
71
73
  updateAvailable: false,
72
- latestVersion: null,
74
+ latestVersion: void 0,
73
75
  issues: [],
74
76
  missingPackages: []
75
77
  };
76
78
  }
77
- const versionPath = join(safewordDir, "version");
78
- const projectVersion = readFileSafe(versionPath)?.trim() ?? null;
79
+ const versionPath = nodePath.join(safewordDirectory, "version");
80
+ const projectVersion = readFileSafe(versionPath)?.trim() ?? void 0;
79
81
  const ctx = createProjectContext(cwd);
80
82
  const result = await reconcile(SAFEWORD_SCHEMA, "upgrade", ctx, { dryRun: true });
81
83
  const issues = [
82
84
  ...findMissingFiles(cwd, result.actions),
83
85
  ...findMissingPatches(cwd, result.actions)
84
86
  ];
85
- if (!exists(join(cwd, ".claude", "settings.json"))) {
87
+ if (!exists(nodePath.join(cwd, ".claude", "settings.json"))) {
86
88
  issues.push("Missing: .claude/settings.json");
87
89
  }
88
90
  return {
@@ -90,7 +92,7 @@ async function checkHealth(cwd) {
90
92
  projectVersion,
91
93
  cliVersion: VERSION,
92
94
  updateAvailable: false,
93
- latestVersion: null,
95
+ latestVersion: void 0,
94
96
  issues,
95
97
  missingPackages: result.packagesToInstall
96
98
  };
@@ -162,4 +164,4 @@ async function check(options) {
162
164
  export {
163
165
  check
164
166
  };
165
- //# sourceMappingURL=check-I2J6THGQ.js.map
167
+ //# sourceMappingURL=check-QMAGWUOA.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/commands/check.ts"],"sourcesContent":["/**\n * Check command - Verify project health and configuration\n *\n * Uses reconcile() with dryRun to detect missing files and configuration issues.\n */\n\nimport nodePath from 'node:path';\n\nimport { reconcile } from '../reconcile.js';\nimport { SAFEWORD_SCHEMA } from '../schema.js';\nimport { createProjectContext } from '../utils/context.js';\nimport { exists, readFileSafe } from '../utils/fs.js';\nimport { header, info, keyValue, success, warn } from '../utils/output.js';\nimport { isNewerVersion } from '../utils/version.js';\nimport { VERSION } from '../version.js';\n\nexport interface CheckOptions {\n offline?: boolean;\n}\n\n/**\n * Check for missing files from write actions\n * @param cwd\n * @param actions\n */\nfunction findMissingFiles(cwd: string, actions: { type: string; path: string }[]): string[] {\n const issues: string[] = [];\n for (const action of actions) {\n if (action.type === 'write' && !exists(nodePath.join(cwd, action.path))) {\n issues.push(`Missing: ${action.path}`);\n }\n }\n return issues;\n}\n\n/**\n * Check for missing text patch markers\n * @param cwd\n * @param actions\n */\nfunction findMissingPatches(\n cwd: string,\n actions: { type: string; path: string; definition?: { marker: string } }[],\n): string[] {\n const issues: string[] = [];\n for (const action of actions) {\n if (action.type !== 'text-patch') continue;\n\n const fullPath = nodePath.join(cwd, action.path);\n if (exists(fullPath)) {\n const content = readFileSafe(fullPath) ?? '';\n if (action.definition && !content.includes(action.definition.marker)) {\n issues.push(`${action.path} missing safeword link`);\n }\n } else {\n issues.push(`${action.path} file missing`);\n }\n }\n return issues;\n}\n\ninterface HealthStatus {\n configured: boolean;\n projectVersion: string | undefined;\n cliVersion: string;\n updateAvailable: boolean;\n latestVersion: string | undefined;\n issues: string[];\n missingPackages: string[];\n}\n\n/**\n * Check for latest version from npm (with timeout)\n * @param timeout\n */\nasync function checkLatestVersion(timeout = 3000): Promise<string | undefined> {\n try {\n const controller = new AbortController();\n const timeoutId = setTimeout(() => {\n controller.abort();\n }, timeout);\n\n const response = await fetch('https://registry.npmjs.org/safeword/latest', {\n signal: controller.signal,\n });\n\n clearTimeout(timeoutId);\n\n if (!response.ok) return undefined;\n\n const data = (await response.json()) as { version?: string };\n return data.version ?? undefined;\n } catch {\n return undefined;\n }\n}\n\n/**\n * Check project configuration health using reconcile dryRun\n * @param cwd\n */\nasync function checkHealth(cwd: string): Promise<HealthStatus> {\n const safewordDirectory = nodePath.join(cwd, '.safeword');\n\n // Check if configured\n if (!exists(safewordDirectory)) {\n return {\n configured: false,\n projectVersion: undefined,\n cliVersion: VERSION,\n updateAvailable: false,\n latestVersion: undefined,\n issues: [],\n missingPackages: [],\n };\n }\n\n // Read project version\n const versionPath = nodePath.join(safewordDirectory, 'version');\n const projectVersion = readFileSafe(versionPath)?.trim() ?? undefined;\n\n // Use reconcile with dryRun to detect issues\n const ctx = createProjectContext(cwd);\n const result = await reconcile(SAFEWORD_SCHEMA, 'upgrade', ctx, { dryRun: true });\n\n // Collect issues from write actions and text patches\n const issues: string[] = [\n ...findMissingFiles(cwd, result.actions),\n ...findMissingPatches(cwd, result.actions),\n ];\n\n // Check for missing .claude/settings.json\n if (!exists(nodePath.join(cwd, '.claude', 'settings.json'))) {\n issues.push('Missing: .claude/settings.json');\n }\n\n return {\n configured: true,\n projectVersion,\n cliVersion: VERSION,\n updateAvailable: false,\n latestVersion: undefined,\n issues,\n missingPackages: result.packagesToInstall,\n };\n}\n\n/**\n * Check for CLI updates and report status\n * @param health\n */\nasync function reportUpdateStatus(health: HealthStatus): Promise<void> {\n info('\\nChecking for updates...');\n const latestVersion = await checkLatestVersion();\n\n if (!latestVersion) {\n warn(\"Couldn't check for updates (offline?)\");\n return;\n }\n\n health.latestVersion = latestVersion;\n health.updateAvailable = isNewerVersion(health.cliVersion, latestVersion);\n\n if (health.updateAvailable) {\n warn(`Update available: v${latestVersion}`);\n info('Run `npm install -g safeword` to upgrade');\n } else {\n success('CLI is up to date');\n }\n}\n\n/**\n * Compare project version vs CLI version and report\n * @param health\n */\nfunction reportVersionMismatch(health: HealthStatus): void {\n if (!health.projectVersion) return;\n\n if (isNewerVersion(health.cliVersion, health.projectVersion)) {\n warn(`Project config (v${health.projectVersion}) is newer than CLI (v${health.cliVersion})`);\n info('Consider upgrading the CLI');\n } else if (isNewerVersion(health.projectVersion, health.cliVersion)) {\n info(`\\nUpgrade available for project config`);\n info(\n `Run \\`safeword upgrade\\` to update from v${health.projectVersion} to v${health.cliVersion}`,\n );\n }\n}\n\n/**\n * Report issues or success\n * @param health\n */\nfunction reportHealthSummary(health: HealthStatus): void {\n if (health.issues.length > 0) {\n header('Issues Found');\n for (const issue of health.issues) {\n warn(issue);\n }\n info('\\nRun `safeword upgrade` to repair configuration');\n return;\n }\n\n if (health.missingPackages.length > 0) {\n header('Missing Packages');\n info(`${health.missingPackages.length} linting packages not installed`);\n info('Run `safeword sync` to install missing packages');\n return;\n }\n\n success('\\nConfiguration is healthy');\n}\n\n/**\n *\n * @param options\n */\nexport async function check(options: CheckOptions): Promise<void> {\n const cwd = process.cwd();\n\n header('Safeword Health Check');\n\n const health = await checkHealth(cwd);\n\n // Not configured\n if (!health.configured) {\n info('Not configured. Run `safeword setup` to initialize.');\n return;\n }\n\n // Show versions\n keyValue('Safeword CLI', `v${health.cliVersion}`);\n keyValue('Project config', health.projectVersion ? `v${health.projectVersion}` : 'unknown');\n\n // Check for updates (unless offline)\n if (options.offline) {\n info('\\nSkipped update check (offline mode)');\n } else {\n await reportUpdateStatus(health);\n }\n\n reportVersionMismatch(health);\n reportHealthSummary(health);\n}\n"],"mappings":"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","names":[]}
@@ -4,11 +4,11 @@ import {
4
4
  getBaseEslintPackages,
5
5
  getConditionalEslintPackages,
6
6
  readJson
7
- } from "./chunk-DXT6TWW4.js";
7
+ } from "./chunk-CLSGXTOL.js";
8
8
 
9
9
  // src/commands/sync.ts
10
- import { join } from "path";
11
10
  import { execSync } from "child_process";
11
+ import nodePath from "path";
12
12
  function getRequiredPlugins(projectType) {
13
13
  const plugins = [...getBaseEslintPackages()];
14
14
  for (const [key, isActive] of Object.entries(projectType)) {
@@ -23,9 +23,9 @@ function getMissingPackages(required, installed) {
23
23
  }
24
24
  async function sync(options = {}) {
25
25
  const cwd = process.cwd();
26
- const safewordDir = join(cwd, ".safeword");
27
- const packageJsonPath = join(cwd, "package.json");
28
- if (!exists(safewordDir)) {
26
+ const safewordDirectory = nodePath.join(cwd, ".safeword");
27
+ const packageJsonPath = nodePath.join(cwd, "package.json");
28
+ if (!exists(safewordDirectory)) {
29
29
  if (!options.quiet) {
30
30
  console.error("Not a safeword project. Run `safeword setup` first.");
31
31
  }
@@ -42,9 +42,9 @@ async function sync(options = {}) {
42
42
  process.exit(1);
43
43
  }
44
44
  const projectType = detectProjectType(packageJson);
45
- const devDeps = packageJson.devDependencies || {};
45
+ const developmentDeps = packageJson.devDependencies || {};
46
46
  const requiredPlugins = getRequiredPlugins(projectType);
47
- const missingPlugins = getMissingPackages(requiredPlugins, devDeps);
47
+ const missingPlugins = getMissingPackages(requiredPlugins, developmentDeps);
48
48
  if (missingPlugins.length === 0) {
49
49
  return;
50
50
  }
@@ -56,7 +56,7 @@ async function sync(options = {}) {
56
56
  cwd,
57
57
  stdio: options.quiet ? "pipe" : "inherit"
58
58
  });
59
- } catch {
59
+ } catch (caughtError) {
60
60
  const pluginList = missingPlugins.join(" ");
61
61
  console.error(`
62
62
  \u2717 Failed to install ESLint plugins
@@ -66,7 +66,7 @@ async function sync(options = {}) {
66
66
  Run manually when online:`);
67
67
  console.error(` npm install -D ${pluginList}
68
68
  `);
69
- process.exit(1);
69
+ throw caughtError;
70
70
  }
71
71
  if (options.stage) {
72
72
  try {
@@ -85,4 +85,4 @@ Run manually when online:`);
85
85
  export {
86
86
  sync
87
87
  };
88
- //# sourceMappingURL=chunk-DES5CSPH.js.map
88
+ //# sourceMappingURL=chunk-4URRFBUS.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/commands/sync.ts"],"sourcesContent":["/**\n * Sync command - Keep linting plugins in sync with project dependencies\n *\n * Detects frameworks in package.json and ensures the corresponding ESLint plugins\n * are installed. Designed to be called from Husky pre-commit hook.\n *\n * Behavior:\n * - Fast exit when nothing needs to change\n * - Installs missing plugins\n * - Optionally stages modified files (--stage flag for pre-commit)\n * - Clear error message if installation fails\n */\n\nimport { execSync } from 'node:child_process';\nimport nodePath from 'node:path';\n\nimport { getBaseEslintPackages, getConditionalEslintPackages } from '../schema.js';\nimport { exists, readJson } from '../utils/fs.js';\nimport {\n detectProjectType,\n type PackageJson,\n type ProjectType,\n} from '../utils/project-detector.js';\n\nexport interface SyncOptions {\n quiet?: boolean;\n stage?: boolean;\n}\n\n/**\n * Get required ESLint packages based on project type.\n * Derives from schema - single source of truth, no separate list to maintain.\n * @param projectType\n */\nfunction getRequiredPlugins(projectType: ProjectType): string[] {\n const plugins: string[] = [...getBaseEslintPackages()];\n\n // Add conditional ESLint packages based on detected project type\n // Note: Next.js already sets react=true in project-detector.ts\n for (const [key, isActive] of Object.entries(projectType)) {\n if (isActive) {\n plugins.push(...getConditionalEslintPackages(key));\n }\n }\n\n return plugins;\n}\n\n/**\n * Check which packages are missing from devDependencies\n * @param required\n * @param installed\n */\nfunction getMissingPackages(required: string[], installed: Record<string, string>): string[] {\n return required.filter(pkg => !(pkg in installed));\n}\n\n/**\n * Sync linting configuration with current project dependencies\n * @param options\n */\nexport async function sync(options: SyncOptions = {}): Promise<void> {\n const cwd = process.cwd();\n const safewordDirectory = nodePath.join(cwd, '.safeword');\n const packageJsonPath = nodePath.join(cwd, 'package.json');\n\n // Must be in a safeword project\n if (!exists(safewordDirectory)) {\n if (!options.quiet) {\n console.error('Not a safeword project. Run `safeword setup` first.');\n }\n process.exit(1);\n }\n\n if (!exists(packageJsonPath)) {\n if (!options.quiet) {\n console.error('No package.json found.');\n }\n process.exit(1);\n }\n\n const packageJson = readJson<PackageJson>(packageJsonPath);\n if (!packageJson) {\n process.exit(1);\n }\n\n // Detect current project type\n const projectType = detectProjectType(packageJson);\n const developmentDeps = packageJson.devDependencies || {};\n\n // Check for missing plugins\n const requiredPlugins = getRequiredPlugins(projectType);\n const missingPlugins = getMissingPackages(requiredPlugins, developmentDeps);\n\n // Fast exit if nothing to install\n if (missingPlugins.length === 0) {\n return;\n }\n\n // Install missing plugins\n if (!options.quiet) {\n console.log(`Installing missing ESLint plugins: ${missingPlugins.join(', ')}`);\n }\n\n try {\n execSync(`npm install -D ${missingPlugins.join(' ')}`, {\n cwd,\n stdio: options.quiet ? 'pipe' : 'inherit',\n });\n } catch (caughtError) {\n // Clear error message for network/install failures\n const pluginList = missingPlugins.join(' ');\n console.error(`\\n✗ Failed to install ESLint plugins\\n`);\n console.error(`Your project needs: ${pluginList}`);\n console.error(`\\nRun manually when online:`);\n console.error(` npm install -D ${pluginList}\\n`);\n throw caughtError;\n }\n\n // Stage modified files if --stage flag is set (for pre-commit hook)\n if (options.stage) {\n try {\n execSync('git add package.json package-lock.json', {\n cwd,\n stdio: 'pipe',\n });\n } catch {\n // Not in a git repo or git add failed - ignore\n }\n }\n\n if (!options.quiet) {\n console.log(`✓ Installed ${missingPlugins.length} ESLint plugin(s)`);\n }\n}\n"],"mappings":";;;;;;;;;AAaA,SAAS,gBAAgB;AACzB,OAAO,cAAc;AAoBrB,SAAS,mBAAmB,aAAoC;AAC9D,QAAM,UAAoB,CAAC,GAAG,sBAAsB,CAAC;AAIrD,aAAW,CAAC,KAAK,QAAQ,KAAK,OAAO,QAAQ,WAAW,GAAG;AACzD,QAAI,UAAU;AACZ,cAAQ,KAAK,GAAG,6BAA6B,GAAG,CAAC;AAAA,IACnD;AAAA,EACF;AAEA,SAAO;AACT;AAOA,SAAS,mBAAmB,UAAoB,WAA6C;AAC3F,SAAO,SAAS,OAAO,SAAO,EAAE,OAAO,UAAU;AACnD;AAMA,eAAsB,KAAK,UAAuB,CAAC,GAAkB;AACnE,QAAM,MAAM,QAAQ,IAAI;AACxB,QAAM,oBAAoB,SAAS,KAAK,KAAK,WAAW;AACxD,QAAM,kBAAkB,SAAS,KAAK,KAAK,cAAc;AAGzD,MAAI,CAAC,OAAO,iBAAiB,GAAG;AAC9B,QAAI,CAAC,QAAQ,OAAO;AAClB,cAAQ,MAAM,qDAAqD;AAAA,IACrE;AACA,YAAQ,KAAK,CAAC;AAAA,EAChB;AAEA,MAAI,CAAC,OAAO,eAAe,GAAG;AAC5B,QAAI,CAAC,QAAQ,OAAO;AAClB,cAAQ,MAAM,wBAAwB;AAAA,IACxC;AACA,YAAQ,KAAK,CAAC;AAAA,EAChB;AAEA,QAAM,cAAc,SAAsB,eAAe;AACzD,MAAI,CAAC,aAAa;AAChB,YAAQ,KAAK,CAAC;AAAA,EAChB;AAGA,QAAM,cAAc,kBAAkB,WAAW;AACjD,QAAM,kBAAkB,YAAY,mBAAmB,CAAC;AAGxD,QAAM,kBAAkB,mBAAmB,WAAW;AACtD,QAAM,iBAAiB,mBAAmB,iBAAiB,eAAe;AAG1E,MAAI,eAAe,WAAW,GAAG;AAC/B;AAAA,EACF;AAGA,MAAI,CAAC,QAAQ,OAAO;AAClB,YAAQ,IAAI,sCAAsC,eAAe,KAAK,IAAI,CAAC,EAAE;AAAA,EAC/E;AAEA,MAAI;AACF,aAAS,kBAAkB,eAAe,KAAK,GAAG,CAAC,IAAI;AAAA,MACrD;AAAA,MACA,OAAO,QAAQ,QAAQ,SAAS;AAAA,IAClC,CAAC;AAAA,EACH,SAAS,aAAa;AAEpB,UAAM,aAAa,eAAe,KAAK,GAAG;AAC1C,YAAQ,MAAM;AAAA;AAAA,CAAwC;AACtD,YAAQ,MAAM,uBAAuB,UAAU,EAAE;AACjD,YAAQ,MAAM;AAAA,0BAA6B;AAC3C,YAAQ,MAAM,oBAAoB,UAAU;AAAA,CAAI;AAChD,UAAM;AAAA,EACR;AAGA,MAAI,QAAQ,OAAO;AACjB,QAAI;AACF,eAAS,0CAA0C;AAAA,QACjD;AAAA,QACA,OAAO;AAAA,MACT,CAAC;AAAA,IACH,QAAQ;AAAA,IAER;AAAA,EACF;AAEA,MAAI,CAAC,QAAQ,OAAO;AAClB,YAAQ,IAAI,oBAAe,eAAe,MAAM,mBAAmB;AAAA,EACrE;AACF;","names":[]}