Skip to content

Commit 62a030f

Browse files
committed
feat(dev): surface the stackblitz editor url in the dev url block
1 parent 29558d5 commit 62a030f

3 files changed

Lines changed: 75 additions & 3 deletions

File tree

packages/nuxt-cli/src/dev/listen.ts

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import { debug, logger } from '../utils/logger'
1616
import { resolveCertificate } from './cert'
1717
import { detectIsolatedEnvironment, isWsl } from './environment'
1818
import { resolvePortlessURLs } from './portless'
19+
import { resolveStackblitzURL } from './stackblitz'
1920
import { startTunnel } from './tunnel'
2021

2122
export interface ListenOptions {
@@ -164,6 +165,7 @@ export async function listen(handler: RequestListener, options: ListenOptions =
164165
const portless = resolvePortlessURLs()
165166
const portlessURL = portless.url && portless.url + baseURL
166167
const portlessShareURL = portless.shareURL && portless.shareURL + baseURL
168+
const stackblitzURL = resolveStackblitzURL()
167169

168170
function getURLs(): ListenURL[] {
169171
const urls: ListenURL[] = []
@@ -173,6 +175,9 @@ export async function listen(handler: RequestListener, options: ListenOptions =
173175
for (const portlessURL of portless.all) {
174176
urls.push({ url: portlessURL + baseURL, type: 'public' })
175177
}
178+
if (stackblitzURL) {
179+
urls.push({ url: stackblitzURL, type: 'public' })
180+
}
176181
if (anyHost) {
177182
urls.push({ url: formatURL('localhost'), type: 'local' })
178183
for (const address of getNetworkAddresses()) {
@@ -185,11 +190,14 @@ export async function listen(handler: RequestListener, options: ListenOptions =
185190
return urls
186191
}
187192

188-
const publicURL = options.publicURL || tunnel?.url || portlessShareURL || portlessURL
193+
// The StackBlitz URL points at the editor rather than at a host another
194+
// device can open, so it is not a QR code candidate.
195+
const shareableURL = options.publicURL || tunnel?.url || portlessShareURL || portlessURL
196+
const publicURL = shareableURL || stackblitzURL
189197

190198
const qrURL = options.qr === false
191199
? undefined
192-
: publicURL
200+
: shareableURL
193201
|| getURLs().find(({ type }) => type === 'network')?.url
194202
|| (options.qr ? url : undefined)
195203

@@ -203,7 +211,7 @@ export async function listen(handler: RequestListener, options: ListenOptions =
203211
for (const { url: displayURL, type } of urls) {
204212
lines.push(line(labelColors[type], labels[type], colors.cyan(displayURL), qr && displayURL === qrURL))
205213
}
206-
if (!anyHost && !tunnel && !portless.url) {
214+
if (!anyHost && !tunnel && !portless.url && !stackblitzURL) {
207215
const isolated = LOOPBACK_HOSTS.has(hostname) ? detectIsolatedEnvironment() : undefined
208216
const hint = isolated
209217
? `use ${colors.white('--host')} to reach this server from outside ${isolated}`
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
import process from 'node:process'
2+
3+
import { provider as currentProvider } from 'std-env'
4+
5+
/**
6+
* Resolve the reachable editor URL for a dev server running inside StackBlitz
7+
* or Codeflow, where the app is served through the editor rather than on a
8+
* port the user can open locally.
9+
*
10+
* The project is identified by the working directory: `/home/projects/<id>`
11+
* in the editor, `/home/<org>/<repo>` in Codeflow. `PWD` is used rather than
12+
* `process.cwd()` because the latter is resolved through symlinks.
13+
*/
14+
export function resolveStackblitzURL(
15+
env: NodeJS.ProcessEnv = process.env,
16+
provider: string | undefined = currentProvider,
17+
): string | undefined {
18+
if (provider !== 'stackblitz') {
19+
return undefined
20+
}
21+
22+
const cwd = env.PWD
23+
if (!cwd) {
24+
return undefined
25+
}
26+
27+
if (cwd.startsWith('/home/projects/')) {
28+
const projectId = cwd.split('/')[3]
29+
return projectId ? `https://stackblitz.com/edit/${projectId}` : undefined
30+
}
31+
32+
if (cwd.startsWith('/home/')) {
33+
const [owner, repository] = cwd.split('/').slice(2)
34+
return owner && repository ? `https://stackblitz.com/edit/~/github.com/${owner}/${repository}` : undefined
35+
}
36+
37+
return undefined
38+
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import { describe, expect, it } from 'vitest'
2+
3+
import { resolveStackblitzURL } from '../../src/dev/stackblitz'
4+
5+
describe('resolveStackblitzURL', () => {
6+
it('should return nothing outside stackblitz', () => {
7+
expect(resolveStackblitzURL({ PWD: '/home/projects/abc123' }, undefined)).toBeUndefined()
8+
expect(resolveStackblitzURL({ PWD: '/home/projects/abc123' }, 'codesandbox')).toBeUndefined()
9+
})
10+
11+
it('should derive the editor url from the project directory', () => {
12+
expect(resolveStackblitzURL({ PWD: '/home/projects/abc123' }, 'stackblitz')).toBe('https://stackblitz.com/edit/abc123')
13+
expect(resolveStackblitzURL({ PWD: '/home/projects/abc123/playground' }, 'stackblitz')).toBe('https://stackblitz.com/edit/abc123')
14+
})
15+
16+
it('should derive the codeflow url from the repository directory', () => {
17+
expect(resolveStackblitzURL({ PWD: '/home/nuxt/cli' }, 'stackblitz')).toBe('https://stackblitz.com/edit/~/github.com/nuxt/cli')
18+
expect(resolveStackblitzURL({ PWD: '/home/nuxt/cli/packages/nuxt-cli' }, 'stackblitz')).toBe('https://stackblitz.com/edit/~/github.com/nuxt/cli')
19+
})
20+
21+
it('should return nothing for an unrecognised directory', () => {
22+
expect(resolveStackblitzURL({ PWD: '/home/daniel' }, 'stackblitz')).toBeUndefined()
23+
expect(resolveStackblitzURL({ PWD: '/workspace/app' }, 'stackblitz')).toBeUndefined()
24+
expect(resolveStackblitzURL({}, 'stackblitz')).toBeUndefined()
25+
})
26+
})

0 commit comments

Comments
 (0)