@sequenceholdings/artifact-studio 0.2.6 → 0.3.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.
- package/VERSION-PIN.md +12 -1
- package/dist/api.d.ts +11 -1
- package/dist/api.js +26 -9
- package/dist/build.d.ts +2 -2
- package/dist/build.js +4 -0
- package/dist/cli.d.ts +1 -0
- package/dist/cli.js +134 -13
- package/dist/dev-link.d.ts +1 -1
- package/dist/dev-link.js +2 -1
- package/dist/gateway/admission.d.ts +56 -0
- package/dist/gateway/admission.js +134 -0
- package/dist/gateway/authorization-cache.d.ts +52 -0
- package/dist/gateway/authorization-cache.js +111 -0
- package/dist/gateway/control-admission.d.ts +48 -0
- package/dist/gateway/control-admission.js +127 -0
- package/dist/gateway/control.d.ts +94 -0
- package/dist/gateway/control.js +116 -0
- package/dist/gateway/hosted-proxy.d.ts +36 -0
- package/dist/gateway/hosted-proxy.js +344 -0
- package/dist/gateway/release-admission.d.ts +23 -0
- package/dist/gateway/release-admission.js +259 -0
- package/dist/gateway/server.d.ts +38 -0
- package/dist/gateway/server.js +382 -0
- package/dist/gateway/session.d.ts +72 -0
- package/dist/gateway/session.js +144 -0
- package/dist/gateway/usage.d.ts +70 -0
- package/dist/gateway/usage.js +116 -0
- package/dist/gateway-main.d.ts +1 -0
- package/dist/gateway-main.js +114 -0
- package/dist/git-clone.d.ts +23 -1
- package/dist/git-clone.js +32 -15
- package/dist/manifest-next.d.ts +217 -0
- package/dist/manifest-next.js +132 -0
- package/dist/manifest-static.d.ts +76 -0
- package/dist/manifest-static.js +124 -0
- package/dist/manifest.d.ts +118 -13
- package/dist/manifest.js +12 -119
- package/dist/next/bridge.d.ts +47 -0
- package/dist/next/bridge.js +236 -0
- package/dist/next/browser-logs.d.ts +7 -0
- package/dist/next/browser-logs.js +51 -0
- package/dist/next/browser.d.ts +6 -0
- package/dist/next/browser.js +57 -0
- package/dist/next/protocol.d.ts +73 -0
- package/dist/next/protocol.js +56 -0
- package/dist/next/release.d.ts +15 -0
- package/dist/next/release.js +46 -0
- package/dist/next/requests.d.ts +15 -0
- package/dist/next/requests.js +59 -0
- package/dist/next/routes.d.ts +4 -0
- package/dist/next/routes.js +21 -0
- package/dist/next/streams.d.ts +16 -0
- package/dist/next/streams.js +132 -0
- package/dist/next/theme.d.ts +10 -0
- package/dist/next/theme.js +45 -0
- package/dist/next/transport.d.ts +15 -0
- package/dist/next/transport.js +13 -0
- package/dist/next-build.d.ts +29 -0
- package/dist/next-build.js +129 -0
- package/dist/next-cache/adapter-runtime.d.ts +51 -0
- package/dist/next-cache/adapter-runtime.js +73 -0
- package/dist/next-cache/component-handler.d.ts +3 -0
- package/dist/next-cache/component-handler.js +10 -0
- package/dist/next-cache/components.d.ts +20 -0
- package/dist/next-cache/components.js +113 -0
- package/dist/next-cache/config.d.ts +9 -0
- package/dist/next-cache/config.js +105 -0
- package/dist/next-cache/incremental-handler.d.ts +18 -0
- package/dist/next-cache/incremental-handler.js +17 -0
- package/dist/next-cache/incremental.d.ts +27 -0
- package/dist/next-cache/incremental.js +99 -0
- package/dist/next-cache/index.d.ts +18 -0
- package/dist/next-cache/index.js +16 -0
- package/dist/next-cache/memory-store.d.ts +27 -0
- package/dist/next-cache/memory-store.js +118 -0
- package/dist/next-cache/redis-scripts.d.ts +3 -0
- package/dist/next-cache/redis-scripts.js +85 -0
- package/dist/next-cache/redis-store.d.ts +18 -0
- package/dist/next-cache/redis-store.js +56 -0
- package/dist/next-cache/runtime.d.ts +29 -0
- package/dist/next-cache/runtime.js +63 -0
- package/dist/next-cache/serialization.d.ts +39 -0
- package/dist/next-cache/serialization.js +333 -0
- package/dist/next-cache/store.d.ts +54 -0
- package/dist/next-cache/store.js +45 -0
- package/dist/next-cache/valkey-transport.d.ts +52 -0
- package/dist/next-cache/valkey-transport.js +189 -0
- package/dist/next-dev-control.d.ts +48 -0
- package/dist/next-dev-control.js +63 -0
- package/dist/next-dev-process.d.ts +17 -0
- package/dist/next-dev-process.js +110 -0
- package/dist/next-dev.d.ts +12 -0
- package/dist/next-dev.js +267 -0
- package/dist/next-image-config.d.ts +9 -0
- package/dist/next-image-config.js +101 -0
- package/dist/next-project.d.ts +22 -0
- package/dist/next-project.js +64 -0
- package/dist/next-public-fonts-local.d.ts +7 -0
- package/dist/next-public-fonts-local.js +64 -0
- package/dist/next-public-fonts.d.ts +13 -0
- package/dist/next-public-fonts.js +49 -0
- package/dist/next-source-hash.d.ts +14 -0
- package/dist/next-source-hash.js +18 -0
- package/dist/next-source.d.ts +35 -0
- package/dist/next-source.js +155 -0
- package/dist/next.d.ts +9 -0
- package/dist/next.js +5 -0
- package/dist/project.d.ts +3 -0
- package/dist/project.js +9 -1
- package/dist/runtime-contracts.d.ts +229 -0
- package/dist/runtime-contracts.js +224 -0
- package/dist/sdk.d.ts +2 -2
- package/dist/server/broker-contract.d.ts +68 -0
- package/dist/server/broker-contract.js +33 -0
- package/dist/server/client.d.ts +81 -0
- package/dist/server/client.js +159 -0
- package/dist/server/logger.d.ts +26 -0
- package/dist/server/logger.js +24 -0
- package/dist/server/release-forms.d.ts +13 -0
- package/dist/server/release-forms.js +124 -0
- package/dist/server/request-context.d.ts +75 -0
- package/dist/server/request-context.js +107 -0
- package/dist/server/runtime-config.d.ts +54 -0
- package/dist/server/runtime-config.js +64 -0
- package/dist/server.d.ts +8 -0
- package/dist/server.js +4 -0
- package/dist/templates/nextjs/.artifactignore +14 -0
- package/dist/templates/nextjs/README.md +157 -0
- package/dist/templates/nextjs/app/about/page.tsx +26 -0
- package/dist/templates/nextjs/app/actions.ts +18 -0
- package/dist/templates/nextjs/app/api/health/route.ts +6 -0
- package/dist/templates/nextjs/app/components/counter.tsx +19 -0
- package/dist/templates/nextjs/app/components/greeting-form.tsx +38 -0
- package/dist/templates/nextjs/app/globals.css +4 -0
- package/dist/templates/nextjs/app/layout.tsx +27 -0
- package/dist/templates/nextjs/app/page.tsx +51 -0
- package/dist/templates/nextjs/app/providers.tsx +107 -0
- package/dist/templates/nextjs/artifact.bundle.yml +55 -0
- package/dist/templates/nextjs/gitignore +9 -0
- package/dist/templates/nextjs/next.config.mjs +27 -0
- package/dist/templates/nextjs/package.json +32 -0
- package/dist/templates/nextjs/pnpm-workspace.yaml +22 -0
- package/dist/templates/nextjs/postcss.config.mjs +5 -0
- package/dist/templates/nextjs/tsconfig.json +20 -0
- package/dist/templates/react-vite/package.json +1 -1
- package/dist/trusted-install.js +62 -2
- package/dist/tunnel/broker.d.ts +75 -0
- package/dist/tunnel/broker.js +463 -0
- package/dist/tunnel/client.d.ts +22 -0
- package/dist/tunnel/client.js +177 -0
- package/dist/tunnel/index.d.ts +6 -0
- package/dist/tunnel/index.js +4 -0
- package/dist/tunnel/io.d.ts +33 -0
- package/dist/tunnel/io.js +108 -0
- package/dist/tunnel/peer.d.ts +25 -0
- package/dist/tunnel/peer.js +147 -0
- package/dist/tunnel/protocol.d.ts +78 -0
- package/dist/tunnel/protocol.js +210 -0
- package/dist/tunnel/redis-auth.d.ts +37 -0
- package/dist/tunnel/redis-auth.js +92 -0
- package/dist/tunnel/redis-ownership.d.ts +38 -0
- package/dist/tunnel/redis-ownership.js +100 -0
- package/dist/tunnel/redis-socket.d.ts +13 -0
- package/dist/tunnel/redis-socket.js +48 -0
- package/dist/tunnel/redis-types.d.ts +75 -0
- package/dist/tunnel/redis-types.js +9 -0
- package/dist/tunnel/redis.d.ts +56 -0
- package/dist/tunnel/redis.js +404 -0
- package/dist/tunnel/transport.d.ts +17 -0
- package/dist/tunnel/transport.js +1 -0
- package/package.json +67 -4
- package/templates/nextjs/.artifactignore +14 -0
- package/templates/nextjs/README.md +157 -0
- package/templates/nextjs/app/about/page.tsx +26 -0
- package/templates/nextjs/app/actions.ts +18 -0
- package/templates/nextjs/app/api/health/route.ts +6 -0
- package/templates/nextjs/app/components/counter.tsx +19 -0
- package/templates/nextjs/app/components/greeting-form.tsx +38 -0
- package/templates/nextjs/app/globals.css +4 -0
- package/templates/nextjs/app/layout.tsx +27 -0
- package/templates/nextjs/app/page.tsx +51 -0
- package/templates/nextjs/app/providers.tsx +107 -0
- package/templates/nextjs/artifact.bundle.yml +55 -0
- package/templates/nextjs/gitignore +9 -0
- package/templates/nextjs/next.config.mjs +27 -0
- package/templates/nextjs/package.json +32 -0
- package/templates/nextjs/pnpm-workspace.yaml +22 -0
- package/templates/nextjs/postcss.config.mjs +5 -0
- package/templates/nextjs/tsconfig.json +20 -0
- package/templates/react-vite/package.json +1 -1
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import { useState } from 'react'
|
|
4
|
+
import { Button } from '@sequenceholdings/atlas-ui/next'
|
|
5
|
+
|
|
6
|
+
export function Counter() {
|
|
7
|
+
const [count, setCount] = useState(0)
|
|
8
|
+
|
|
9
|
+
return (
|
|
10
|
+
<div className="flex items-center gap-stack-group">
|
|
11
|
+
<Button type="button" onClick={() => setCount(value => value + 1)}>
|
|
12
|
+
Increment
|
|
13
|
+
</Button>
|
|
14
|
+
<output className="font-mono text-lg tabular-nums" aria-live="polite">
|
|
15
|
+
{count}
|
|
16
|
+
</output>
|
|
17
|
+
</div>
|
|
18
|
+
)
|
|
19
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import { useActionState } from 'react'
|
|
4
|
+
import { Button, Input } from '@sequenceholdings/atlas-ui/next'
|
|
5
|
+
import { greet, type GreetingState } from '../actions'
|
|
6
|
+
|
|
7
|
+
const initialState: GreetingState = { message: '', error: null }
|
|
8
|
+
|
|
9
|
+
export function GreetingForm() {
|
|
10
|
+
const [state, action, pending] = useActionState(greet, initialState)
|
|
11
|
+
|
|
12
|
+
return (
|
|
13
|
+
<form action={action} className="space-y-4">
|
|
14
|
+
<div className="space-y-2">
|
|
15
|
+
<label htmlFor="name" className="text-sm font-medium">Your name</label>
|
|
16
|
+
<Input
|
|
17
|
+
id="name"
|
|
18
|
+
name="name"
|
|
19
|
+
autoComplete="given-name"
|
|
20
|
+
required
|
|
21
|
+
maxLength={80}
|
|
22
|
+
aria-invalid={Boolean(state.error)}
|
|
23
|
+
aria-describedby="greeting-result"
|
|
24
|
+
/>
|
|
25
|
+
</div>
|
|
26
|
+
<Button type="submit" disabled={pending}>
|
|
27
|
+
{pending ? 'Sending…' : 'Say hello'}
|
|
28
|
+
</Button>
|
|
29
|
+
<p
|
|
30
|
+
id="greeting-result"
|
|
31
|
+
aria-live="polite"
|
|
32
|
+
className={state.error ? 'text-sm text-destructive' : 'text-sm text-foreground-secondary'}
|
|
33
|
+
>
|
|
34
|
+
{state.error ?? state.message}
|
|
35
|
+
</p>
|
|
36
|
+
</form>
|
|
37
|
+
)
|
|
38
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { Metadata } from 'next'
|
|
2
|
+
import { headers } from 'next/headers'
|
|
3
|
+
import type { ReactNode } from 'react'
|
|
4
|
+
import {
|
|
5
|
+
getArtifactBrowserBootstrap,
|
|
6
|
+
getArtifactRequestContext,
|
|
7
|
+
} from '@sequenceholdings/artifact-studio/server'
|
|
8
|
+
import { Providers } from './providers'
|
|
9
|
+
import './globals.css'
|
|
10
|
+
|
|
11
|
+
export const metadata: Metadata = {
|
|
12
|
+
title: '{{title}}',
|
|
13
|
+
description: 'A full-stack application managed by Atlas',
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export default async function RootLayout({ children }: { children: ReactNode }) {
|
|
17
|
+
const context = await getArtifactRequestContext({ headers: await headers() })
|
|
18
|
+
const bootstrap = context ? getArtifactBrowserBootstrap(context) : null
|
|
19
|
+
|
|
20
|
+
return (
|
|
21
|
+
<html lang="en" className={bootstrap?.initialContext.theme?.dark ? 'dark' : undefined}>
|
|
22
|
+
<body className="min-h-screen bg-background font-sans text-foreground antialiased">
|
|
23
|
+
<Providers bootstrap={bootstrap}>{children}</Providers>
|
|
24
|
+
</body>
|
|
25
|
+
</html>
|
|
26
|
+
)
|
|
27
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import Link from 'next/link'
|
|
2
|
+
import { connection } from 'next/server'
|
|
3
|
+
import { Counter } from './components/counter'
|
|
4
|
+
import { GreetingForm } from './components/greeting-form'
|
|
5
|
+
|
|
6
|
+
export default async function HomePage() {
|
|
7
|
+
await connection()
|
|
8
|
+
const renderedAt = new Date().toISOString()
|
|
9
|
+
|
|
10
|
+
return (
|
|
11
|
+
<main className="mx-auto max-w-5xl px-roomy py-10 sm:px-8">
|
|
12
|
+
<header className="mb-10 border-b border-border pb-8">
|
|
13
|
+
<p className="mb-3 text-xs font-semibold uppercase tracking-widest text-primary">
|
|
14
|
+
Artifact Studio / Next.js
|
|
15
|
+
</p>
|
|
16
|
+
<h1 className="text-3xl font-semibold tracking-tight">{{title}}</h1>
|
|
17
|
+
<p className="mt-3 max-w-2xl text-foreground-secondary">
|
|
18
|
+
Build your interface and server together. Develop on your machine,
|
|
19
|
+
preview in Atlas and publish an application that runs independently.
|
|
20
|
+
</p>
|
|
21
|
+
</header>
|
|
22
|
+
|
|
23
|
+
<div className="grid gap-stack-section md:grid-cols-2">
|
|
24
|
+
<section className="rounded-lg border border-border bg-card p-6" aria-labelledby="client-heading">
|
|
25
|
+
<h2 id="client-heading" className="text-lg font-semibold">Client interaction</h2>
|
|
26
|
+
<p className="mb-6 mt-2 text-sm text-muted-foreground">
|
|
27
|
+
Increment the counter, then edit its label. Fast Refresh preserves
|
|
28
|
+
client state for compatible edits.
|
|
29
|
+
</p>
|
|
30
|
+
<Counter />
|
|
31
|
+
</section>
|
|
32
|
+
|
|
33
|
+
<section className="rounded-lg border border-border bg-card p-6" aria-labelledby="server-heading">
|
|
34
|
+
<h2 id="server-heading" className="text-lg font-semibold">Server action</h2>
|
|
35
|
+
<p className="mb-6 mt-2 text-sm text-muted-foreground">
|
|
36
|
+
Submit a name to a validated server action. This example does not
|
|
37
|
+
store data or require a connected resource.
|
|
38
|
+
</p>
|
|
39
|
+
<GreetingForm />
|
|
40
|
+
</section>
|
|
41
|
+
</div>
|
|
42
|
+
|
|
43
|
+
<footer className="mt-8 flex flex-wrap items-center justify-between gap-stack-group text-sm text-muted-foreground">
|
|
44
|
+
<p>Rendered on the server: <time dateTime={renderedAt}>{renderedAt}</time></p>
|
|
45
|
+
<Link href="/about" className="font-medium text-primary underline-offset-4 hover:underline">
|
|
46
|
+
How this application works
|
|
47
|
+
</Link>
|
|
48
|
+
</footer>
|
|
49
|
+
</main>
|
|
50
|
+
)
|
|
51
|
+
}
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import { Suspense, useEffect, useRef, useState, type ReactNode } from 'react'
|
|
4
|
+
import { usePathname, useRouter, useSearchParams } from 'next/navigation'
|
|
5
|
+
import { PortalContainerProvider } from '@sequenceholdings/atlas-ui/next'
|
|
6
|
+
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
|
|
7
|
+
import {
|
|
8
|
+
createNextArtifactBridge,
|
|
9
|
+
createNextArtifactThemeApplier,
|
|
10
|
+
installNextArtifactBrowserIntegration,
|
|
11
|
+
type NextArtifactBridge,
|
|
12
|
+
type NextArtifactBridgeBootstrap,
|
|
13
|
+
type NextArtifactInitialContext,
|
|
14
|
+
} from '@sequenceholdings/artifact-studio/next'
|
|
15
|
+
|
|
16
|
+
type BrowserBootstrap = NextArtifactBridgeBootstrap & {
|
|
17
|
+
initialContext: NextArtifactInitialContext
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function Providers({ children, bootstrap }: {
|
|
21
|
+
children: ReactNode
|
|
22
|
+
bootstrap: BrowserBootstrap | null
|
|
23
|
+
}) {
|
|
24
|
+
const [queryClient] = useState(() => new QueryClient())
|
|
25
|
+
|
|
26
|
+
return (
|
|
27
|
+
<QueryClientProvider client={queryClient}>
|
|
28
|
+
<PortalContainerProvider>
|
|
29
|
+
<Suspense fallback={null}>
|
|
30
|
+
<AtlasConnection bootstrap={bootstrap} />
|
|
31
|
+
</Suspense>
|
|
32
|
+
{children}
|
|
33
|
+
</PortalContainerProvider>
|
|
34
|
+
</QueryClientProvider>
|
|
35
|
+
)
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function AtlasConnection({ bootstrap }: { bootstrap: BrowserBootstrap | null }) {
|
|
39
|
+
const router = useRouter()
|
|
40
|
+
const pathname = usePathname()
|
|
41
|
+
const searchParams = useSearchParams()
|
|
42
|
+
const currentBridge = useRef<NextArtifactBridge | null>(null)
|
|
43
|
+
const [connectionError, setConnectionError] = useState(false)
|
|
44
|
+
|
|
45
|
+
useEffect(() => {
|
|
46
|
+
if (!bootstrap) return
|
|
47
|
+
let active = true
|
|
48
|
+
const theme = createNextArtifactThemeApplier({ root: document.documentElement })
|
|
49
|
+
const bridge = createNextArtifactBridge({
|
|
50
|
+
bootstrap,
|
|
51
|
+
initialContext: bootstrap.initialContext,
|
|
52
|
+
onNavigate: ({ path, replace }) => replace ? router.replace(path) : router.push(path),
|
|
53
|
+
})
|
|
54
|
+
currentBridge.current = bridge
|
|
55
|
+
const removeBrowserIntegration = installNextArtifactBrowserIntegration({ bridge })
|
|
56
|
+
const applyTheme = () => {
|
|
57
|
+
const snapshot = bridge.getSnapshot()
|
|
58
|
+
if (snapshot.theme) theme.apply({ theme: snapshot.theme })
|
|
59
|
+
}
|
|
60
|
+
const updateRoute = () => bridge.routeChanged({
|
|
61
|
+
path: `${window.location.pathname}${window.location.search}${window.location.hash}`,
|
|
62
|
+
})
|
|
63
|
+
const updateTitle = () => {
|
|
64
|
+
if (bridge.getSnapshot().status === 'ready') {
|
|
65
|
+
bridge.setDocumentTitle({ label: document.title })
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
applyTheme()
|
|
69
|
+
const unsubscribe = bridge.subscribe(applyTheme)
|
|
70
|
+
const titleObserver = new MutationObserver(updateTitle)
|
|
71
|
+
titleObserver.observe(document.head, { childList: true, characterData: true, subtree: true })
|
|
72
|
+
window.addEventListener('hashchange', updateRoute)
|
|
73
|
+
updateRoute()
|
|
74
|
+
void bridge.connect().then(() => {
|
|
75
|
+
if (!active) return
|
|
76
|
+
setConnectionError(false)
|
|
77
|
+
updateTitle()
|
|
78
|
+
}).catch(() => {
|
|
79
|
+
if (active) setConnectionError(true)
|
|
80
|
+
})
|
|
81
|
+
|
|
82
|
+
return () => {
|
|
83
|
+
active = false
|
|
84
|
+
window.removeEventListener('hashchange', updateRoute)
|
|
85
|
+
titleObserver.disconnect()
|
|
86
|
+
unsubscribe()
|
|
87
|
+
removeBrowserIntegration()
|
|
88
|
+
bridge.dispose()
|
|
89
|
+
theme.dispose()
|
|
90
|
+
currentBridge.current = null
|
|
91
|
+
}
|
|
92
|
+
}, [bootstrap, router])
|
|
93
|
+
|
|
94
|
+
useEffect(() => {
|
|
95
|
+
const query = searchParams.toString()
|
|
96
|
+
currentBridge.current?.routeChanged({
|
|
97
|
+
path: `${pathname}${query ? `?${query}` : ''}${window.location.hash}`,
|
|
98
|
+
})
|
|
99
|
+
}, [pathname, searchParams])
|
|
100
|
+
|
|
101
|
+
if (!connectionError) return null
|
|
102
|
+
return (
|
|
103
|
+
<p role="alert" className="border-b border-border bg-card px-roomy py-default text-sm text-destructive">
|
|
104
|
+
The Atlas connection was interrupted. Reopen this preview from Artifact Studio.
|
|
105
|
+
</p>
|
|
106
|
+
)
|
|
107
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
bundle:
|
|
2
|
+
name: {{slug}}
|
|
3
|
+
schema_version: 2
|
|
4
|
+
|
|
5
|
+
artifact:
|
|
6
|
+
project_id: {{slug}}
|
|
7
|
+
title: "{{title}}"
|
|
8
|
+
description: "A full-stack Next.js application managed by Atlas"
|
|
9
|
+
type: nextjs
|
|
10
|
+
app_root: .
|
|
11
|
+
|
|
12
|
+
runtime:
|
|
13
|
+
sdk: sequence
|
|
14
|
+
react: 19
|
|
15
|
+
node: 24
|
|
16
|
+
next: '16.3'
|
|
17
|
+
|
|
18
|
+
# Browser permissions and server permissions are separate declarations.
|
|
19
|
+
capabilities:
|
|
20
|
+
data:
|
|
21
|
+
read: []
|
|
22
|
+
write: []
|
|
23
|
+
api:
|
|
24
|
+
read: []
|
|
25
|
+
write: []
|
|
26
|
+
functions:
|
|
27
|
+
invoke: []
|
|
28
|
+
|
|
29
|
+
server:
|
|
30
|
+
capabilities:
|
|
31
|
+
data:
|
|
32
|
+
read: []
|
|
33
|
+
write: []
|
|
34
|
+
api:
|
|
35
|
+
read: []
|
|
36
|
+
write: []
|
|
37
|
+
functions:
|
|
38
|
+
invoke: []
|
|
39
|
+
secrets: []
|
|
40
|
+
egress: []
|
|
41
|
+
resources:
|
|
42
|
+
cpu: 1
|
|
43
|
+
memory_mb: 1024
|
|
44
|
+
concurrency: 20
|
|
45
|
+
min_instances: 0
|
|
46
|
+
max_instances: 3
|
|
47
|
+
timeout_seconds: 60
|
|
48
|
+
|
|
49
|
+
build:
|
|
50
|
+
public_env: {}
|
|
51
|
+
|
|
52
|
+
# Add a registered Atlas environment with `seq-studio artifact link -e <env>`.
|
|
53
|
+
targets:
|
|
54
|
+
local:
|
|
55
|
+
url: http://localhost:5001
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { withArtifactNextConfig } from '@sequenceholdings/artifact-studio/next-cache/config'
|
|
2
|
+
|
|
3
|
+
const devOrigin = process.env.ARTIFACT_STUDIO_DEV_ORIGIN
|
|
4
|
+
|
|
5
|
+
function allowedDevOrigins() {
|
|
6
|
+
if (!devOrigin) return []
|
|
7
|
+
const url = new URL(devOrigin)
|
|
8
|
+
if (
|
|
9
|
+
!['http:', 'https:'].includes(url.protocol) ||
|
|
10
|
+
url.username || url.password || url.pathname !== '/' || url.search || url.hash ||
|
|
11
|
+
url.hostname.includes('*')
|
|
12
|
+
) {
|
|
13
|
+
throw new Error('ARTIFACT_STUDIO_DEV_ORIGIN must be an exact HTTP(S) origin.')
|
|
14
|
+
}
|
|
15
|
+
// Next matches hostnames; the Atlas gateway validates the complete origin.
|
|
16
|
+
return [url.hostname]
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/** @type {import('next').NextConfig} */
|
|
20
|
+
const config = {
|
|
21
|
+
output: 'standalone',
|
|
22
|
+
poweredByHeader: false,
|
|
23
|
+
reactStrictMode: true,
|
|
24
|
+
allowedDevOrigins: allowedDevOrigins(),
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export default withArtifactNextConfig(config)
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "{{slug}}",
|
|
3
|
+
"private": true,
|
|
4
|
+
"type": "module",
|
|
5
|
+
"packageManager": "pnpm@10.28.2",
|
|
6
|
+
"engines": {
|
|
7
|
+
"node": ">=24 <25"
|
|
8
|
+
},
|
|
9
|
+
"scripts": {
|
|
10
|
+
"dev": "next dev --webpack --hostname 127.0.0.1",
|
|
11
|
+
"build": "next build",
|
|
12
|
+
"start": "next start --hostname 127.0.0.1",
|
|
13
|
+
"type-check": "next typegen && tsc --noEmit"
|
|
14
|
+
},
|
|
15
|
+
"dependencies": {
|
|
16
|
+
"@sequenceholdings/artifact-studio": "{{artifactStudioVersion}}",
|
|
17
|
+
"@sequenceholdings/atlas-ui": "3.4.0",
|
|
18
|
+
"@tanstack/react-query": "^4.36.1",
|
|
19
|
+
"next": "16.3.4",
|
|
20
|
+
"react": "19.2.6",
|
|
21
|
+
"react-dom": "19.2.6",
|
|
22
|
+
"server-only": "0.0.1"
|
|
23
|
+
},
|
|
24
|
+
"devDependencies": {
|
|
25
|
+
"@tailwindcss/postcss": "^4.2.2",
|
|
26
|
+
"@types/node": "^24.0.0",
|
|
27
|
+
"@types/react": "^19.2.10",
|
|
28
|
+
"@types/react-dom": "^19.2.3",
|
|
29
|
+
"tailwindcss": "^4.2.2",
|
|
30
|
+
"typescript": "^5.6.0"
|
|
31
|
+
}
|
|
32
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
packages:
|
|
2
|
+
- '.'
|
|
3
|
+
|
|
4
|
+
minimumReleaseAge: 10080
|
|
5
|
+
# The Sequence SDK chain is published from the Studio repo's audited release
|
|
6
|
+
# pipeline, so a new artifact-studio release must install without waiting out
|
|
7
|
+
# the seven-day quarantine. Scoped to the exact first-party packages a fresh
|
|
8
|
+
# scaffold resolves, not @sequenceholdings/*.
|
|
9
|
+
minimumReleaseAgeExclude:
|
|
10
|
+
- '@sequenceholdings/artifact-studio'
|
|
11
|
+
- '@sequenceholdings/atlas-ui'
|
|
12
|
+
- '@sequenceholdings/custom-roles-manifest'
|
|
13
|
+
- '@sequenceholdings/lattice-form-renderer'
|
|
14
|
+
- '@sequenceholdings/orm'
|
|
15
|
+
- '@sequenceholdings/storage'
|
|
16
|
+
strictDepBuilds: true
|
|
17
|
+
allowBuilds:
|
|
18
|
+
'@tailwindcss/oxide': false
|
|
19
|
+
# Reached through the SDK's static builder and ORM tooling, never run by a
|
|
20
|
+
# Next.js app; its platform binary installs as an optional dependency.
|
|
21
|
+
esbuild: false
|
|
22
|
+
sharp: false
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2022",
|
|
4
|
+
"lib": ["dom", "dom.iterable", "esnext"],
|
|
5
|
+
"allowJs": true,
|
|
6
|
+
"skipLibCheck": true,
|
|
7
|
+
"strict": true,
|
|
8
|
+
"noEmit": true,
|
|
9
|
+
"esModuleInterop": true,
|
|
10
|
+
"module": "esnext",
|
|
11
|
+
"moduleResolution": "bundler",
|
|
12
|
+
"resolveJsonModule": true,
|
|
13
|
+
"isolatedModules": true,
|
|
14
|
+
"jsx": "react-jsx",
|
|
15
|
+
"incremental": true,
|
|
16
|
+
"plugins": [{ "name": "next" }]
|
|
17
|
+
},
|
|
18
|
+
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts", ".next/dev/types/**/*.ts"],
|
|
19
|
+
"exclude": ["node_modules"]
|
|
20
|
+
}
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
},
|
|
10
10
|
"dependencies": {},
|
|
11
11
|
"devDependencies": {
|
|
12
|
-
"@sequenceholdings/artifact-studio": "^0.
|
|
12
|
+
"@sequenceholdings/artifact-studio": "^0.3.0",
|
|
13
13
|
"@sequenceholdings/atlas-ui": "^3.1.1",
|
|
14
14
|
"@tailwindcss/postcss": "^4.2.2",
|
|
15
15
|
"@tailwindcss/vite": "^4.2.2",
|
package/dist/trusted-install.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { readFileSync, readdirSync } from 'node:fs';
|
|
2
2
|
import { join } from 'node:path';
|
|
3
|
+
import { load as parseYaml } from 'js-yaml';
|
|
3
4
|
import { classifyLockfileOrigin } from './lockfile-origin.js';
|
|
4
5
|
export class ArtifactInstallTrustError extends Error {
|
|
5
6
|
name = 'ArtifactInstallTrustError';
|
|
@@ -20,6 +21,21 @@ const ALLOWED_TARBALL_PREFIXES = [
|
|
|
20
21
|
'https://libraries.cgr.dev/javascript/',
|
|
21
22
|
'https://libraries.cgr.dev/javascript-upstream/',
|
|
22
23
|
];
|
|
24
|
+
/**
|
|
25
|
+
* Exact off-registry tarballs remote installs accept, each tied to one
|
|
26
|
+
* dependency name and one integrity hash the lockfile must pin. SheetJS ships
|
|
27
|
+
* 0.20.x only from its CDN; the registry stops at 0.18.5, which has known
|
|
28
|
+
* parser vulnerabilities.
|
|
29
|
+
*/
|
|
30
|
+
const PINNED_TARBALLS = new Map([
|
|
31
|
+
[
|
|
32
|
+
'https://cdn.sheetjs.com/xlsx-0.20.3/xlsx-0.20.3.tgz',
|
|
33
|
+
{
|
|
34
|
+
name: 'xlsx',
|
|
35
|
+
integrity: 'sha512-oLDq3jw7AcLqKWH2AhCpVTZl8mf6X2YReP+Neh0SJUzV/BdZYjth94tG5toiMB1PPrYtxOCfaoUCkvtuH+3AJA==',
|
|
36
|
+
},
|
|
37
|
+
],
|
|
38
|
+
]);
|
|
23
39
|
const EXCLUDED_DIRS = new Set(['node_modules', '.git']);
|
|
24
40
|
/** Platform peers the CLI aliases at build time — must not be prod-installed remotely. */
|
|
25
41
|
export const REMOTE_INSTALL_PLATFORM_PACKAGES = new Set([
|
|
@@ -73,10 +89,15 @@ function assertNoPackageManagerSelection(pkg) {
|
|
|
73
89
|
}
|
|
74
90
|
}
|
|
75
91
|
}
|
|
76
|
-
function
|
|
92
|
+
function isPinnedTarballSpec(name, spec) {
|
|
93
|
+
return typeof spec === 'string' && PINNED_TARBALLS.get(spec)?.name === name;
|
|
94
|
+
}
|
|
95
|
+
function assertRegistryOnlySpecMap(map, label, { allowPinnedTarballs = false } = {}) {
|
|
77
96
|
if (!map || typeof map !== 'object')
|
|
78
97
|
return;
|
|
79
98
|
for (const [name, spec] of Object.entries(map)) {
|
|
99
|
+
if (allowPinnedTarballs && isPinnedTarballSpec(name, spec))
|
|
100
|
+
continue;
|
|
80
101
|
if (isNonRegistrySpec(spec)) {
|
|
81
102
|
throw new ArtifactInstallTrustError(`${label} "${name}" uses a non-registry spec ("${String(spec)}") — only registry semver ranges are allowed`);
|
|
82
103
|
}
|
|
@@ -114,7 +135,7 @@ function assertRegistryOnlyPackageJson(dir) {
|
|
|
114
135
|
throw new ArtifactInstallTrustError('package.json is not valid JSON');
|
|
115
136
|
}
|
|
116
137
|
for (const section of DEPENDENCY_SECTIONS) {
|
|
117
|
-
assertRegistryOnlySpecMap(pkg[section], 'Dependency');
|
|
138
|
+
assertRegistryOnlySpecMap(pkg[section], 'Dependency', { allowPinnedTarballs: true });
|
|
118
139
|
}
|
|
119
140
|
assertRegistryOnlySpecMap(pkg.resolutions, 'Resolution override');
|
|
120
141
|
assertNoPackageManagerSelection(pkg);
|
|
@@ -148,6 +169,42 @@ function normalizeYamlScalar(raw) {
|
|
|
148
169
|
}
|
|
149
170
|
return trimmed;
|
|
150
171
|
}
|
|
172
|
+
/**
|
|
173
|
+
* Check resolved `packages` entries, not lockfile text, so a comment or a YAML
|
|
174
|
+
* form the text scan misses cannot stand in for a real resolution.
|
|
175
|
+
*/
|
|
176
|
+
function assertParsedPackageResolutions(lockfileText) {
|
|
177
|
+
let doc;
|
|
178
|
+
try {
|
|
179
|
+
doc = parseYaml(lockfileText);
|
|
180
|
+
}
|
|
181
|
+
catch {
|
|
182
|
+
throw new ArtifactInstallTrustError('pnpm-lock.yaml is not valid YAML');
|
|
183
|
+
}
|
|
184
|
+
const packages = doc && typeof doc === 'object' ? doc.packages : undefined;
|
|
185
|
+
if (!packages || typeof packages !== 'object')
|
|
186
|
+
return;
|
|
187
|
+
for (const [key, entry] of Object.entries(packages)) {
|
|
188
|
+
const resolution = entry && typeof entry === 'object'
|
|
189
|
+
? entry.resolution
|
|
190
|
+
: undefined;
|
|
191
|
+
if (!resolution || typeof resolution !== 'object')
|
|
192
|
+
continue;
|
|
193
|
+
const { tarball, integrity } = resolution;
|
|
194
|
+
if (typeof tarball !== 'string')
|
|
195
|
+
continue;
|
|
196
|
+
const pinned = PINNED_TARBALLS.get(tarball);
|
|
197
|
+
if (pinned) {
|
|
198
|
+
if (!key.startsWith(`${pinned.name}@`) || integrity !== pinned.integrity) {
|
|
199
|
+
throw new ArtifactInstallTrustError(`pnpm-lock.yaml must pin integrity ${pinned.integrity} for ${pinned.name} from ${tarball} — regenerate the lockfile`);
|
|
200
|
+
}
|
|
201
|
+
continue;
|
|
202
|
+
}
|
|
203
|
+
if (!ALLOWED_TARBALL_PREFIXES.some((prefix) => tarball.startsWith(prefix))) {
|
|
204
|
+
throw new ArtifactInstallTrustError(`pnpm-lock.yaml tarball resolution "${tarball}" is not from the Chainguard registry`);
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
}
|
|
151
208
|
function assertChainguardLockfile(dir) {
|
|
152
209
|
const lockfileText = readFileSync(join(dir, 'pnpm-lock.yaml'), 'utf8');
|
|
153
210
|
if (classifyLockfileOrigin(lockfileText) !== 'chainguard') {
|
|
@@ -159,10 +216,13 @@ function assertChainguardLockfile(dir) {
|
|
|
159
216
|
}
|
|
160
217
|
for (const match of lockfileText.matchAll(LOCKFILE_TARBALL_RE)) {
|
|
161
218
|
const tarball = normalizeYamlScalar(match[2] ?? '');
|
|
219
|
+
if (PINNED_TARBALLS.has(tarball))
|
|
220
|
+
continue;
|
|
162
221
|
if (!ALLOWED_TARBALL_PREFIXES.some((prefix) => tarball.startsWith(prefix))) {
|
|
163
222
|
throw new ArtifactInstallTrustError(`pnpm-lock.yaml tarball resolution "${tarball}" is not from the Chainguard registry`);
|
|
164
223
|
}
|
|
165
224
|
}
|
|
225
|
+
assertParsedPackageResolutions(lockfileText);
|
|
166
226
|
}
|
|
167
227
|
/**
|
|
168
228
|
* Reject install-control files and non-registry dependency specs before a remote
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import type { IncomingMessage, ServerResponse } from 'node:http';
|
|
2
|
+
import type { Duplex } from 'node:stream';
|
|
3
|
+
import { type TunnelLimits } from './peer.js';
|
|
4
|
+
import type { TunnelRelay } from './transport.js';
|
|
5
|
+
export interface TunnelLease {
|
|
6
|
+
sessionId: string;
|
|
7
|
+
epoch: number;
|
|
8
|
+
ownerId: string;
|
|
9
|
+
projectId: string;
|
|
10
|
+
environment: string;
|
|
11
|
+
expiresAt: number;
|
|
12
|
+
publicOrigin: string;
|
|
13
|
+
}
|
|
14
|
+
export interface TunnelOwner {
|
|
15
|
+
nodeId: string;
|
|
16
|
+
epoch: number;
|
|
17
|
+
expiresAt: number;
|
|
18
|
+
}
|
|
19
|
+
/** Production adapters must atomically fence epochs across nodes. Ingress routes
|
|
20
|
+
* to lookup().nodeId; this module never silently forwards to an arbitrary node. */
|
|
21
|
+
export interface TunnelOwnershipStore {
|
|
22
|
+
claim(lease: TunnelLease, nodeId: string): Promise<boolean>;
|
|
23
|
+
lookup(sessionId: string): Promise<TunnelOwner | null>;
|
|
24
|
+
release(sessionId: string, epoch: number, nodeId: string): Promise<void>;
|
|
25
|
+
}
|
|
26
|
+
export interface GatewayAuthorization {
|
|
27
|
+
ownerId: string;
|
|
28
|
+
publicOrigin: string;
|
|
29
|
+
/** Signed, audience-scoped context minted by Atlas, never a user bearer. */
|
|
30
|
+
requestContext?: string;
|
|
31
|
+
}
|
|
32
|
+
export interface TunnelBrokerOptions {
|
|
33
|
+
nodeId: string;
|
|
34
|
+
routing: TunnelOwnershipStore;
|
|
35
|
+
authorizeTunnel(request: IncomingMessage): Promise<TunnelLease | null>;
|
|
36
|
+
authorizeRequest(request: IncomingMessage, sessionId: string): Promise<GatewayAuthorization | null>;
|
|
37
|
+
validateLease(lease: TunnelLease): Promise<boolean>;
|
|
38
|
+
relay?: TunnelRelay;
|
|
39
|
+
resolveLease?(sessionId: string): Promise<TunnelLease | null>;
|
|
40
|
+
onTunnelClosed?(lease: TunnelLease): void;
|
|
41
|
+
limits?: Partial<TunnelLimits>;
|
|
42
|
+
}
|
|
43
|
+
/** Transport only: all identity, cookie bootstrap and FGA checks belong to the
|
|
44
|
+
* mandatory Atlas callbacks. An unconfigured broker cannot accept connections. */
|
|
45
|
+
export declare class ArtifactTunnelBroker {
|
|
46
|
+
private readonly options;
|
|
47
|
+
private readonly connections;
|
|
48
|
+
private readonly limits;
|
|
49
|
+
private readonly tunnels;
|
|
50
|
+
private readonly pendingConnections;
|
|
51
|
+
private readonly ownerTimers;
|
|
52
|
+
private closing;
|
|
53
|
+
constructor(options: TunnelBrokerOptions);
|
|
54
|
+
handleTunnelUpgrade(request: IncomingMessage, socket: Duplex, head: Buffer): Promise<void>;
|
|
55
|
+
handleHttp(request: IncomingMessage, response: ServerResponse, sessionId: string): Promise<void>;
|
|
56
|
+
handleWebSocketUpgrade(request: IncomingMessage, socket: Duplex, head: Buffer, sessionId: string): Promise<void>;
|
|
57
|
+
private authorize;
|
|
58
|
+
private valid;
|
|
59
|
+
private remoteConnection;
|
|
60
|
+
private attach;
|
|
61
|
+
private receive;
|
|
62
|
+
private finish;
|
|
63
|
+
private abort;
|
|
64
|
+
/** Immediate revocation and graceful service shutdown hook. */
|
|
65
|
+
disconnect(sessionId: string): void;
|
|
66
|
+
close(): void;
|
|
67
|
+
}
|
|
68
|
+
/** For one-process local development/tests only. Production must provide a shared
|
|
69
|
+
* compare-and-swap store and route ingress to the claimed owner. */
|
|
70
|
+
export declare class SingleProcessTunnelOwnership implements TunnelOwnershipStore {
|
|
71
|
+
private readonly owners;
|
|
72
|
+
claim(lease: TunnelLease, nodeId: string): Promise<boolean>;
|
|
73
|
+
lookup(sessionId: string): Promise<TunnelOwner | null>;
|
|
74
|
+
release(sessionId: string, epoch: number, nodeId: string): Promise<void>;
|
|
75
|
+
}
|