@warlock.js/core 5.33.0 → 5.34.1

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 (120) hide show
  1. package/CHANGELOG.md +36 -46
  2. package/esm/cli/commands/check/check-command.action.mjs +2 -1
  3. package/esm/cli/commands/check/check-command.action.mjs.map +1 -1
  4. package/esm/cli/commands/check.command.mjs +2 -2
  5. package/esm/cli/commands/check.command.mjs.map +1 -1
  6. package/esm/cli/commands/e2e.command.mjs +63 -0
  7. package/esm/cli/commands/e2e.command.mjs.map +1 -0
  8. package/esm/cli/commands/load-e2e-spec.mjs +47 -0
  9. package/esm/cli/commands/load-e2e-spec.mjs.map +1 -0
  10. package/esm/cli/framework-cli-commands.mjs +2 -0
  11. package/esm/cli/framework-cli-commands.mjs.map +1 -1
  12. package/esm/dev-server/health-checker/checkers/oxlint-health-checker.mjs +1 -1
  13. package/esm/dev-server/health-checker/default-health-checkers.mjs +1 -6
  14. package/esm/dev-server/health-checker/default-health-checkers.mjs.map +1 -1
  15. package/esm/dev-server/health-checker/file-health-checker.contract.d.mts +0 -2
  16. package/esm/dev-server/health-checker/file-health-checker.contract.d.mts.map +1 -1
  17. package/esm/dev-server/health-checker/file-health-result.d.mts +1 -1
  18. package/esm/dev-server/health-checker/file-health-result.mjs.map +1 -1
  19. package/esm/e2e/browser.d.mts +4 -0
  20. package/esm/e2e/browser.d.mts.map +1 -0
  21. package/esm/e2e/browser.mjs +88 -0
  22. package/esm/e2e/browser.mjs.map +1 -0
  23. package/esm/e2e/cdp.d.mts +152 -0
  24. package/esm/e2e/cdp.d.mts.map +1 -0
  25. package/esm/e2e/cdp.mjs +106 -0
  26. package/esm/e2e/cdp.mjs.map +1 -0
  27. package/esm/e2e/fixture.mjs +195 -0
  28. package/esm/e2e/fixture.mjs.map +1 -0
  29. package/esm/e2e/index.d.mts +5 -0
  30. package/esm/e2e/index.mjs +6 -0
  31. package/esm/e2e/owned-process.mjs +193 -0
  32. package/esm/e2e/owned-process.mjs.map +1 -0
  33. package/esm/e2e/page.d.mts +64 -0
  34. package/esm/e2e/page.d.mts.map +1 -0
  35. package/esm/e2e/page.mjs +376 -0
  36. package/esm/e2e/page.mjs.map +1 -0
  37. package/esm/e2e/run-e2e.d.mts +17 -0
  38. package/esm/e2e/run-e2e.d.mts.map +1 -0
  39. package/esm/e2e/run-e2e.mjs +143 -0
  40. package/esm/e2e/run-e2e.mjs.map +1 -0
  41. package/esm/e2e/types.d.mts +123 -0
  42. package/esm/e2e/types.d.mts.map +1 -0
  43. package/esm/e2e/types.mjs +24 -0
  44. package/esm/e2e/types.mjs.map +1 -0
  45. package/esm/generations/features/web.feature.mjs +7 -4
  46. package/esm/generations/features/web.feature.mjs.map +1 -1
  47. package/esm/generations/stubs.mjs +18 -35
  48. package/esm/generations/stubs.mjs.map +1 -1
  49. package/esm/index.d.mts +2 -2
  50. package/esm/secrets/errors.d.mts +1 -1
  51. package/esm/secrets/errors.d.mts.map +1 -1
  52. package/esm/secrets/errors.mjs +2 -0
  53. package/esm/secrets/errors.mjs.map +1 -1
  54. package/esm/secrets/file-backend.mjs +1 -0
  55. package/esm/secrets/file-backend.mjs.map +1 -1
  56. package/esm/secrets/index.d.mts +2 -2
  57. package/esm/secrets/keyring-backend.mjs +1 -0
  58. package/esm/secrets/keyring-backend.mjs.map +1 -1
  59. package/esm/secrets/memory-backend.d.mts +1 -0
  60. package/esm/secrets/memory-backend.d.mts.map +1 -1
  61. package/esm/secrets/memory-backend.mjs +1 -0
  62. package/esm/secrets/memory-backend.mjs.map +1 -1
  63. package/esm/secrets/resolve-driver.mjs +14 -0
  64. package/esm/secrets/resolve-driver.mjs.map +1 -0
  65. package/esm/secrets/secret-store.d.mts +4 -2
  66. package/esm/secrets/secret-store.d.mts.map +1 -1
  67. package/esm/secrets/secret-store.mjs +22 -8
  68. package/esm/secrets/secret-store.mjs.map +1 -1
  69. package/esm/secrets/secrets.d.mts +2 -0
  70. package/esm/secrets/secrets.d.mts.map +1 -1
  71. package/esm/secrets/secrets.mjs +13 -1
  72. package/esm/secrets/secrets.mjs.map +1 -1
  73. package/esm/secrets/types.d.mts +8 -0
  74. package/esm/secrets/types.d.mts.map +1 -1
  75. package/esm/tooling/check/lint-step.d.mts +3 -3
  76. package/esm/tooling/check/lint-step.d.mts.map +1 -1
  77. package/esm/tooling/check/lint-step.mjs +3 -47
  78. package/esm/tooling/check/lint-step.mjs.map +1 -1
  79. package/esm/tooling/check/run-check.d.mts +2 -2
  80. package/esm/tooling/check/run-check.mjs +4 -2
  81. package/esm/tooling/check/run-check.mjs.map +1 -1
  82. package/esm/tooling/problem.d.mts +1 -1
  83. package/esm/tooling/problem.d.mts.map +1 -1
  84. package/esm/tooling/problem.mjs.map +1 -1
  85. package/esm/updater/package-manager.mjs +5 -1
  86. package/esm/updater/package-manager.mjs.map +1 -1
  87. package/esm/upgrade/codemods/index.mjs +2 -1
  88. package/esm/upgrade/codemods/index.mjs.map +1 -1
  89. package/esm/upgrade/codemods/oxc-toolchain.mjs +59 -10
  90. package/esm/upgrade/codemods/oxc-toolchain.mjs.map +1 -1
  91. package/esm/upgrade/codemods/oxlint-rule-schemas.mjs +6 -0
  92. package/esm/upgrade/codemods/oxlint-rule-schemas.mjs.map +1 -0
  93. package/esm/upgrade/codemods/port-eslint-rules.mjs +343 -0
  94. package/esm/upgrade/codemods/port-eslint-rules.mjs.map +1 -0
  95. package/esm/upgrade/codemods/toolchain-templates.mjs +1 -1
  96. package/esm/upgrade/codemods/toolchain-templates.mjs.map +1 -1
  97. package/esm/upgrade/codemods/validate-oxlint-rule.mjs +60 -0
  98. package/esm/upgrade/codemods/validate-oxlint-rule.mjs.map +1 -0
  99. package/esm/upgrade/notes.mjs +14 -7
  100. package/esm/upgrade/notes.mjs.map +1 -1
  101. package/esm/warlock-config/types.d.mts +0 -1
  102. package/esm/warlock-config/types.d.mts.map +1 -1
  103. package/llms-full.txt +269 -44
  104. package/llms.txt +4 -3
  105. package/package.json +19 -20
  106. package/skills/create-module/SKILL.md +2 -2
  107. package/skills/run-app/SKILL.md +3 -3
  108. package/skills/store-secrets/SKILL.md +60 -2
  109. package/skills/test-browser/SKILL.md +119 -0
  110. package/skills/upgrade-from-5-29/SKILL.md +70 -29
  111. package/skills/warlock-check/SKILL.md +3 -3
  112. package/skills/warlock-doctor/SKILL.md +2 -3
  113. package/skills/write-cli-command/SKILL.md +6 -2
  114. package/esm/dev-server/health-checker/checkers/eslint-health-checker.mjs +0 -152
  115. package/esm/dev-server/health-checker/checkers/eslint-health-checker.mjs.map +0 -1
  116. package/esm/dev-server/health-checker/eslint-support.mjs +0 -27
  117. package/esm/dev-server/health-checker/eslint-support.mjs.map +0 -1
  118. package/esm/dev-server/health-checker/workers/eslint-health.worker.d.mts +0 -1
  119. package/esm/dev-server/health-checker/workers/eslint-health.worker.mjs +0 -183
  120. package/esm/dev-server/health-checker/workers/eslint-health.worker.mjs.map +0 -1
@@ -1,7 +1,9 @@
1
1
  import { config } from "../config/config-getter.mjs";
2
2
  import { storagePath } from "../utils/paths.mjs";
3
3
  import { SecretStoreError } from "./errors.mjs";
4
+ import { MemorySecretBackend } from "./memory-backend.mjs";
4
5
  import { FileSecretBackend } from "./file-backend.mjs";
6
+ import { resolveSecretsDriver } from "./resolve-driver.mjs";
5
7
  import { KeyringSecretBackend } from "./keyring-backend.mjs";
6
8
  import { registerLogSecret } from "@warlock.js/logger";
7
9
 
@@ -11,7 +13,8 @@ var SecretStore = class {
11
13
  constructor(options) {
12
14
  if (typeof options.namespace !== "string" || !options.namespace.trim() || Buffer.from(options.namespace).toString("utf8") !== options.namespace) throw new SecretStoreError("INVALID_NAMESPACE");
13
15
  this.namespace = options.namespace;
14
- this.backend = options.backend;
16
+ const driver = options.backend ? "auto" : resolveSecretsDriver(options);
17
+ this.selectedBackend = options.backend ?? (driver === "memory" ? new MemorySecretBackend() : void 0);
15
18
  const encryptionKey = options.encryptionKey;
16
19
  this.file = new FileSecretBackend(options.directory ?? storagePath("secrets"), typeof encryptionKey === "function" ? encryptionKey : () => encryptionKey ?? config.key("encryption.key"));
17
20
  }
@@ -28,6 +31,17 @@ var SecretStore = class {
28
31
  this.primary ??= KeyringSecretBackend.load(this.namespace);
29
32
  return this.primary;
30
33
  }
34
+ /** Resolve the active store using the same memoized probe as credential operations. */
35
+ async backend() {
36
+ return this.safe(async () => {
37
+ if (this.selectedBackend) {
38
+ const type = this.selectedBackend.type ?? "memory";
39
+ if (type === "keychain" || type === "file" || type === "memory") return type;
40
+ throw new SecretStoreError("UNAVAILABLE");
41
+ }
42
+ return await this.keyring() ? "keychain" : "file";
43
+ });
44
+ }
31
45
  register(name, value) {
32
46
  registerLogSecret(value ?? "", [name, this.environmentName(name)]);
33
47
  }
@@ -54,7 +68,7 @@ var SecretStore = class {
54
68
  this.register(name);
55
69
  return this.safe(async () => {
56
70
  let value;
57
- if (this.backend) value = await this.backend.get(this.namespace, name);
71
+ if (this.selectedBackend) value = await this.selectedBackend.get(this.namespace, name);
58
72
  else {
59
73
  value = await (await this.keyring())?.get(this.namespace, name);
60
74
  value ??= this.environment(name);
@@ -72,10 +86,10 @@ var SecretStore = class {
72
86
  if (typeof value !== "string") throw new SecretStoreError("INVALID_VALUE");
73
87
  this.register(name, value);
74
88
  return this.safe(async () => {
75
- if (this.backend) {
76
- const previous = await this.backend.get(this.namespace, name);
89
+ if (this.selectedBackend) {
90
+ const previous = await this.selectedBackend.get(this.namespace, name);
77
91
  if (previous !== void 0) this.register(name, previous);
78
- return this.backend.set(this.namespace, name, value);
92
+ return this.selectedBackend.set(this.namespace, name, value);
79
93
  }
80
94
  this.assertWritable(name);
81
95
  const primary = await this.keyring();
@@ -95,10 +109,10 @@ var SecretStore = class {
95
109
  this.validate(name);
96
110
  this.register(name);
97
111
  return this.safe(async () => {
98
- if (this.backend) {
99
- const previous = await this.backend.get(this.namespace, name);
112
+ if (this.selectedBackend) {
113
+ const previous = await this.selectedBackend.get(this.namespace, name);
100
114
  if (previous !== void 0) this.register(name, previous);
101
- return this.backend.remove(this.namespace, name);
115
+ return this.selectedBackend.remove(this.namespace, name);
102
116
  }
103
117
  this.assertWritable(name);
104
118
  const primary = await this.keyring();
@@ -1 +1 @@
1
- {"version":3,"file":"secret-store.mjs","names":[],"sources":["../../../../../../../../packages/core/src/secrets/secret-store.ts"],"sourcesContent":["import { registerLogSecret } from \"@warlock.js/logger\";\nimport { config } from \"../config/config-getter\";\nimport { storagePath } from \"../utils/paths\";\nimport { SecretStoreError } from \"./errors\";\nimport { FileSecretBackend } from \"./file-backend\";\nimport { KeyringSecretBackend } from \"./keyring-backend\";\nimport type { SecretBackend, SecretStoreOptions } from \"./types\";\n\n/** Lazily prepared, application-scoped credentials. All operations are asynchronous. */\nexport class SecretStore {\n private primary: Promise<KeyringSecretBackend | undefined> | undefined;\n private readonly file: FileSecretBackend;\n private readonly backend: SecretBackend | undefined;\n public readonly namespace: string;\n\n public constructor(options: SecretStoreOptions) {\n if (\n typeof options.namespace !== \"string\" ||\n !options.namespace.trim() ||\n Buffer.from(options.namespace).toString(\"utf8\") !== options.namespace\n ) {\n throw new SecretStoreError(\"INVALID_NAMESPACE\");\n }\n this.namespace = options.namespace;\n this.backend = options.backend;\n const encryptionKey = options.encryptionKey;\n this.file = new FileSecretBackend(\n options.directory ?? storagePath(\"secrets\"),\n typeof encryptionKey === \"function\"\n ? encryptionKey\n : () => encryptionKey ?? config.key<string | undefined>(\"encryption.key\"),\n );\n }\n\n private validate(name: string): void {\n if (\n typeof name !== \"string\" ||\n !name.length ||\n name.length > 256 ||\n Buffer.from(name).toString(\"utf8\") !== name\n ) {\n throw new SecretStoreError(\"INVALID_NAME\");\n }\n }\n\n /** Exact, collision-free environment variable name (UTF-8 encoded as uppercase hex). */\n public environmentName(name: string): string {\n this.validate(name);\n const hex = (value: string) => Buffer.from(value).toString(\"hex\").toUpperCase();\n return `WARLOCK_SECRET_${hex(this.namespace)}_${hex(name)}`;\n }\n\n private keyring(): Promise<KeyringSecretBackend | undefined> {\n this.primary ??= KeyringSecretBackend.load(this.namespace);\n return this.primary;\n }\n\n private register(name: string, value?: string): void {\n registerLogSecret(value ?? \"\", [name, this.environmentName(name)]);\n }\n\n private environment(name: string): string | undefined {\n return process.env[this.environmentName(name)];\n }\n\n private assertWritable(name: string): void {\n const value = this.environment(name);\n if (value !== undefined) {\n this.register(name, value);\n throw new SecretStoreError(\"READ_ONLY\");\n }\n }\n\n private async safe<T>(operation: () => Promise<T>): Promise<T> {\n try {\n return await operation();\n } catch (error) {\n if (error instanceof SecretStoreError) throw error;\n // Custom backends may throw messages containing values. Do not propagate them.\n throw new SecretStoreError(\"UNAVAILABLE\");\n }\n }\n\n public async get(name: string): Promise<string | undefined> {\n this.validate(name);\n this.register(name);\n return this.safe(async () => {\n let value: string | undefined;\n if (this.backend) {\n value = await this.backend.get(this.namespace, name);\n } else {\n const primary = await this.keyring();\n value = await primary?.get(this.namespace, name);\n value ??= this.environment(name);\n value ??= await this.file.get(this.namespace, name);\n }\n if (value !== undefined) this.register(name, value);\n return value;\n });\n }\n\n public async has(name: string): Promise<boolean> {\n return (await this.get(name)) !== undefined;\n }\n\n public async set(name: string, value: string): Promise<void> {\n this.validate(name);\n if (typeof value !== \"string\") throw new SecretStoreError(\"INVALID_VALUE\");\n // Register before any backend call, including failed writes.\n this.register(name, value);\n return this.safe(async () => {\n if (this.backend) {\n const previous = await this.backend.get(this.namespace, name);\n if (previous !== undefined) this.register(name, previous);\n return this.backend.set(this.namespace, name, value);\n }\n this.assertWritable(name);\n const primary = await this.keyring();\n const backend = primary ?? this.file;\n if (primary) {\n const fallback = await this.file.get(this.namespace, name);\n if (fallback !== undefined) this.register(name, fallback);\n }\n // Retain masking for the old value after replacement as well.\n const previous = await backend.get(this.namespace, name);\n if (previous !== undefined) this.register(name, previous);\n await backend.set(this.namespace, name, value);\n if (primary) await this.file.remove(this.namespace, name);\n });\n }\n\n /** Remove every writable layer so a fallback value cannot reappear. */\n public async remove(name: string): Promise<boolean> {\n this.validate(name);\n this.register(name);\n return this.safe(async () => {\n if (this.backend) {\n const previous = await this.backend.get(this.namespace, name);\n if (previous !== undefined) this.register(name, previous);\n return this.backend.remove(this.namespace, name);\n }\n this.assertWritable(name);\n const primary = await this.keyring();\n const previous = await primary?.get(this.namespace, name);\n if (previous !== undefined) this.register(name, previous);\n const fromFile = await this.file.get(this.namespace, name);\n if (fromFile !== undefined) this.register(name, fromFile);\n const removed = (await primary?.remove(this.namespace, name)) ?? false;\n return (await this.file.remove(this.namespace, name)) || removed;\n });\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"secret-store.mjs","names":[],"sources":["../../../../../../../../packages/core/src/secrets/secret-store.ts"],"sourcesContent":["import { registerLogSecret } from \"@warlock.js/logger\";\nimport { config } from \"../config/config-getter\";\nimport { storagePath } from \"../utils/paths\";\nimport { SecretStoreError } from \"./errors\";\nimport { FileSecretBackend } from \"./file-backend\";\nimport { MemorySecretBackend } from \"./memory-backend\";\nimport { resolveSecretsDriver } from \"./resolve-driver\";\nimport { KeyringSecretBackend } from \"./keyring-backend\";\nimport type { SecretBackend, SecretBackendType, SecretStoreOptions } from \"./types\";\n\n/** Lazily prepared, application-scoped credentials. All operations are asynchronous. */\nexport class SecretStore {\n private primary: Promise<KeyringSecretBackend | undefined> | undefined;\n private readonly file: FileSecretBackend;\n private readonly selectedBackend: SecretBackend | undefined;\n public readonly namespace: string;\n\n public constructor(options: SecretStoreOptions) {\n if (\n typeof options.namespace !== \"string\" ||\n !options.namespace.trim() ||\n Buffer.from(options.namespace).toString(\"utf8\") !== options.namespace\n ) {\n throw new SecretStoreError(\"INVALID_NAMESPACE\");\n }\n this.namespace = options.namespace;\n const driver = options.backend ? \"auto\" : resolveSecretsDriver(options);\n this.selectedBackend =\n options.backend ?? (driver === \"memory\" ? new MemorySecretBackend() : undefined);\n const encryptionKey = options.encryptionKey;\n this.file = new FileSecretBackend(\n options.directory ?? storagePath(\"secrets\"),\n typeof encryptionKey === \"function\"\n ? encryptionKey\n : () => encryptionKey ?? config.key<string | undefined>(\"encryption.key\"),\n );\n }\n\n private validate(name: string): void {\n if (\n typeof name !== \"string\" ||\n !name.length ||\n name.length > 256 ||\n Buffer.from(name).toString(\"utf8\") !== name\n ) {\n throw new SecretStoreError(\"INVALID_NAME\");\n }\n }\n\n /** Exact, collision-free environment variable name (UTF-8 encoded as uppercase hex). */\n public environmentName(name: string): string {\n this.validate(name);\n const hex = (value: string) => Buffer.from(value).toString(\"hex\").toUpperCase();\n return `WARLOCK_SECRET_${hex(this.namespace)}_${hex(name)}`;\n }\n\n private keyring(): Promise<KeyringSecretBackend | undefined> {\n this.primary ??= KeyringSecretBackend.load(this.namespace);\n return this.primary;\n }\n\n /** Resolve the active store using the same memoized probe as credential operations. */\n public async backend(): Promise<SecretBackendType> {\n return this.safe(async () => {\n if (this.selectedBackend) {\n const type = this.selectedBackend.type ?? \"memory\";\n if (type === \"keychain\" || type === \"file\" || type === \"memory\") return type;\n throw new SecretStoreError(\"UNAVAILABLE\");\n }\n return (await this.keyring()) ? \"keychain\" : \"file\";\n });\n }\n\n private register(name: string, value?: string): void {\n registerLogSecret(value ?? \"\", [name, this.environmentName(name)]);\n }\n\n private environment(name: string): string | undefined {\n return process.env[this.environmentName(name)];\n }\n\n private assertWritable(name: string): void {\n const value = this.environment(name);\n if (value !== undefined) {\n this.register(name, value);\n throw new SecretStoreError(\"READ_ONLY\");\n }\n }\n\n private async safe<T>(operation: () => Promise<T>): Promise<T> {\n try {\n return await operation();\n } catch (error) {\n if (error instanceof SecretStoreError) throw error;\n // Custom backends may throw messages containing values. Do not propagate them.\n throw new SecretStoreError(\"UNAVAILABLE\");\n }\n }\n\n public async get(name: string): Promise<string | undefined> {\n this.validate(name);\n this.register(name);\n return this.safe(async () => {\n let value: string | undefined;\n if (this.selectedBackend) {\n value = await this.selectedBackend.get(this.namespace, name);\n } else {\n const primary = await this.keyring();\n value = await primary?.get(this.namespace, name);\n value ??= this.environment(name);\n value ??= await this.file.get(this.namespace, name);\n }\n if (value !== undefined) this.register(name, value);\n return value;\n });\n }\n\n public async has(name: string): Promise<boolean> {\n return (await this.get(name)) !== undefined;\n }\n\n public async set(name: string, value: string): Promise<void> {\n this.validate(name);\n if (typeof value !== \"string\") throw new SecretStoreError(\"INVALID_VALUE\");\n // Register before any backend call, including failed writes.\n this.register(name, value);\n return this.safe(async () => {\n if (this.selectedBackend) {\n const previous = await this.selectedBackend.get(this.namespace, name);\n if (previous !== undefined) this.register(name, previous);\n return this.selectedBackend.set(this.namespace, name, value);\n }\n this.assertWritable(name);\n const primary = await this.keyring();\n const backend = primary ?? this.file;\n if (primary) {\n const fallback = await this.file.get(this.namespace, name);\n if (fallback !== undefined) this.register(name, fallback);\n }\n // Retain masking for the old value after replacement as well.\n const previous = await backend.get(this.namespace, name);\n if (previous !== undefined) this.register(name, previous);\n await backend.set(this.namespace, name, value);\n if (primary) await this.file.remove(this.namespace, name);\n });\n }\n\n /** Remove every writable layer so a fallback value cannot reappear. */\n public async remove(name: string): Promise<boolean> {\n this.validate(name);\n this.register(name);\n return this.safe(async () => {\n if (this.selectedBackend) {\n const previous = await this.selectedBackend.get(this.namespace, name);\n if (previous !== undefined) this.register(name, previous);\n return this.selectedBackend.remove(this.namespace, name);\n }\n this.assertWritable(name);\n const primary = await this.keyring();\n const previous = await primary?.get(this.namespace, name);\n if (previous !== undefined) this.register(name, previous);\n const fromFile = await this.file.get(this.namespace, name);\n if (fromFile !== undefined) this.register(name, fromFile);\n const removed = (await primary?.remove(this.namespace, name)) ?? false;\n return (await this.file.remove(this.namespace, name)) || removed;\n });\n }\n}\n"],"mappings":"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"}
@@ -1,6 +1,8 @@
1
+ import { SecretBackendType } from "./types.mjs";
1
2
  //#region ../../packages/core/src/secrets/secrets.d.ts
2
3
  /** App identity and configuration are resolved on first use, after config has loaded. */
3
4
  export declare const secrets: {
5
+ backend(): Promise<SecretBackendType>;
4
6
  get(name: string): Promise<string | undefined>;
5
7
  set(name: string, value: string): Promise<void>;
6
8
  remove(name: string): Promise<boolean>;
@@ -1 +1 @@
1
- {"version":3,"file":"secrets.d.mts","names":[],"sources":["../../../../../../../../packages/core/src/secrets/secrets.ts"],"mappings":";;qBAuCa;EACL,IAAG,eAAgB;EAGnB,IAAG,cAAa,gBAAkB;EAGlC,OAAM,eAAgB;EAGtB,IAAG,eAAgB;EAGzB,gBAAe"}
1
+ {"version":3,"file":"secrets.d.mts","names":[],"sources":["../../../../../../../../packages/core/src/secrets/secrets.ts"],"mappings":";;;qBA6Ca;EACL,WAAW,QAAQ;EAGnB,IAAG,eAAgB;EAGnB,IAAG,cAAa,gBAAkB;EAGlC,OAAM,eAAgB;EAGtB,IAAG,eAAgB;EAGzB,gBAAe"}
@@ -1,5 +1,6 @@
1
1
  import { config } from "../config/config-getter.mjs";
2
2
  import { rootPath, storagePath } from "../utils/paths.mjs";
3
+ import { resolveSecretsDriver } from "./resolve-driver.mjs";
3
4
  import { SecretStore } from "./secret-store.mjs";
4
5
  import { createHash } from "node:crypto";
5
6
  import path from "node:path";
@@ -23,14 +24,22 @@ function current() {
23
24
  const appName = config.key("app.appName") || path.basename(root);
24
25
  const namespace = config.key("secrets.namespace") ?? `${appName}:${createHash("sha256").update(root).digest("hex")}`;
25
26
  const directory = config.key("secrets.directory") ?? storagePath("secrets");
27
+ const options = {
28
+ driver: config.key("secrets.driver"),
29
+ allowMemoryInProduction: config.key("secrets.allowMemoryInProduction")
30
+ };
31
+ const driver = testBackend ? "auto" : resolveSecretsDriver(options);
26
32
  const identity = JSON.stringify([
27
33
  root,
28
34
  namespace,
29
- directory
35
+ directory,
36
+ driver
30
37
  ]);
31
38
  let store = stores.get(identity);
32
39
  if (!store) {
33
40
  store = new SecretStore({
41
+ ...options,
42
+ driver,
34
43
  namespace,
35
44
  directory,
36
45
  backend: testBackend
@@ -41,6 +50,9 @@ function current() {
41
50
  }
42
51
  /** App identity and configuration are resolved on first use, after config has loaded. */
43
52
  const secrets = {
53
+ async backend() {
54
+ return current().backend();
55
+ },
44
56
  async get(name) {
45
57
  return current().get(name);
46
58
  },
@@ -1 +1 @@
1
- {"version":3,"file":"secrets.mjs","names":[],"sources":["../../../../../../../../packages/core/src/secrets/secrets.ts"],"sourcesContent":["import { createHash } from \"node:crypto\";\nimport path from \"node:path\";\nimport { config } from \"../config/config-getter\";\nimport { rootPath, storagePath } from \"../utils/paths\";\nimport { SecretStore } from \"./secret-store\";\nimport type { SecretBackend } from \"./types\";\n\nlet testBackend: SecretBackend | undefined;\nlet stores = new Map<string, SecretStore>();\n\n/** @internal The test kit swaps only the facade, never explicitly constructed stores. */\nexport function replaceSecretsBackend(backend: SecretBackend): () => void {\n const previous = testBackend;\n const previousStores = stores;\n testBackend = backend;\n stores = new Map();\n return () => {\n testBackend = previous;\n stores = previousStores;\n };\n}\n\nfunction current(): SecretStore {\n const root = rootPath();\n const appName = config.key<string | undefined>(\"app.appName\") || path.basename(root);\n const namespace =\n config.key<string | undefined>(\"secrets.namespace\") ??\n `${appName}:${createHash(\"sha256\").update(root).digest(\"hex\")}`;\n const directory = config.key<string | undefined>(\"secrets.directory\") ?? storagePath(\"secrets\");\n const identity = JSON.stringify([root, namespace, directory]);\n let store = stores.get(identity);\n if (!store) {\n store = new SecretStore({ namespace, directory, backend: testBackend });\n stores.set(identity, store);\n }\n return store;\n}\n\n/** App identity and configuration are resolved on first use, after config has loaded. */\nexport const secrets = {\n async get(name: string): Promise<string | undefined> {\n return current().get(name);\n },\n async set(name: string, value: string): Promise<void> {\n return current().set(name, value);\n },\n async remove(name: string): Promise<boolean> {\n return current().remove(name);\n },\n async has(name: string): Promise<boolean> {\n return current().has(name);\n },\n environmentName(name: string): string {\n return current().environmentName(name);\n },\n};\n"],"mappings":";;;;;;;AAOA,IAAI;AACJ,IAAI,yBAAS,IAAI,IAAyB;;AAG1C,SAAgB,sBAAsB,SAAoC;CACxE,MAAM,WAAW;CACjB,MAAM,iBAAiB;CACvB,cAAc;CACd,yBAAS,IAAI,IAAI;CACjB,aAAa;EACX,cAAc;EACd,SAAS;CACX;AACF;AAEA,SAAS,UAAuB;CAC9B,MAAM,OAAO,SAAS;CACtB,MAAM,UAAU,OAAO,IAAwB,aAAa,KAAK,KAAK,SAAS,IAAI;CACnF,MAAM,YACJ,OAAO,IAAwB,mBAAmB,KAClD,GAAG,QAAQ,GAAG,WAAW,QAAQ,CAAC,CAAC,OAAO,IAAI,CAAC,CAAC,OAAO,KAAK;CAC9D,MAAM,YAAY,OAAO,IAAwB,mBAAmB,KAAK,YAAY,SAAS;CAC9F,MAAM,WAAW,KAAK,UAAU;EAAC;EAAM;EAAW;CAAS,CAAC;CAC5D,IAAI,QAAQ,OAAO,IAAI,QAAQ;CAC/B,IAAI,CAAC,OAAO;EACV,QAAQ,IAAI,YAAY;GAAE;GAAW;GAAW,SAAS;EAAY,CAAC;EACtE,OAAO,IAAI,UAAU,KAAK;CAC5B;CACA,OAAO;AACT;;AAGA,MAAa,UAAU;CACrB,MAAM,IAAI,MAA2C;EACnD,OAAO,QAAQ,CAAC,CAAC,IAAI,IAAI;CAC3B;CACA,MAAM,IAAI,MAAc,OAA8B;EACpD,OAAO,QAAQ,CAAC,CAAC,IAAI,MAAM,KAAK;CAClC;CACA,MAAM,OAAO,MAAgC;EAC3C,OAAO,QAAQ,CAAC,CAAC,OAAO,IAAI;CAC9B;CACA,MAAM,IAAI,MAAgC;EACxC,OAAO,QAAQ,CAAC,CAAC,IAAI,IAAI;CAC3B;CACA,gBAAgB,MAAsB;EACpC,OAAO,QAAQ,CAAC,CAAC,gBAAgB,IAAI;CACvC;AACF"}
1
+ {"version":3,"file":"secrets.mjs","names":[],"sources":["../../../../../../../../packages/core/src/secrets/secrets.ts"],"sourcesContent":["import { createHash } from \"node:crypto\";\nimport path from \"node:path\";\nimport { config } from \"../config/config-getter\";\nimport { rootPath, storagePath } from \"../utils/paths\";\nimport { resolveSecretsDriver } from \"./resolve-driver\";\nimport { SecretStore } from \"./secret-store\";\nimport type { SecretBackend, SecretBackendType, SecretsConfigurations } from \"./types\";\n\nlet testBackend: SecretBackend | undefined;\nlet stores = new Map<string, SecretStore>();\n\n/** @internal The test kit swaps only the facade, never explicitly constructed stores. */\nexport function replaceSecretsBackend(backend: SecretBackend): () => void {\n const previous = testBackend;\n const previousStores = stores;\n testBackend = backend;\n stores = new Map();\n return () => {\n testBackend = previous;\n stores = previousStores;\n };\n}\n\nfunction current(): SecretStore {\n const root = rootPath();\n const appName = config.key<string | undefined>(\"app.appName\") || path.basename(root);\n const namespace =\n config.key<string | undefined>(\"secrets.namespace\") ??\n `${appName}:${createHash(\"sha256\").update(root).digest(\"hex\")}`;\n const directory = config.key<string | undefined>(\"secrets.directory\") ?? storagePath(\"secrets\");\n const options: SecretsConfigurations = {\n driver: config.key<SecretsConfigurations[\"driver\"]>(\"secrets.driver\"),\n allowMemoryInProduction: config.key<boolean | undefined>(\"secrets.allowMemoryInProduction\"),\n };\n const driver = testBackend ? \"auto\" : resolveSecretsDriver(options);\n const identity = JSON.stringify([root, namespace, directory, driver]);\n let store = stores.get(identity);\n if (!store) {\n store = new SecretStore({ ...options, driver, namespace, directory, backend: testBackend });\n stores.set(identity, store);\n }\n return store;\n}\n\n/** App identity and configuration are resolved on first use, after config has loaded. */\nexport const secrets = {\n async backend(): Promise<SecretBackendType> {\n return current().backend();\n },\n async get(name: string): Promise<string | undefined> {\n return current().get(name);\n },\n async set(name: string, value: string): Promise<void> {\n return current().set(name, value);\n },\n async remove(name: string): Promise<boolean> {\n return current().remove(name);\n },\n async has(name: string): Promise<boolean> {\n return current().has(name);\n },\n environmentName(name: string): string {\n return current().environmentName(name);\n },\n};\n"],"mappings":";;;;;;;;AAQA,IAAI;AACJ,IAAI,yBAAS,IAAI,IAAyB;;AAG1C,SAAgB,sBAAsB,SAAoC;CACxE,MAAM,WAAW;CACjB,MAAM,iBAAiB;CACvB,cAAc;CACd,yBAAS,IAAI,IAAI;CACjB,aAAa;EACX,cAAc;EACd,SAAS;CACX;AACF;AAEA,SAAS,UAAuB;CAC9B,MAAM,OAAO,SAAS;CACtB,MAAM,UAAU,OAAO,IAAwB,aAAa,KAAK,KAAK,SAAS,IAAI;CACnF,MAAM,YACJ,OAAO,IAAwB,mBAAmB,KAClD,GAAG,QAAQ,GAAG,WAAW,QAAQ,CAAC,CAAC,OAAO,IAAI,CAAC,CAAC,OAAO,KAAK;CAC9D,MAAM,YAAY,OAAO,IAAwB,mBAAmB,KAAK,YAAY,SAAS;CAC9F,MAAM,UAAiC;EACrC,QAAQ,OAAO,IAAqC,gBAAgB;EACpE,yBAAyB,OAAO,IAAyB,iCAAiC;CAC5F;CACA,MAAM,SAAS,cAAc,SAAS,qBAAqB,OAAO;CAClE,MAAM,WAAW,KAAK,UAAU;EAAC;EAAM;EAAW;EAAW;CAAM,CAAC;CACpE,IAAI,QAAQ,OAAO,IAAI,QAAQ;CAC/B,IAAI,CAAC,OAAO;EACV,QAAQ,IAAI,YAAY;GAAE,GAAG;GAAS;GAAQ;GAAW;GAAW,SAAS;EAAY,CAAC;EAC1F,OAAO,IAAI,UAAU,KAAK;CAC5B;CACA,OAAO;AACT;;AAGA,MAAa,UAAU;CACrB,MAAM,UAAsC;EAC1C,OAAO,QAAQ,CAAC,CAAC,QAAQ;CAC3B;CACA,MAAM,IAAI,MAA2C;EACnD,OAAO,QAAQ,CAAC,CAAC,IAAI,IAAI;CAC3B;CACA,MAAM,IAAI,MAAc,OAA8B;EACpD,OAAO,QAAQ,CAAC,CAAC,IAAI,MAAM,KAAK;CAClC;CACA,MAAM,OAAO,MAAgC;EAC3C,OAAO,QAAQ,CAAC,CAAC,OAAO,IAAI;CAC9B;CACA,MAAM,IAAI,MAAgC;EACxC,OAAO,QAAQ,CAAC,CAAC,IAAI,IAAI;CAC3B;CACA,gBAAgB,MAAsB;EACpC,OAAO,QAAQ,CAAC,CAAC,gBAAgB,IAAI;CACvC;AACF"}
@@ -1,11 +1,19 @@
1
1
  //#region ../../packages/core/src/secrets/types.d.ts
2
+ /** Active storage selection, independent of the source of an individual secret. */
3
+ export type SecretBackendType = "keychain" | "file" | "memory";
2
4
  /** Backend implementations must never log credentials or attach them to errors. */
3
5
  export interface SecretBackend {
6
+ /** Report injected storage; omitted metadata defaults to memory for test overrides. */
7
+ readonly type?: SecretBackendType;
4
8
  get(namespace: string, name: string): Promise<string | undefined>;
5
9
  set(namespace: string, name: string, value: string): Promise<void>;
6
10
  remove(namespace: string, name: string): Promise<boolean>;
7
11
  }
8
12
  export type SecretsConfigurations = {
13
+ /** auto selects keychain/environment/files; memory is isolated to this process. */
14
+ driver?: "auto" | "memory";
15
+ /** Explicitly permit the volatile memory driver under NODE_ENV=production. */
16
+ allowMemoryInProduction?: boolean;
9
17
  /** Stable identity shared only by processes belonging to this app installation. */
10
18
  namespace?: string;
11
19
  /** Trusted parent location; defaults to storage/secrets. */
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.mts","names":[],"sources":["../../../../../../../../packages/core/src/secrets/types.ts"],"mappings":";;iBACiB;EACf,IAAI,mBAAmB,eAAe;EACtC,IAAI,mBAAmB,cAAc,gBAAgB;EACrD,OAAO,mBAAmB,eAAe;;YAG/B;;EAEV;;EAEA;;YAGU,qBAAqB;EAC/B;;EAEA,UAAU;;EAEV"}
1
+ {"version":3,"file":"types.d.mts","names":[],"sources":["../../../../../../../../packages/core/src/secrets/types.ts"],"mappings":";;YACY;;iBAGK;;WAEN,OAAO;EAChB,IAAI,mBAAmB,eAAe;EACtC,IAAI,mBAAmB,cAAc,gBAAgB;EACrD,OAAO,mBAAmB,eAAe;;YAG/B;;EAEV;;EAEA;;EAEA;;EAEA;;YAGU,qBAAqB;EAC/B;;EAEA,UAAU;;EAEV"}
@@ -3,10 +3,10 @@ import { ToolingProblem } from "../problem.mjs";
3
3
  /** What the lint step found, and which linters ran. */
4
4
  export type LintOutcome = {
5
5
  problems: ToolingProblem[];
6
- /** The linters that ran (`eslint`, `oxlint`); empty when the app configures none. */
6
+ /** The linters that ran (`oxlint`); empty when the app configures none. */
7
7
  linters: string[];
8
8
  };
9
- /** Oxlint by default. ESLint requires an explicit selection and a flat config. */
10
- export declare function runLint(cwd: string, files: readonly string[], selection?: "oxlint" | "eslint"): Promise<LintOutcome>;
9
+ /** Run configured Oxlint over lintable source files. */
10
+ export declare function runLint(cwd: string, files: readonly string[]): Promise<LintOutcome>;
11
11
  //#endregion
12
12
  //# sourceMappingURL=lint-step.d.mts.map
@@ -1 +1 @@
1
- {"version":3,"file":"lint-step.d.mts","names":[],"sources":["../../../../../../../../../packages/core/src/tooling/check/lint-step.ts"],"mappings":";;;YAWY;EACV,UAAU;;EAEV;;;wBAiFoB,QACpB,aACA,0BACA,kCACC,QAAQ"}
1
+ {"version":3,"file":"lint-step.d.mts","names":[],"sources":["../../../../../../../../../packages/core/src/tooling/check/lint-step.ts"],"mappings":";;;YASY;EACV,UAAU;;EAEV;;;wBAmDoB,QAAQ,aAAa,2BAA2B,QAAQ"}
@@ -1,8 +1,6 @@
1
- import { hasEslint, warnEslintDeprecation } from "../../dev-server/health-checker/eslint-support.mjs";
2
1
  import { OxlintRunner, findOxlint, hasOxlintConfig } from "../../dev-server/health-checker/oxlint-runner.mjs";
3
2
  import { createProblem } from "../problem.mjs";
4
3
  import path from "node:path";
5
- import { ESLint } from "eslint";
6
4
 
7
5
  //#region ../../packages/core/src/tooling/check/lint-step.ts
8
6
  const LINTABLE = /\.[cm]?[jt]sx?$/i;
@@ -19,31 +17,6 @@ function toolFailure(tool, error) {
19
17
  message: `${tool} could not run: ${message.split("\n")[0]}`
20
18
  });
21
19
  }
22
- async function runEslint(cwd, files) {
23
- const results = await new ESLint({
24
- cwd,
25
- errorOnUnmatchedPattern: false,
26
- warnIgnored: false
27
- }).lintFiles([...files]);
28
- const problems = [];
29
- for (const result of results) {
30
- const file = path.relative(cwd, result.filePath).split(path.sep).join("/");
31
- for (const message of result.messages) {
32
- const error = message.severity === 2 || message.fatal === true;
33
- problems.push(createProblem({
34
- code: error ? "WL7201" : "WL7202",
35
- source: "eslint",
36
- severity: error ? "error" : "warning",
37
- message: message.message,
38
- file,
39
- line: message.line || null,
40
- column: message.column || null,
41
- rule: message.ruleId ?? null
42
- }));
43
- }
44
- }
45
- return problems;
46
- }
47
20
  async function runOxlint(cwd, files) {
48
21
  const runner = new OxlintRunner();
49
22
  runner.initialize(cwd);
@@ -65,8 +38,8 @@ async function runOxlint(cwd, files) {
65
38
  await runner.shutdown();
66
39
  }
67
40
  }
68
- /** Oxlint by default. ESLint requires an explicit selection and a flat config. */
69
- async function runLint(cwd, files, selection = "oxlint") {
41
+ /** Run configured Oxlint over lintable source files. */
42
+ async function runLint(cwd, files) {
70
43
  const lintable = files.filter((file) => LINTABLE.test(file));
71
44
  const problems = [];
72
45
  const linters = [];
@@ -74,24 +47,7 @@ async function runLint(cwd, files, selection = "oxlint") {
74
47
  problems,
75
48
  linters
76
49
  };
77
- if (selection === "eslint") {
78
- warnEslintDeprecation();
79
- if (!hasEslint(cwd)) return {
80
- linters: ["eslint"],
81
- problems: [createProblem({
82
- code: "WL8002",
83
- source: "environment",
84
- message: "Cannot find ESLint flat config in the app."
85
- })]
86
- };
87
- linters.push("eslint");
88
- try {
89
- problems.push(...await runEslint(cwd, lintable));
90
- } catch (error) {
91
- problems.push(toolFailure("ESLint", error));
92
- }
93
- }
94
- if (selection === "oxlint" && hasOxlintConfig(cwd)) {
50
+ if (hasOxlintConfig(cwd)) {
95
51
  linters.push("oxlint");
96
52
  if (!findOxlint(cwd)) problems.push(createProblem({
97
53
  code: "WL8002",
@@ -1 +1 @@
1
- {"version":3,"file":"lint-step.mjs","names":[],"sources":["../../../../../../../../../packages/core/src/tooling/check/lint-step.ts"],"sourcesContent":["import { ESLint } from \"eslint\";\nimport path from \"node:path\";\nimport { hasEslint, warnEslintDeprecation } from \"../../dev-server/health-checker/eslint-support\";\nimport {\n findOxlint,\n hasOxlintConfig,\n OxlintRunner,\n} from \"../../dev-server/health-checker/oxlint-runner\";\nimport { createProblem, type ToolingProblem } from \"../problem\";\n\n/** What the lint step found, and which linters ran. */\nexport type LintOutcome = {\n problems: ToolingProblem[];\n /** The linters that ran (`eslint`, `oxlint`); empty when the app configures none. */\n linters: string[];\n};\n\nconst LINTABLE = /\\.[cm]?[jt]sx?$/i;\n\nfunction errorMessage(error: unknown): string {\n return error instanceof Error ? error.message : String(error);\n}\n\n/** A missing package/plugin is the machine's install; anything else is the tool crashing. */\nfunction toolFailure(tool: string, error: unknown): ToolingProblem {\n const message = errorMessage(error);\n const missing = /cannot find (?:module|package)|ERR_MODULE_NOT_FOUND/i.test(message);\n\n return createProblem({\n code: missing ? \"WL8002\" : \"WL8007\",\n source: \"environment\",\n message: `${tool} could not run: ${message.split(\"\\n\")[0]}`,\n });\n}\n\nasync function runEslint(cwd: string, files: readonly string[]): Promise<ToolingProblem[]> {\n // The same flat-config lookup the dev server's ESLint checker uses.\n const eslint = new ESLint({ cwd, errorOnUnmatchedPattern: false, warnIgnored: false });\n const results = await eslint.lintFiles([...files]);\n const problems: ToolingProblem[] = [];\n\n for (const result of results) {\n const file = path.relative(cwd, result.filePath).split(path.sep).join(\"/\");\n\n for (const message of result.messages) {\n const error = message.severity === 2 || message.fatal === true;\n\n problems.push(\n createProblem({\n code: error ? \"WL7201\" : \"WL7202\",\n source: \"eslint\",\n severity: error ? \"error\" : \"warning\",\n message: message.message,\n file,\n line: message.line || null,\n column: message.column || null,\n rule: message.ruleId ?? null,\n }),\n );\n }\n }\n\n return problems;\n}\n\nasync function runOxlint(cwd: string, files: readonly string[]): Promise<ToolingProblem[]> {\n const runner = new OxlintRunner();\n\n runner.initialize(cwd);\n\n try {\n const results = await runner.checkFiles(\n files.map((file) => ({ path: path.join(cwd, file), relativePath: file })),\n );\n\n return results.flatMap((result) =>\n [...result.errors, ...result.warnings].map((message) =>\n createProblem({\n code: message.type === \"error\" ? \"WL7201\" : \"WL7202\",\n source: \"oxlint\",\n severity: message.type,\n message: message.message,\n file: message.relativePath,\n line: message.lineNumber,\n column: message.columnNumber,\n rule: message.ruleId ?? null,\n }),\n ),\n );\n } finally {\n await runner.shutdown();\n }\n}\n\n/** Oxlint by default. ESLint requires an explicit selection and a flat config. */\nexport async function runLint(\n cwd: string,\n files: readonly string[],\n selection: \"oxlint\" | \"eslint\" = \"oxlint\",\n): Promise<LintOutcome> {\n const lintable = files.filter((file) => LINTABLE.test(file));\n const problems: ToolingProblem[] = [];\n const linters: string[] = [];\n\n // ESLint lints the whole directory when handed no pattern; nothing to lint\n // means nothing to run.\n if (lintable.length === 0) return { problems, linters };\n\n if (selection === \"eslint\") {\n warnEslintDeprecation();\n if (!hasEslint(cwd)) {\n return {\n linters: [\"eslint\"],\n problems: [\n createProblem({\n code: \"WL8002\",\n source: \"environment\",\n message: \"Cannot find ESLint flat config in the app.\",\n }),\n ],\n };\n }\n linters.push(\"eslint\");\n\n try {\n problems.push(...(await runEslint(cwd, lintable)));\n } catch (error) {\n problems.push(toolFailure(\"ESLint\", error));\n }\n }\n\n if (selection === \"oxlint\" && hasOxlintConfig(cwd)) {\n linters.push(\"oxlint\");\n\n if (!findOxlint(cwd)) {\n problems.push(\n createProblem({\n code: \"WL8002\",\n source: \"environment\",\n message: \"An oxlint config exists but the oxlint package is not installed in the app.\",\n }),\n );\n } else {\n try {\n problems.push(...(await runOxlint(cwd, lintable)));\n } catch (error) {\n problems.push(toolFailure(\"oxlint\", error));\n }\n }\n }\n\n return { problems, linters };\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"lint-step.mjs","names":[],"sources":["../../../../../../../../../packages/core/src/tooling/check/lint-step.ts"],"sourcesContent":["import path from \"node:path\";\nimport {\n findOxlint,\n hasOxlintConfig,\n OxlintRunner,\n} from \"../../dev-server/health-checker/oxlint-runner\";\nimport { createProblem, type ToolingProblem } from \"../problem\";\n\n/** What the lint step found, and which linters ran. */\nexport type LintOutcome = {\n problems: ToolingProblem[];\n /** The linters that ran (`oxlint`); empty when the app configures none. */\n linters: string[];\n};\n\nconst LINTABLE = /\\.[cm]?[jt]sx?$/i;\n\nfunction errorMessage(error: unknown): string {\n return error instanceof Error ? error.message : String(error);\n}\n\n/** A missing package/plugin is the machine's install; anything else is the tool crashing. */\nfunction toolFailure(tool: string, error: unknown): ToolingProblem {\n const message = errorMessage(error);\n const missing = /cannot find (?:module|package)|ERR_MODULE_NOT_FOUND/i.test(message);\n\n return createProblem({\n code: missing ? \"WL8002\" : \"WL8007\",\n source: \"environment\",\n message: `${tool} could not run: ${message.split(\"\\n\")[0]}`,\n });\n}\n\nasync function runOxlint(cwd: string, files: readonly string[]): Promise<ToolingProblem[]> {\n const runner = new OxlintRunner();\n\n runner.initialize(cwd);\n\n try {\n const results = await runner.checkFiles(\n files.map((file) => ({ path: path.join(cwd, file), relativePath: file })),\n );\n\n return results.flatMap((result) =>\n [...result.errors, ...result.warnings].map((message) =>\n createProblem({\n code: message.type === \"error\" ? \"WL7201\" : \"WL7202\",\n source: \"oxlint\",\n severity: message.type,\n message: message.message,\n file: message.relativePath,\n line: message.lineNumber,\n column: message.columnNumber,\n rule: message.ruleId ?? null,\n }),\n ),\n );\n } finally {\n await runner.shutdown();\n }\n}\n\n/** Run configured Oxlint over lintable source files. */\nexport async function runLint(cwd: string, files: readonly string[]): Promise<LintOutcome> {\n const lintable = files.filter((file) => LINTABLE.test(file));\n const problems: ToolingProblem[] = [];\n const linters: string[] = [];\n\n if (lintable.length === 0) return { problems, linters };\n\n if (hasOxlintConfig(cwd)) {\n linters.push(\"oxlint\");\n\n if (!findOxlint(cwd)) {\n problems.push(\n createProblem({\n code: \"WL8002\",\n source: \"environment\",\n message: \"An oxlint config exists but the oxlint package is not installed in the app.\",\n }),\n );\n } else {\n try {\n problems.push(...(await runOxlint(cwd, lintable)));\n } catch (error) {\n problems.push(toolFailure(\"oxlint\", error));\n }\n }\n }\n\n return { problems, linters };\n}\n"],"mappings":";;;;;AAeA,MAAM,WAAW;AAEjB,SAAS,aAAa,OAAwB;CAC5C,OAAO,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;AAC9D;;AAGA,SAAS,YAAY,MAAc,OAAgC;CACjE,MAAM,UAAU,aAAa,KAAK;CAClC,MAAM,UAAU,uDAAuD,KAAK,OAAO;CAEnF,OAAO,cAAc;EACnB,MAAM,UAAU,WAAW;EAC3B,QAAQ;EACR,SAAS,GAAG,KAAK,kBAAkB,QAAQ,MAAM,IAAI,CAAC,CAAC;CACzD,CAAC;AACH;AAEA,eAAe,UAAU,KAAa,OAAqD;CACzF,MAAM,SAAS,IAAI,aAAa;CAEhC,OAAO,WAAW,GAAG;CAErB,IAAI;EAKF,QAAO,MAJe,OAAO,WAC3B,MAAM,KAAK,UAAU;GAAE,MAAM,KAAK,KAAK,KAAK,IAAI;GAAG,cAAc;EAAK,EAAE,CAC1E,EAEc,CAAC,SAAS,WACtB,CAAC,GAAG,OAAO,QAAQ,GAAG,OAAO,QAAQ,CAAC,CAAC,KAAK,YAC1C,cAAc;GACZ,MAAM,QAAQ,SAAS,UAAU,WAAW;GAC5C,QAAQ;GACR,UAAU,QAAQ;GAClB,SAAS,QAAQ;GACjB,MAAM,QAAQ;GACd,MAAM,QAAQ;GACd,QAAQ,QAAQ;GAChB,MAAM,QAAQ,UAAU;EAC1B,CAAC,CACH,CACF;CACF,UAAU;EACR,MAAM,OAAO,SAAS;CACxB;AACF;;AAGA,eAAsB,QAAQ,KAAa,OAAgD;CACzF,MAAM,WAAW,MAAM,QAAQ,SAAS,SAAS,KAAK,IAAI,CAAC;CAC3D,MAAM,WAA6B,CAAC;CACpC,MAAM,UAAoB,CAAC;CAE3B,IAAI,SAAS,WAAW,GAAG,OAAO;EAAE;EAAU;CAAQ;CAEtD,IAAI,gBAAgB,GAAG,GAAG;EACxB,QAAQ,KAAK,QAAQ;EAErB,IAAI,CAAC,WAAW,GAAG,GACjB,SAAS,KACP,cAAc;GACZ,MAAM;GACN,QAAQ;GACR,SAAS;EACX,CAAC,CACH;OAEA,IAAI;GACF,SAAS,KAAK,GAAI,MAAM,UAAU,KAAK,QAAQ,CAAE;EACnD,SAAS,OAAO;GACd,SAAS,KAAK,YAAY,UAAU,KAAK,CAAC;EAC5C;CAEJ;CAEA,OAAO;EAAE;EAAU;CAAQ;AAC7B"}
@@ -9,8 +9,8 @@ export type TypecheckRunner = (options: TypecheckOptions) => Promise<TypecheckRe
9
9
  export type CheckOptions = {
10
10
  /** Project root. Default: `process.cwd()`. */
11
11
  cwd?: string;
12
- /** Oxlint by default; ESLint is deprecated and requires explicit selection. */
13
- lint?: "oxlint" | "eslint";
12
+ /** Oxlint is the only supported linter. */
13
+ lint?: "oxlint";
14
14
  /** Limit every step to the files changed against `base` (default HEAD) plus untracked files. */
15
15
  changed?: boolean;
16
16
  /** Compare with `git merge-base <base> HEAD` instead of HEAD. Implies `changed`. */
@@ -29,6 +29,8 @@ function statusOf(problems) {
29
29
  * list, and every step after `environment` is `blocked`.
30
30
  */
31
31
  async function runCheck(options = {}) {
32
+ const selection = options.lint;
33
+ if (selection !== void 0 && selection !== "oxlint") throw new Error("ESLint support was removed in 5.34.0. Oxlint/Oxfmt is the default; run npx warlock upgrade --from 5.33 --to 5.34.0.");
32
34
  const cwd = path.resolve(options.cwd ?? process.cwd());
33
35
  const changed = options.changed === true || options.base !== void 0;
34
36
  const skip = new Set(options.skip ?? []);
@@ -119,10 +121,10 @@ async function runCheck(options = {}) {
119
121
  detail: "no files to lint",
120
122
  problems: []
121
123
  };
122
- const { linters, problems: found } = await runLint(cwd, lintFiles, options.lint);
124
+ const { linters, problems: found } = await runLint(cwd, lintFiles);
123
125
  if (linters.length === 0) return {
124
126
  status: "skipped",
125
- detail: "no Oxlint config (ESLint requires --lint eslint)",
127
+ detail: "no Oxlint config",
126
128
  problems: []
127
129
  };
128
130
  return {
@@ -1 +1 @@
1
- {"version":3,"file":"run-check.mjs","names":[],"sources":["../../../../../../../../../packages/core/src/tooling/check/run-check.ts"],"sourcesContent":["import path from \"node:path\";\nimport { createProblem, type ToolingProblem } from \"../problem\";\nimport {\n runTypecheck,\n type TypecheckOptions,\n type TypecheckResult,\n} from \"../typecheck/run-typecheck\";\nimport { type GitRunner, listChangedFiles } from \"./changed-files\";\nimport {\n CHECK_STEPS,\n type CheckReport,\n type CheckStep,\n type CheckStepName,\n type CheckStepStatus,\n checkExitCode,\n} from \"./check-report.types\";\nimport { runConventions } from \"./conventions-step\";\nimport { checkNodeVersion } from \"./environment-step\";\nimport { runLint } from \"./lint-step\";\nimport { listSourceFiles, listTestFiles } from \"./source-files\";\nimport {\n findVitest,\n parseVitestReport,\n runVitest,\n type VitestRunner,\n vitestArguments,\n vitestRunFailure,\n} from \"./tests-step\";\n\n/** Runs the typecheck step. The CLI swaps in a child process so a heap overflow cannot end `check`. */\nexport type TypecheckRunner = (\n options: TypecheckOptions,\n) => Promise<TypecheckResult> | TypecheckResult;\n\n/** Inputs of {@link runCheck}. */\nexport type CheckOptions = {\n /** Project root. Default: `process.cwd()`. */\n cwd?: string;\n /** Oxlint by default; ESLint is deprecated and requires explicit selection. */\n lint?: \"oxlint\" | \"eslint\";\n /** Limit every step to the files changed against `base` (default HEAD) plus untracked files. */\n changed?: boolean;\n /** Compare with `git merge-base <base> HEAD` instead of HEAD. Implies `changed`. */\n base?: string;\n /** Steps not to run. */\n skip?: readonly CheckStepName[];\n /** tsconfig path, relative to `cwd`. Default: `tsconfig.json`. */\n project?: string;\n /** How git is run. */\n git?: GitRunner;\n /** How the typecheck runs. Default: in this process. */\n typecheck?: TypecheckRunner;\n /** How vitest runs. */\n vitest?: VitestRunner;\n /** Where the app's vitest CLI is. Default: resolved from the app root. */\n vitestBinary?: string | null;\n /** The Node.js version to check. Default: the running one. */\n nodeVersion?: string;\n};\n\nfunction errorMessage(error: unknown): string {\n return error instanceof Error ? error.message : String(error);\n}\n\nfunction statusOf(problems: readonly ToolingProblem[]): CheckStepStatus {\n const errors = problems.filter((problem) => problem.severity === \"error\");\n\n if (errors.some((problem) => problem.category === \"environment\")) return \"blocked\";\n\n return errors.length > 0 ? \"failed\" : \"passed\";\n}\n\n/** Bundles one step's timing, status and problems. */\ntype StepOutcome = { status?: CheckStepStatus; detail: string | null; problems: ToolingProblem[] };\n\n/**\n * The single verify command: typecheck, lint, the tests affected by the\n * change, and the framework's convention checks, as one report with one entry\n * per problem and an exit code that tells \"your code\" (1) from \"this machine\"\n * (2).\n *\n * Every step runs even when an earlier one failed, so one run lists every\n * problem. The only exception: with `changed`, a git failure leaves no file\n * list, and every step after `environment` is `blocked`.\n */\nexport async function runCheck(options: CheckOptions = {}): Promise<CheckReport> {\n const cwd = path.resolve(options.cwd ?? process.cwd());\n const changed = options.changed === true || options.base !== undefined;\n const skip = new Set(options.skip ?? []);\n const steps: CheckStep[] = [];\n const problems: ToolingProblem[] = [];\n let files: string[] | null = null;\n let filesBlocked = false;\n\n const run = async (name: CheckStepName, body: () => Promise<StepOutcome> | StepOutcome) => {\n const startedAt = Date.now();\n\n if (skip.has(name)) {\n steps.push({ name, status: \"skipped\", durationMs: 0, detail: \"skipped with --skip\" });\n return;\n }\n\n if (filesBlocked && name !== \"environment\") {\n steps.push({\n name,\n status: \"blocked\",\n durationMs: 0,\n detail: \"the changed files could not be listed\",\n });\n return;\n }\n\n let outcome: StepOutcome;\n\n try {\n outcome = await body();\n } catch (error) {\n outcome = {\n detail: null,\n problems: [\n createProblem({\n code: \"WL8007\",\n source: \"environment\",\n message: `The ${name} step crashed: ${errorMessage(error)}`,\n }),\n ],\n };\n }\n\n problems.push(...outcome.problems);\n steps.push({\n name,\n status: outcome.status ?? statusOf(outcome.problems),\n durationMs: Date.now() - startedAt,\n detail: outcome.detail,\n });\n };\n\n await run(\"environment\", async () => {\n const found = checkNodeVersion(cwd, options.nodeVersion);\n\n if (changed) {\n const listed = await listChangedFiles({ cwd, base: options.base, git: options.git });\n\n if (\"problem\" in listed) {\n filesBlocked = true;\n found.push(listed.problem);\n } else {\n files = listed.files;\n }\n }\n\n return {\n detail: `Node ${options.nodeVersion ?? process.versions.node}${files ? `, ${files.length} changed file(s)` : \"\"}`,\n problems: found,\n };\n });\n\n const targets = async (): Promise<string[]> => files ?? (await listSourceFiles(cwd));\n\n await run(\"typecheck\", async () => {\n if (files !== null && files.length === 0) {\n return { status: \"skipped\", detail: \"no changed files\", problems: [] };\n }\n\n const typecheck = options.typecheck ?? runTypecheck;\n const result = await typecheck({\n cwd,\n project: options.project,\n ...(files !== null ? { files } : {}),\n });\n\n return {\n detail: `${result.checkedFiles} file(s) checked in ${result.durationMs}ms`,\n problems: result.problems,\n };\n });\n\n await run(\"lint\", async () => {\n const lintFiles = await targets();\n\n if (lintFiles.length === 0) {\n return { status: \"skipped\", detail: \"no files to lint\", problems: [] };\n }\n\n const { linters, problems: found } = await runLint(cwd, lintFiles, options.lint);\n\n if (linters.length === 0) {\n return {\n status: \"skipped\",\n detail: \"no Oxlint config (ESLint requires --lint eslint)\",\n problems: [],\n };\n }\n\n return { detail: `${linters.join(\" + \")} over ${lintFiles.length} file(s)`, problems: found };\n });\n\n await run(\"tests\", async () => {\n if (files !== null && files.length === 0) {\n return { status: \"skipped\", detail: \"no changed files\", problems: [] };\n }\n\n const binary = options.vitestBinary === undefined ? findVitest(cwd) : options.vitestBinary;\n\n if (!binary) {\n if ((await listTestFiles(cwd)).length === 0) {\n return { status: \"skipped\", detail: \"no test files\", problems: [] };\n }\n\n return {\n detail: null,\n problems: [\n createProblem({\n code: \"WL8002\",\n source: \"environment\",\n message: \"The project has test files but vitest is not installed in the app.\",\n }),\n ],\n };\n }\n\n const vitest = options.vitest ?? runVitest;\n const outcome = await vitest(binary, vitestArguments(files), cwd);\n const found =\n outcome.report === undefined\n ? vitestRunFailure(outcome)\n : parseVitestReport(outcome.report, cwd);\n const total =\n typeof outcome.report === \"object\" && outcome.report !== null\n ? Reflect.get(outcome.report, \"numTotalTests\")\n : undefined;\n\n return {\n detail: typeof total === \"number\" ? `${total} test(s) ran` : null,\n problems: found,\n };\n });\n\n await run(\"conventions\", async () => {\n const conventionFiles = await targets();\n\n return {\n detail: `${conventionFiles.length} file(s) checked`,\n problems: runConventions(cwd, conventionFiles, options.project),\n };\n });\n\n // Keep the declared order even if a future step is added out of sequence.\n steps.sort((first, second) => CHECK_STEPS.indexOf(first.name) - CHECK_STEPS.indexOf(second.name));\n\n const exitCode = checkExitCode(problems);\n const errors = problems.filter((problem) => problem.severity === \"error\");\n\n return {\n schemaVersion: 1,\n ok: exitCode === 0,\n exitCode,\n changed,\n files,\n steps,\n problems,\n summary: {\n errors: errors.length,\n warnings: problems.length - errors.length,\n code: errors.filter((problem) => problem.category === \"code\").length,\n environment: errors.filter((problem) => problem.category === \"environment\").length,\n },\n };\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"run-check.mjs","names":[],"sources":["../../../../../../../../../packages/core/src/tooling/check/run-check.ts"],"sourcesContent":["import path from \"node:path\";\nimport { createProblem, type ToolingProblem } from \"../problem\";\nimport {\n runTypecheck,\n type TypecheckOptions,\n type TypecheckResult,\n} from \"../typecheck/run-typecheck\";\nimport { type GitRunner, listChangedFiles } from \"./changed-files\";\nimport {\n CHECK_STEPS,\n type CheckReport,\n type CheckStep,\n type CheckStepName,\n type CheckStepStatus,\n checkExitCode,\n} from \"./check-report.types\";\nimport { runConventions } from \"./conventions-step\";\nimport { checkNodeVersion } from \"./environment-step\";\nimport { runLint } from \"./lint-step\";\nimport { listSourceFiles, listTestFiles } from \"./source-files\";\nimport {\n findVitest,\n parseVitestReport,\n runVitest,\n type VitestRunner,\n vitestArguments,\n vitestRunFailure,\n} from \"./tests-step\";\n\n/** Runs the typecheck step. The CLI swaps in a child process so a heap overflow cannot end `check`. */\nexport type TypecheckRunner = (\n options: TypecheckOptions,\n) => Promise<TypecheckResult> | TypecheckResult;\n\n/** Inputs of {@link runCheck}. */\nexport type CheckOptions = {\n /** Project root. Default: `process.cwd()`. */\n cwd?: string;\n /** Oxlint is the only supported linter. */\n lint?: \"oxlint\";\n /** Limit every step to the files changed against `base` (default HEAD) plus untracked files. */\n changed?: boolean;\n /** Compare with `git merge-base <base> HEAD` instead of HEAD. Implies `changed`. */\n base?: string;\n /** Steps not to run. */\n skip?: readonly CheckStepName[];\n /** tsconfig path, relative to `cwd`. Default: `tsconfig.json`. */\n project?: string;\n /** How git is run. */\n git?: GitRunner;\n /** How the typecheck runs. Default: in this process. */\n typecheck?: TypecheckRunner;\n /** How vitest runs. */\n vitest?: VitestRunner;\n /** Where the app's vitest CLI is. Default: resolved from the app root. */\n vitestBinary?: string | null;\n /** The Node.js version to check. Default: the running one. */\n nodeVersion?: string;\n};\n\nfunction errorMessage(error: unknown): string {\n return error instanceof Error ? error.message : String(error);\n}\n\nfunction statusOf(problems: readonly ToolingProblem[]): CheckStepStatus {\n const errors = problems.filter((problem) => problem.severity === \"error\");\n\n if (errors.some((problem) => problem.category === \"environment\")) return \"blocked\";\n\n return errors.length > 0 ? \"failed\" : \"passed\";\n}\n\n/** Bundles one step's timing, status and problems. */\ntype StepOutcome = { status?: CheckStepStatus; detail: string | null; problems: ToolingProblem[] };\n\n/**\n * The single verify command: typecheck, lint, the tests affected by the\n * change, and the framework's convention checks, as one report with one entry\n * per problem and an exit code that tells \"your code\" (1) from \"this machine\"\n * (2).\n *\n * Every step runs even when an earlier one failed, so one run lists every\n * problem. The only exception: with `changed`, a git failure leaves no file\n * list, and every step after `environment` is `blocked`.\n */\nexport async function runCheck(options: CheckOptions = {}): Promise<CheckReport> {\n const selection: unknown = options.lint;\n if (selection !== undefined && selection !== \"oxlint\")\n throw new Error(\n \"ESLint support was removed in 5.34.0. Oxlint/Oxfmt is the default; run npx warlock upgrade --from 5.33 --to 5.34.0.\",\n );\n const cwd = path.resolve(options.cwd ?? process.cwd());\n const changed = options.changed === true || options.base !== undefined;\n const skip = new Set(options.skip ?? []);\n const steps: CheckStep[] = [];\n const problems: ToolingProblem[] = [];\n let files: string[] | null = null;\n let filesBlocked = false;\n\n const run = async (name: CheckStepName, body: () => Promise<StepOutcome> | StepOutcome) => {\n const startedAt = Date.now();\n\n if (skip.has(name)) {\n steps.push({ name, status: \"skipped\", durationMs: 0, detail: \"skipped with --skip\" });\n return;\n }\n\n if (filesBlocked && name !== \"environment\") {\n steps.push({\n name,\n status: \"blocked\",\n durationMs: 0,\n detail: \"the changed files could not be listed\",\n });\n return;\n }\n\n let outcome: StepOutcome;\n\n try {\n outcome = await body();\n } catch (error) {\n outcome = {\n detail: null,\n problems: [\n createProblem({\n code: \"WL8007\",\n source: \"environment\",\n message: `The ${name} step crashed: ${errorMessage(error)}`,\n }),\n ],\n };\n }\n\n problems.push(...outcome.problems);\n steps.push({\n name,\n status: outcome.status ?? statusOf(outcome.problems),\n durationMs: Date.now() - startedAt,\n detail: outcome.detail,\n });\n };\n\n await run(\"environment\", async () => {\n const found = checkNodeVersion(cwd, options.nodeVersion);\n\n if (changed) {\n const listed = await listChangedFiles({ cwd, base: options.base, git: options.git });\n\n if (\"problem\" in listed) {\n filesBlocked = true;\n found.push(listed.problem);\n } else {\n files = listed.files;\n }\n }\n\n return {\n detail: `Node ${options.nodeVersion ?? process.versions.node}${files ? `, ${files.length} changed file(s)` : \"\"}`,\n problems: found,\n };\n });\n\n const targets = async (): Promise<string[]> => files ?? (await listSourceFiles(cwd));\n\n await run(\"typecheck\", async () => {\n if (files !== null && files.length === 0) {\n return { status: \"skipped\", detail: \"no changed files\", problems: [] };\n }\n\n const typecheck = options.typecheck ?? runTypecheck;\n const result = await typecheck({\n cwd,\n project: options.project,\n ...(files !== null ? { files } : {}),\n });\n\n return {\n detail: `${result.checkedFiles} file(s) checked in ${result.durationMs}ms`,\n problems: result.problems,\n };\n });\n\n await run(\"lint\", async () => {\n const lintFiles = await targets();\n\n if (lintFiles.length === 0) {\n return { status: \"skipped\", detail: \"no files to lint\", problems: [] };\n }\n\n const { linters, problems: found } = await runLint(cwd, lintFiles);\n\n if (linters.length === 0) {\n return {\n status: \"skipped\",\n detail: \"no Oxlint config\",\n problems: [],\n };\n }\n\n return { detail: `${linters.join(\" + \")} over ${lintFiles.length} file(s)`, problems: found };\n });\n\n await run(\"tests\", async () => {\n if (files !== null && files.length === 0) {\n return { status: \"skipped\", detail: \"no changed files\", problems: [] };\n }\n\n const binary = options.vitestBinary === undefined ? findVitest(cwd) : options.vitestBinary;\n\n if (!binary) {\n if ((await listTestFiles(cwd)).length === 0) {\n return { status: \"skipped\", detail: \"no test files\", problems: [] };\n }\n\n return {\n detail: null,\n problems: [\n createProblem({\n code: \"WL8002\",\n source: \"environment\",\n message: \"The project has test files but vitest is not installed in the app.\",\n }),\n ],\n };\n }\n\n const vitest = options.vitest ?? runVitest;\n const outcome = await vitest(binary, vitestArguments(files), cwd);\n const found =\n outcome.report === undefined\n ? vitestRunFailure(outcome)\n : parseVitestReport(outcome.report, cwd);\n const total =\n typeof outcome.report === \"object\" && outcome.report !== null\n ? Reflect.get(outcome.report, \"numTotalTests\")\n : undefined;\n\n return {\n detail: typeof total === \"number\" ? `${total} test(s) ran` : null,\n problems: found,\n };\n });\n\n await run(\"conventions\", async () => {\n const conventionFiles = await targets();\n\n return {\n detail: `${conventionFiles.length} file(s) checked`,\n problems: runConventions(cwd, conventionFiles, options.project),\n };\n });\n\n // Keep the declared order even if a future step is added out of sequence.\n steps.sort((first, second) => CHECK_STEPS.indexOf(first.name) - CHECK_STEPS.indexOf(second.name));\n\n const exitCode = checkExitCode(problems);\n const errors = problems.filter((problem) => problem.severity === \"error\");\n\n return {\n schemaVersion: 1,\n ok: exitCode === 0,\n exitCode,\n changed,\n files,\n steps,\n problems,\n summary: {\n errors: errors.length,\n warnings: problems.length - errors.length,\n code: errors.filter((problem) => problem.category === \"code\").length,\n environment: errors.filter((problem) => problem.category === \"environment\").length,\n },\n };\n}\n"],"mappings":"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"}
@@ -1,7 +1,7 @@
1
1
  import { ErrorCodeCategory, WarlockErrorCode } from "../errors/error-codes.mjs";
2
2
  //#region ../../packages/core/src/tooling/problem.d.ts
3
3
  /** The tool that found a problem. */
4
- export type ProblemSource = "typescript" | "eslint" | "oxlint" | "vitest" | "convention" | "environment" | "doctor";
4
+ export type ProblemSource = "typescript" | "oxlint" | "vitest" | "convention" | "environment" | "doctor";
5
5
  /**
6
6
  * One problem, in the shape every machine-readable command prints
7
7
  * (`warlock check --json`, `warlock typecheck --json`, `warlock doctor --json`).
@@ -1 +1 @@
1
- {"version":3,"file":"problem.d.mts","names":[],"sources":["../../../../../../../../packages/core/src/tooling/problem.ts"],"mappings":";;;YAQY;;;;;;;;YAgBA;;EAEV,MAAM;;EAEN,UAAU;;EAEV;;EAEA,QAAQ;;EAER;;EAEA;;EAEA;;EAEA;;EAEA;;EAEA;;EAEA;;EAEA;;;YAIU;EACV,MAAM;EACN,QAAQ;EACR;EACA;EACA;EACA;EACA;EACA;;;;;;wBAOc,cAAc,OAAO,eAAe"}
1
+ {"version":3,"file":"problem.d.mts","names":[],"sources":["../../../../../../../../packages/core/src/tooling/problem.ts"],"mappings":";;;YAQY;;;;;;;;YAeA;;EAEV,MAAM;;EAEN,UAAU;;EAEV;;EAEA,QAAQ;;EAER;;EAEA;;EAEA;;EAEA;;EAEA;;EAEA;;EAEA;;EAEA;;;YAIU;EACV,MAAM;EACN,QAAQ;EACR;EACA;EACA;EACA;EACA;EACA;;;;;;wBAOc,cAAc,OAAO,eAAe"}
@@ -1 +1 @@
1
- {"version":3,"file":"problem.mjs","names":[],"sources":["../../../../../../../../packages/core/src/tooling/problem.ts"],"sourcesContent":["import {\n ERROR_CODES,\n errorCodeDocsUrl,\n type ErrorCodeCategory,\n type WarlockErrorCode,\n} from \"../errors/error-codes\";\n\n/** The tool that found a problem. */\nexport type ProblemSource =\n | \"typescript\"\n | \"eslint\"\n | \"oxlint\"\n | \"vitest\"\n | \"convention\"\n | \"environment\"\n | \"doctor\";\n\n/**\n * One problem, in the shape every machine-readable command prints\n * (`warlock check --json`, `warlock typecheck --json`, `warlock doctor --json`).\n *\n * Every key is always present, so a consumer never has to test for one:\n * `file`, `line` and `column` are `null` when the problem has no position.\n */\nexport type ToolingProblem = {\n /** Stable Warlock error code; `warlock explain <code>` prints the fix. */\n code: WarlockErrorCode;\n /** `code` = fix the project; `environment` = fix this machine. */\n category: ErrorCodeCategory;\n /** `error` fails the run; `warning` is reported and does not. */\n severity: \"error\" | \"warning\";\n /** The tool that reported it. */\n source: ProblemSource;\n /** Project-relative POSIX path, or `null`. */\n file: string | null;\n /** 1-based line, or `null`. */\n line: number | null;\n /** 1-based column, or `null`. */\n column: number | null;\n /** What is wrong, in the reporting tool's words. */\n message: string;\n /** One line on what to do, from the code registry. */\n hint: string;\n /** Slug of the skill that explains the fix. */\n skill: string;\n /** Docs URL of the code. */\n docs: string;\n /** The tool's own rule or code (`TS2345`, `no-unused-vars`, a test name), or `null`. */\n rule: string | null;\n};\n\n/** The fields a reporter supplies; the rest come from the registry. */\nexport type ProblemInput = {\n code: WarlockErrorCode;\n source: ProblemSource;\n message: string;\n severity?: \"error\" | \"warning\";\n file?: string | null;\n line?: number | null;\n column?: number | null;\n rule?: string | null;\n};\n\n/**\n * Build a {@link ToolingProblem}, filling `category`, `hint`, `skill` and `docs`\n * from the code registry so every reporter describes a code the same way.\n */\nexport function createProblem(input: ProblemInput): ToolingProblem {\n const definition = ERROR_CODES[input.code];\n\n return {\n code: input.code,\n category: definition.category,\n severity: input.severity ?? \"error\",\n source: input.source,\n file: input.file ?? null,\n line: input.line ?? null,\n column: input.column ?? null,\n message: input.message,\n hint: definition.fix,\n skill: definition.skill,\n docs: errorCodeDocsUrl(input.code),\n rule: input.rule ?? null,\n };\n}\n"],"mappings":";;;;;;;AAmEA,SAAgB,cAAc,OAAqC;CACjE,MAAM,aAAa,YAAY,MAAM;CAErC,OAAO;EACL,MAAM,MAAM;EACZ,UAAU,WAAW;EACrB,UAAU,MAAM,YAAY;EAC5B,QAAQ,MAAM;EACd,MAAM,MAAM,QAAQ;EACpB,MAAM,MAAM,QAAQ;EACpB,QAAQ,MAAM,UAAU;EACxB,SAAS,MAAM;EACf,MAAM,WAAW;EACjB,OAAO,WAAW;EAClB,MAAM,iBAAiB,MAAM,IAAI;EACjC,MAAM,MAAM,QAAQ;CACtB;AACF"}
1
+ {"version":3,"file":"problem.mjs","names":[],"sources":["../../../../../../../../packages/core/src/tooling/problem.ts"],"sourcesContent":["import {\n ERROR_CODES,\n errorCodeDocsUrl,\n type ErrorCodeCategory,\n type WarlockErrorCode,\n} from \"../errors/error-codes\";\n\n/** The tool that found a problem. */\nexport type ProblemSource =\n | \"typescript\"\n | \"oxlint\"\n | \"vitest\"\n | \"convention\"\n | \"environment\"\n | \"doctor\";\n\n/**\n * One problem, in the shape every machine-readable command prints\n * (`warlock check --json`, `warlock typecheck --json`, `warlock doctor --json`).\n *\n * Every key is always present, so a consumer never has to test for one:\n * `file`, `line` and `column` are `null` when the problem has no position.\n */\nexport type ToolingProblem = {\n /** Stable Warlock error code; `warlock explain <code>` prints the fix. */\n code: WarlockErrorCode;\n /** `code` = fix the project; `environment` = fix this machine. */\n category: ErrorCodeCategory;\n /** `error` fails the run; `warning` is reported and does not. */\n severity: \"error\" | \"warning\";\n /** The tool that reported it. */\n source: ProblemSource;\n /** Project-relative POSIX path, or `null`. */\n file: string | null;\n /** 1-based line, or `null`. */\n line: number | null;\n /** 1-based column, or `null`. */\n column: number | null;\n /** What is wrong, in the reporting tool's words. */\n message: string;\n /** One line on what to do, from the code registry. */\n hint: string;\n /** Slug of the skill that explains the fix. */\n skill: string;\n /** Docs URL of the code. */\n docs: string;\n /** The tool's own rule or code (`TS2345`, `no-unused-vars`, a test name), or `null`. */\n rule: string | null;\n};\n\n/** The fields a reporter supplies; the rest come from the registry. */\nexport type ProblemInput = {\n code: WarlockErrorCode;\n source: ProblemSource;\n message: string;\n severity?: \"error\" | \"warning\";\n file?: string | null;\n line?: number | null;\n column?: number | null;\n rule?: string | null;\n};\n\n/**\n * Build a {@link ToolingProblem}, filling `category`, `hint`, `skill` and `docs`\n * from the code registry so every reporter describes a code the same way.\n */\nexport function createProblem(input: ProblemInput): ToolingProblem {\n const definition = ERROR_CODES[input.code];\n\n return {\n code: input.code,\n category: definition.category,\n severity: input.severity ?? \"error\",\n source: input.source,\n file: input.file ?? null,\n line: input.line ?? null,\n column: input.column ?? null,\n message: input.message,\n hint: definition.fix,\n skill: definition.skill,\n docs: errorCodeDocsUrl(input.code),\n rule: input.rule ?? null,\n };\n}\n"],"mappings":";;;;;;;AAkEA,SAAgB,cAAc,OAAqC;CACjE,MAAM,aAAa,YAAY,MAAM;CAErC,OAAO;EACL,MAAM,MAAM;EACZ,UAAU,WAAW;EACrB,UAAU,MAAM,YAAY;EAC5B,QAAQ,MAAM;EACd,MAAM,MAAM,QAAQ;EACpB,MAAM,MAAM,QAAQ;EACpB,QAAQ,MAAM,UAAU;EACxB,SAAS,MAAM;EACf,MAAM,WAAW;EACjB,OAAO,WAAW;EAClB,MAAM,iBAAiB,MAAM,IAAI;EACjC,MAAM,MAAM,QAAQ;CACtB;AACF"}
@@ -48,7 +48,11 @@ async function detectPackageManager(startDir = rootPath()) {
48
48
  const declared = await readDeclaredPackageManager(dir);
49
49
  if (declared) return declared;
50
50
  }
51
- for (const dir of dirs) if (await fileExistsAsync(path.join(dir, "pnpm-workspace.yaml"))) return "pnpm";
51
+ for (const dir of dirs) if (await fileExistsAsync(path.join(dir, "pnpm-workspace.yaml"))) {
52
+ if (await fileExistsAsync(path.join(dir, "pnpm-lock.yaml"))) return "pnpm";
53
+ for (const { file, packageManager } of LOCKFILES) if (await fileExistsAsync(path.join(dir, file))) return packageManager;
54
+ return "pnpm";
55
+ }
52
56
  for (const dir of dirs) for (const { file, packageManager } of LOCKFILES) if (await fileExistsAsync(path.join(dir, file))) return packageManager;
53
57
  const agent = process.env.npm_config_user_agent?.split("/")[0];
54
58
  if (agent === "pnpm" || agent === "yarn" || agent === "bun") return agent;
@@ -1 +1 @@
1
- {"version":3,"file":"package-manager.mjs","names":[],"sources":["../../../../../../../../packages/core/src/updater/package-manager.ts"],"sourcesContent":["import { fileExistsAsync } from \"@warlock.js/fs\";\nimport { readFile } from \"node:fs/promises\";\nimport path from \"node:path\";\nimport { rootPath } from \"../utils\";\n\n/** Package managers the framework knows how to drive. */\nexport type PackageManager = \"npm\" | \"yarn\" | \"pnpm\" | \"bun\";\n\n/**\n * The lockfile that identifies each package manager, in detection order.\n * Bun is checked first because a Bun project may also carry a `yarn.lock`\n * (Bun writes one for tooling compatibility) — matching yarn there would run\n * the wrong installer against the wrong lockfile.\n */\nconst LOCKFILES: ReadonlyArray<{ file: string; packageManager: PackageManager }> = [\n { file: \"bun.lock\", packageManager: \"bun\" },\n { file: \"bun.lockb\", packageManager: \"bun\" },\n { file: \"pnpm-lock.yaml\", packageManager: \"pnpm\" },\n { file: \"yarn.lock\", packageManager: \"yarn\" },\n { file: \"package-lock.json\", packageManager: \"npm\" },\n];\n\n/**\n * Detect the project's package manager from its lockfile, falling back to\n * npm when none is present. Shared by `warlock update` and `warlock add` so\n * both agree on a project that happens to carry more than one lockfile.\n */\nexport async function detectPackageManager(startDir: string = rootPath()): Promise<PackageManager> {\n const dirs: string[] = [];\n\n for (let dir = path.resolve(startDir); ; dir = path.dirname(dir)) {\n dirs.push(dir);\n\n if (path.dirname(dir) === dir) break;\n }\n\n // 1. nearest declared `packageManager` field\n for (const dir of dirs) {\n const declared = await readDeclaredPackageManager(dir);\n\n if (declared) return declared;\n }\n\n // 2. a pnpm workspace marker beats any (possibly stray) lockfile\n for (const dir of dirs) {\n if (await fileExistsAsync(path.join(dir, \"pnpm-workspace.yaml\"))) return \"pnpm\";\n }\n\n // 3. lockfiles, nearest level first\n for (const dir of dirs) {\n for (const { file, packageManager } of LOCKFILES) {\n if (await fileExistsAsync(path.join(dir, file))) return packageManager;\n }\n }\n\n const agent = process.env.npm_config_user_agent?.split(\"/\")[0];\n\n if (agent === \"pnpm\" || agent === \"yarn\" || agent === \"bun\") return agent;\n\n return \"npm\";\n}\n\n/** The manager named by the app's `packageManager` field, if any. */\nasync function readDeclaredPackageManager(dir: string): Promise<PackageManager | undefined> {\n try {\n const json = JSON.parse(await readFile(path.join(dir, \"package.json\"), \"utf8\"));\n const name = String(json.packageManager ?? \"\").split(\"@\")[0];\n\n if (name === \"pnpm\" || name === \"yarn\" || name === \"bun\" || name === \"npm\") return name;\n } catch {\n // no readable package.json — fall through to lockfiles\n }\n\n return undefined;\n}\n\n/**\n * The lockfile-syncing install command for the given manager. No package\n * arguments — `warlock update` rewrites the versions in package.json first,\n * then a plain install reconciles `node_modules` to match.\n */\nexport function getInstallCommand(packageManager: PackageManager): string {\n switch (packageManager) {\n case \"yarn\":\n return \"yarn install\";\n\n case \"pnpm\":\n return \"pnpm install\";\n\n case \"bun\":\n return \"bun install\";\n\n default:\n return \"npm install\";\n }\n}\n\n/**\n * The command that installs *specific* packages — what `warlock add` needs.\n * Distinct from {@link getInstallCommand}, which only reconciles what is\n * already written into package.json.\n */\nexport function getAddCommand(packageManager: PackageManager): string {\n switch (packageManager) {\n case \"yarn\":\n return \"yarn add\";\n\n case \"pnpm\":\n return \"pnpm add\";\n\n case \"bun\":\n return \"bun add\";\n\n default:\n return \"npm install\";\n }\n}\n\n/**\n * The package-manager command that saves an exact version. Warlock family\n * dependencies use this path so the version selected by the executing Core is\n * preserved verbatim in package.json instead of being widened by a manager's\n * default save prefix.\n */\nexport function getExactAddCommand(packageManager: PackageManager): string {\n switch (packageManager) {\n case \"yarn\":\n return \"yarn add --exact\";\n\n case \"pnpm\":\n return \"pnpm add --save-exact\";\n\n case \"bun\":\n return \"bun add --exact\";\n\n default:\n return \"npm install --save-exact\";\n }\n}\n"],"mappings":";;;;;;;;;;;;;AAcA,MAAM,YAA6E;CACjF;EAAE,MAAM;EAAY,gBAAgB;CAAM;CAC1C;EAAE,MAAM;EAAa,gBAAgB;CAAM;CAC3C;EAAE,MAAM;EAAkB,gBAAgB;CAAO;CACjD;EAAE,MAAM;EAAa,gBAAgB;CAAO;CAC5C;EAAE,MAAM;EAAqB,gBAAgB;CAAM;AACrD;;;;;;AAOA,eAAsB,qBAAqB,WAAmB,SAAS,GAA4B;CACjG,MAAM,OAAiB,CAAC;CAExB,KAAK,IAAI,MAAM,KAAK,QAAQ,QAAQ,IAAK,MAAM,KAAK,QAAQ,GAAG,GAAG;EAChE,KAAK,KAAK,GAAG;EAEb,IAAI,KAAK,QAAQ,GAAG,MAAM,KAAK;CACjC;CAGA,KAAK,MAAM,OAAO,MAAM;EACtB,MAAM,WAAW,MAAM,2BAA2B,GAAG;EAErD,IAAI,UAAU,OAAO;CACvB;CAGA,KAAK,MAAM,OAAO,MAChB,IAAI,MAAM,gBAAgB,KAAK,KAAK,KAAK,qBAAqB,CAAC,GAAG,OAAO;CAI3E,KAAK,MAAM,OAAO,MAChB,KAAK,MAAM,EAAE,MAAM,oBAAoB,WACrC,IAAI,MAAM,gBAAgB,KAAK,KAAK,KAAK,IAAI,CAAC,GAAG,OAAO;CAI5D,MAAM,QAAQ,QAAQ,IAAI,uBAAuB,MAAM,GAAG,CAAC,CAAC;CAE5D,IAAI,UAAU,UAAU,UAAU,UAAU,UAAU,OAAO,OAAO;CAEpE,OAAO;AACT;;AAGA,eAAe,2BAA2B,KAAkD;CAC1F,IAAI;EACF,MAAM,OAAO,KAAK,MAAM,MAAM,SAAS,KAAK,KAAK,KAAK,cAAc,GAAG,MAAM,CAAC;EAC9E,MAAM,OAAO,OAAO,KAAK,kBAAkB,EAAE,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC;EAE1D,IAAI,SAAS,UAAU,SAAS,UAAU,SAAS,SAAS,SAAS,OAAO,OAAO;CACrF,QAAQ,CAER;AAGF;;;;;;AAOA,SAAgB,kBAAkB,gBAAwC;CACxE,QAAQ,gBAAR;EACE,KAAK,QACH,OAAO;EAET,KAAK,QACH,OAAO;EAET,KAAK,OACH,OAAO;EAET,SACE,OAAO;CACX;AACF;;;;;;AAOA,SAAgB,cAAc,gBAAwC;CACpE,QAAQ,gBAAR;EACE,KAAK,QACH,OAAO;EAET,KAAK,QACH,OAAO;EAET,KAAK,OACH,OAAO;EAET,SACE,OAAO;CACX;AACF;;;;;;;AAQA,SAAgB,mBAAmB,gBAAwC;CACzE,QAAQ,gBAAR;EACE,KAAK,QACH,OAAO;EAET,KAAK,QACH,OAAO;EAET,KAAK,OACH,OAAO;EAET,SACE,OAAO;CACX;AACF"}
1
+ {"version":3,"file":"package-manager.mjs","names":[],"sources":["../../../../../../../../packages/core/src/updater/package-manager.ts"],"sourcesContent":["import { fileExistsAsync } from \"@warlock.js/fs\";\nimport { readFile } from \"node:fs/promises\";\nimport path from \"node:path\";\nimport { rootPath } from \"../utils\";\n\n/** Package managers the framework knows how to drive. */\nexport type PackageManager = \"npm\" | \"yarn\" | \"pnpm\" | \"bun\";\n\n/**\n * The lockfile that identifies each package manager, in detection order.\n * Bun is checked first because a Bun project may also carry a `yarn.lock`\n * (Bun writes one for tooling compatibility) — matching yarn there would run\n * the wrong installer against the wrong lockfile.\n */\nconst LOCKFILES: ReadonlyArray<{ file: string; packageManager: PackageManager }> = [\n { file: \"bun.lock\", packageManager: \"bun\" },\n { file: \"bun.lockb\", packageManager: \"bun\" },\n { file: \"pnpm-lock.yaml\", packageManager: \"pnpm\" },\n { file: \"yarn.lock\", packageManager: \"yarn\" },\n { file: \"package-lock.json\", packageManager: \"npm\" },\n];\n\n/**\n * Detect the project's package manager from its lockfile, falling back to\n * npm when none is present. Shared by `warlock update` and `warlock add` so\n * both agree on a project that happens to carry more than one lockfile.\n */\nexport async function detectPackageManager(startDir: string = rootPath()): Promise<PackageManager> {\n const dirs: string[] = [];\n\n for (let dir = path.resolve(startDir); ; dir = path.dirname(dir)) {\n dirs.push(dir);\n\n if (path.dirname(dir) === dir) break;\n }\n\n // 1. nearest declared `packageManager` field\n for (const dir of dirs) {\n const declared = await readDeclaredPackageManager(dir);\n\n if (declared) return declared;\n }\n\n // 2. a workspace governs descendants, but its own real lockfile wins over a marker\n for (const dir of dirs) {\n if (await fileExistsAsync(path.join(dir, \"pnpm-workspace.yaml\"))) {\n if (await fileExistsAsync(path.join(dir, \"pnpm-lock.yaml\"))) return \"pnpm\";\n for (const { file, packageManager } of LOCKFILES) {\n if (await fileExistsAsync(path.join(dir, file))) return packageManager;\n }\n return \"pnpm\";\n }\n }\n\n // 3. lockfiles, nearest level first\n for (const dir of dirs) {\n for (const { file, packageManager } of LOCKFILES) {\n if (await fileExistsAsync(path.join(dir, file))) return packageManager;\n }\n }\n\n const agent = process.env.npm_config_user_agent?.split(\"/\")[0];\n\n if (agent === \"pnpm\" || agent === \"yarn\" || agent === \"bun\") return agent;\n\n return \"npm\";\n}\n\n/** The manager named by the app's `packageManager` field, if any. */\nasync function readDeclaredPackageManager(dir: string): Promise<PackageManager | undefined> {\n try {\n const json = JSON.parse(await readFile(path.join(dir, \"package.json\"), \"utf8\"));\n const name = String(json.packageManager ?? \"\").split(\"@\")[0];\n\n if (name === \"pnpm\" || name === \"yarn\" || name === \"bun\" || name === \"npm\") return name;\n } catch {\n // no readable package.json — fall through to lockfiles\n }\n\n return undefined;\n}\n\n/**\n * The lockfile-syncing install command for the given manager. No package\n * arguments — `warlock update` rewrites the versions in package.json first,\n * then a plain install reconciles `node_modules` to match.\n */\nexport function getInstallCommand(packageManager: PackageManager): string {\n switch (packageManager) {\n case \"yarn\":\n return \"yarn install\";\n\n case \"pnpm\":\n return \"pnpm install\";\n\n case \"bun\":\n return \"bun install\";\n\n default:\n return \"npm install\";\n }\n}\n\n/**\n * The command that installs *specific* packages — what `warlock add` needs.\n * Distinct from {@link getInstallCommand}, which only reconciles what is\n * already written into package.json.\n */\nexport function getAddCommand(packageManager: PackageManager): string {\n switch (packageManager) {\n case \"yarn\":\n return \"yarn add\";\n\n case \"pnpm\":\n return \"pnpm add\";\n\n case \"bun\":\n return \"bun add\";\n\n default:\n return \"npm install\";\n }\n}\n\n/**\n * The package-manager command that saves an exact version. Warlock family\n * dependencies use this path so the version selected by the executing Core is\n * preserved verbatim in package.json instead of being widened by a manager's\n * default save prefix.\n */\nexport function getExactAddCommand(packageManager: PackageManager): string {\n switch (packageManager) {\n case \"yarn\":\n return \"yarn add --exact\";\n\n case \"pnpm\":\n return \"pnpm add --save-exact\";\n\n case \"bun\":\n return \"bun add --exact\";\n\n default:\n return \"npm install --save-exact\";\n }\n}\n"],"mappings":";;;;;;;;;;;;;AAcA,MAAM,YAA6E;CACjF;EAAE,MAAM;EAAY,gBAAgB;CAAM;CAC1C;EAAE,MAAM;EAAa,gBAAgB;CAAM;CAC3C;EAAE,MAAM;EAAkB,gBAAgB;CAAO;CACjD;EAAE,MAAM;EAAa,gBAAgB;CAAO;CAC5C;EAAE,MAAM;EAAqB,gBAAgB;CAAM;AACrD;;;;;;AAOA,eAAsB,qBAAqB,WAAmB,SAAS,GAA4B;CACjG,MAAM,OAAiB,CAAC;CAExB,KAAK,IAAI,MAAM,KAAK,QAAQ,QAAQ,IAAK,MAAM,KAAK,QAAQ,GAAG,GAAG;EAChE,KAAK,KAAK,GAAG;EAEb,IAAI,KAAK,QAAQ,GAAG,MAAM,KAAK;CACjC;CAGA,KAAK,MAAM,OAAO,MAAM;EACtB,MAAM,WAAW,MAAM,2BAA2B,GAAG;EAErD,IAAI,UAAU,OAAO;CACvB;CAGA,KAAK,MAAM,OAAO,MAChB,IAAI,MAAM,gBAAgB,KAAK,KAAK,KAAK,qBAAqB,CAAC,GAAG;EAChE,IAAI,MAAM,gBAAgB,KAAK,KAAK,KAAK,gBAAgB,CAAC,GAAG,OAAO;EACpE,KAAK,MAAM,EAAE,MAAM,oBAAoB,WACrC,IAAI,MAAM,gBAAgB,KAAK,KAAK,KAAK,IAAI,CAAC,GAAG,OAAO;EAE1D,OAAO;CACT;CAIF,KAAK,MAAM,OAAO,MAChB,KAAK,MAAM,EAAE,MAAM,oBAAoB,WACrC,IAAI,MAAM,gBAAgB,KAAK,KAAK,KAAK,IAAI,CAAC,GAAG,OAAO;CAI5D,MAAM,QAAQ,QAAQ,IAAI,uBAAuB,MAAM,GAAG,CAAC,CAAC;CAE5D,IAAI,UAAU,UAAU,UAAU,UAAU,UAAU,OAAO,OAAO;CAEpE,OAAO;AACT;;AAGA,eAAe,2BAA2B,KAAkD;CAC1F,IAAI;EACF,MAAM,OAAO,KAAK,MAAM,MAAM,SAAS,KAAK,KAAK,KAAK,cAAc,GAAG,MAAM,CAAC;EAC9E,MAAM,OAAO,OAAO,KAAK,kBAAkB,EAAE,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC;EAE1D,IAAI,SAAS,UAAU,SAAS,UAAU,SAAS,SAAS,SAAS,OAAO,OAAO;CACrF,QAAQ,CAER;AAGF;;;;;;AAOA,SAAgB,kBAAkB,gBAAwC;CACxE,QAAQ,gBAAR;EACE,KAAK,QACH,OAAO;EAET,KAAK,QACH,OAAO;EAET,KAAK,OACH,OAAO;EAET,SACE,OAAO;CACX;AACF;;;;;;AAOA,SAAgB,cAAc,gBAAwC;CACpE,QAAQ,gBAAR;EACE,KAAK,QACH,OAAO;EAET,KAAK,QACH,OAAO;EAET,KAAK,OACH,OAAO;EAET,SACE,OAAO;CACX;AACF;;;;;;;AAQA,SAAgB,mBAAmB,gBAAwC;CACzE,QAAQ,gBAAR;EACE,KAAK,QACH,OAAO;EAET,KAAK,QACH,OAAO;EAET,KAAK,OACH,OAAO;EAET,SACE,OAAO;CACX;AACF"}
@@ -2,7 +2,7 @@ import { importedLocalNames, importsFrom, manualAt, objectProperty, objectProper
2
2
  import { headViewport } from "./head-viewport.mjs";
3
3
  import { loggerSyncCalls } from "./logger-sync-calls.mjs";
4
4
  import { node2215, warlockVersions } from "./package-json.mjs";
5
- import { oxcToolchain } from "./oxc-toolchain.mjs";
5
+ import { oxcToolchain, oxcToolchainRemoval } from "./oxc-toolchain.mjs";
6
6
  import { requestLogSnapshot } from "./request-log-snapshot.mjs";
7
7
  import { aiAnthropicBody, aiCompactionOptIn, aiFinishReason, aiToolDispatch, rateLimitDefaults, sealContainerCatch, sealRefineOutput, sealUnionOutput, sealValidateResult } from "./review-sites.mjs";
8
8
 
@@ -28,6 +28,7 @@ const CODEMODS = [
28
28
  sealUnionOutput,
29
29
  aiCompactionOptIn,
30
30
  oxcToolchain,
31
+ oxcToolchainRemoval,
31
32
  warlockVersions
32
33
  ];
33
34
 
@@ -1 +1 @@
1
- {"version":3,"file":"index.mjs","names":[],"sources":["../../../../../../../../../packages/core/src/upgrade/codemods/index.ts"],"sourcesContent":["import type { Codemod } from \"../types\";\nimport { headViewport } from \"./head-viewport\";\nimport { loggerSyncCalls } from \"./logger-sync-calls\";\nimport { node2215, warlockVersions } from \"./package-json\";\nimport { oxcToolchain } from \"./oxc-toolchain\";\nimport { requestLogSnapshot } from \"./request-log-snapshot\";\nimport {\n aiAnthropicBody,\n aiCompactionOptIn,\n aiFinishReason,\n aiToolDispatch,\n rateLimitDefaults,\n sealContainerCatch,\n sealRefineOutput,\n sealUnionOutput,\n sealValidateResult,\n} from \"./review-sites\";\n\n/**\n * Every codemod, oldest release first. A codemod runs when the app upgrades\n * across its `version` (`from < version <= to`); `\"target\"` ones run on every\n * upgrade. Add one here for each breaking change, and name its id next to the\n * `**BREAKING**` entry in the package changelog.\n */\nexport const CODEMODS: readonly Codemod[] = [\n loggerSyncCalls,\n requestLogSnapshot,\n headViewport,\n sealValidateResult,\n aiToolDispatch,\n aiAnthropicBody,\n aiFinishReason,\n node2215,\n rateLimitDefaults,\n sealRefineOutput,\n sealContainerCatch,\n sealUnionOutput,\n aiCompactionOptIn,\n oxcToolchain,\n warlockVersions,\n];\n\nexport * from \"./helpers\";\n"],"mappings":";;;;;;;;;;;;;;;AAwBA,MAAa,WAA+B;CAC1C;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;AACF"}
1
+ {"version":3,"file":"index.mjs","names":[],"sources":["../../../../../../../../../packages/core/src/upgrade/codemods/index.ts"],"sourcesContent":["import type { Codemod } from \"../types\";\nimport { headViewport } from \"./head-viewport\";\nimport { loggerSyncCalls } from \"./logger-sync-calls\";\nimport { node2215, warlockVersions } from \"./package-json\";\nimport { oxcToolchain, oxcToolchainRemoval } from \"./oxc-toolchain\";\nimport { requestLogSnapshot } from \"./request-log-snapshot\";\nimport {\n aiAnthropicBody,\n aiCompactionOptIn,\n aiFinishReason,\n aiToolDispatch,\n rateLimitDefaults,\n sealContainerCatch,\n sealRefineOutput,\n sealUnionOutput,\n sealValidateResult,\n} from \"./review-sites\";\n\n/**\n * Every codemod, oldest release first. A codemod runs when the app upgrades\n * across its `version` (`from < version <= to`); `\"target\"` ones run on every\n * upgrade. Add one here for each breaking change, and name its id next to the\n * `**BREAKING**` entry in the package changelog.\n */\nexport const CODEMODS: readonly Codemod[] = [\n loggerSyncCalls,\n requestLogSnapshot,\n headViewport,\n sealValidateResult,\n aiToolDispatch,\n aiAnthropicBody,\n aiFinishReason,\n node2215,\n rateLimitDefaults,\n sealRefineOutput,\n sealContainerCatch,\n sealUnionOutput,\n aiCompactionOptIn,\n oxcToolchain,\n oxcToolchainRemoval,\n warlockVersions,\n];\n\nexport * from \"./helpers\";\n"],"mappings":";;;;;;;;;;;;;;;AAwBA,MAAa,WAA+B;CAC1C;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;AACF"}