1#[derive(Debug, Clone, Copy)]
7pub struct ResourceTemplate {
8 pub name: &'static str,
9 pub description: &'static str,
10 pub contents: &'static str,
11 pub companions: &'static [CompanionFile],
12 pub companion_trackers: &'static [CompanionTracker],
13}
14
15#[derive(Debug, Clone, Copy)]
16pub struct CompanionFile {
17 pub name: &'static str,
18 pub contents: &'static str,
19}
20
21#[derive(Debug, Clone, Copy)]
25pub struct CompanionTracker {
26 pub name: &'static str,
27 pub audience: &'static str,
28 pub merge_with_source: bool,
29}
30
31pub const RESOURCE_TEMPLATES: &[ResourceTemplate] = &[
32 ResourceTemplate {
33 name: "process",
34 description: "a branch-local long-running process with its own port",
35 companions: &[],
36 companion_trackers: &[],
37 contents: r#"kind = "process"
38ownership = "branch"
39# Trackers or resources that must be ready first, e.g. ["deps", "runtime-env"].
40depends_on = []
41
42[ports]
43app = { start = 3100, env = "PORT" }
44
45[actions.start]
46# Edit to your dev command, e.g. "pnpm dev" or "bin/rails server".
47command = "npm run dev"
48long_running = true
49
50[actions.stop]
51signal = "term"
52
53[exports]
54APP_URL = "http://127.0.0.1:{{ports.app}}"
55
56# Most tools read their port from a committed config file rather than argv.
57# Uncomment and point this at yours. There is no template file: `find` names
58# the project's working default, so a clone without newgit still starts on it.
59#
60# `find` is literal, never a regex, and must match exactly once — which is
61# also the drift detector. When the default changes upstream, the bind fails
62# naming the file and the string instead of quietly doing nothing.
63#
64# [[render]]
65# path = "vite.config.ts"
66# replace = [
67# { find = "port: 3000", with = "port: {{ports.app}}" },
68# ]
69"#,
70 },
71 ResourceTemplate {
72 name: "pnpm",
73 description: "dependency install via pnpm; recomputed per instance, hardlinked from one store",
74 companions: &[CompanionFile {
77 name: "pnpm-store",
78 contents: r#"kind = "external-store"
79ownership = "user"
80
81[checkpoint]
82mode = "hash"
83paths = ["pnpm-lock.yaml"]
84
85[restore]
86mode = "none"
87"#,
88 }],
89 companion_trackers: &[],
90 contents: r#"# Every instance installs its own dependencies: two branches with
91# different lockfiles must not share a tree, or one branch's install
92# rewrites the other's. What that costs is your package manager's call.
93# pnpm hardlinks from one shared store, so instance ten adds directory
94# entries, not gigabytes; `npm ci` expands a full copy every time.
95kind = "command"
96ownership = "workspace"
97depends_on = ["pnpm-store"]
98
99[identity]
100paths = ["package.json", "pnpm-lock.yaml"]
101
102[actions.prepare]
103command = "pnpm install --frozen-lockfile"
104
105[checkpoint]
106mode = "hash"
107paths = ["package.json", "pnpm-lock.yaml"]
108
109[restore]
110mode = "recompute"
111action = "prepare"
112"#,
113 },
114 ResourceTemplate {
115 name: "command-snapshot",
116 description: "a daemon-owned database captured through the daemon into a tracker",
117 companions: &[],
118 companion_trackers: &[CompanionTracker {
121 name: "db-snapshots",
122 audience: "project-devs",
123 merge_with_source: false,
124 }],
125 contents: r#"kind = "command-snapshot"
126ownership = "branch"
127
128# A branch-local database, named after the instance so instances never share
129# one. Edit the commands for your database; the shape is what matters:
130# checkpoint emits a dump into the lane, restore reads it back.
131
132[actions.prepare]
133command = "createdb {{branch.slug}} || true"
134
135# A convenience command, not a lifecycle hook: no stage runs `migrate`, and
136# nothing but you ever will — `newgit action <this resource>.migrate`. Only
137# `prepare` is run for you (by `spawn`, and by a `recompute` restore).
138[actions.migrate]
139command = "npm run db:migrate"
140
141[checkpoint]
142mode = "command"
143command = "pg_dump {{branch.slug}} > {{snapshot.path}}/db.sql && echo {{snapshot.path}}/db.sql"
144into_tracker = "db-snapshots"
145
146[restore]
147mode = "command"
148command = "dropdb {{branch.slug}} --if-exists && createdb {{branch.slug}} && psql --quiet {{branch.slug}} < {{state_ref}}"
149
150[cleanup]
151command = "dropdb {{branch.slug}} --if-exists"
152
153[exports]
154DATABASE_URL = "postgres://localhost/{{branch.slug}}"
155"#,
156 },
157 ResourceTemplate {
158 name: "external",
159 description: "a resource another system owns; newgit holds only a handle",
160 companions: &[],
161 companion_trackers: &[],
162 contents: r#"kind = "external"
163ownership = "external"
164
165# newgit does not own this resource, so it never assumes deletion semantics
166# it did not author: `ownership = "external"` means cleanup runs exactly the
167# command below and nothing else.
168#
169# `captures` reads names out of the prepare command's stdout — either a flat
170# JSON object or KEY=VALUE lines — and publishes them as this resource's
171# exports, so `newgit run` and the hooks below can use them.
172#
173# When `captures` is set, stdout belongs to newgit: send anything else the
174# command prints to stderr, or a chatty CLI's progress output will be
175# interleaved with the values and they will not parse. A declared name that
176# never turns up is reported as a warning, not an error.
177
178[actions.prepare]
179# Edit to your provisioning command.
180command = "cloudctl preview create --branch {{branch.name}} --json"
181captures = ["PREVIEW_ID", "PREVIEW_URL"]
182
183[checkpoint]
184mode = "external"
185state_ref = "{{exports.PREVIEW_ID}}"
186
187[restore]
188# The handle is recorded, not re-created: an undo does not rewind another
189# system. Change to `mode = "command"` if yours can be rewound.
190mode = "external"
191
192[cleanup]
193command = "cloudctl preview delete {{state_ref}}"
194"#,
195 },
196];
197
198pub fn resource_template(name: &str) -> Option<&'static ResourceTemplate> {
199 RESOURCE_TEMPLATES
200 .iter()
201 .find(|template| template.name == name)
202}