diff --git a/.changeset/fix-plugin-sentry.md b/.changeset/fix-plugin-sentry.md new file mode 100644 index 000000000..eac85a8f5 --- /dev/null +++ b/.changeset/fix-plugin-sentry.md @@ -0,0 +1,5 @@ +--- +"@stackflow/plugin-sentry": minor +--- + +fix(plugin-sentry): decouple Sentry init from plugin, fix dependency structure, remove internal API usage diff --git a/.pnp.cjs b/.pnp.cjs index 067fd887a..c23207057 100755 --- a/.pnp.cjs +++ b/.pnp.cjs @@ -70,6 +70,10 @@ const RAW_RUNTIME_STATE = "name": "@stackflow/plugin-renderer-web",\ "reference": "workspace:extensions/plugin-renderer-web"\ },\ + {\ + "name": "@stackflow/plugin-sentry",\ + "reference": "workspace:extensions/plugin-sentry"\ + },\ {\ "name": "@stackflow/plugin-stack-depth-change",\ "reference": "workspace:extensions/plugin-stack-depth-change"\ @@ -106,6 +110,7 @@ const RAW_RUNTIME_STATE = ["@stackflow/plugin-preload", ["virtual:413bca98ff76262f6f1f73762ccc4b7edee04a5da42f3d6b9ed2cb2d6dbc397b2094da59b50f6e828091c88e7b5f86990feff596c43f0eb50a58fc42aae64a20#workspace:extensions/plugin-preload", "workspace:extensions/plugin-preload"]],\ ["@stackflow/plugin-renderer-basic", ["virtual:413bca98ff76262f6f1f73762ccc4b7edee04a5da42f3d6b9ed2cb2d6dbc397b2094da59b50f6e828091c88e7b5f86990feff596c43f0eb50a58fc42aae64a20#workspace:extensions/plugin-renderer-basic", "workspace:extensions/plugin-renderer-basic"]],\ ["@stackflow/plugin-renderer-web", ["workspace:extensions/plugin-renderer-web"]],\ + ["@stackflow/plugin-sentry", ["workspace:extensions/plugin-sentry"]],\ ["@stackflow/plugin-stack-depth-change", ["virtual:413bca98ff76262f6f1f73762ccc4b7edee04a5da42f3d6b9ed2cb2d6dbc397b2094da59b50f6e828091c88e7b5f86990feff596c43f0eb50a58fc42aae64a20#workspace:extensions/plugin-stack-depth-change", "workspace:extensions/plugin-stack-depth-change"]],\ ["@stackflow/react", ["virtual:413bca98ff76262f6f1f73762ccc4b7edee04a5da42f3d6b9ed2cb2d6dbc397b2094da59b50f6e828091c88e7b5f86990feff596c43f0eb50a58fc42aae64a20#workspace:integrations/react", "workspace:integrations/react"]],\ ["@stackflow/react-ui-core", ["virtual:669046a185e83900af978519e5adddf8e8f1f8fed824849248ba56cf8fcd4e4208872f27e14c3c844d3b769f42be1ba6e0aa90f12df9fa6c38a55aedee211f53#workspace:extensions/react-ui-core", "workspace:extensions/react-ui-core"]]\ @@ -2747,6 +2752,71 @@ const RAW_RUNTIME_STATE = "linkType": "HARD"\ }]\ ]],\ + ["@sentry-internal/browser-utils", [\ + ["npm:10.46.0", {\ + "packageLocation": "./.yarn/cache/@sentry-internal-browser-utils-npm-10.46.0-c2bfafc8a9-6ae7c6cc8d.zip/node_modules/@sentry-internal/browser-utils/",\ + "packageDependencies": [\ + ["@sentry-internal/browser-utils", "npm:10.46.0"],\ + ["@sentry/core", "npm:10.46.0"]\ + ],\ + "linkType": "HARD"\ + }]\ + ]],\ + ["@sentry-internal/feedback", [\ + ["npm:10.46.0", {\ + "packageLocation": "./.yarn/cache/@sentry-internal-feedback-npm-10.46.0-94a0d30ebb-e6c0b1fa93.zip/node_modules/@sentry-internal/feedback/",\ + "packageDependencies": [\ + ["@sentry-internal/feedback", "npm:10.46.0"],\ + ["@sentry/core", "npm:10.46.0"]\ + ],\ + "linkType": "HARD"\ + }]\ + ]],\ + ["@sentry-internal/replay", [\ + ["npm:10.46.0", {\ + "packageLocation": "./.yarn/cache/@sentry-internal-replay-npm-10.46.0-21ab33d9b1-e7c3e9e880.zip/node_modules/@sentry-internal/replay/",\ + "packageDependencies": [\ + ["@sentry-internal/replay", "npm:10.46.0"],\ + ["@sentry-internal/browser-utils", "npm:10.46.0"],\ + ["@sentry/core", "npm:10.46.0"]\ + ],\ + "linkType": "HARD"\ + }]\ + ]],\ + ["@sentry-internal/replay-canvas", [\ + ["npm:10.46.0", {\ + "packageLocation": "./.yarn/cache/@sentry-internal-replay-canvas-npm-10.46.0-e0dee1cb26-0ddfacfc79.zip/node_modules/@sentry-internal/replay-canvas/",\ + "packageDependencies": [\ + ["@sentry-internal/replay-canvas", "npm:10.46.0"],\ + ["@sentry-internal/replay", "npm:10.46.0"],\ + ["@sentry/core", "npm:10.46.0"]\ + ],\ + "linkType": "HARD"\ + }]\ + ]],\ + ["@sentry/browser", [\ + ["npm:10.46.0", {\ + "packageLocation": "./.yarn/cache/@sentry-browser-npm-10.46.0-91e2f4dcc5-f447c01f96.zip/node_modules/@sentry/browser/",\ + "packageDependencies": [\ + ["@sentry/browser", "npm:10.46.0"],\ + ["@sentry-internal/browser-utils", "npm:10.46.0"],\ + ["@sentry-internal/feedback", "npm:10.46.0"],\ + ["@sentry-internal/replay", "npm:10.46.0"],\ + ["@sentry-internal/replay-canvas", "npm:10.46.0"],\ + ["@sentry/core", "npm:10.46.0"]\ + ],\ + "linkType": "HARD"\ + }]\ + ]],\ + ["@sentry/core", [\ + ["npm:10.46.0", {\ + "packageLocation": "./.yarn/cache/@sentry-core-npm-10.46.0-8ff3972576-bbe823f9de.zip/node_modules/@sentry/core/",\ + "packageDependencies": [\ + ["@sentry/core", "npm:10.46.0"]\ + ],\ + "linkType": "HARD"\ + }]\ + ]],\ ["@sinclair/typebox", [\ ["npm:0.27.8", {\ "packageLocation": "./.yarn/cache/@sinclair-typebox-npm-0.27.8-23e206d653-297f95ff77.zip/node_modules/@sinclair/typebox/",\ @@ -3333,6 +3403,21 @@ const RAW_RUNTIME_STATE = "linkType": "SOFT"\ }]\ ]],\ + ["@stackflow/plugin-sentry", [\ + ["workspace:extensions/plugin-sentry", {\ + "packageLocation": "./extensions/plugin-sentry/",\ + "packageDependencies": [\ + ["@stackflow/plugin-sentry", "workspace:extensions/plugin-sentry"],\ + ["@sentry/browser", "npm:10.46.0"],\ + ["@sentry/core", "npm:10.46.0"],\ + ["@stackflow/core", "workspace:core"],\ + ["@stackflow/esbuild-config", "workspace:packages/esbuild-config"],\ + ["esbuild", "npm:0.23.0"],\ + ["typescript", "patch:typescript@npm%3A5.5.3#optional!builtin::version=5.5.3&hash=379a07"]\ + ],\ + "linkType": "SOFT"\ + }]\ + ]],\ ["@stackflow/plugin-stack-depth-change", [\ ["virtual:413bca98ff76262f6f1f73762ccc4b7edee04a5da42f3d6b9ed2cb2d6dbc397b2094da59b50f6e828091c88e7b5f86990feff596c43f0eb50a58fc42aae64a20#workspace:extensions/plugin-stack-depth-change", {\ "packageLocation": "./.yarn/__virtual__/@stackflow-plugin-stack-depth-change-virtual-05c63f7f2d/1/extensions/plugin-stack-depth-change/",\ diff --git a/.yarn/cache/@sentry-browser-npm-10.46.0-91e2f4dcc5-f447c01f96.zip b/.yarn/cache/@sentry-browser-npm-10.46.0-91e2f4dcc5-f447c01f96.zip new file mode 100644 index 000000000..069970cd2 Binary files /dev/null and b/.yarn/cache/@sentry-browser-npm-10.46.0-91e2f4dcc5-f447c01f96.zip differ diff --git a/.yarn/cache/@sentry-core-npm-10.46.0-8ff3972576-bbe823f9de.zip b/.yarn/cache/@sentry-core-npm-10.46.0-8ff3972576-bbe823f9de.zip new file mode 100644 index 000000000..4cdf17fd8 Binary files /dev/null and b/.yarn/cache/@sentry-core-npm-10.46.0-8ff3972576-bbe823f9de.zip differ diff --git a/.yarn/cache/@sentry-internal-browser-utils-npm-10.46.0-c2bfafc8a9-6ae7c6cc8d.zip b/.yarn/cache/@sentry-internal-browser-utils-npm-10.46.0-c2bfafc8a9-6ae7c6cc8d.zip new file mode 100644 index 000000000..cda366224 Binary files /dev/null and b/.yarn/cache/@sentry-internal-browser-utils-npm-10.46.0-c2bfafc8a9-6ae7c6cc8d.zip differ diff --git a/.yarn/cache/@sentry-internal-feedback-npm-10.46.0-94a0d30ebb-e6c0b1fa93.zip b/.yarn/cache/@sentry-internal-feedback-npm-10.46.0-94a0d30ebb-e6c0b1fa93.zip new file mode 100644 index 000000000..6283f0373 Binary files /dev/null and b/.yarn/cache/@sentry-internal-feedback-npm-10.46.0-94a0d30ebb-e6c0b1fa93.zip differ diff --git a/.yarn/cache/@sentry-internal-replay-canvas-npm-10.46.0-e0dee1cb26-0ddfacfc79.zip b/.yarn/cache/@sentry-internal-replay-canvas-npm-10.46.0-e0dee1cb26-0ddfacfc79.zip new file mode 100644 index 000000000..9db56d6fe Binary files /dev/null and b/.yarn/cache/@sentry-internal-replay-canvas-npm-10.46.0-e0dee1cb26-0ddfacfc79.zip differ diff --git a/.yarn/cache/@sentry-internal-replay-npm-10.46.0-21ab33d9b1-e7c3e9e880.zip b/.yarn/cache/@sentry-internal-replay-npm-10.46.0-21ab33d9b1-e7c3e9e880.zip new file mode 100644 index 000000000..0b2e31986 Binary files /dev/null and b/.yarn/cache/@sentry-internal-replay-npm-10.46.0-21ab33d9b1-e7c3e9e880.zip differ diff --git a/extensions/plugin-sentry/README.md b/extensions/plugin-sentry/README.md new file mode 100644 index 000000000..5b43e7260 --- /dev/null +++ b/extensions/plugin-sentry/README.md @@ -0,0 +1,37 @@ +# plugin-sentry + +Stackflow plugin for Sentry browser tracing. Automatically creates navigation spans and breadcrumbs for activity transitions (`push`, `pop`, `replace`) and step transitions (`stepPush`, `stepPop`, `stepReplace`). + +## Setup + +1. Initialize Sentry with the `stackflowBrowserTracingIntegration`: + +```typescript +import * as Sentry from "@sentry/browser"; +import { stackflowBrowserTracingIntegration } from "@stackflow/plugin-sentry"; + +Sentry.init({ + dsn: "https://xxx.ingest.us.sentry.io/xxx", + integrations: [ + stackflowBrowserTracingIntegration(), + // ... other integrations + ], +}); +``` + +2. Add `sentryPlugin()` to your stackflow configuration: + +```typescript +import { stackflow } from "@stackflow/react"; +import { sentryPlugin } from "@stackflow/plugin-sentry"; + +const { Stack, useFlow } = stackflow({ + activities: { + // ... + }, + plugins: [ + sentryPlugin(), + // ... other plugins + ], +}); +``` diff --git a/extensions/plugin-sentry/esbuild.config.js b/extensions/plugin-sentry/esbuild.config.js new file mode 100644 index 000000000..b84dfb4db --- /dev/null +++ b/extensions/plugin-sentry/esbuild.config.js @@ -0,0 +1,29 @@ +const { context } = require("esbuild"); +const config = require("@stackflow/esbuild-config"); +const pkg = require("./package.json"); + +const watch = process.argv.includes("--watch"); +const external = Object.keys({ + ...pkg.dependencies, + ...pkg.peerDependencies, +}); + +Promise.all([ + context({ + ...config({}), + format: "cjs", + external, + }).then((ctx) => + watch ? ctx.watch() : ctx.rebuild().then(() => ctx.dispose()), + ), + context({ + ...config({}), + format: "esm", + outExtension: { + ".js": ".mjs", + }, + external, + }).then((ctx) => + watch ? ctx.watch() : ctx.rebuild().then(() => ctx.dispose()), + ), +]).catch(() => process.exit(1)); diff --git a/extensions/plugin-sentry/package.json b/extensions/plugin-sentry/package.json new file mode 100644 index 000000000..501f2fee5 --- /dev/null +++ b/extensions/plugin-sentry/package.json @@ -0,0 +1,45 @@ +{ + "name": "@stackflow/plugin-sentry", + "version": "0.0.0", + "repository": { + "type": "git", + "url": "https://github.com/daangn/stackflow.git", + "directory": "extensions/plugin-sentry" + }, + "license": "MIT", + "exports": { + ".": { + "types": "./dist/index.d.ts", + "require": "./dist/index.js", + "import": "./dist/index.mjs" + } + }, + "main": "./dist/index.js", + "module": "./dist/index.mjs", + "types": "./dist/index.d.ts", + "files": [ + "dist", + "src", + "README.md" + ], + "scripts": { + "build": "yarn build:js && yarn build:dts", + "build:dts": "tsc --emitDeclarationOnly", + "build:js": "node ./esbuild.config.js", + "clean": "rimraf dist", + "dev": "yarn build:js --watch && yarn build:dts --watch" + }, + "peerDependencies": { + "@sentry/browser": ">=8.0.0", + "@sentry/core": ">=8.0.0", + "@stackflow/core": "^1.1.0-canary.0" + }, + "devDependencies": { + "@sentry/browser": "^10.46.0", + "@sentry/core": "^10.46.0", + "@stackflow/core": "^1.1.0", + "@stackflow/esbuild-config": "^1.0.3", + "esbuild": "^0.23.0", + "typescript": "^5.5.3" + } +} diff --git a/extensions/plugin-sentry/src/index.ts b/extensions/plugin-sentry/src/index.ts new file mode 100644 index 000000000..d3493bafe --- /dev/null +++ b/extensions/plugin-sentry/src/index.ts @@ -0,0 +1,2 @@ +export { sentryPlugin } from "./sentryPlugin"; +export { stackflowBrowserTracingIntegration } from "./integration"; diff --git a/extensions/plugin-sentry/src/integration.ts b/extensions/plugin-sentry/src/integration.ts new file mode 100644 index 000000000..9f4a2f3e9 --- /dev/null +++ b/extensions/plugin-sentry/src/integration.ts @@ -0,0 +1,43 @@ +import { + browserTracingIntegration as originalBrowserTracingIntegration, + startBrowserTracingPageLoadSpan, +} from "@sentry/browser"; +import { + SEMANTIC_ATTRIBUTE_SENTRY_OP, + SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN, + SEMANTIC_ATTRIBUTE_SENTRY_SOURCE, +} from "@sentry/core"; + +export function stackflowBrowserTracingIntegration( + options: Parameters[0] = {}, +) { + const browserTracingIntegrationInstance = originalBrowserTracingIntegration({ + ...options, + instrumentNavigation: false, + instrumentPageLoad: false, + }); + const { instrumentPageLoad = true } = options; + + return { + ...browserTracingIntegrationInstance, + afterAllSetup( + client: Parameters[0], + ) { + browserTracingIntegrationInstance.afterAllSetup(client); + + const initialWindowLocation = + typeof window !== "undefined" ? window.location : undefined; + + if (instrumentPageLoad && initialWindowLocation) { + startBrowserTracingPageLoadSpan(client, { + name: initialWindowLocation.pathname, + attributes: { + [SEMANTIC_ATTRIBUTE_SENTRY_OP]: "pageload", + [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: "auto.pageload.stackflow", + [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: "url", + }, + }); + } + }, + }; +} diff --git a/extensions/plugin-sentry/src/sentryPlugin.ts b/extensions/plugin-sentry/src/sentryPlugin.ts new file mode 100644 index 000000000..67a9bcd72 --- /dev/null +++ b/extensions/plugin-sentry/src/sentryPlugin.ts @@ -0,0 +1,103 @@ +import * as Sentry from "@sentry/browser"; +import { + SEMANTIC_ATTRIBUTE_SENTRY_OP, + SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN, + SEMANTIC_ATTRIBUTE_SENTRY_SOURCE, +} from "@sentry/core"; +import type { StackflowPlugin } from "@stackflow/core"; + +type NavigationAction = + | "push" + | "pop" + | "replace" + | "stepPush" + | "stepPop" + | "stepReplace"; + +function startNavigationSpan( + action: NavigationAction, + activityName: string, + params: Record, +): void { + const client = Sentry.getClient(); + if (!client) return; + + Sentry.startBrowserTracingNavigationSpan(client, { + name: `${action} ${activityName}`, + attributes: { + [SEMANTIC_ATTRIBUTE_SENTRY_OP]: "navigation", + [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: "auto.navigation.stackflow", + [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: "route", + "stackflow.action": action, + "stackflow.activity": activityName, + ...prefixKeys("stackflow.params", params), + }, + }); +} + +function addNavigationBreadcrumb( + action: NavigationAction, + activityName: string, + params: Record, +): void { + Sentry.addBreadcrumb({ + category: "navigation", + message: `${action} ${activityName}`, + level: "info", + data: params, + }); +} + +function prefixKeys( + prefix: string, + params: Record, +): Record { + const result: Record = {}; + for (const [key, value] of Object.entries(params)) { + if (value !== undefined) { + result[`${prefix}.${key}`] = value; + } + } + return result; +} + +export function sentryPlugin(): StackflowPlugin { + return () => ({ + key: "plugin-sentry", + onPushed({ effect }) { + const { name, params } = effect.activity; + startNavigationSpan("push", name, params); + addNavigationBreadcrumb("push", name, params); + }, + onPopped({ effect }) { + const { name, params } = effect.activity; + startNavigationSpan("pop", name, params); + addNavigationBreadcrumb("pop", name, params); + }, + onReplaced({ effect }) { + const { name, params } = effect.activity; + startNavigationSpan("replace", name, params); + addNavigationBreadcrumb("replace", name, params); + }, + onStepPushed({ effect }) { + const { name, params } = effect.activity; + const stepParams = effect.step.params; + startNavigationSpan("stepPush", name, { ...params, ...stepParams }); + addNavigationBreadcrumb("stepPush", name, { ...params, ...stepParams }); + }, + onStepPopped({ effect }) { + const { name, params } = effect.activity; + startNavigationSpan("stepPop", name, params); + addNavigationBreadcrumb("stepPop", name, params); + }, + onStepReplaced({ effect }) { + const { name, params } = effect.activity; + const stepParams = effect.step.params; + startNavigationSpan("stepReplace", name, { ...params, ...stepParams }); + addNavigationBreadcrumb("stepReplace", name, { + ...params, + ...stepParams, + }); + }, + }); +} diff --git a/extensions/plugin-sentry/tsconfig.json b/extensions/plugin-sentry/tsconfig.json new file mode 100644 index 000000000..b1d123121 --- /dev/null +++ b/extensions/plugin-sentry/tsconfig.json @@ -0,0 +1,8 @@ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "baseUrl": "./src", + "outDir": "./dist" + }, + "exclude": ["./dist"] +} diff --git a/yarn.lock b/yarn.lock index 185f4f694..387e61fa1 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1970,6 +1970,64 @@ __metadata: languageName: node linkType: hard +"@sentry-internal/browser-utils@npm:10.46.0": + version: 10.46.0 + resolution: "@sentry-internal/browser-utils@npm:10.46.0" + dependencies: + "@sentry/core": "npm:10.46.0" + checksum: 10/6ae7c6cc8df1ff03c81ec0bc7ae066eec3be447f24e661035129c0679293dcc9687943bb441e533a6dbc5d850c1ec595042b77f0190dcd742a6dc83f355f7dd7 + languageName: node + linkType: hard + +"@sentry-internal/feedback@npm:10.46.0": + version: 10.46.0 + resolution: "@sentry-internal/feedback@npm:10.46.0" + dependencies: + "@sentry/core": "npm:10.46.0" + checksum: 10/e6c0b1fa93ca3cca2ad8b3f5f9b8fa6f499378c30c0c9f9aebb9e7da17242e2c21c1f79f049c50bfda9932cd078c52ccd1b6bfa39b3e06d188b5a6a33350fcc3 + languageName: node + linkType: hard + +"@sentry-internal/replay-canvas@npm:10.46.0": + version: 10.46.0 + resolution: "@sentry-internal/replay-canvas@npm:10.46.0" + dependencies: + "@sentry-internal/replay": "npm:10.46.0" + "@sentry/core": "npm:10.46.0" + checksum: 10/0ddfacfc7960c03332be554241c5f51fe400cfc1130dfb98a9d32754951ad047019dbc34d8dfb8e29fa0bd8cba7a2466766768e75a4fcf68d708fc5b511f0ed6 + languageName: node + linkType: hard + +"@sentry-internal/replay@npm:10.46.0": + version: 10.46.0 + resolution: "@sentry-internal/replay@npm:10.46.0" + dependencies: + "@sentry-internal/browser-utils": "npm:10.46.0" + "@sentry/core": "npm:10.46.0" + checksum: 10/e7c3e9e880b24a975f08b7320b1a230fea015af5b8705f08d19cf7679ec7a1083edb827c41ec00e8e7a9656f5d5a5912661f2e29fded3bc1cb98804509e44697 + languageName: node + linkType: hard + +"@sentry/browser@npm:^10.46.0": + version: 10.46.0 + resolution: "@sentry/browser@npm:10.46.0" + dependencies: + "@sentry-internal/browser-utils": "npm:10.46.0" + "@sentry-internal/feedback": "npm:10.46.0" + "@sentry-internal/replay": "npm:10.46.0" + "@sentry-internal/replay-canvas": "npm:10.46.0" + "@sentry/core": "npm:10.46.0" + checksum: 10/f447c01f96c703301423c5600647120720d9a7d2c2f82df5cc4aaa526c6665e8115ab6ee8f6ee16f7954703054211bf4bde10875250e246bc79940d514aa887b + languageName: node + linkType: hard + +"@sentry/core@npm:10.46.0, @sentry/core@npm:^10.46.0": + version: 10.46.0 + resolution: "@sentry/core@npm:10.46.0" + checksum: 10/bbe823f9dea49ff87973a7d9bdf934016e7663e5269f2b09e1888a40e6e1718bab5c565cc30d77053c49e676d2694b15c886df3cbfb7045abd76f9c510731adb + languageName: node + linkType: hard + "@sinclair/typebox@npm:^0.27.8": version: 0.27.8 resolution: "@sinclair/typebox@npm:0.27.8" @@ -2342,6 +2400,23 @@ __metadata: languageName: unknown linkType: soft +"@stackflow/plugin-sentry@workspace:extensions/plugin-sentry": + version: 0.0.0-use.local + resolution: "@stackflow/plugin-sentry@workspace:extensions/plugin-sentry" + dependencies: + "@sentry/browser": "npm:^10.46.0" + "@sentry/core": "npm:^10.46.0" + "@stackflow/core": "npm:^1.1.0" + "@stackflow/esbuild-config": "npm:^1.0.3" + esbuild: "npm:^0.23.0" + typescript: "npm:^5.5.3" + peerDependencies: + "@sentry/browser": ">=8.0.0" + "@sentry/core": ">=8.0.0" + "@stackflow/core": ^1.1.0-canary.0 + languageName: unknown + linkType: soft + "@stackflow/plugin-stack-depth-change@npm:^1.1.5, @stackflow/plugin-stack-depth-change@workspace:extensions/plugin-stack-depth-change": version: 0.0.0-use.local resolution: "@stackflow/plugin-stack-depth-change@workspace:extensions/plugin-stack-depth-change"