@rebasepro/cli 0.24.0 → 0.24.1-canary.g56af4a4

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 (118) hide show
  1. package/bin/rebase.js +3 -0
  2. package/dist/agent-setup-B31F00DC.js +324 -0
  3. package/dist/agent-setup-B31F00DC.js.map +1 -0
  4. package/dist/api-keys-BPBi5nOF.js +524 -0
  5. package/dist/api-keys-BPBi5nOF.js.map +1 -0
  6. package/dist/apps-CPXNElX4.js +256 -0
  7. package/dist/apps-CPXNElX4.js.map +1 -0
  8. package/dist/args-BnmsEXvu.js +109 -0
  9. package/dist/args-BnmsEXvu.js.map +1 -0
  10. package/dist/atlas-dev-database-DTMuEScS.js +62 -0
  11. package/dist/atlas-dev-database-DTMuEScS.js.map +1 -0
  12. package/dist/auth-DvY94ZuL.js +395 -0
  13. package/dist/auth-DvY94ZuL.js.map +1 -0
  14. package/dist/branch-pointer-DIhKbQLp.js +2 -0
  15. package/dist/build-CFwUx-Ff.js +336 -0
  16. package/dist/build-CFwUx-Ff.js.map +1 -0
  17. package/dist/bundle-BQhP_xiA.js +1617 -0
  18. package/dist/bundle-BQhP_xiA.js.map +1 -0
  19. package/dist/cli.d.ts +2 -2
  20. package/dist/cloud-CkTr-yt0.js +9640 -0
  21. package/dist/cloud-CkTr-yt0.js.map +1 -0
  22. package/dist/collection-drift-BKj_BoMq.js +106 -0
  23. package/dist/collection-drift-BKj_BoMq.js.map +1 -0
  24. package/dist/commands/api-keys.d.ts +2 -0
  25. package/dist/commands/db.d.ts +37 -24
  26. package/dist/commands/dev.d.ts +103 -0
  27. package/dist/commands/generate_sdk.d.ts +26 -0
  28. package/dist/commands/skills.d.ts +22 -7
  29. package/dist/commands/storage.d.ts +46 -0
  30. package/dist/consent-BWHaYx5p.js +76 -0
  31. package/dist/consent-BWHaYx5p.js.map +1 -0
  32. package/dist/context-BqhRydBg.js +1234 -0
  33. package/dist/context-BqhRydBg.js.map +1 -0
  34. package/dist/daemon-DBn-shxX.js +2 -0
  35. package/dist/{daemon-entry-LTFKtpHy.js → daemon-entry-Da-D3rmH.js} +3 -4
  36. package/dist/daemon-entry-Da-D3rmH.js.map +1 -0
  37. package/dist/db-CMpBK3sv.js +1252 -0
  38. package/dist/db-CMpBK3sv.js.map +1 -0
  39. package/dist/derive-RPIsAVO4.js +474 -0
  40. package/dist/derive-RPIsAVO4.js.map +1 -0
  41. package/dist/dev-C9P5Yp9k.js +1593 -0
  42. package/dist/dev-C9P5Yp9k.js.map +1 -0
  43. package/dist/dev-db/atlas-dev-database.d.ts +32 -0
  44. package/dist/dev-db/daemon-entry.d.ts +13 -0
  45. package/dist/dev-preflight-N1evJL3S.js +2 -0
  46. package/dist/doctor-DYs-NqFI.js +771 -0
  47. package/dist/doctor-DYs-NqFI.js.map +1 -0
  48. package/dist/eject-BT6t37D0.js +335 -0
  49. package/dist/eject-BT6t37D0.js.map +1 -0
  50. package/dist/fold-static-C1EtHlfV.js +201 -0
  51. package/dist/fold-static-C1EtHlfV.js.map +1 -0
  52. package/dist/function-portability-BIvlvR9q.js +265 -0
  53. package/dist/function-portability-BIvlvR9q.js.map +1 -0
  54. package/dist/generate_sdk-UV635zSe.js +477 -0
  55. package/dist/generate_sdk-UV635zSe.js.map +1 -0
  56. package/dist/index.es.js +141 -24674
  57. package/dist/index.es.js.map +1 -1
  58. package/dist/init-fXeBcxuN.js +1042 -0
  59. package/dist/init-fXeBcxuN.js.map +1 -0
  60. package/dist/manifest-DzlC60WX.js +871 -0
  61. package/dist/manifest-DzlC60WX.js.map +1 -0
  62. package/dist/normalize-imports-CmVdE9iZ.js +100 -0
  63. package/dist/normalize-imports-CmVdE9iZ.js.map +1 -0
  64. package/dist/{package-manager-CXQevsE1.js → package-manager-DSEOwTqM.js} +2 -2
  65. package/dist/{package-manager-CXQevsE1.js.map → package-manager-DSEOwTqM.js.map} +1 -1
  66. package/dist/path-flags-DUBuI2i1.js +77 -0
  67. package/dist/path-flags-DUBuI2i1.js.map +1 -0
  68. package/dist/precompress.d.ts +17 -0
  69. package/dist/prepare-Btn9Ewbn.js +1052 -0
  70. package/dist/prepare-Btn9Ewbn.js.map +1 -0
  71. package/dist/{project-DmaGLmH2.js → project-Wp2zoyeN.js} +33 -4
  72. package/dist/project-Wp2zoyeN.js.map +1 -0
  73. package/dist/resolve-CJLkfFV0.js +2 -0
  74. package/dist/resources-DT1COn_6.js +161 -0
  75. package/dist/resources-DT1COn_6.js.map +1 -0
  76. package/dist/schema-BDBWvCHu.js +125 -0
  77. package/dist/schema-BDBWvCHu.js.map +1 -0
  78. package/dist/skills-DTFibT1m.js +642 -0
  79. package/dist/skills-DTFibT1m.js.map +1 -0
  80. package/dist/start-rU2e3Kp5.js +228 -0
  81. package/dist/start-rU2e3Kp5.js.map +1 -0
  82. package/dist/{state-C59Elrnt.js → state-Do7ZJYkF.js} +49 -4
  83. package/dist/state-Do7ZJYkF.js.map +1 -0
  84. package/dist/status-BR9oyi-y.js +351 -0
  85. package/dist/status-BR9oyi-y.js.map +1 -0
  86. package/dist/storage-DyvrSTH3.js +303 -0
  87. package/dist/storage-DyvrSTH3.js.map +1 -0
  88. package/dist/telemetry-CAWNCugs.js +338 -0
  89. package/dist/telemetry-CAWNCugs.js.map +1 -0
  90. package/dist/telemetry-DbLqGIBW.js +129 -0
  91. package/dist/telemetry-DbLqGIBW.js.map +1 -0
  92. package/dist/unknown-command-DNwDcX3w.js +98 -0
  93. package/dist/unknown-command-DNwDcX3w.js.map +1 -0
  94. package/dist/upgrade-D-5-Od96.js +1265 -0
  95. package/dist/upgrade-D-5-Od96.js.map +1 -0
  96. package/dist/utils/project.d.ts +16 -0
  97. package/dist/{version-DsgBmP22.js → version-Buyyo1ut.js} +2 -2
  98. package/dist/{version-DsgBmP22.js.map → version-Buyyo1ut.js.map} +1 -1
  99. package/package.json +7 -7
  100. package/templates/eject/backend/src/env.ts +1 -1
  101. package/templates/eject/backend/src/index.ts +10 -2
  102. package/templates/template/README.md +1 -1
  103. package/templates/template/ai-instructions.md +2 -1
  104. package/templates/template/frontend/vite.config.ts +11 -3
  105. package/templates/template/scripts/example.ts +2 -1
  106. package/dist/branch-pointer-kk9F5FBO.js +0 -111
  107. package/dist/branch-pointer-kk9F5FBO.js.map +0 -1
  108. package/dist/constraints-DKGbNfUW.js +0 -48
  109. package/dist/constraints-DKGbNfUW.js.map +0 -1
  110. package/dist/daemon-C8p32i-n.js +0 -357
  111. package/dist/daemon-C8p32i-n.js.map +0 -1
  112. package/dist/daemon-entry-LTFKtpHy.js.map +0 -1
  113. package/dist/dev-preflight-CNLl4rdo.js +0 -367
  114. package/dist/dev-preflight-CNLl4rdo.js.map +0 -1
  115. package/dist/project-DmaGLmH2.js.map +0 -1
  116. package/dist/resolve-BAeRUBhg.js +0 -74
  117. package/dist/resolve-BAeRUBhg.js.map +0 -1
  118. package/dist/state-C59Elrnt.js.map +0 -1
@@ -0,0 +1,351 @@
1
+ import { d as readEnvFile, i as failAsJson, p as requireProjectRoot } from "./project-Wp2zoyeN.js";
2
+ import { n as parseCommandArgs, r as wantsHelp } from "./args-BnmsEXvu.js";
3
+ import { d as findBackendApp, g as resolveBackendPaths, o as ManifestError, p as loadManifest } from "./manifest-DzlC60WX.js";
4
+ import { c as withImplicitDefaults, n as deriveOptionsFor, o as readResourceGraphFile, r as deriveResourceGraph, s as serializeResourceGraph, t as RESOURCE_GRAPH_FILENAME } from "./derive-RPIsAVO4.js";
5
+ import { i as findRunningDaemon, n as resolveDevDatabase, o as managedUrl, resolveActiveBranch, resolveComposeUrl } from "./prepare-Btn9Ewbn.js";
6
+ import chalk from "chalk";
7
+ import fs from "fs";
8
+ import path from "path";
9
+ import { DEFAULT_RESOURCE_KEY, envBasesForResource, resourceEnvSuffix, resourceToDataSource } from "@rebasepro/types";
10
+ //#region src/resources/status.ts
11
+ /**
12
+ * What a project declared, what the environment binds it to, and whether the
13
+ * two meet.
14
+ *
15
+ * ## Why this exists
16
+ *
17
+ * Every piece of this was already knowable and none of it was visible in one
18
+ * place. `rebase resources` lists what you declared. The environment holds the
19
+ * variables. The resolvers decide whether a source is usable. So the question a
20
+ * developer actually has — *"why does uploading to `media` answer 501"* — could
21
+ * only be answered by knowing the suffix rule, deriving the variable name by
22
+ * hand, and checking it against a `.env` they had to find first.
23
+ *
24
+ * The answer is mechanical, so it should be printed rather than taught.
25
+ *
26
+ * ## Why it calls the resolvers instead of re-deciding
27
+ *
28
+ * The verdict comes from `resolveDataSources` and `resolveStorageBackend` — the
29
+ * same functions that run at boot. A status view that decided for itself what
30
+ * "configured" means would be a second implementation of the rule, and the
31
+ * first time it disagreed it would be reassuring a developer about a deployment
32
+ * that is about to refuse to start.
33
+ *
34
+ * The variable *names* come from each kind's `envBases`, and the verdicts from
35
+ * the resolver `@rebasepro/server` registers per kind — one object per kind,
36
+ * held to `envBases` by `resource-env-bases.test.ts`. A kind this view has
37
+ * never heard of is judged by whatever resolver its package registered.
38
+ */
39
+ /** Set, and not set to the empty string — which the resolvers treat as absent. */
40
+ function isSet(env, name) {
41
+ const value = env[name];
42
+ return typeof value === "string" && value !== "";
43
+ }
44
+ /**
45
+ * The bindings one declaration reads, in the order a reader should scan them.
46
+ *
47
+ * A `direct`-transport resource gets none: the browser reaches it with a
48
+ * provider SDK and the backend binds nothing for it, so listing variables would
49
+ * invite someone to set variables that are never read.
50
+ */
51
+ function bindingsFor(declaration, env, accountScopedBases) {
52
+ if (declaration.transport !== "server") return [];
53
+ const suffix = resourceEnvSuffix(declaration.key);
54
+ const account = typeof declaration.options.account === "string" ? resourceEnvSuffix(declaration.options.account) : void 0;
55
+ return envBasesForResource(declaration).map((base) => {
56
+ const name = `${base}${suffix}`;
57
+ const binding = {
58
+ name,
59
+ set: isSet(env, name)
60
+ };
61
+ if (account !== void 0 && !binding.set && accountScopedBases.includes(base)) binding.fallback = {
62
+ name: `${base}${account}`,
63
+ set: isSet(env, `${base}${account}`)
64
+ };
65
+ return binding;
66
+ });
67
+ }
68
+ /**
69
+ * The environment plus the managed database's URL, for the whole-set check.
70
+ *
71
+ * A copy: the bindings a reader sees are computed against the real `.env`, so
72
+ * nothing here can make the view claim a variable is set that is not. This is
73
+ * only what `resolveDataSources` is handed, and it is handed it so the check
74
+ * judges the environment the runtime would actually get rather than throwing
75
+ * "Data source "(default)" has no connection string" about a database that is
76
+ * running and reachable.
77
+ */
78
+ function withManagedUrls(env, graph, url) {
79
+ const filled = { ...env };
80
+ const keys = [DEFAULT_RESOURCE_KEY, ...graph.resources.filter((r) => r.kind === "database").map((r) => r.key)];
81
+ for (const key of keys) {
82
+ const name = `DATABASE_URL${resourceEnvSuffix(key)}`;
83
+ if (!isSet(filled, name)) filled[name] = url;
84
+ }
85
+ return filled;
86
+ }
87
+ /**
88
+ * Resolve every declared resource against an environment.
89
+ *
90
+ * Each kind's verdict comes from the resolver `@rebasepro/server` registered
91
+ * for it — the same one boot calls — so this view cannot say "ready" about a
92
+ * binding boot would refuse. A kind with no resolver is reported as such
93
+ * rather than skipped: a resource nobody shows is one nobody remembers to
94
+ * configure, and a kind this runtime cannot bind is exactly what boot will
95
+ * refuse by name.
96
+ *
97
+ * `resolveDataSources` is then run once over the whole set, so anything it
98
+ * would refuse across sources (a driver package it cannot resolve for a custom
99
+ * engine) is reported rather than missed by a view that only looked per row.
100
+ */
101
+ function computeStatus(graph, env, resolvers) {
102
+ const resources = [];
103
+ const production = resolvers.production ?? false;
104
+ const managed = resolvers.managedDatabase;
105
+ /** Database rows the managed database answered for, so `blocked` can skip them. */
106
+ let managedCovered = 0;
107
+ for (const { declaration, implicit } of withImplicitDefaults(graph)) {
108
+ const resolver = resolvers.resolverFor(declaration.kind);
109
+ const bindings = bindingsFor(declaration, env, resolver?.accountScoped ?? []);
110
+ const base = {
111
+ kind: declaration.kind,
112
+ key: declaration.key,
113
+ engine: declaration.engine,
114
+ transport: declaration.transport,
115
+ ...typeof declaration.options.account === "string" ? { account: declaration.options.account } : {},
116
+ implicit,
117
+ bindings,
118
+ ...declaration.usedBy && declaration.usedBy.length > 0 ? { usedBy: declaration.usedBy } : {}
119
+ };
120
+ if (!resolver) {
121
+ resources.push({
122
+ ...base,
123
+ state: "broken",
124
+ detail: `this runtime has no resolver for a "${declaration.kind}" — boot refuses it by name. Upgrade @rebasepro/server, or remove the declaration.`
125
+ });
126
+ continue;
127
+ }
128
+ if (managed && declaration.kind === "database" && bindings.every((b) => !b.set && !b.fallback?.set)) {
129
+ managedCovered += 1;
130
+ resources.push({
131
+ ...base,
132
+ state: "ready",
133
+ detail: managed.url ? "the managed development database (PGlite), this project only" : "the managed development database (PGlite), this project only — not running; start it with `rebase dev`"
134
+ });
135
+ continue;
136
+ }
137
+ const verdict = resolver.resolve(declaration, env, {
138
+ production,
139
+ defaultBasePath: "uploads"
140
+ });
141
+ switch (verdict.state) {
142
+ case "ready":
143
+ resources.push({
144
+ ...base,
145
+ state: "ready",
146
+ ...verdict.standsIn ? { standsIn: verdict.standsIn } : {},
147
+ detail: verdict.detail ?? ""
148
+ });
149
+ break;
150
+ case "code":
151
+ resources.push({
152
+ ...base,
153
+ state: "ready",
154
+ detail: verdict.detail ?? "needs no environment configuration"
155
+ });
156
+ break;
157
+ case "unbound":
158
+ resources.push({
159
+ ...base,
160
+ state: "unconfigured",
161
+ detail: verdict.detail ?? "declared, not configured"
162
+ });
163
+ break;
164
+ case "blocked":
165
+ resources.push({
166
+ ...base,
167
+ state: implicit || bindings.every((b) => !b.set && !b.fallback?.set) ? "unconfigured" : "broken",
168
+ detail: verdict.detail ?? "boot refuses this binding"
169
+ });
170
+ break;
171
+ }
172
+ }
173
+ let blocked;
174
+ const managedSetUnknown = managedCovered > 0 && !managed?.url;
175
+ if (resources.every((r) => r.state === "ready") && !managedSetUnknown) try {
176
+ resolvers.resolveDataSources(managed?.url ? withManagedUrls(env, graph, managed.url) : env, graph.resources.filter((r) => r.kind === "database").map(resourceToDataSource));
177
+ } catch (err) {
178
+ blocked = err instanceof Error ? err.message : String(err);
179
+ }
180
+ return {
181
+ resources,
182
+ ...blocked !== void 0 ? { blocked } : {}
183
+ };
184
+ }
185
+ //#endregion
186
+ //#region src/commands/status.ts
187
+ /**
188
+ * `rebase status` — what this project is, and whether it can reach its things.
189
+ *
190
+ * The model a developer has to hold is small and was nowhere visible:
191
+ *
192
+ * rebase.json where the code is, and who runs the server
193
+ * config/resources.ts what the project needs
194
+ * the environment how to reach each of them
195
+ *
196
+ * Everything else — `rebase.resources.json`, the bundle manifest — is a
197
+ * generated copy of the middle one, for readers that cannot run the code. A
198
+ * developer never writes them and should not have to think about them.
199
+ *
200
+ * This command prints the three together, resolved. It exists because the
201
+ * question people actually arrive with — "why does uploading to `media` answer
202
+ * 501" — was answerable only by knowing the `<BASE>__<KEY>` suffix rule,
203
+ * deriving the variable name by hand, and checking it against a `.env` they had
204
+ * to find first. Every step of that is mechanical, so it is printed instead.
205
+ */
206
+ function usage() {
207
+ console.log(`
208
+ ${chalk.bold("rebase status")} — what this project declares, and whether it is configured
209
+
210
+ ${chalk.bold("Usage")}
211
+ rebase status Show every resource and the variables it reads
212
+ rebase status --json The same, as JSON
213
+
214
+ ${chalk.bold("Reads")}
215
+ rebase.json which apps exist, and who owns the server process
216
+ <config>/resources.ts the databases, buckets, topics and queues declared
217
+ backend/crons, backend/functions the crons and functions, by filename
218
+ .env whether each one's variables are set
219
+ `);
220
+ }
221
+ var MARK = {
222
+ ready: chalk.green("✓"),
223
+ unconfigured: chalk.yellow("○"),
224
+ broken: chalk.red("✗")
225
+ };
226
+ function printResource(status) {
227
+ const name = status.key === "(default)" ? chalk.bold("(default)") : chalk.bold(status.key);
228
+ const tags = [chalk.gray(status.engine)];
229
+ if (status.transport !== "server") tags.push(chalk.yellow(status.transport));
230
+ if (status.account) tags.push(chalk.gray(`account:${status.account}`));
231
+ if (status.implicit) tags.push(chalk.gray("implicit"));
232
+ if (status.standsIn) tags.push(chalk.yellow(`local stand-in for ${status.standsIn}`));
233
+ console.log(` ${MARK[status.state]} ${name} ${tags.join(" · ")}`);
234
+ if (status.usedBy && status.usedBy.length > 0) console.log(` ${chalk.gray("used by")} ${chalk.gray(status.usedBy.join(", "))}`);
235
+ let hiddenOptional = 0;
236
+ for (const binding of status.bindings) if (binding.set) console.log(` ${chalk.green("✓")} ${chalk.gray(binding.name)}`);
237
+ else if (binding.fallback?.set) console.log(` ${chalk.green("✓")} ${chalk.gray(binding.fallback.name)} ${chalk.gray(`(shared, for ${binding.name})`)}`);
238
+ else if (status.state === "ready") hiddenOptional += 1;
239
+ else {
240
+ const also = binding.fallback ? chalk.gray(` or ${binding.fallback.name}`) : "";
241
+ console.log(` ${chalk.gray("·")} ${chalk.gray(binding.name)}${also} ${chalk.gray("not set")}`);
242
+ }
243
+ if (hiddenOptional > 0) console.log(chalk.gray(` ${hiddenOptional} optional variable${hiddenOptional === 1 ? "" : "s"} not set`));
244
+ if (status.detail) {
245
+ const colour = status.state === "broken" ? chalk.red : status.state === "unconfigured" ? chalk.yellow : chalk.gray;
246
+ console.log(` ${colour(`└ ${status.detail}`)}`);
247
+ }
248
+ }
249
+ async function statusCommand(rawArgs) {
250
+ if (wantsHelp(rawArgs)) return usage();
251
+ const { flags } = parseCommandArgs({
252
+ spec: { "--json": Boolean },
253
+ rawArgs,
254
+ commandWords: 1,
255
+ command: "status",
256
+ maxPositionals: 0
257
+ });
258
+ const projectRoot = requireProjectRoot();
259
+ const asJson = Boolean(flags["--json"]);
260
+ let backend;
261
+ try {
262
+ const found = findBackendApp(loadManifest(projectRoot).manifest);
263
+ if (!found) {
264
+ if (asJson) failAsJson("This project declares no backend app, so it declares no resources.", "no_backend_app", "Add a backend app to rebase.json, or run `rebase apps init`.");
265
+ console.error(chalk.red("This project declares no backend app, so it declares no resources."));
266
+ process.exitCode = 1;
267
+ return;
268
+ }
269
+ backend = found;
270
+ } catch (err) {
271
+ if (err instanceof ManifestError) {
272
+ if (asJson) failAsJson(err.message, "manifest_invalid", "Fix rebase.json, then run this again.", err.issues.map((issue) => ({
273
+ path: issue.path,
274
+ message: issue.message
275
+ })));
276
+ console.error(chalk.red(err.message));
277
+ for (const issue of err.issues) console.error(` ${chalk.gray(issue.path)} ${issue.message}`);
278
+ process.exitCode = 1;
279
+ return;
280
+ }
281
+ throw err;
282
+ }
283
+ const { name: backendName, app: backendApp } = backend;
284
+ const paths = resolveBackendPaths(backendApp, projectRoot);
285
+ const { graph, issues } = await deriveResourceGraph(deriveOptionsFor(projectRoot, backendApp));
286
+ if (issues.length > 0) {
287
+ if (asJson) failAsJson(`${issues.length} problem(s) in the declared resources.`, "resource_declaration_invalid", "Fix the declarations the issues name, then run this again.", issues.map((issue) => ({
288
+ path: issue.path,
289
+ message: issue.message
290
+ })));
291
+ console.error(chalk.red(`\n✗ ${issues.length} problem(s) in the declared resources:\n`));
292
+ for (const issue of issues) console.error(` ${chalk.bold(issue.path)} ${issue.message}`);
293
+ console.error("");
294
+ process.exitCode = 1;
295
+ return;
296
+ }
297
+ const env = readEnvFile(projectRoot);
298
+ const production = env.NODE_ENV === "production";
299
+ const database = resolveDevDatabase({
300
+ env: {},
301
+ envFile: env,
302
+ branch: resolveActiveBranch(projectRoot, env),
303
+ composeUrl: resolveComposeUrl(projectRoot, env)
304
+ });
305
+ const daemon = database.kind === "managed" ? await findRunningDaemon(projectRoot) : null;
306
+ const managedDatabase = !production && database.kind === "managed" ? { url: daemon ? managedUrl(daemon.port) : null } : void 0;
307
+ const server = await import("@rebasepro/server");
308
+ const { resources, blocked } = computeStatus(graph, env, {
309
+ resolverFor: server.resourceResolver,
310
+ resolveDataSources: server.resolveDataSources,
311
+ production,
312
+ ...managedDatabase ? { managedDatabase } : {}
313
+ });
314
+ if (asJson) {
315
+ console.log(JSON.stringify({
316
+ backend: backendName,
317
+ runtime: backendApp.runtime,
318
+ resources,
319
+ blocked
320
+ }, null, 2));
321
+ return;
322
+ }
323
+ const ownership = backendApp.runtime === "managed" ? "Rebase's runtime boots your bundle" : "this project builds its own image and entrypoint";
324
+ console.log("");
325
+ console.log(` ${chalk.bold(backendName)} ${chalk.gray("·")} ${chalk.cyan(backendApp.runtime)} ${chalk.gray(ownership)}`);
326
+ const resourcesFile = path.join(paths.config, "resources.ts");
327
+ const declaresAnything = fs.existsSync(path.join(projectRoot, resourcesFile));
328
+ console.log(` ${chalk.gray("declared in")} ${chalk.gray(resourcesFile)}${declaresAnything ? "" : chalk.gray(" (none — the defaults below are implicit)")}`);
329
+ console.log(` ${chalk.gray("configured by")} ${chalk.gray(".env")}`);
330
+ const kinds = [...new Set(resources.map((r) => r.kind))];
331
+ for (const kind of kinds) {
332
+ console.log("");
333
+ console.log(` ${chalk.bold.underline(`${kind}s`)}`);
334
+ for (const status of resources.filter((r) => r.kind === kind)) printResource(status);
335
+ }
336
+ if (blocked) {
337
+ console.log("");
338
+ console.log(` ${chalk.red("✗")} ${chalk.red(blocked)}`);
339
+ }
340
+ const committed = readResourceGraphFile(projectRoot);
341
+ if (committed !== serializeResourceGraph(graph) && !(committed === null && graph.resources.length === 0)) {
342
+ console.log("");
343
+ console.log(` ${chalk.yellow("○")} ${RESOURCE_GRAPH_FILENAME} is ${committed === null ? "missing" : "stale"} — ${chalk.blue("rebase resources --write")}`);
344
+ console.log(chalk.gray(" a host reads it to decide what to provision before it runs anything"));
345
+ }
346
+ console.log("");
347
+ }
348
+ //#endregion
349
+ export { statusCommand };
350
+
351
+ //# sourceMappingURL=status-BR9oyi-y.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"status-BR9oyi-y.js","names":[],"sources":["../src/resources/status.ts","../src/commands/status.ts"],"sourcesContent":["/**\n * What a project declared, what the environment binds it to, and whether the\n * two meet.\n *\n * ## Why this exists\n *\n * Every piece of this was already knowable and none of it was visible in one\n * place. `rebase resources` lists what you declared. The environment holds the\n * variables. The resolvers decide whether a source is usable. So the question a\n * developer actually has — *\"why does uploading to `media` answer 501\"* — could\n * only be answered by knowing the suffix rule, deriving the variable name by\n * hand, and checking it against a `.env` they had to find first.\n *\n * The answer is mechanical, so it should be printed rather than taught.\n *\n * ## Why it calls the resolvers instead of re-deciding\n *\n * The verdict comes from `resolveDataSources` and `resolveStorageBackend` — the\n * same functions that run at boot. A status view that decided for itself what\n * \"configured\" means would be a second implementation of the rule, and the\n * first time it disagreed it would be reassuring a developer about a deployment\n * that is about to refuse to start.\n *\n * The variable *names* come from each kind's `envBases`, and the verdicts from\n * the resolver `@rebasepro/server` registers per kind — one object per kind,\n * held to `envBases` by `resource-env-bases.test.ts`. A kind this view has\n * never heard of is judged by whatever resolver its package registered.\n */\nimport { withImplicitDefaults } from \"./derive\";\nimport {\n DEFAULT_RESOURCE_KEY,\n envBasesForResource,\n resourceEnvSuffix,\n resourceToDataSource,\n type ResourceDeclaration,\n type ResourceGraph\n} from \"@rebasepro/types\";\n\n/** One environment variable a resource reads, and whether it is there. */\nexport interface ResourceBinding {\n /** The variable this resource reads, suffix included. */\n name: string;\n set: boolean;\n /**\n * The account-scoped name consulted when `name` is unset.\n *\n * Only provider-level bindings have one, and only when the resource named\n * an `account`. Shown because a developer looking at an unset\n * `S3_ACCESS_KEY_ID__MEDIA` needs to know it is not the whole story.\n */\n fallback?: { name: string; set: boolean };\n}\n\n/** Whether a declared resource can actually be reached. */\nexport type ResourceState =\n /** Bound and usable. */\n | \"ready\"\n /** Nothing set for it. Legal for a bucket, fatal for a database. */\n | \"unconfigured\"\n /** Set, and set wrongly. The deployment refuses to start. */\n | \"broken\";\n\nexport interface ResourceStatus {\n kind: string;\n key: string;\n engine: string;\n transport: string;\n /** Present when this resource shares another's credentials. */\n account?: string;\n /** True for a resource nobody declared, which exists anyway. */\n implicit: boolean;\n bindings: ResourceBinding[];\n state: ResourceState;\n /** What this means, in the terms the developer will meet it in. */\n detail: string;\n /** Set when a local directory stands in for this engine, in development. */\n standsIn?: string;\n /** What in the project reaches this resource, from the graph. */\n usedBy?: readonly string[];\n}\n\nexport type EnvBag = Record<string, string | undefined>;\n\n/** Set, and not set to the empty string — which the resolvers treat as absent. */\nfunction isSet(env: EnvBag, name: string): boolean {\n const value = env[name];\n return typeof value === \"string\" && value !== \"\";\n}\n\n/**\n * The bindings one declaration reads, in the order a reader should scan them.\n *\n * A `direct`-transport resource gets none: the browser reaches it with a\n * provider SDK and the backend binds nothing for it, so listing variables would\n * invite someone to set variables that are never read.\n */\nexport function bindingsFor(\n declaration: ResourceDeclaration,\n env: EnvBag,\n accountScopedBases: readonly string[]\n): ResourceBinding[] {\n if (declaration.transport !== \"server\") return [];\n\n const suffix = resourceEnvSuffix(declaration.key);\n const account = typeof declaration.options.account === \"string\"\n ? resourceEnvSuffix(declaration.options.account)\n : undefined;\n\n return envBasesForResource(declaration).map((base) => {\n const name = `${base}${suffix}`;\n const binding: ResourceBinding = { name, set: isSet(env, name) };\n\n // Only the provider-level bindings fall back, and only for a resource\n // that named an account. The bucket name never does — it is what\n // distinguishes one source from another.\n if (account !== undefined && !binding.set && accountScopedBases.includes(base)) {\n binding.fallback = { name: `${base}${account}`, set: isSet(env, `${base}${account}`) };\n }\n return binding;\n });\n}\n\n/**\n * What the managed development database covers, when a project is on it.\n *\n * `rebase init` leaves `DATABASE_URL` commented out on purpose and the managed\n * PGlite fills the vacuum — the documented first-run state. Judging that state\n * by the environment alone produced `○ (default) postgres · DATABASE_URL not\n * set` and the remedy \"set DATABASE_URL\", at the same moment `rebase db url`\n * was printing a working connection string for the same project and\n * `rebase dev` was serving from it. The status view was reading the one place\n * the answer deliberately is not.\n *\n * Resolved by the caller, because deciding it needs the project's `.env`, its\n * branch pointer and its compose file, and this module stays free of all three.\n */\nexport interface ManagedDatabaseStatus {\n /**\n * The daemon's connection string, or null when it is not running.\n *\n * Null is not an error: the managed database exists per project and starts\n * on demand. It only means there is nothing to run the whole-set check\n * against, and the row says so rather than implying a misconfiguration.\n */\n url: string | null;\n}\n\n/**\n * The resolver-side of status, as `@rebasepro/server` registers it per kind.\n *\n * Passed in rather than imported so this module stays testable without a\n * server, and so a kind registered by a plugin is judged by the resolver the\n * plugin brought with it rather than by a switch here.\n */\nexport interface StatusResolvers {\n /** `resourceResolver(kind)` from `@rebasepro/server`. */\n resolverFor: (kind: string) => {\n accountScoped?: readonly string[];\n resolve: (\n declaration: ResourceDeclaration,\n env: EnvBag,\n context: { production: boolean; defaultBasePath: string }\n ) => { state: \"ready\" | \"unbound\" | \"blocked\" | \"code\"; detail?: string; standsIn?: string };\n } | undefined;\n /** `resolveDataSources` from `@rebasepro/server`, for the whole-set check. */\n resolveDataSources: (env: EnvBag, definitions: unknown) => unknown;\n /** Whether the process being judged is production. Status judges for development by default. */\n production?: boolean;\n /**\n * Set when this project runs on the managed development database.\n *\n * Absent for a project that named its own connection string, and absent in\n * production — where an unset `DATABASE_URL` is the failure it has always\n * been.\n */\n managedDatabase?: ManagedDatabaseStatus;\n}\n\n/**\n * The environment plus the managed database's URL, for the whole-set check.\n *\n * A copy: the bindings a reader sees are computed against the real `.env`, so\n * nothing here can make the view claim a variable is set that is not. This is\n * only what `resolveDataSources` is handed, and it is handed it so the check\n * judges the environment the runtime would actually get rather than throwing\n * \"Data source \"(default)\" has no connection string\" about a database that is\n * running and reachable.\n */\nfunction withManagedUrls(env: EnvBag, graph: ResourceGraph, url: string): EnvBag {\n const filled: EnvBag = { ...env };\n const keys = [\n DEFAULT_RESOURCE_KEY,\n ...graph.resources.filter(r => r.kind === \"database\").map(r => r.key)\n ];\n for (const key of keys) {\n const name = `DATABASE_URL${resourceEnvSuffix(key)}`;\n if (!isSet(filled, name)) filled[name] = url;\n }\n\n return filled;\n}\n\n/**\n * Resolve every declared resource against an environment.\n *\n * Each kind's verdict comes from the resolver `@rebasepro/server` registered\n * for it — the same one boot calls — so this view cannot say \"ready\" about a\n * binding boot would refuse. A kind with no resolver is reported as such\n * rather than skipped: a resource nobody shows is one nobody remembers to\n * configure, and a kind this runtime cannot bind is exactly what boot will\n * refuse by name.\n *\n * `resolveDataSources` is then run once over the whole set, so anything it\n * would refuse across sources (a driver package it cannot resolve for a custom\n * engine) is reported rather than missed by a view that only looked per row.\n */\nexport function computeStatus(\n graph: ResourceGraph,\n env: EnvBag,\n resolvers: StatusResolvers\n): { resources: ResourceStatus[]; blocked?: string } {\n const resources: ResourceStatus[] = [];\n const production = resolvers.production ?? false;\n const managed = resolvers.managedDatabase;\n /** Database rows the managed database answered for, so `blocked` can skip them. */\n let managedCovered = 0;\n\n for (const { declaration, implicit } of withImplicitDefaults(graph)) {\n const resolver = resolvers.resolverFor(declaration.kind);\n const bindings = bindingsFor(declaration, env, resolver?.accountScoped ?? []);\n const base: Omit<ResourceStatus, \"state\" | \"detail\"> = {\n kind: declaration.kind,\n key: declaration.key,\n engine: declaration.engine,\n transport: declaration.transport,\n ...(typeof declaration.options.account === \"string\"\n ? { account: declaration.options.account }\n : {}),\n implicit,\n bindings,\n ...(declaration.usedBy && declaration.usedBy.length > 0 ? { usedBy: declaration.usedBy } : {})\n };\n\n if (!resolver) {\n resources.push({\n ...base,\n state: \"broken\",\n detail: `this runtime has no resolver for a \"${declaration.kind}\" — boot refuses it by name. ` +\n \"Upgrade @rebasepro/server, or remove the declaration.\"\n });\n continue;\n }\n\n // Answered before the resolver, because the resolver reads the\n // environment and the whole point of the managed database is that the\n // environment is deliberately empty. Only for a database nobody bound:\n // a developer who set `DATABASE_URL__ANALYTICS` to a warehouse of their\n // own has said which database that is, and the managed one fills a\n // vacuum, never a choice.\n if (managed && declaration.kind === \"database\" && bindings.every(b => !b.set && !b.fallback?.set)) {\n managedCovered += 1;\n resources.push({\n ...base,\n state: \"ready\",\n detail: managed.url\n ? \"the managed development database (PGlite), this project only\"\n : \"the managed development database (PGlite), this project only — \"\n + \"not running; start it with `rebase dev`\"\n });\n continue;\n }\n\n const verdict = resolver.resolve(declaration, env, { production, defaultBasePath: \"uploads\" });\n switch (verdict.state) {\n case \"ready\":\n resources.push({\n ...base,\n state: \"ready\",\n ...(verdict.standsIn ? { standsIn: verdict.standsIn } : {}),\n detail: verdict.detail ?? \"\"\n });\n break;\n case \"code\":\n resources.push({ ...base, state: \"ready\", detail: verdict.detail ?? \"needs no environment configuration\" });\n break;\n case \"unbound\":\n resources.push({ ...base, state: \"unconfigured\", detail: verdict.detail ?? \"declared, not configured\" });\n break;\n case \"blocked\":\n resources.push({\n ...base,\n // A database the environment names nothing for is the\n // ordinary first-run state, and reads as unconfigured; a\n // binding that is set and set wrongly is broken.\n state: implicit || bindings.every(b => !b.set && !b.fallback?.set) ? \"unconfigured\" : \"broken\",\n detail: verdict.detail ?? \"boot refuses this binding\"\n });\n break;\n }\n }\n\n // Only asked when every row already looks fine. Its failures overlap with\n // the per-resource ones, and a banner repeating a line printed two rows\n // above teaches nothing — what it is here for is the failure no row models,\n // such as a custom engine whose driver package cannot be resolved.\n let blocked: string | undefined;\n // Not run when the managed database answered for a row and is not running:\n // there is no connection string to check the set against, and inventing one\n // to keep the check formally \"run\" is how a green tick gets printed for\n // work nobody did. The row says the daemon is down; that is the finding.\n const managedSetUnknown = managedCovered > 0 && !managed?.url;\n if (resources.every(r => r.state === \"ready\") && !managedSetUnknown) {\n try {\n resolvers.resolveDataSources(\n // The managed URL for the rows it answered for, so the whole-set\n // check judges the same environment the runtime would get.\n managed?.url ? withManagedUrls(env, graph, managed.url) : env,\n graph.resources.filter(r => r.kind === \"database\").map(resourceToDataSource)\n );\n } catch (err) {\n blocked = err instanceof Error ? err.message : String(err);\n }\n }\n\n return { resources, ...(blocked !== undefined ? { blocked } : {}) };\n}\n","/**\n * `rebase status` — what this project is, and whether it can reach its things.\n *\n * The model a developer has to hold is small and was nowhere visible:\n *\n * rebase.json where the code is, and who runs the server\n * config/resources.ts what the project needs\n * the environment how to reach each of them\n *\n * Everything else — `rebase.resources.json`, the bundle manifest — is a\n * generated copy of the middle one, for readers that cannot run the code. A\n * developer never writes them and should not have to think about them.\n *\n * This command prints the three together, resolved. It exists because the\n * question people actually arrive with — \"why does uploading to `media` answer\n * 501\" — was answerable only by knowing the `<BASE>__<KEY>` suffix rule,\n * deriving the variable name by hand, and checking it against a `.env` they had\n * to find first. Every step of that is mechanical, so it is printed instead.\n */\nimport chalk from \"chalk\";\nimport fs from \"fs\";\nimport path from \"path\";\nimport { parseCommandArgs, wantsHelp } from \"../utils/args\";\nimport { failAsJson, readEnvFile, requireProjectRoot } from \"../utils/project\";\nimport { findBackendApp, loadManifest, ManifestError, resolveBackendPaths } from \"../manifest\";\nimport { deriveOptionsFor, deriveResourceGraph, RESOURCE_GRAPH_FILENAME, readResourceGraphFile, serializeResourceGraph } from \"../resources/derive\";\nimport { computeStatus, type ResourceStatus } from \"../resources/status\";\nimport { findRunningDaemon, managedUrl } from \"../dev-db/daemon\";\nimport { resolveActiveBranch, resolveComposeUrl } from \"../dev-db/prepare\";\nimport { resolveDevDatabase } from \"../dev-db/resolve\";\nimport type { RebaseBackendAppConfig } from \"@rebasepro/types\";\n\nfunction usage(): void {\n console.log(`\n${chalk.bold(\"rebase status\")} — what this project declares, and whether it is configured\n\n${chalk.bold(\"Usage\")}\n rebase status Show every resource and the variables it reads\n rebase status --json The same, as JSON\n\n${chalk.bold(\"Reads\")}\n rebase.json which apps exist, and who owns the server process\n <config>/resources.ts the databases, buckets, topics and queues declared\n backend/crons, backend/functions the crons and functions, by filename\n .env whether each one's variables are set\n`);\n}\n\nconst MARK = {\n ready: chalk.green(\"✓\"),\n unconfigured: chalk.yellow(\"○\"),\n broken: chalk.red(\"✗\")\n} as const;\n\nfunction printResource(status: ResourceStatus): void {\n const name = status.key === \"(default)\" ? chalk.bold(\"(default)\") : chalk.bold(status.key);\n const tags = [chalk.gray(status.engine)];\n if (status.transport !== \"server\") tags.push(chalk.yellow(status.transport));\n if (status.account) tags.push(chalk.gray(`account:${status.account}`));\n if (status.implicit) tags.push(chalk.gray(\"implicit\"));\n // A stand-in is not a binding. It is shown in yellow beside a green tick:\n // uploads work here, and the same declaration answers 501 in production\n // until the variable is set.\n if (status.standsIn) tags.push(chalk.yellow(`local stand-in for ${status.standsIn}`));\n\n console.log(` ${MARK[status.state]} ${name} ${tags.join(\" · \")}`);\n if (status.usedBy && status.usedBy.length > 0) {\n console.log(` ${chalk.gray(\"used by\")} ${chalk.gray(status.usedBy.join(\", \"))}`);\n }\n\n let hiddenOptional = 0;\n for (const binding of status.bindings) {\n if (binding.set) {\n console.log(` ${chalk.green(\"✓\")} ${chalk.gray(binding.name)}`);\n } else if (binding.fallback?.set) {\n // Not a gap: the account form is the variable this bucket is\n // actually reading, and showing only the unset per-key name would\n // send someone to set the one they were deliberately avoiding.\n console.log(` ${chalk.green(\"✓\")} ${chalk.gray(binding.fallback.name)} ${chalk.gray(`(shared, for ${binding.name})`)}`);\n } else if (status.state === \"ready\") {\n // It resolved without this one, so this one is optional — which is\n // the resolver's answer, not a second table of required names kept\n // here. Listing seven unset optionals under a working database is\n // how a status view teaches people to skim past the line that\n // matters.\n hiddenOptional += 1;\n } else {\n const also = binding.fallback ? chalk.gray(` or ${binding.fallback.name}`) : \"\";\n console.log(` ${chalk.gray(\"·\")} ${chalk.gray(binding.name)}${also} ${chalk.gray(\"not set\")}`);\n }\n }\n if (hiddenOptional > 0) {\n console.log(chalk.gray(` ${hiddenOptional} optional variable${hiddenOptional === 1 ? \"\" : \"s\"} not set`));\n }\n\n if (status.detail) {\n const colour = status.state === \"broken\" ? chalk.red : status.state === \"unconfigured\" ? chalk.yellow : chalk.gray;\n console.log(` ${colour(`└ ${status.detail}`)}`);\n }\n}\n\nexport async function statusCommand(rawArgs: string[]): Promise<void> {\n if (wantsHelp(rawArgs)) return usage();\n\n const { flags } = parseCommandArgs({\n spec: { \"--json\": Boolean },\n rawArgs,\n commandWords: 1,\n command: \"status\",\n maxPositionals: 0\n });\n\n const projectRoot = requireProjectRoot();\n\n // Every exit below has a `--json` arm, because `--json` means stdout holds\n // one JSON object on *every* exit of the command. It used to mean that for\n // the one failure `requireProjectRoot` owns and nothing else, so a caller\n // parsing stdout got an envelope outside a project and an empty stream\n // inside a broken one — the case it actually has to handle.\n const asJson = Boolean(flags[\"--json\"]);\n\n let backend: { name: string; app: RebaseBackendAppConfig };\n try {\n const loaded = loadManifest(projectRoot);\n const found = findBackendApp(loaded.manifest);\n if (!found) {\n if (asJson) {\n failAsJson(\n \"This project declares no backend app, so it declares no resources.\",\n \"no_backend_app\",\n \"Add a backend app to rebase.json, or run `rebase apps init`.\"\n );\n }\n console.error(chalk.red(\"This project declares no backend app, so it declares no resources.\"));\n process.exitCode = 1;\n return;\n }\n backend = found;\n } catch (err) {\n if (err instanceof ManifestError) {\n if (asJson) {\n failAsJson(err.message, \"manifest_invalid\", \"Fix rebase.json, then run this again.\",\n err.issues.map(issue => ({ path: issue.path, message: issue.message })));\n }\n console.error(chalk.red(err.message));\n for (const issue of err.issues) console.error(` ${chalk.gray(issue.path)} ${issue.message}`);\n process.exitCode = 1;\n return;\n }\n throw err;\n }\n\n const { name: backendName, app: backendApp } = backend;\n const paths = resolveBackendPaths(backendApp, projectRoot);\n\n const { graph, issues } = await deriveResourceGraph(deriveOptionsFor(projectRoot, backendApp));\n if (issues.length > 0) {\n if (asJson) {\n failAsJson(\n `${issues.length} problem(s) in the declared resources.`,\n \"resource_declaration_invalid\",\n \"Fix the declarations the issues name, then run this again.\",\n issues.map(issue => ({ path: issue.path, message: issue.message }))\n );\n }\n console.error(chalk.red(`\\n✗ ${issues.length} problem(s) in the declared resources:\\n`));\n for (const issue of issues) console.error(` ${chalk.bold(issue.path)} ${issue.message}`);\n console.error(\"\");\n process.exitCode = 1;\n return;\n }\n\n // The project's own `.env`, not this process's environment: `rebase status`\n // is asking what a deployment of THIS repository would do, and the shell a\n // developer happens to be standing in is not that.\n const env = readEnvFile(projectRoot);\n\n // Which database this project is on, decided by the same ordered rule\n // `rebase dev` and `rebase db url` use — and read from the same `.env`, not\n // from this shell, for the reason above.\n //\n // Nothing is started. `status` is a question, and a question that boots a\n // 180 MB Postgres to answer itself is a different command. So a managed\n // database that is not running is reported as exactly that, rather than as\n // a project with no database — which is what this view used to say while\n // `rebase db url` printed a working connection string for it.\n const production = env.NODE_ENV === \"production\";\n const database = resolveDevDatabase({\n env: {},\n envFile: env,\n branch: resolveActiveBranch(projectRoot, env),\n composeUrl: resolveComposeUrl(projectRoot, env)\n });\n const daemon = database.kind === \"managed\" ? await findRunningDaemon(projectRoot) : null;\n const managedDatabase = !production && database.kind === \"managed\"\n ? { url: daemon ? managedUrl(daemon.port) : null }\n : undefined;\n\n const server = await import(\"@rebasepro/server\");\n const { resources, blocked } = computeStatus(graph, env, {\n resolverFor: server.resourceResolver as never,\n resolveDataSources: server.resolveDataSources as never,\n // Judged as production when the .env says so; a stand-in local\n // directory for an unbound bucket is only ever offered to development.\n production,\n ...(managedDatabase ? { managedDatabase } : {})\n });\n\n if (asJson) {\n console.log(JSON.stringify({ backend: backendName, runtime: backendApp.runtime, resources, blocked }, null, 2));\n return;\n }\n\n const ownership = backendApp.runtime === \"managed\"\n ? \"Rebase's runtime boots your bundle\"\n : \"this project builds its own image and entrypoint\";\n\n console.log(\"\");\n console.log(` ${chalk.bold(backendName)} ${chalk.gray(\"·\")} ${chalk.cyan(backendApp.runtime)} ${chalk.gray(ownership)}`);\n // Named whether or not it exists, and said plainly when it does not. A\n // project with no `resources.ts` is legal — it has one default database and\n // one default bucket — but \"declared in <a file you do not have>\" reads as\n // a broken install rather than as the default it is.\n const resourcesFile = path.join(paths.config, \"resources.ts\");\n const declaresAnything = fs.existsSync(path.join(projectRoot, resourcesFile));\n console.log(` ${chalk.gray(\"declared in\")} ${chalk.gray(resourcesFile)}${\n declaresAnything ? \"\" : chalk.gray(\" (none — the defaults below are implicit)\")}`);\n console.log(` ${chalk.gray(\"configured by\")} ${chalk.gray(\".env\")}`);\n\n const kinds = [...new Set(resources.map(r => r.kind))];\n for (const kind of kinds) {\n console.log(\"\");\n console.log(` ${chalk.bold.underline(`${kind}s`)}`);\n for (const status of resources.filter(r => r.kind === kind)) printResource(status);\n }\n\n if (blocked) {\n console.log(\"\");\n console.log(` ${chalk.red(\"✗\")} ${chalk.red(blocked)}`);\n }\n\n // Only worth a line when it is wrong. A generated file that matches its\n // declarations is not news, and a status view that reports its own\n // bookkeeping as an item trains people to skim past the items that matter.\n const committed = readResourceGraphFile(projectRoot);\n const expected = serializeResourceGraph(graph);\n if (committed !== expected && !(committed === null && graph.resources.length === 0)) {\n console.log(\"\");\n console.log(` ${chalk.yellow(\"○\")} ${RESOURCE_GRAPH_FILENAME} is ${committed === null ? \"missing\" : \"stale\"} — ${chalk.blue(\"rebase resources --write\")}`);\n console.log(chalk.gray(\" a host reads it to decide what to provision before it runs anything\"));\n }\n\n console.log(\"\");\n}\n"],"mappings":"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"}