diff --git a/cmd/sst/mosaic/mosaic.go b/cmd/sst/mosaic/mosaic.go index a33a8962a..acd3b2ee5 100644 --- a/cmd/sst/mosaic/mosaic.go +++ b/cmd/sst/mosaic/mosaic.go @@ -87,7 +87,8 @@ func CmdMosaic(c *cli.Cli) error { if err != nil { return err } - for name, args := range p.App().Providers { + for name, a := range p.App().Providers { + args := a switch name { case "aws": wg.Go(func() error { @@ -127,13 +128,13 @@ func CmdMosaic(c *cli.Cli) error { case unknown := <-evts: switch evt := unknown.(type) { case *project.CompleteEvent: - for _, r := range evt.Receivers { - if r.Dev.Command == "" { + for _, d := range evt.Devs { + if d.Command == "" { continue } - dir := filepath.Join(cwd, r.Directory) - slog.Info("mosaic", "receiver", r.Name, "directory", dir) - multi.AddPane(r.Name, append([]string{currentExecutable, "mosaic"}, strings.Split(r.Dev.Command, " ")...), r.Name, dir, true) + dir := filepath.Join(cwd, d.Directory) + slog.Info("mosaic", "dev", d.Name, "directory", dir) + multi.AddPane(d.Name, append([]string{currentExecutable, "mosaic"}, strings.Split(d.Command, " ")...), d.Name, dir, true) } break } diff --git a/cmd/sst/mosaic/multiplexer/multiplexer.go b/cmd/sst/mosaic/multiplexer/multiplexer.go index a28194432..d121d5b04 100644 --- a/cmd/sst/mosaic/multiplexer/multiplexer.go +++ b/cmd/sst/mosaic/multiplexer/multiplexer.go @@ -283,17 +283,19 @@ func (m *Model) draw() { style := tcell.StyleDefault if index == m.selected { style = style.Background(tcell.ColorGray) + style = style.Bold(true) if m.focus == "sidebar" { style = style.Background(tcell.ColorOrangeRed) } style = style.Foreground(tcell.ColorWhite) + title.SetRight("< ", style) } text := item.title title.SetStyle(style) title.SetLeft(" "+text, tcell.StyleDefault) if item.status == paneStatusStopped { - title.SetRight("(-)", tcell.StyleDefault) + title.SetRight("- ", tcell.StyleDefault) } m.sidebarWidget.AddWidget(title, 0) } diff --git a/cmd/sst/mosaic/server/client.go b/cmd/sst/mosaic/server/client.go index 71d658637..6d8e5e127 100644 --- a/cmd/sst/mosaic/server/client.go +++ b/cmd/sst/mosaic/server/client.go @@ -83,8 +83,8 @@ func Stream(ctx context.Context, url string, types ...interface{}) (chan any, er return out, nil } -func Env(ctx context.Context, receiverID string, url string) (map[string]string, error) { - req, err := http.NewRequestWithContext(ctx, "GET", url+"/api/env?receiverID="+receiverID, nil) +func Env(ctx context.Context, directory string, url string) (map[string]string, error) { + req, err := http.NewRequestWithContext(ctx, "GET", url+"/api/env?directory="+directory, nil) if err != nil { return nil, err } diff --git a/cmd/sst/mosaic/server/server.go b/cmd/sst/mosaic/server/server.go index 91b7a5f22..029ae3928 100644 --- a/cmd/sst/mosaic/server/server.go +++ b/cmd/sst/mosaic/server/server.go @@ -91,31 +91,31 @@ func (s *Server) Start(ctx context.Context, p *project.Project) error { }) s.Mux.HandleFunc("/api/env", func(w http.ResponseWriter, r *http.Request) { - receiverID := r.URL.Query().Get("receiverID") - var receiver *project.Receiver + directory := r.URL.Query().Get("directory") + var dev *project.Dev cwd, _ := os.Getwd() - for _, r := range complete.Receivers { - full := filepath.Join(cwd, r.Directory) - slog.Info("matching receiver", "full", full, "receiverID", receiverID) - if full == receiverID { - receiver = &r + for _, d := range complete.Devs { + full := filepath.Join(cwd, d.Directory) + slog.Info("matching dev", "full", full, "directory", directory) + if full == directory { + dev = &d break } } - if receiver == nil { - slog.Info("receiver not found", "receiverID", receiverID) - http.Error(w, "receiver not found", http.StatusNotFound) + if dev == nil { + slog.Info("dev not found", "directory", directory) + http.Error(w, "dev not found", http.StatusNotFound) return } env := map[string]string{} - if receiver.Aws != nil && receiver.Aws.Role != "" { + if dev.Aws != nil && dev.Aws.Role != "" { prov, _ := p.Provider("aws") awsProvider := prov.(*provider.AwsProvider) stsClient := sts.NewFromConfig(awsProvider.Config()) sessionName := "sst-dev" - slog.Info("assuming role", "role", receiver.Aws.Role) + slog.Info("assuming role", "role", dev.Aws.Role) result, err := stsClient.AssumeRole(r.Context(), &sts.AssumeRoleInput{ - RoleArn: &receiver.Aws.Role, + RoleArn: &dev.Aws.Role, RoleSessionName: &sessionName, DurationSeconds: awssdk.Int32(3600), }) @@ -128,14 +128,14 @@ func (s *Server) Start(ctx context.Context, p *project.Project) error { env["AWS_SECRET_ACCESS_KEY"] = *result.Credentials.SecretAccessKey env["AWS_SESSION_TOKEN"] = *result.Credentials.SessionToken } - slog.Info("receiver", "links", receiver.Links) - for _, resource := range receiver.Links { + slog.Info("dev", "links", dev.Links) + for _, resource := range dev.Links { value := complete.Links[resource] jsonValue, _ := json.Marshal(value) env["SST_RESOURCE_"+resource] = string(jsonValue) } env["SST_RESOURCE_App"] = fmt.Sprintf(`{"name": "%s", "stage": "%s" }`, p.App().Name, p.App().Stage) - for key, value := range receiver.Environment { + for key, value := range dev.Environment { slog.Info("setting env", "key", key, "value", value) env[key] = value } diff --git a/pkg/platform/src/components/aws/astro.ts b/pkg/platform/src/components/aws/astro.ts index 6beca0ac9..7a1877082 100644 --- a/pkg/platform/src/components/aws/astro.ts +++ b/pkg/platform/src/components/aws/astro.ts @@ -333,11 +333,23 @@ export class Astro extends Component implements Link.Linkable { const { sitePath, partition } = prepare(args, opts); if ($dev) { + const server = createDevServer(parent, name, args); this.registerOutputs({ _metadata: { mode: "placeholder", path: sitePath, - server: createDevServer(parent, name, args).arn, + server: server.arn, + }, + _dev: { + directory: sitePath, + links: output(args.link || []) + .apply(Link.build) + .apply((links) => links.map((link) => link.name)), + aws: { + role: server.nodes.role.arn, + }, + environment: args.environment, + command: "npm run dev", }, }); return; @@ -372,19 +384,6 @@ export class Astro extends Component implements Link.Linkable { url: distribution.apply((d) => d.domainUrl ?? d.url), edge: plan.edge, server: serverFunction.arn, - dev: { - command: "npm run dev", - }, - }, - _receiver: { - directory: sitePath, - links: [], - environment: output(args.environment).apply((env) => ({ - ...env, - })), - dev: { - command: "npm run dev", - }, }, }); diff --git a/pkg/platform/src/components/aws/nextjs.ts b/pkg/platform/src/components/aws/nextjs.ts index c7007700a..bd6827774 100644 --- a/pkg/platform/src/components/aws/nextjs.ts +++ b/pkg/platform/src/components/aws/nextjs.ts @@ -476,6 +476,12 @@ export class Nextjs extends Component implements Link.Linkable { path: sitePath, server: createDevServer(parent, name, args).arn, }, + _dev: { + directory: sitePath, + dev: { + command: "npm run dev", + }, + }, }); return; } diff --git a/pkg/platform/src/components/aws/nuxt.ts b/pkg/platform/src/components/aws/nuxt.ts index 47493ddde..27a9e3a86 100644 --- a/pkg/platform/src/components/aws/nuxt.ts +++ b/pkg/platform/src/components/aws/nuxt.ts @@ -1,6 +1,6 @@ import fs from "fs"; import path from "path"; -import { ComponentResourceOptions, Output, all } from "@pulumi/pulumi"; +import { ComponentResourceOptions, Output, all, output } from "@pulumi/pulumi"; import { Function } from "./function.js"; import { SsrSiteArgs, @@ -323,11 +323,23 @@ export class Nuxt extends Component implements Link.Linkable { const parent = this; const { sitePath, partition } = prepare(args, opts); if ($dev) { + const server = createDevServer(parent, name, args); this.registerOutputs({ _metadata: { mode: "placeholder", path: sitePath, - server: createDevServer(parent, name, args).arn, + server: server.arn, + }, + _dev: { + directory: sitePath, + links: output(args.link || []) + .apply(Link.build) + .apply((links) => links.map((link) => link.name)), + aws: { + role: server.nodes.role.arn, + }, + environment: args.environment, + command: "npm run dev", }, }); return; diff --git a/pkg/platform/src/components/aws/react.ts b/pkg/platform/src/components/aws/react.ts index 3c4fb3f23..9db3872a5 100644 --- a/pkg/platform/src/components/aws/react.ts +++ b/pkg/platform/src/components/aws/react.ts @@ -339,12 +339,24 @@ export class React extends Component implements Link.Linkable { const edge = normalizeEdge(); const { sitePath, partition } = prepare(args, opts); if ($dev) { + const server = createDevServer(parent, name, args); this.registerOutputs({ _metadata: { mode: "placeholder", path: sitePath, edge, - server: createDevServer(parent, name, args).arn, + server: server.arn, + }, + _dev: { + directory: sitePath, + links: output(args.link || []) + .apply(Link.build) + .apply((links) => links.map((link) => link.name)), + aws: { + role: server.nodes.role.arn, + }, + environment: args.environment, + command: "npm run dev", }, }); return; diff --git a/pkg/platform/src/components/aws/remix.ts b/pkg/platform/src/components/aws/remix.ts index 0dfef29db..cd576ae99 100644 --- a/pkg/platform/src/components/aws/remix.ts +++ b/pkg/platform/src/components/aws/remix.ts @@ -333,12 +333,24 @@ export class Remix extends Component implements Link.Linkable { const edge = normalizeEdge(); const { sitePath, partition } = prepare(args, opts); if ($dev) { + const server = createDevServer(parent, name, args); this.registerOutputs({ _metadata: { mode: "placeholder", path: sitePath, edge, - server: createDevServer(parent, name, args).arn, + server: server.arn, + }, + _dev: { + directory: sitePath, + links: output(args.link || []) + .apply(Link.build) + .apply((links) => links.map((link) => link.name)), + aws: { + role: server.nodes.role.arn, + }, + environment: args.environment, + command: "npm run dev", }, }); return; diff --git a/pkg/platform/src/components/aws/service.ts b/pkg/platform/src/components/aws/service.ts index 2ac5a19b4..b452e9182 100644 --- a/pkg/platform/src/components/aws/service.ts +++ b/pkg/platform/src/components/aws/service.ts @@ -679,7 +679,22 @@ export class Service extends Component implements Link.Linkable { ? path.dirname(imageArgs.dockerfile) : imageArgs.context, ), - dev: args.dev, + links: linkData.apply((input) => input.map((item) => item.name)), + environment: { + ...args.environment, + AWS_REGION: region, + }, + aws: { + role: taskRole.arn, + }, + })), + _dev: imageArgs.apply((imageArgs) => ({ + directory: path.join( + imageArgs.dockerfile + ? path.dirname(imageArgs.dockerfile) + : imageArgs.context, + ), + command: output(args.dev).apply((v) => v?.command), links: linkData.apply((input) => input.map((item) => item.name)), environment: { ...args.environment, diff --git a/pkg/platform/src/components/aws/solid-start.ts b/pkg/platform/src/components/aws/solid-start.ts index 748ba0320..9f2255807 100644 --- a/pkg/platform/src/components/aws/solid-start.ts +++ b/pkg/platform/src/components/aws/solid-start.ts @@ -1,6 +1,6 @@ import fs from "fs"; import path from "path"; -import { ComponentResourceOptions, Output, all } from "@pulumi/pulumi"; +import { ComponentResourceOptions, Output, all, output } from "@pulumi/pulumi"; import { Function } from "./function.js"; import { SsrSiteArgs, @@ -324,11 +324,23 @@ export class SolidStart extends Component implements Link.Linkable { const parent = this; const { sitePath, partition } = prepare(args, opts); if ($dev) { + const server = createDevServer(parent, name, args); this.registerOutputs({ _metadata: { mode: "placeholder", path: sitePath, - server: createDevServer(parent, name, args).arn, + server: server.arn, + }, + _dev: { + directory: sitePath, + links: output(args.link || []) + .apply(Link.build) + .apply((links) => links.map((link) => link.name)), + aws: { + role: server.nodes.role.arn, + }, + environment: args.environment, + command: "npm run dev", }, }); return; diff --git a/pkg/platform/src/components/aws/svelte-kit.ts b/pkg/platform/src/components/aws/svelte-kit.ts index 18e7d7889..ccfe9f6c1 100644 --- a/pkg/platform/src/components/aws/svelte-kit.ts +++ b/pkg/platform/src/components/aws/svelte-kit.ts @@ -334,12 +334,24 @@ export class SvelteKit extends Component implements Link.Linkable { const { sitePath, partition } = prepare(args, opts); if ($dev) { + const server = createDevServer(parent, name, args); this.registerOutputs({ _metadata: { mode: "placeholder", path: sitePath, edge, - server: createDevServer(parent, name, args).arn, + server: server.arn, + }, + _dev: { + directory: sitePath, + links: output(args.link || []) + .apply(Link.build) + .apply((links) => links.map((link) => link.name)), + aws: { + role: server.nodes.role.arn, + }, + environment: args.environment, + command: "npm run dev", }, }); return; diff --git a/pkg/platform/src/components/base/base-static-site.ts b/pkg/platform/src/components/base/base-static-site.ts index 9b2cadfd6..c16329775 100644 --- a/pkg/platform/src/components/base/base-static-site.ts +++ b/pkg/platform/src/components/base/base-static-site.ts @@ -319,6 +319,11 @@ export function cleanup( ) { return { _hint: url, + _dev: { + directory: sitePath, + environment: environment, + command: "npm run dev", + }, _receiver: all([sitePath, environment]).apply( ([sitePath, environment]) => ({ directory: sitePath, diff --git a/pkg/project/stack.go b/pkg/project/stack.go index e2d21fda4..c84b424ec 100644 --- a/pkg/project/stack.go +++ b/pkg/project/stack.go @@ -65,11 +65,20 @@ type Receiver struct { AwsRole string `json:"awsRole"` Cloudflare *CloudflareReceiver `json:"cloudflare"` Aws *AwsReceiver `json:"aws"` - Dev struct { - Command string `json:"command"` - } `json:"dev"` } +type Dev struct { + Name string `json:"name"` + Command string `json:"command"` + Directory string `json:"directory"` + Links []string `json:"links"` + Environment map[string]string `json:"environment"` + Aws *struct { + Role string `json:"role"` + } `json:"aws"` +} +type Devs map[string]Dev + type CloudflareReceiver struct { } @@ -98,6 +107,7 @@ type CompleteEvent struct { Links Links Warps Warps Receivers Receivers + Devs Devs Outputs map[string]interface{} Hints map[string]string Errors []Error @@ -801,6 +811,7 @@ func getCompletedEvent(ctx context.Context, stack auto.Stack) (*CompleteEvent, e complete := &CompleteEvent{ Links: Links{}, Receivers: Receivers{}, + Devs: Devs{}, Warps: Warps{}, Hints: map[string]string{}, Outputs: map[string]interface{}{}, @@ -829,6 +840,14 @@ func getCompletedEvent(ctx context.Context, stack auto.Stack) (*CompleteEvent, e complete.Receivers[entry.Directory] = entry } + if match, ok := outputs["_dev"].(map[string]interface{}); ok { + data, _ := json.Marshal(match) + var entry Dev + json.Unmarshal(data, &entry) + entry.Name = resource.URN.Name() + complete.Devs[entry.Name] = entry + } + if hint, ok := outputs["_hint"].(string); ok { complete.Hints[string(resource.URN)] = hint }