@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.
Files changed (190) hide show
  1. package/VERSION-PIN.md +12 -1
  2. package/dist/api.d.ts +11 -1
  3. package/dist/api.js +26 -9
  4. package/dist/build.d.ts +2 -2
  5. package/dist/build.js +4 -0
  6. package/dist/cli.d.ts +1 -0
  7. package/dist/cli.js +134 -13
  8. package/dist/dev-link.d.ts +1 -1
  9. package/dist/dev-link.js +2 -1
  10. package/dist/gateway/admission.d.ts +56 -0
  11. package/dist/gateway/admission.js +134 -0
  12. package/dist/gateway/authorization-cache.d.ts +52 -0
  13. package/dist/gateway/authorization-cache.js +111 -0
  14. package/dist/gateway/control-admission.d.ts +48 -0
  15. package/dist/gateway/control-admission.js +127 -0
  16. package/dist/gateway/control.d.ts +94 -0
  17. package/dist/gateway/control.js +116 -0
  18. package/dist/gateway/hosted-proxy.d.ts +36 -0
  19. package/dist/gateway/hosted-proxy.js +344 -0
  20. package/dist/gateway/release-admission.d.ts +23 -0
  21. package/dist/gateway/release-admission.js +259 -0
  22. package/dist/gateway/server.d.ts +38 -0
  23. package/dist/gateway/server.js +382 -0
  24. package/dist/gateway/session.d.ts +72 -0
  25. package/dist/gateway/session.js +144 -0
  26. package/dist/gateway/usage.d.ts +70 -0
  27. package/dist/gateway/usage.js +116 -0
  28. package/dist/gateway-main.d.ts +1 -0
  29. package/dist/gateway-main.js +114 -0
  30. package/dist/git-clone.d.ts +23 -1
  31. package/dist/git-clone.js +32 -15
  32. package/dist/manifest-next.d.ts +217 -0
  33. package/dist/manifest-next.js +132 -0
  34. package/dist/manifest-static.d.ts +76 -0
  35. package/dist/manifest-static.js +124 -0
  36. package/dist/manifest.d.ts +118 -13
  37. package/dist/manifest.js +12 -119
  38. package/dist/next/bridge.d.ts +47 -0
  39. package/dist/next/bridge.js +236 -0
  40. package/dist/next/browser-logs.d.ts +7 -0
  41. package/dist/next/browser-logs.js +51 -0
  42. package/dist/next/browser.d.ts +6 -0
  43. package/dist/next/browser.js +57 -0
  44. package/dist/next/protocol.d.ts +73 -0
  45. package/dist/next/protocol.js +56 -0
  46. package/dist/next/release.d.ts +15 -0
  47. package/dist/next/release.js +46 -0
  48. package/dist/next/requests.d.ts +15 -0
  49. package/dist/next/requests.js +59 -0
  50. package/dist/next/routes.d.ts +4 -0
  51. package/dist/next/routes.js +21 -0
  52. package/dist/next/streams.d.ts +16 -0
  53. package/dist/next/streams.js +132 -0
  54. package/dist/next/theme.d.ts +10 -0
  55. package/dist/next/theme.js +45 -0
  56. package/dist/next/transport.d.ts +15 -0
  57. package/dist/next/transport.js +13 -0
  58. package/dist/next-build.d.ts +29 -0
  59. package/dist/next-build.js +129 -0
  60. package/dist/next-cache/adapter-runtime.d.ts +51 -0
  61. package/dist/next-cache/adapter-runtime.js +73 -0
  62. package/dist/next-cache/component-handler.d.ts +3 -0
  63. package/dist/next-cache/component-handler.js +10 -0
  64. package/dist/next-cache/components.d.ts +20 -0
  65. package/dist/next-cache/components.js +113 -0
  66. package/dist/next-cache/config.d.ts +9 -0
  67. package/dist/next-cache/config.js +105 -0
  68. package/dist/next-cache/incremental-handler.d.ts +18 -0
  69. package/dist/next-cache/incremental-handler.js +17 -0
  70. package/dist/next-cache/incremental.d.ts +27 -0
  71. package/dist/next-cache/incremental.js +99 -0
  72. package/dist/next-cache/index.d.ts +18 -0
  73. package/dist/next-cache/index.js +16 -0
  74. package/dist/next-cache/memory-store.d.ts +27 -0
  75. package/dist/next-cache/memory-store.js +118 -0
  76. package/dist/next-cache/redis-scripts.d.ts +3 -0
  77. package/dist/next-cache/redis-scripts.js +85 -0
  78. package/dist/next-cache/redis-store.d.ts +18 -0
  79. package/dist/next-cache/redis-store.js +56 -0
  80. package/dist/next-cache/runtime.d.ts +29 -0
  81. package/dist/next-cache/runtime.js +63 -0
  82. package/dist/next-cache/serialization.d.ts +39 -0
  83. package/dist/next-cache/serialization.js +333 -0
  84. package/dist/next-cache/store.d.ts +54 -0
  85. package/dist/next-cache/store.js +45 -0
  86. package/dist/next-cache/valkey-transport.d.ts +52 -0
  87. package/dist/next-cache/valkey-transport.js +189 -0
  88. package/dist/next-dev-control.d.ts +48 -0
  89. package/dist/next-dev-control.js +63 -0
  90. package/dist/next-dev-process.d.ts +17 -0
  91. package/dist/next-dev-process.js +110 -0
  92. package/dist/next-dev.d.ts +12 -0
  93. package/dist/next-dev.js +267 -0
  94. package/dist/next-image-config.d.ts +9 -0
  95. package/dist/next-image-config.js +101 -0
  96. package/dist/next-project.d.ts +22 -0
  97. package/dist/next-project.js +64 -0
  98. package/dist/next-public-fonts-local.d.ts +7 -0
  99. package/dist/next-public-fonts-local.js +64 -0
  100. package/dist/next-public-fonts.d.ts +13 -0
  101. package/dist/next-public-fonts.js +49 -0
  102. package/dist/next-source-hash.d.ts +14 -0
  103. package/dist/next-source-hash.js +18 -0
  104. package/dist/next-source.d.ts +35 -0
  105. package/dist/next-source.js +155 -0
  106. package/dist/next.d.ts +9 -0
  107. package/dist/next.js +5 -0
  108. package/dist/project.d.ts +3 -0
  109. package/dist/project.js +9 -1
  110. package/dist/runtime-contracts.d.ts +229 -0
  111. package/dist/runtime-contracts.js +224 -0
  112. package/dist/sdk.d.ts +2 -2
  113. package/dist/server/broker-contract.d.ts +68 -0
  114. package/dist/server/broker-contract.js +33 -0
  115. package/dist/server/client.d.ts +81 -0
  116. package/dist/server/client.js +159 -0
  117. package/dist/server/logger.d.ts +26 -0
  118. package/dist/server/logger.js +24 -0
  119. package/dist/server/release-forms.d.ts +13 -0
  120. package/dist/server/release-forms.js +124 -0
  121. package/dist/server/request-context.d.ts +75 -0
  122. package/dist/server/request-context.js +107 -0
  123. package/dist/server/runtime-config.d.ts +54 -0
  124. package/dist/server/runtime-config.js +64 -0
  125. package/dist/server.d.ts +8 -0
  126. package/dist/server.js +4 -0
  127. package/dist/templates/nextjs/.artifactignore +14 -0
  128. package/dist/templates/nextjs/README.md +157 -0
  129. package/dist/templates/nextjs/app/about/page.tsx +26 -0
  130. package/dist/templates/nextjs/app/actions.ts +18 -0
  131. package/dist/templates/nextjs/app/api/health/route.ts +6 -0
  132. package/dist/templates/nextjs/app/components/counter.tsx +19 -0
  133. package/dist/templates/nextjs/app/components/greeting-form.tsx +38 -0
  134. package/dist/templates/nextjs/app/globals.css +4 -0
  135. package/dist/templates/nextjs/app/layout.tsx +27 -0
  136. package/dist/templates/nextjs/app/page.tsx +51 -0
  137. package/dist/templates/nextjs/app/providers.tsx +107 -0
  138. package/dist/templates/nextjs/artifact.bundle.yml +55 -0
  139. package/dist/templates/nextjs/gitignore +9 -0
  140. package/dist/templates/nextjs/next.config.mjs +27 -0
  141. package/dist/templates/nextjs/package.json +32 -0
  142. package/dist/templates/nextjs/pnpm-workspace.yaml +22 -0
  143. package/dist/templates/nextjs/postcss.config.mjs +5 -0
  144. package/dist/templates/nextjs/tsconfig.json +20 -0
  145. package/dist/templates/react-vite/package.json +1 -1
  146. package/dist/trusted-install.js +62 -2
  147. package/dist/tunnel/broker.d.ts +75 -0
  148. package/dist/tunnel/broker.js +463 -0
  149. package/dist/tunnel/client.d.ts +22 -0
  150. package/dist/tunnel/client.js +177 -0
  151. package/dist/tunnel/index.d.ts +6 -0
  152. package/dist/tunnel/index.js +4 -0
  153. package/dist/tunnel/io.d.ts +33 -0
  154. package/dist/tunnel/io.js +108 -0
  155. package/dist/tunnel/peer.d.ts +25 -0
  156. package/dist/tunnel/peer.js +147 -0
  157. package/dist/tunnel/protocol.d.ts +78 -0
  158. package/dist/tunnel/protocol.js +210 -0
  159. package/dist/tunnel/redis-auth.d.ts +37 -0
  160. package/dist/tunnel/redis-auth.js +92 -0
  161. package/dist/tunnel/redis-ownership.d.ts +38 -0
  162. package/dist/tunnel/redis-ownership.js +100 -0
  163. package/dist/tunnel/redis-socket.d.ts +13 -0
  164. package/dist/tunnel/redis-socket.js +48 -0
  165. package/dist/tunnel/redis-types.d.ts +75 -0
  166. package/dist/tunnel/redis-types.js +9 -0
  167. package/dist/tunnel/redis.d.ts +56 -0
  168. package/dist/tunnel/redis.js +404 -0
  169. package/dist/tunnel/transport.d.ts +17 -0
  170. package/dist/tunnel/transport.js +1 -0
  171. package/package.json +67 -4
  172. package/templates/nextjs/.artifactignore +14 -0
  173. package/templates/nextjs/README.md +157 -0
  174. package/templates/nextjs/app/about/page.tsx +26 -0
  175. package/templates/nextjs/app/actions.ts +18 -0
  176. package/templates/nextjs/app/api/health/route.ts +6 -0
  177. package/templates/nextjs/app/components/counter.tsx +19 -0
  178. package/templates/nextjs/app/components/greeting-form.tsx +38 -0
  179. package/templates/nextjs/app/globals.css +4 -0
  180. package/templates/nextjs/app/layout.tsx +27 -0
  181. package/templates/nextjs/app/page.tsx +51 -0
  182. package/templates/nextjs/app/providers.tsx +107 -0
  183. package/templates/nextjs/artifact.bundle.yml +55 -0
  184. package/templates/nextjs/gitignore +9 -0
  185. package/templates/nextjs/next.config.mjs +27 -0
  186. package/templates/nextjs/package.json +32 -0
  187. package/templates/nextjs/pnpm-workspace.yaml +22 -0
  188. package/templates/nextjs/postcss.config.mjs +5 -0
  189. package/templates/nextjs/tsconfig.json +20 -0
  190. package/templates/react-vite/package.json +1 -1
@@ -0,0 +1,6 @@
1
+ export function GET() {
2
+ return Response.json(
3
+ { ok: true, framework: 'nextjs' },
4
+ { headers: { 'Cache-Control': 'no-store' } },
5
+ )
6
+ }
@@ -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,4 @@
1
+ @import "tailwindcss";
2
+ @import "@sequenceholdings/atlas-ui/tokens.css";
3
+
4
+ @source "../node_modules/@sequenceholdings/atlas-ui/dist";
@@ -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,9 @@
1
+ node_modules/
2
+ .next/
3
+ next-env.d.ts
4
+ .artifact/
5
+ .artifact-studio/
6
+ .env*
7
+ *.tsbuildinfo
8
+ coverage/
9
+ .DS_Store
@@ -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,5 @@
1
+ export default {
2
+ plugins: {
3
+ '@tailwindcss/postcss': {},
4
+ },
5
+ }
@@ -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.2.4",
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",
@@ -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 assertRegistryOnlySpecMap(map, label) {
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
+ }