-
Notifications
You must be signed in to change notification settings - Fork 390
fix(ts-sdk): emit v0 root-import deprecation via process.emitWarning #3933
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
sushaan-k
wants to merge
1
commit into
hatchet-dev:main
Choose a base branch
from
sushaan-k:aarya/v0-deprecation-emit-warning
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
+208
−16
Open
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,9 +1,5 @@ | ||
| import { emitV0RemovedWarning } from './util/v0-deprecation-warning'; | ||
|
|
||
| export * from './legacy/step'; | ||
|
|
||
| console.warn( | ||
| '\x1b[31mDeprecation warning: The v0 sdk, including the step module has been deprecated and has been removed in release v1.14.0.\x1b[0m' | ||
| ); | ||
| console.warn( | ||
| '\x1b[32mPlease migrate to v1 SDK instead: https://docs.hatchet.run/home/v1-sdk-improvements\x1b[0m' | ||
| ); | ||
| console.warn('--------------------------------'); | ||
| emitV0RemovedWarning('step'); |
117 changes: 117 additions & 0 deletions
117
sdks/typescript/src/util/v0-deprecation-warning.test.ts
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,117 @@ | ||
| import { | ||
| V0_DEPRECATION_CODE, | ||
| _resetEmittedV0Warnings, | ||
| emitV0RemovedWarning, | ||
| } from './v0-deprecation-warning'; | ||
|
|
||
| describe('emitV0RemovedWarning', () => { | ||
| let emitWarningSpy: jest.SpyInstance; | ||
|
|
||
| beforeEach(() => { | ||
| _resetEmittedV0Warnings(); | ||
| emitWarningSpy = jest.spyOn(process, 'emitWarning').mockImplementation(() => {}); | ||
| }); | ||
|
|
||
| afterEach(() => { | ||
| emitWarningSpy.mockRestore(); | ||
| }); | ||
|
|
||
| it('emits via process.emitWarning with the stable HATCHET_V0_REMOVED code', () => { | ||
| emitV0RemovedWarning('workflow'); | ||
|
|
||
| expect(emitWarningSpy).toHaveBeenCalledTimes(1); | ||
| const [[message, opts]] = emitWarningSpy.mock.calls; | ||
| expect(message).toContain('workflow module'); | ||
| expect(message).toContain('v1.14.0'); | ||
| expect(message).toContain('https://docs.hatchet.run/home/v1-sdk-improvements'); | ||
| expect(opts).toMatchObject({ | ||
| type: 'DeprecationWarning', | ||
| code: V0_DEPRECATION_CODE, | ||
| }); | ||
| }); | ||
|
|
||
| it('passes the optional detail string through to process.emitWarning', () => { | ||
| emitV0RemovedWarning('workflow', 'ConcurrencyLimitStrategy has moved.'); | ||
|
|
||
| expect(emitWarningSpy).toHaveBeenCalledTimes(1); | ||
| const [[, opts]] = emitWarningSpy.mock.calls; | ||
| expect(opts.detail).toBe('ConcurrencyLimitStrategy has moved.'); | ||
| }); | ||
|
|
||
| it('deduplicates per submodule across repeated calls', () => { | ||
| emitV0RemovedWarning('workflow'); | ||
| emitV0RemovedWarning('workflow'); | ||
| emitV0RemovedWarning('workflow'); | ||
|
|
||
| expect(emitWarningSpy).toHaveBeenCalledTimes(1); | ||
| }); | ||
|
|
||
| it('emits separate warnings for different submodules', () => { | ||
| emitV0RemovedWarning('workflow'); | ||
| emitV0RemovedWarning('step'); | ||
|
|
||
| expect(emitWarningSpy).toHaveBeenCalledTimes(2); | ||
| expect(emitWarningSpy.mock.calls[0][0]).toContain('workflow module'); | ||
| expect(emitWarningSpy.mock.calls[1][0]).toContain('step module'); | ||
| }); | ||
|
|
||
| it('routes to console.warn when process.throwDeprecation is set, bypassing emitWarning', () => { | ||
| // Under --throw-deprecation or process.throwDeprecation=true, Node queues | ||
| // `throw warning` on the next tick AFTER emitWarning returns, so a | ||
| // try/catch around the call cannot intercept it. The helper must check | ||
| // the flag up front and avoid calling emitWarning at all. | ||
| const originalThrowDeprecation = process.throwDeprecation; | ||
| const consoleWarnSpy = jest.spyOn(console, 'warn').mockImplementation(() => {}); | ||
| process.throwDeprecation = true; | ||
|
|
||
| try { | ||
| expect(() => emitV0RemovedWarning('workflow')).not.toThrow(); | ||
| expect(emitWarningSpy).not.toHaveBeenCalled(); | ||
| expect(consoleWarnSpy).toHaveBeenCalledTimes(1); | ||
| expect(consoleWarnSpy.mock.calls[0][0]).toContain(V0_DEPRECATION_CODE); | ||
| expect(consoleWarnSpy.mock.calls[0][0]).toContain('workflow module'); | ||
| } finally { | ||
| process.throwDeprecation = originalThrowDeprecation; | ||
| consoleWarnSpy.mockRestore(); | ||
| } | ||
| }); | ||
|
|
||
| it('falls back to console.warn if emitWarning throws synchronously (non-Node hosts)', () => { | ||
| // Defense-in-depth: a polyfilled `process.emitWarning` in a non-Node | ||
| // host could throw synchronously. Real Node throws asynchronously under | ||
| // --throw-deprecation; that path is exercised by the test above. | ||
| emitWarningSpy.mockImplementation(() => { | ||
| throw new Error('synchronous emitWarning failure'); | ||
| }); | ||
| const consoleWarnSpy = jest.spyOn(console, 'warn').mockImplementation(() => {}); | ||
|
|
||
| try { | ||
| expect(() => emitV0RemovedWarning('workflow')).not.toThrow(); | ||
| expect(emitWarningSpy).toHaveBeenCalledTimes(1); | ||
| expect(consoleWarnSpy).toHaveBeenCalledTimes(1); | ||
| expect(consoleWarnSpy.mock.calls[0][0]).toContain(V0_DEPRECATION_CODE); | ||
| } finally { | ||
| consoleWarnSpy.mockRestore(); | ||
| } | ||
| }); | ||
|
|
||
| it('falls back to console.warn when process.emitWarning is unavailable', () => { | ||
| emitWarningSpy.mockRestore(); | ||
| const original = process.emitWarning; | ||
| // Simulate a runtime that doesn't expose emitWarning. | ||
| (process as unknown as { emitWarning?: typeof process.emitWarning }).emitWarning = | ||
| undefined as unknown as typeof process.emitWarning; | ||
| const consoleWarnSpy = jest.spyOn(console, 'warn').mockImplementation(() => {}); | ||
|
|
||
| try { | ||
| emitV0RemovedWarning('step'); | ||
|
|
||
| expect(consoleWarnSpy).toHaveBeenCalledTimes(1); | ||
| expect(consoleWarnSpy.mock.calls[0][0]).toContain(V0_DEPRECATION_CODE); | ||
| expect(consoleWarnSpy.mock.calls[0][0]).toContain('step module'); | ||
| } finally { | ||
| consoleWarnSpy.mockRestore(); | ||
| (process as unknown as { emitWarning: typeof process.emitWarning }).emitWarning = original; | ||
| } | ||
| }); | ||
| }); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,84 @@ | ||
| /** | ||
| * v0 SDK root-import deprecation warnings. | ||
| * | ||
| * The legacy `workflow` and `step` re-exports under the root specifier need | ||
| * to keep nagging consumers to migrate to v1, but the original implementation | ||
| * used `console.warn` at module evaluation time, which: | ||
| * - cannot be silenced by Node's standard `--no-deprecation`, | ||
| * `--no-warnings`, or `--no-warnings=DeprecationWarning` flags; | ||
| * - has no stable `code` for `process.on('warning', ...)` handlers; and | ||
| * - is duplicated when both submodules are loaded (which happens for | ||
| * anyone importing from the root, since `index.ts` re-exports both). | ||
| * | ||
| * Switching to `process.emitWarning` with a fixed code makes the warnings | ||
| * filterable, dedupable, and consistent with the rest of Node's deprecation | ||
|
|
||
| * surface, while still being visible by default. | ||
| */ | ||
|
|
||
| export const V0_DEPRECATION_CODE = 'HATCHET_V0_REMOVED'; | ||
| const MIGRATION_URL = 'https://docs.hatchet.run/home/v1-sdk-improvements'; | ||
|
|
||
| const emittedSubmodules = new Set<string>(); | ||
|
|
||
| /** Reset hook for tests. Not part of the public API. */ | ||
| export function _resetEmittedV0Warnings(): void { | ||
| emittedSubmodules.clear(); | ||
| } | ||
|
|
||
| function fallbackConsoleWarn(message: string, detail?: string): void { | ||
| console.warn(`[${V0_DEPRECATION_CODE}] ${message}${detail ? `\n${detail}` : ''}`); | ||
| } | ||
|
|
||
| /** | ||
| * Emit a deduplicated v0-removal deprecation warning for a given submodule. | ||
| * | ||
| * Each unique `submodule` is emitted at most once per process. Uses | ||
| * `process.emitWarning` when available so consumers can suppress or filter | ||
| * via standard Node mechanisms. | ||
| * | ||
| * Importing the SDK must never abort module evaluation, since the root | ||
| * specifier still re-exports v0 `workflow` and `step` for transitive | ||
| * consumers who only use v1 APIs. Two cases would otherwise crash them: | ||
| * | ||
| * 1. `process.throwDeprecation` (set by `--throw-deprecation` or directly). | ||
| * Node queues a `throw warning` on the next tick after `emitWarning` | ||
| * returns, so a `try`/`catch` around the call would not catch it. We | ||
| * check the flag up front and route to `console.warn` instead. | ||
| * 2. Runtimes that don't expose `process.emitWarning` at all (older | ||
| * browsers, certain bundler shims). Same fallback applies. | ||
| * | ||
| * The remaining `try`/`catch` is belt-and-suspenders for non-Node hosts | ||
| * where a polyfilled `emitWarning` could throw synchronously. | ||
| * | ||
| * @param submodule - The legacy v0 submodule being imported (e.g. "workflow", "step"). | ||
| * @param detail - Optional follow-up sentence appended to the warning detail. | ||
| */ | ||
| export function emitV0RemovedWarning(submodule: string, detail?: string): void { | ||
| if (emittedSubmodules.has(submodule)) { | ||
| return; | ||
| } | ||
| emittedSubmodules.add(submodule); | ||
|
|
||
| const message = | ||
| `The v0 SDK, including the ${submodule} module, has been deprecated and was removed in v1.14.0. ` + | ||
| `Please migrate to the v1 SDK: ${MIGRATION_URL}`; | ||
|
|
||
| const hasProcess = typeof process !== 'undefined'; | ||
| const hasEmitWarning = hasProcess && typeof process.emitWarning === 'function'; | ||
| const willThrowAsync = hasProcess && process.throwDeprecation === true; | ||
|
|
||
| if (!hasEmitWarning || willThrowAsync) { | ||
| fallbackConsoleWarn(message, detail); | ||
| return; | ||
| } | ||
|
|
||
| try { | ||
| process.emitWarning(message, { | ||
| type: 'DeprecationWarning', | ||
| code: V0_DEPRECATION_CODE, | ||
| detail, | ||
| }); | ||
| } catch { | ||
| fallbackConsoleWarn(message, detail); | ||
| } | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,16 +1,11 @@ | ||
| import { ConcurrencyLimitStrategy, StickyStrategy } from '@hatchet/v1'; | ||
| import { emitV0RemovedWarning } from './util/v0-deprecation-warning'; | ||
|
|
||
| export * from './legacy/workflow'; | ||
|
|
||
| export { ConcurrencyLimitStrategy, StickyStrategy }; | ||
|
|
||
| console.warn( | ||
| '\x1b[31mDeprecation warning: The v0 sdk, including the workflow module has been deprecated and has been removed in release v1.14.0.\x1b[0m' | ||
| emitV0RemovedWarning( | ||
| 'workflow', | ||
| 'ConcurrencyLimitStrategy and StickyStrategy have been moved to @hatchet-dev/typescript-sdk/v1.' | ||
| ); | ||
| console.warn( | ||
| '\x1b[31mPlease migrate to v1 SDK instead: https://docs.hatchet.run/home/v1-sdk-improvements\x1b[0m' | ||
| ); | ||
| console.warn( | ||
| 'ConcurrencyLimitStrategy, StickyStrategy have been moved to @hatchet-dev/typescript-sdk/v1' | ||
| ); | ||
| console.warn('--------------------------------'); |
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
non-blocking suggestion: I don’t think this source comment needs to enumerate the exact Node CLI flags. Since those are Node-owned behavior and can vary by version, I would slightly prefer keeping the source comment focused on the reason for
process.emitWarning: it gives consumers structured warning metadata and a stableHATCHET_V0_REMOVEDcode that standard Node warning controls can filter or suppress. That would also avoid teaching the undocumented--no-warnings=DeprecationWarningform in source.