@rebasepro/cli 0.23.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.
- package/README.md +19 -3
- package/bin/rebase.js +4 -1
- package/dist/agent-setup-B31F00DC.js +324 -0
- package/dist/agent-setup-B31F00DC.js.map +1 -0
- package/dist/api-keys-BPBi5nOF.js +524 -0
- package/dist/api-keys-BPBi5nOF.js.map +1 -0
- package/dist/apps-CPXNElX4.js +256 -0
- package/dist/apps-CPXNElX4.js.map +1 -0
- package/dist/args-BnmsEXvu.js +109 -0
- package/dist/args-BnmsEXvu.js.map +1 -0
- package/dist/atlas-dev-database-DTMuEScS.js +62 -0
- package/dist/atlas-dev-database-DTMuEScS.js.map +1 -0
- package/dist/auth-DvY94ZuL.js +395 -0
- package/dist/auth-DvY94ZuL.js.map +1 -0
- package/dist/branch-pointer-DIhKbQLp.js +2 -0
- package/dist/build-CFwUx-Ff.js +336 -0
- package/dist/build-CFwUx-Ff.js.map +1 -0
- package/dist/bundle-BQhP_xiA.js +1617 -0
- package/dist/bundle-BQhP_xiA.js.map +1 -0
- package/dist/bundle.d.ts +6 -2
- package/dist/cli.d.ts +23 -2
- package/dist/cloud-CkTr-yt0.js +9640 -0
- package/dist/cloud-CkTr-yt0.js.map +1 -0
- package/dist/collection-drift-BKj_BoMq.js +106 -0
- package/dist/collection-drift-BKj_BoMq.js.map +1 -0
- package/dist/commands/__test_support__/parser-specs.d.ts +13 -0
- package/dist/commands/agent-setup.d.ts +116 -0
- package/dist/commands/api-keys.d.ts +86 -23
- package/dist/commands/apps.d.ts +17 -0
- package/dist/commands/auth.d.ts +7 -0
- package/dist/commands/build.d.ts +5 -1
- package/dist/commands/cloud/auth.d.ts +4 -3
- package/dist/commands/cloud/bundle-deploy.d.ts +45 -0
- package/dist/commands/cloud/context.d.ts +44 -5
- package/dist/commands/cloud/cron.d.ts +53 -0
- package/dist/commands/cloud/debug.d.ts +134 -0
- package/dist/commands/cloud/index.d.ts +89 -12
- package/dist/commands/cloud/token-capabilities.d.ts +76 -0
- package/dist/commands/cloud/tokens.d.ts +23 -0
- package/dist/commands/db.d.ts +52 -25
- package/dist/commands/dev.d.ts +181 -7
- package/dist/commands/doctor.d.ts +5 -0
- package/dist/commands/generate_sdk.d.ts +29 -2
- package/dist/commands/init.d.ts +57 -14
- package/dist/commands/schema.d.ts +6 -0
- package/dist/commands/skills.d.ts +126 -7
- package/dist/commands/start.d.ts +29 -0
- package/dist/commands/storage.d.ts +46 -0
- package/dist/consent-BWHaYx5p.js +76 -0
- package/dist/consent-BWHaYx5p.js.map +1 -0
- package/dist/context-BqhRydBg.js +1234 -0
- package/dist/context-BqhRydBg.js.map +1 -0
- package/dist/daemon-DBn-shxX.js +2 -0
- package/dist/{daemon-entry-LTFKtpHy.js → daemon-entry-Da-D3rmH.js} +3 -4
- package/dist/daemon-entry-Da-D3rmH.js.map +1 -0
- package/dist/db-CMpBK3sv.js +1252 -0
- package/dist/db-CMpBK3sv.js.map +1 -0
- package/dist/derive-RPIsAVO4.js +474 -0
- package/dist/derive-RPIsAVO4.js.map +1 -0
- package/dist/dev-C9P5Yp9k.js +1593 -0
- package/dist/dev-C9P5Yp9k.js.map +1 -0
- package/dist/dev-db/atlas-dev-database.d.ts +32 -0
- package/dist/dev-db/daemon-entry.d.ts +13 -0
- package/dist/dev-db/daemon.d.ts +10 -0
- package/dist/dev-db/prepare.d.ts +10 -0
- package/dist/dev-preflight-N1evJL3S.js +2 -0
- package/dist/doctor-DYs-NqFI.js +771 -0
- package/dist/doctor-DYs-NqFI.js.map +1 -0
- package/dist/eject-BT6t37D0.js +335 -0
- package/dist/eject-BT6t37D0.js.map +1 -0
- package/dist/fold-static-C1EtHlfV.js +201 -0
- package/dist/fold-static-C1EtHlfV.js.map +1 -0
- package/dist/fold-static.d.ts +26 -3
- package/dist/function-portability-BIvlvR9q.js +265 -0
- package/dist/function-portability-BIvlvR9q.js.map +1 -0
- package/dist/generate_sdk-UV635zSe.js +477 -0
- package/dist/generate_sdk-UV635zSe.js.map +1 -0
- package/dist/index.es.js +189 -22095
- package/dist/index.es.js.map +1 -1
- package/dist/init-fXeBcxuN.js +1042 -0
- package/dist/init-fXeBcxuN.js.map +1 -0
- package/dist/manifest-DzlC60WX.js +871 -0
- package/dist/manifest-DzlC60WX.js.map +1 -0
- package/dist/manifest.d.ts +41 -1
- package/dist/normalize-imports-CmVdE9iZ.js +100 -0
- package/dist/normalize-imports-CmVdE9iZ.js.map +1 -0
- package/dist/package-manager-DSEOwTqM.js +193 -0
- package/dist/package-manager-DSEOwTqM.js.map +1 -0
- package/dist/path-flags-DUBuI2i1.js +77 -0
- package/dist/path-flags-DUBuI2i1.js.map +1 -0
- package/dist/precompress.d.ts +17 -0
- package/dist/prepare-Btn9Ewbn.js +1052 -0
- package/dist/prepare-Btn9Ewbn.js.map +1 -0
- package/dist/{project-3WCbrrfW.js → project-Wp2zoyeN.js} +34 -184
- package/dist/project-Wp2zoyeN.js.map +1 -0
- package/dist/resolve-CJLkfFV0.js +2 -0
- package/dist/resources-DT1COn_6.js +161 -0
- package/dist/resources-DT1COn_6.js.map +1 -0
- package/dist/schema-BDBWvCHu.js +125 -0
- package/dist/schema-BDBWvCHu.js.map +1 -0
- package/dist/skills-DTFibT1m.js +642 -0
- package/dist/skills-DTFibT1m.js.map +1 -0
- package/dist/start-rU2e3Kp5.js +228 -0
- package/dist/start-rU2e3Kp5.js.map +1 -0
- package/dist/{state-C59Elrnt.js → state-Do7ZJYkF.js} +49 -4
- package/dist/state-Do7ZJYkF.js.map +1 -0
- package/dist/status-BR9oyi-y.js +351 -0
- package/dist/status-BR9oyi-y.js.map +1 -0
- package/dist/storage-DyvrSTH3.js +303 -0
- package/dist/storage-DyvrSTH3.js.map +1 -0
- package/dist/telemetry-CAWNCugs.js +338 -0
- package/dist/telemetry-CAWNCugs.js.map +1 -0
- package/dist/telemetry-DbLqGIBW.js +129 -0
- package/dist/telemetry-DbLqGIBW.js.map +1 -0
- package/dist/unknown-command-DNwDcX3w.js +98 -0
- package/dist/unknown-command-DNwDcX3w.js.map +1 -0
- package/dist/upgrade-D-5-Od96.js +1265 -0
- package/dist/upgrade-D-5-Od96.js.map +1 -0
- package/dist/upgrade.d.ts +14 -2
- package/dist/utils/path-flags.d.ts +18 -0
- package/dist/utils/project.d.ts +16 -0
- package/dist/version-Buyyo1ut.js +58 -0
- package/dist/version-Buyyo1ut.js.map +1 -0
- package/package.json +7 -7
- package/templates/eject/backend/src/env.ts +1 -1
- package/templates/eject/backend/src/index.ts +23 -2
- package/templates/overlays/baas/ai-instructions.md +1 -1
- package/templates/overlays/baas/config/storage.ts +5 -3
- package/templates/template/.cursorrules +1 -1
- package/templates/template/.env.example +10 -6
- package/templates/template/.github/copilot-instructions.md +1 -1
- package/templates/template/.mcp.json +2 -2
- package/templates/template/.windsurfrules +1 -1
- package/templates/template/AGENTS.md +1 -1
- package/templates/template/CLAUDE.md +1 -1
- package/templates/template/README.md +11 -12
- package/templates/template/ai-instructions.md +5 -4
- package/templates/template/backend/functions/hello.ts +8 -0
- package/templates/template/backend/package.json +1 -1
- package/templates/template/config/storage.ts +5 -3
- package/templates/template/docker-compose.yml +6 -6
- package/templates/template/frontend/vite.config.ts +11 -3
- package/templates/template/gitignore +6 -1
- package/templates/template/package.json +4 -3
- package/templates/template/scripts/example.ts +6 -4
- package/dist/branch-pointer-dBiClJ0p.js +0 -111
- package/dist/branch-pointer-dBiClJ0p.js.map +0 -1
- package/dist/constraints-DKGbNfUW.js +0 -48
- package/dist/constraints-DKGbNfUW.js.map +0 -1
- package/dist/daemon-DR9Tk0La.js +0 -355
- package/dist/daemon-DR9Tk0La.js.map +0 -1
- package/dist/daemon-entry-LTFKtpHy.js.map +0 -1
- package/dist/dev-preflight-CNLl4rdo.js +0 -367
- package/dist/dev-preflight-CNLl4rdo.js.map +0 -1
- package/dist/project-3WCbrrfW.js.map +0 -1
- package/dist/resolve-BAeRUBhg.js +0 -74
- package/dist/resolve-BAeRUBhg.js.map +0 -1
- package/dist/state-C59Elrnt.js.map +0 -1
|
@@ -0,0 +1,474 @@
|
|
|
1
|
+
import { t as __exportAll } from "./rolldown-runtime-D7D4PA-g.js";
|
|
2
|
+
import { n as cliVersion } from "./version-Buyyo1ut.js";
|
|
3
|
+
import { g as resolveBackendPaths } from "./manifest-DzlC60WX.js";
|
|
4
|
+
import { t as analyseFunctionsDirectory } from "./function-portability-BIvlvR9q.js";
|
|
5
|
+
import fs from "fs";
|
|
6
|
+
import path from "path";
|
|
7
|
+
import { pathToFileURL } from "url";
|
|
8
|
+
import { createRequire } from "module";
|
|
9
|
+
import { DEFAULT_RESOURCE_KEY, buildResourceGraph, declareFunction, declaredQueueConsumers, declaredResources, declaredSubscriptions, findEnvSuffixCollision, isResourceHandle, resetDeclaredQueueConsumers, resetDeclaredResources, resetDeclaredSubscriptions, resolveResourceRefs, resourceId, resourceKeyOf } from "@rebasepro/types";
|
|
10
|
+
//#region src/resources/derive.ts
|
|
11
|
+
/**
|
|
12
|
+
* Deriving the resource graph from a project's config, and recording it.
|
|
13
|
+
*
|
|
14
|
+
* ## Why the graph is derived rather than authored
|
|
15
|
+
*
|
|
16
|
+
* A resource is declared once, in code. But a host has to know what a project
|
|
17
|
+
* needs *before* it runs anything — that is how a console can say "this project
|
|
18
|
+
* wants a `media` bucket and has none" on a first deploy, and it is the reason
|
|
19
|
+
* storage topology used to be hand-written into `rebase.json` in the first
|
|
20
|
+
* place. A `custom` runtime makes it sharper still: it emits no bundle
|
|
21
|
+
* manifest, so without a committed artifact there is nothing to read at all.
|
|
22
|
+
*
|
|
23
|
+
* So the graph is derived by evaluating the config and written to
|
|
24
|
+
* `rebase.resources.json`, which is committed. Same arrangement as
|
|
25
|
+
* `schema.generated.ts`: authored in one place, generated into another, and a
|
|
26
|
+
* gate fails if the two disagree. The file is an *output*, never an input —
|
|
27
|
+
* which is what removes the old silent merge, because there is no longer a
|
|
28
|
+
* second declaration for anything to be merged with.
|
|
29
|
+
*
|
|
30
|
+
* ## Why evaluation rather than static analysis
|
|
31
|
+
*
|
|
32
|
+
* Encore parses the source and builds the graph from the AST. Rebase already
|
|
33
|
+
* evaluates config to load collections, and evaluation is exact where a parser
|
|
34
|
+
* is approximate — a resource declared in a loop, or behind a helper, is
|
|
35
|
+
* invisible to a parser and obvious to an interpreter. The cost is running user
|
|
36
|
+
* code at build time, which this project already accepts everywhere else.
|
|
37
|
+
*/
|
|
38
|
+
var derive_exports = /* @__PURE__ */ __exportAll({
|
|
39
|
+
RESOURCE_GRAPH_FILENAME: () => RESOURCE_GRAPH_FILENAME,
|
|
40
|
+
deriveOptionsFor: () => deriveOptionsFor,
|
|
41
|
+
deriveResourceGraph: () => deriveResourceGraph,
|
|
42
|
+
describeIssue: () => describeIssue,
|
|
43
|
+
projectResourceGraph: () => projectResourceGraph,
|
|
44
|
+
readResourceGraphFile: () => readResourceGraphFile,
|
|
45
|
+
serializeResourceGraph: () => serializeResourceGraph,
|
|
46
|
+
withImplicitDefaults: () => withImplicitDefaults,
|
|
47
|
+
writeResourceGraphFile: () => writeResourceGraphFile
|
|
48
|
+
});
|
|
49
|
+
/** The committed, generated record of what a project needs. */
|
|
50
|
+
var RESOURCE_GRAPH_FILENAME = "rebase.resources.json";
|
|
51
|
+
/**
|
|
52
|
+
* Which `@rebasepro/*` package a module error is about, when it is about one.
|
|
53
|
+
*
|
|
54
|
+
* Both directions of a version skew arrive as an error about the *user's* file:
|
|
55
|
+
* `resources.ts The requested module '@rebasepro/types' does not provide an
|
|
56
|
+
* export named 'queue'`. Nothing in that says which copy of `@rebasepro/types`
|
|
57
|
+
* is installed, what version this CLI is, or that the two disagree — so the
|
|
58
|
+
* reader goes looking for a typo in code they just copied out of the docs.
|
|
59
|
+
*
|
|
60
|
+
* Two shapes, because the two directions produce different errors: a named
|
|
61
|
+
* export the installed copy is too old for, and a package the installed tree
|
|
62
|
+
* does not have at all.
|
|
63
|
+
*/
|
|
64
|
+
var NAMED_EXPORT_MISSING = /does not provide an export named ['"]([^'"]+)['"]/;
|
|
65
|
+
var SPECIFIER = /(@rebasepro\/[a-z0-9-]+)/;
|
|
66
|
+
/** The version of a package as resolved from the project, or null. */
|
|
67
|
+
function installedVersion(specifier, from) {
|
|
68
|
+
try {
|
|
69
|
+
const manifest = createRequire(path.join(from, "noop.js")).resolve(`${specifier}/package.json`);
|
|
70
|
+
const version = JSON.parse(fs.readFileSync(manifest, "utf-8")).version;
|
|
71
|
+
return typeof version === "string" ? version : null;
|
|
72
|
+
} catch {
|
|
73
|
+
return null;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* The message an issue carries, with the skew named when there is one.
|
|
78
|
+
*
|
|
79
|
+
* `main` runs 497 commits ahead of npm `latest` and the docs site publishes from
|
|
80
|
+
* `main`, so the shape this exists for is routine: a scaffold pinned to the
|
|
81
|
+
* published version, config code written against today's documentation, and an
|
|
82
|
+
* error about the config file. The fix is one command and the error named none
|
|
83
|
+
* of it — the same failure as `404 No PUT route on collection 'projects'`, one
|
|
84
|
+
* layer down.
|
|
85
|
+
*
|
|
86
|
+
* Says nothing extra when the two versions agree, or when either is unreadable:
|
|
87
|
+
* a sentence naming one version twice is worse than no sentence.
|
|
88
|
+
*/
|
|
89
|
+
function describeIssue(err, projectRoot, ownVersion = cliVersion()) {
|
|
90
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
91
|
+
const code = err?.code;
|
|
92
|
+
if (!(NAMED_EXPORT_MISSING.exec(message) !== null || code === "ERR_MODULE_NOT_FOUND")) return message;
|
|
93
|
+
const specifier = SPECIFIER.exec(message)?.[1];
|
|
94
|
+
if (!specifier) return message;
|
|
95
|
+
const lead = /[.!?]$/.test(message.trim()) ? message.trim() : `${message.trim()}.`;
|
|
96
|
+
const installed = installedVersion(specifier, projectRoot);
|
|
97
|
+
if (installed === null) return `${lead} ${specifier} is not installed in this project; this CLI is ${ownVersion}. Run pnpm add ${specifier}@${ownVersion}`;
|
|
98
|
+
if (ownVersion === "unknown" || installed === ownVersion) return message;
|
|
99
|
+
return compareVersions(ownVersion, installed) > 0 ? `${lead} ${specifier} ${installed} is installed; this CLI is ${ownVersion}. Run pnpm add ${specifier}@${ownVersion}` : `${lead} ${specifier} ${installed} is installed; this CLI is ${ownVersion}, which is older. Update the CLI to ${installed}, or pin ${specifier} back to ${ownVersion}`;
|
|
100
|
+
}
|
|
101
|
+
/** -1 / 0 / 1 on the numeric parts, ignoring any prerelease tail. */
|
|
102
|
+
function compareVersions(a, b) {
|
|
103
|
+
const parts = (v) => v.split("-")[0].split(".").map((n) => Number(n) || 0);
|
|
104
|
+
const [x, y] = [parts(a), parts(b)];
|
|
105
|
+
for (let i = 0; i < 3; i++) if ((x[i] ?? 0) !== (y[i] ?? 0)) return (x[i] ?? 0) < (y[i] ?? 0) ? -1 : 1;
|
|
106
|
+
return 0;
|
|
107
|
+
}
|
|
108
|
+
/** Files that declare resources, in the order they are loaded. */
|
|
109
|
+
var RESOURCE_ENTRY_NAMES = [
|
|
110
|
+
"resources.ts",
|
|
111
|
+
"resources.js",
|
|
112
|
+
"resources/index.ts",
|
|
113
|
+
"resources/index.js"
|
|
114
|
+
];
|
|
115
|
+
/** Collection files, which may also declare a resource beside the collection using it. */
|
|
116
|
+
function collectionFiles(configDir) {
|
|
117
|
+
const dir = path.join(configDir, "collections");
|
|
118
|
+
if (!fs.existsSync(dir) || !fs.statSync(dir).isDirectory()) return [];
|
|
119
|
+
return fs.readdirSync(dir).filter((f) => /\.(ts|js|mts|mjs)$/.test(f) && !/\.(test|spec|d)\./.test(f)).sort().map((f) => path.join(dir, f));
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* Make the project's own TypeScript loader active in this process.
|
|
123
|
+
*
|
|
124
|
+
* `import()` below relies on tsx's resolver, and nothing had registered it:
|
|
125
|
+
* `rebase build` runs from `node_modules/.bin/rebase`, which is plain Node.
|
|
126
|
+
* On a modern Node that *looks* like it works — native type stripping imports
|
|
127
|
+
* `collections/index.ts` happily — right up to the first relative specifier
|
|
128
|
+
* inside it. The scaffolded template writes `import posts from "./posts.js"`,
|
|
129
|
+
* which is the correct thing for TypeScript to emit and which native stripping
|
|
130
|
+
* does not remap back to `posts.ts`. So `rebase build` failed on every
|
|
131
|
+
* scaffolded project with `Cannot find module …/posts.js`, and the failure
|
|
132
|
+
* looked like a broken template rather than a missing loader.
|
|
133
|
+
*
|
|
134
|
+
* Resolved from the project, not from this package, exactly as `resolveTsx`
|
|
135
|
+
* does for the schema-generation subprocess: tsx is the project's dependency
|
|
136
|
+
* (the scaffold declares it), and taking it from anywhere else would mean
|
|
137
|
+
* evaluating a project's config with a loader it did not choose.
|
|
138
|
+
*
|
|
139
|
+
* Best-effort by design. A project with no tsx keeps the previous behaviour —
|
|
140
|
+
* which is correct for one whose config is plain JavaScript, or whose imports
|
|
141
|
+
* carry no extension — rather than failing on a dependency it never needed.
|
|
142
|
+
* Registered once per process; `register` is cheap but not free, and deriving
|
|
143
|
+
* twice in one process is an ordinary thing for a watch mode to do.
|
|
144
|
+
*/
|
|
145
|
+
var tsxRegistered = false;
|
|
146
|
+
async function registerProjectTsx(configDir) {
|
|
147
|
+
if (tsxRegistered) return;
|
|
148
|
+
tsxRegistered = true;
|
|
149
|
+
const projectRoot = path.dirname(configDir);
|
|
150
|
+
const bases = [
|
|
151
|
+
configDir,
|
|
152
|
+
projectRoot,
|
|
153
|
+
path.join(projectRoot, "backend")
|
|
154
|
+
];
|
|
155
|
+
for (const base of bases) try {
|
|
156
|
+
const api = await import(pathToFileURL(createRequire(path.join(base, "noop.js")).resolve("tsx/esm/api")).href);
|
|
157
|
+
const register = api.register ?? api.default?.register;
|
|
158
|
+
if (typeof register === "function") {
|
|
159
|
+
register();
|
|
160
|
+
return;
|
|
161
|
+
}
|
|
162
|
+
} catch {}
|
|
163
|
+
}
|
|
164
|
+
/**
|
|
165
|
+
* Import a module for its side effects.
|
|
166
|
+
*
|
|
167
|
+
* Plain `import()` so tsx's loader hooks resolve `.ts` and workspace
|
|
168
|
+
* specifiers, exactly as the collection loader does.
|
|
169
|
+
*/
|
|
170
|
+
async function evaluate(filePath) {
|
|
171
|
+
return await import(pathToFileURL(filePath).href);
|
|
172
|
+
}
|
|
173
|
+
/** The derive options for a backend app, from its manifest entry. */
|
|
174
|
+
function deriveOptionsFor(projectRoot, app) {
|
|
175
|
+
const paths = resolveBackendPaths(app, projectRoot);
|
|
176
|
+
return {
|
|
177
|
+
projectRoot,
|
|
178
|
+
configDir: path.join(projectRoot, paths.config),
|
|
179
|
+
cronsDir: path.join(projectRoot, paths.crons),
|
|
180
|
+
functionsDir: path.join(projectRoot, paths.functions)
|
|
181
|
+
};
|
|
182
|
+
}
|
|
183
|
+
/** Whether a module's default export looks like a collection. */
|
|
184
|
+
function isCollectionLike(value) {
|
|
185
|
+
return typeof value === "object" && value !== null && typeof value.slug === "string" && typeof value.properties === "object";
|
|
186
|
+
}
|
|
187
|
+
/**
|
|
188
|
+
* The resources a collection reaches: its database, and every bucket a
|
|
189
|
+
* storage property stores into. Only resources the graph declares get an
|
|
190
|
+
* edge — the implicit default database is not in the graph, so a collection
|
|
191
|
+
* that names no `dataSource` records nothing.
|
|
192
|
+
*/
|
|
193
|
+
function collectionEdges(collection, declared) {
|
|
194
|
+
const edges = [];
|
|
195
|
+
const from = `collection:${collection.slug}`;
|
|
196
|
+
const databaseId = resourceId("database", collection.dataSource ?? DEFAULT_RESOURCE_KEY);
|
|
197
|
+
if (declared.has(databaseId)) edges.push([databaseId, from]);
|
|
198
|
+
const walk = (properties, prefix) => {
|
|
199
|
+
for (const [name, raw] of Object.entries(properties ?? {})) {
|
|
200
|
+
const property = raw;
|
|
201
|
+
if (!property || typeof property !== "object") continue;
|
|
202
|
+
if (property.storage) {
|
|
203
|
+
const bucketId = resourceId("bucket", property.storage.storageSource === void 0 ? DEFAULT_RESOURCE_KEY : resourceKeyOf(property.storage.storageSource));
|
|
204
|
+
if (declared.has(bucketId)) edges.push([bucketId, `property:${collection.slug}.${prefix}${name}`]);
|
|
205
|
+
}
|
|
206
|
+
if (property.properties) walk(property.properties, `${prefix}${name}.`);
|
|
207
|
+
if (property.of && typeof property.of === "object") walk({ [name]: property.of }, `${prefix}`);
|
|
208
|
+
}
|
|
209
|
+
};
|
|
210
|
+
walk(collection.properties, "");
|
|
211
|
+
return edges;
|
|
212
|
+
}
|
|
213
|
+
/**
|
|
214
|
+
* A cron file that would not import, said in terms somebody can act on.
|
|
215
|
+
*
|
|
216
|
+
* The graph is derived by evaluating each cron module, in a process with none
|
|
217
|
+
* of the deployment's environment — so a cron that reaches a module validating
|
|
218
|
+
* `DATABASE_URL` at import fails here and works perfectly at boot. The raw
|
|
219
|
+
* message is a Zod error about a variable, which reads as "the environment is
|
|
220
|
+
* wrong" when the environment is not the point.
|
|
221
|
+
*/
|
|
222
|
+
function cronLoadIssue(problem) {
|
|
223
|
+
return `${summariseCronProblem(problem)}\n \`rebase resources\` evaluates each cron file to read its schedule, and it is a build step: no .env, no secrets. Move work that needs the deployment's environment inside the handler — \`const { x } = await import("…")\` — so the module scope imports nothing that reads configuration.`;
|
|
224
|
+
}
|
|
225
|
+
/**
|
|
226
|
+
* The cron loader's `<file> (threw: <message>)`, as one line.
|
|
227
|
+
*
|
|
228
|
+
* The message is whatever the module threw, and the common case is a schema
|
|
229
|
+
* validator: `loadEnv()` at module scope with no `.env` present throws a
|
|
230
|
+
* `ZodError`, whose `.message` is the JSON array of its issues. That arrived
|
|
231
|
+
* here verbatim — ten lines of `{ "expected": "string", "code":
|
|
232
|
+
* "invalid_type", … }` — with the sentence that says what to do underneath it,
|
|
233
|
+
* so the reader met "Invalid input: expected string, received undefined" twice
|
|
234
|
+
* before reaching the explanation.
|
|
235
|
+
*
|
|
236
|
+
* A validator's own sentences are the useful half of that object, so they are
|
|
237
|
+
* kept and the serialisation is dropped.
|
|
238
|
+
*/
|
|
239
|
+
function summariseCronProblem(problem) {
|
|
240
|
+
const match = problem.match(/^(.*) \(threw: ([\s\S]*)\)$/);
|
|
241
|
+
if (!match) return problem.split("\n")[0];
|
|
242
|
+
const [, file, thrown] = match;
|
|
243
|
+
return `${file}: ${summariseThrownMessage(thrown)}`;
|
|
244
|
+
}
|
|
245
|
+
/** One sentence out of a thrown message, whatever shape it arrived in. */
|
|
246
|
+
function summariseThrownMessage(message) {
|
|
247
|
+
const trimmed = message.trim();
|
|
248
|
+
if (trimmed.startsWith("[") || trimmed.startsWith("{")) try {
|
|
249
|
+
const parsed = JSON.parse(trimmed);
|
|
250
|
+
const sentences = [...new Set((Array.isArray(parsed) ? parsed : [parsed]).map((issue) => issue?.message).filter((m) => typeof m === "string" && m.length > 0))];
|
|
251
|
+
if (sentences.length > 0) return sentences.join("; ");
|
|
252
|
+
} catch {}
|
|
253
|
+
return trimmed.split("\n")[0];
|
|
254
|
+
}
|
|
255
|
+
/**
|
|
256
|
+
* Evaluate a project's config and return the graph it declares.
|
|
257
|
+
*
|
|
258
|
+
* Clears any previously registered declarations first, so deriving twice in one
|
|
259
|
+
* process — a watch mode, a test — describes the project rather than the union
|
|
260
|
+
* of every project seen so far.
|
|
261
|
+
*/
|
|
262
|
+
async function deriveResourceGraph(options) {
|
|
263
|
+
const { configDir, includeCollections = true, cronsDir, functionsDir } = options;
|
|
264
|
+
const projectRoot = options.projectRoot ?? path.dirname(configDir);
|
|
265
|
+
await registerProjectTsx(configDir);
|
|
266
|
+
resetDeclaredResources();
|
|
267
|
+
resetDeclaredSubscriptions();
|
|
268
|
+
resetDeclaredQueueConsumers();
|
|
269
|
+
const issues = [];
|
|
270
|
+
const usedBy = /* @__PURE__ */ new Map();
|
|
271
|
+
const recordUse = (resource, by) => {
|
|
272
|
+
const list = usedBy.get(resource) ?? [];
|
|
273
|
+
if (!list.includes(by)) list.push(by);
|
|
274
|
+
usedBy.set(resource, list);
|
|
275
|
+
};
|
|
276
|
+
const handleExports = /* @__PURE__ */ new Map();
|
|
277
|
+
for (const name of RESOURCE_ENTRY_NAMES) {
|
|
278
|
+
const candidate = path.join(configDir, name);
|
|
279
|
+
if (!fs.existsSync(candidate)) continue;
|
|
280
|
+
try {
|
|
281
|
+
const mod = await evaluate(candidate);
|
|
282
|
+
for (const [exportName, value] of Object.entries(mod)) if (isResourceHandle(value)) handleExports.set(exportName, resourceId(value.kind, value.key));
|
|
283
|
+
} catch (err) {
|
|
284
|
+
issues.push({
|
|
285
|
+
path: path.relative(configDir, candidate),
|
|
286
|
+
message: describeIssue(err, projectRoot)
|
|
287
|
+
});
|
|
288
|
+
}
|
|
289
|
+
break;
|
|
290
|
+
}
|
|
291
|
+
const collections = [];
|
|
292
|
+
if (includeCollections) for (const entry of collectionFiles(configDir)) try {
|
|
293
|
+
const mod = await evaluate(entry);
|
|
294
|
+
if (isCollectionLike(mod.default)) collections.push(resolveResourceRefs(mod.default));
|
|
295
|
+
} catch (err) {
|
|
296
|
+
issues.push({
|
|
297
|
+
path: path.relative(configDir, entry),
|
|
298
|
+
message: describeIssue(err, projectRoot)
|
|
299
|
+
});
|
|
300
|
+
}
|
|
301
|
+
if (cronsDir && fs.existsSync(cronsDir)) {
|
|
302
|
+
const previousLevel = process.env.LOG_LEVEL;
|
|
303
|
+
process.env.LOG_LEVEL = "error";
|
|
304
|
+
try {
|
|
305
|
+
const { loadCronJobsWithDiagnostics } = await import("@rebasepro/server");
|
|
306
|
+
const { problems } = await loadCronJobsWithDiagnostics(cronsDir, (url) => import(url));
|
|
307
|
+
for (const problem of problems) issues.push({
|
|
308
|
+
path: path.relative(projectRoot, cronsDir),
|
|
309
|
+
message: cronLoadIssue(problem)
|
|
310
|
+
});
|
|
311
|
+
} catch (err) {
|
|
312
|
+
issues.push({
|
|
313
|
+
path: path.relative(projectRoot, cronsDir),
|
|
314
|
+
message: cronLoadIssue(err instanceof Error ? err.message : String(err))
|
|
315
|
+
});
|
|
316
|
+
} finally {
|
|
317
|
+
if (previousLevel === void 0) delete process.env.LOG_LEVEL;
|
|
318
|
+
else process.env.LOG_LEVEL = previousLevel;
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
if (functionsDir && fs.existsSync(functionsDir)) for (const report of analyseFunctionsDirectory(functionsDir, projectRoot)) {
|
|
322
|
+
const requires = [...new Set(report.issues.filter((issue) => issue.kind === "node-builtin" || issue.kind === "node-only-package").map((issue) => issue.message))];
|
|
323
|
+
try {
|
|
324
|
+
declareFunction(report.name, {
|
|
325
|
+
file: report.file,
|
|
326
|
+
portable: report.portable,
|
|
327
|
+
...requires.length > 0 ? { requires } : {}
|
|
328
|
+
});
|
|
329
|
+
} catch (err) {
|
|
330
|
+
issues.push({
|
|
331
|
+
path: report.file,
|
|
332
|
+
message: describeIssue(err, projectRoot)
|
|
333
|
+
});
|
|
334
|
+
}
|
|
335
|
+
for (const imported of report.resourceImports) {
|
|
336
|
+
const resource = handleExports.get(imported);
|
|
337
|
+
if (resource) recordUse(resource, `function:${report.name}`);
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
const declared = new Set(declaredResources().map((r) => resourceId(r.kind, r.key)));
|
|
341
|
+
for (const collection of collections) for (const [resource, by] of collectionEdges(collection, declared)) recordUse(resource, by);
|
|
342
|
+
for (const sub of declaredSubscriptions()) {
|
|
343
|
+
const id = resourceId("topic", sub.topic);
|
|
344
|
+
if (declared.has(id)) recordUse(id, `subscription:${sub.topic}.${sub.name}`);
|
|
345
|
+
}
|
|
346
|
+
for (const consumer of declaredQueueConsumers()) {
|
|
347
|
+
const id = resourceId("queue", consumer.queue);
|
|
348
|
+
if (declared.has(id)) recordUse(id, `handler:${consumer.queue}`);
|
|
349
|
+
}
|
|
350
|
+
const graph = buildResourceGraph(usedBy);
|
|
351
|
+
const byKind = /* @__PURE__ */ new Map();
|
|
352
|
+
for (const r of graph.resources) byKind.set(r.kind, [...byKind.get(r.kind) ?? [], r.key]);
|
|
353
|
+
for (const [kind, keys] of byKind) {
|
|
354
|
+
const collision = findEnvSuffixCollision(keys);
|
|
355
|
+
if (collision) issues.push({
|
|
356
|
+
path: `${kind}.${collision.b}`,
|
|
357
|
+
message: `"${collision.a}" and "${collision.b}" both bind from ${collision.suffix}, so one would read the other's configuration. Rename one.`
|
|
358
|
+
});
|
|
359
|
+
}
|
|
360
|
+
const topics = new Set(graph.resources.filter((r) => r.kind === "topic").map((r) => r.key));
|
|
361
|
+
for (const sub of declaredSubscriptions()) if (!topics.has(sub.topic)) issues.push({
|
|
362
|
+
path: `topic.${sub.topic}`,
|
|
363
|
+
message: `Subscription "${sub.name}" is declared on topic "${sub.topic}", which nothing declares. The handler would never run.`
|
|
364
|
+
});
|
|
365
|
+
const databases = new Set(graph.resources.filter((r) => r.kind === "database").map((r) => r.key));
|
|
366
|
+
const buckets = new Set(graph.resources.filter((r) => r.kind === "bucket").map((r) => r.key));
|
|
367
|
+
for (const collection of collections) {
|
|
368
|
+
if (collection.dataSource && collection.dataSource !== DEFAULT_RESOURCE_KEY && !databases.has(collection.dataSource)) issues.push({
|
|
369
|
+
path: `collection.${collection.slug}`,
|
|
370
|
+
message: `routes to dataSource "${collection.dataSource}", which nothing declares. Add database("${collection.dataSource}") to resources.ts, or import the handle and use it here.`
|
|
371
|
+
});
|
|
372
|
+
for (const [name, raw] of Object.entries(collection.properties ?? {})) {
|
|
373
|
+
const key = raw?.storage?.storageSource;
|
|
374
|
+
if (typeof key === "string" && key !== DEFAULT_RESOURCE_KEY && buckets.size > 0 && !buckets.has(key)) issues.push({
|
|
375
|
+
path: `collection.${collection.slug}.${name}`,
|
|
376
|
+
message: `stores into bucket "${key}", which nothing declares. Add bucket("${key}") to resources.ts.`
|
|
377
|
+
});
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
return {
|
|
381
|
+
graph,
|
|
382
|
+
issues
|
|
383
|
+
};
|
|
384
|
+
}
|
|
385
|
+
/** The graph as it is written to disk: stable key order, trailing newline. */
|
|
386
|
+
function serializeResourceGraph(graph) {
|
|
387
|
+
const resources = graph.resources.map((r) => ({
|
|
388
|
+
kind: r.kind,
|
|
389
|
+
key: r.key,
|
|
390
|
+
engine: r.engine,
|
|
391
|
+
transport: r.transport,
|
|
392
|
+
...r.label !== void 0 ? { label: r.label } : {},
|
|
393
|
+
...Object.keys(r.options).length > 0 ? { options: r.options } : {},
|
|
394
|
+
...r.usedBy && r.usedBy.length > 0 ? { usedBy: r.usedBy } : {}
|
|
395
|
+
}));
|
|
396
|
+
return JSON.stringify({
|
|
397
|
+
$generated: `Generated from config by \`rebase resources\`. Edit the declarations, not this file.`,
|
|
398
|
+
version: graph.version,
|
|
399
|
+
resources
|
|
400
|
+
}, null, 4) + "\n";
|
|
401
|
+
}
|
|
402
|
+
/** Read the committed graph, or null when a project has none yet. */
|
|
403
|
+
function readResourceGraphFile(projectRoot) {
|
|
404
|
+
const file = path.join(projectRoot, RESOURCE_GRAPH_FILENAME);
|
|
405
|
+
return fs.existsSync(file) ? fs.readFileSync(file, "utf-8") : null;
|
|
406
|
+
}
|
|
407
|
+
/**
|
|
408
|
+
* Write the graph, and say whether the file changed.
|
|
409
|
+
*
|
|
410
|
+
* The boolean is what a `--check` mode reports on: a project whose committed
|
|
411
|
+
* graph disagrees with its config has a host reading one thing and a runtime
|
|
412
|
+
* doing another.
|
|
413
|
+
*/
|
|
414
|
+
function writeResourceGraphFile(projectRoot, graph) {
|
|
415
|
+
const file = path.join(projectRoot, RESOURCE_GRAPH_FILENAME);
|
|
416
|
+
const next = serializeResourceGraph(graph);
|
|
417
|
+
if ((fs.existsSync(file) ? fs.readFileSync(file, "utf-8") : null) === next) return {
|
|
418
|
+
changed: false,
|
|
419
|
+
file
|
|
420
|
+
};
|
|
421
|
+
fs.writeFileSync(file, next);
|
|
422
|
+
return {
|
|
423
|
+
changed: true,
|
|
424
|
+
file
|
|
425
|
+
};
|
|
426
|
+
}
|
|
427
|
+
/**
|
|
428
|
+
* Add the resources a project has without declaring them.
|
|
429
|
+
*
|
|
430
|
+
* A backend has a database whether or not anyone said so, and a project that
|
|
431
|
+
* declares no buckets still gets one default storage source from the plain
|
|
432
|
+
* unsuffixed variables. Both are load-bearing defaults and both are invisible
|
|
433
|
+
* in the graph, so a status view built only from declarations would show an
|
|
434
|
+
* empty screen to the majority of projects — the ones that most need to be told
|
|
435
|
+
* which variable their one database reads.
|
|
436
|
+
*/
|
|
437
|
+
function withImplicitDefaults(graph) {
|
|
438
|
+
const entries = graph.resources.map((declaration) => ({
|
|
439
|
+
declaration,
|
|
440
|
+
implicit: false
|
|
441
|
+
}));
|
|
442
|
+
const has = (kind) => graph.resources.some((r) => r.kind === kind);
|
|
443
|
+
if (!has("database")) entries.unshift({
|
|
444
|
+
declaration: {
|
|
445
|
+
kind: "database",
|
|
446
|
+
key: DEFAULT_RESOURCE_KEY,
|
|
447
|
+
engine: "postgres",
|
|
448
|
+
transport: "server",
|
|
449
|
+
options: {}
|
|
450
|
+
},
|
|
451
|
+
implicit: true
|
|
452
|
+
});
|
|
453
|
+
if (!has("bucket")) entries.push({
|
|
454
|
+
declaration: {
|
|
455
|
+
kind: "bucket",
|
|
456
|
+
key: DEFAULT_RESOURCE_KEY,
|
|
457
|
+
engine: "local",
|
|
458
|
+
transport: "server",
|
|
459
|
+
options: {}
|
|
460
|
+
},
|
|
461
|
+
implicit: true
|
|
462
|
+
});
|
|
463
|
+
return entries;
|
|
464
|
+
}
|
|
465
|
+
function projectResourceGraph(graph) {
|
|
466
|
+
return withImplicitDefaults(graph).map(({ declaration, implicit }) => implicit ? {
|
|
467
|
+
...declaration,
|
|
468
|
+
implicit: true
|
|
469
|
+
} : declaration);
|
|
470
|
+
}
|
|
471
|
+
//#endregion
|
|
472
|
+
export { projectResourceGraph as a, withImplicitDefaults as c, derive_exports as i, writeResourceGraphFile as l, deriveOptionsFor as n, readResourceGraphFile as o, deriveResourceGraph as r, serializeResourceGraph as s, RESOURCE_GRAPH_FILENAME as t };
|
|
473
|
+
|
|
474
|
+
//# sourceMappingURL=derive-RPIsAVO4.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"derive-RPIsAVO4.js","names":[],"sources":["../src/resources/derive.ts"],"sourcesContent":["/**\n * Deriving the resource graph from a project's config, and recording it.\n *\n * ## Why the graph is derived rather than authored\n *\n * A resource is declared once, in code. But a host has to know what a project\n * needs *before* it runs anything — that is how a console can say \"this project\n * wants a `media` bucket and has none\" on a first deploy, and it is the reason\n * storage topology used to be hand-written into `rebase.json` in the first\n * place. A `custom` runtime makes it sharper still: it emits no bundle\n * manifest, so without a committed artifact there is nothing to read at all.\n *\n * So the graph is derived by evaluating the config and written to\n * `rebase.resources.json`, which is committed. Same arrangement as\n * `schema.generated.ts`: authored in one place, generated into another, and a\n * gate fails if the two disagree. The file is an *output*, never an input —\n * which is what removes the old silent merge, because there is no longer a\n * second declaration for anything to be merged with.\n *\n * ## Why evaluation rather than static analysis\n *\n * Encore parses the source and builds the graph from the AST. Rebase already\n * evaluates config to load collections, and evaluation is exact where a parser\n * is approximate — a resource declared in a loop, or behind a helper, is\n * invisible to a parser and obvious to an interpreter. The cost is running user\n * code at build time, which this project already accepts everywhere else.\n */\nimport fs from \"fs\";\nimport path from \"path\";\nimport { createRequire } from \"module\";\nimport { pathToFileURL } from \"url\";\nimport {\n DEFAULT_RESOURCE_KEY,\n buildResourceGraph,\n declareFunction,\n declaredQueueConsumers,\n declaredResources,\n declaredSubscriptions,\n findEnvSuffixCollision,\n isResourceHandle,\n resetDeclaredQueueConsumers,\n resetDeclaredResources,\n resetDeclaredSubscriptions,\n resolveResourceRefs,\n resourceId,\n resourceKeyOf,\n type CollectionConfig,\n type RebaseBackendAppConfig,\n type ResourceDeclaration,\n type ResourceGraph\n} from \"@rebasepro/types\";\nimport { analyseFunctionsDirectory } from \"../function-portability\";\nimport { resolveBackendPaths } from \"../manifest\";\nimport { cliVersion } from \"../utils/version\";\n\n/** The committed, generated record of what a project needs. */\nexport const RESOURCE_GRAPH_FILENAME = \"rebase.resources.json\";\n\n/** A problem found while deriving, reported with the rest rather than thrown one at a time. */\nexport interface ResourceIssue {\n path: string;\n message: string;\n}\n\n/**\n * Which `@rebasepro/*` package a module error is about, when it is about one.\n *\n * Both directions of a version skew arrive as an error about the *user's* file:\n * `resources.ts The requested module '@rebasepro/types' does not provide an\n * export named 'queue'`. Nothing in that says which copy of `@rebasepro/types`\n * is installed, what version this CLI is, or that the two disagree — so the\n * reader goes looking for a typo in code they just copied out of the docs.\n *\n * Two shapes, because the two directions produce different errors: a named\n * export the installed copy is too old for, and a package the installed tree\n * does not have at all.\n */\nconst NAMED_EXPORT_MISSING = /does not provide an export named ['\"]([^'\"]+)['\"]/;\nconst SPECIFIER = /(@rebasepro\\/[a-z0-9-]+)/;\n\n/** The version of a package as resolved from the project, or null. */\nfunction installedVersion(specifier: string, from: string): string | null {\n try {\n const require = createRequire(path.join(from, \"noop.js\"));\n const manifest = require.resolve(`${specifier}/package.json`);\n const version: unknown = JSON.parse(fs.readFileSync(manifest, \"utf-8\")).version;\n return typeof version === \"string\" ? version : null;\n } catch {\n return null;\n }\n}\n\n/**\n * The message an issue carries, with the skew named when there is one.\n *\n * `main` runs 497 commits ahead of npm `latest` and the docs site publishes from\n * `main`, so the shape this exists for is routine: a scaffold pinned to the\n * published version, config code written against today's documentation, and an\n * error about the config file. The fix is one command and the error named none\n * of it — the same failure as `404 No PUT route on collection 'projects'`, one\n * layer down.\n *\n * Says nothing extra when the two versions agree, or when either is unreadable:\n * a sentence naming one version twice is worse than no sentence.\n */\nexport function describeIssue(err: unknown, projectRoot: string, ownVersion = cliVersion()): string {\n const message = err instanceof Error ? err.message : String(err);\n const code = (err as { code?: unknown } | null)?.code;\n const missingExport = NAMED_EXPORT_MISSING.exec(message);\n const isSkewShaped = missingExport !== null || code === \"ERR_MODULE_NOT_FOUND\";\n if (!isSkewShaped) return message;\n\n const specifier = SPECIFIER.exec(message)?.[1];\n if (!specifier) return message;\n\n // One line, not two: every caller renders an issue as `path message` on a\n // single row, so a newline here would land the useful half unindented under\n // the file name.\n const lead = /[.!?]$/.test(message.trim()) ? message.trim() : `${message.trim()}.`;\n\n const installed = installedVersion(specifier, projectRoot);\n if (installed === null) {\n return `${lead} ${specifier} is not installed in this project; this CLI is ${ownVersion}. `\n + `Run pnpm add ${specifier}@${ownVersion}`;\n }\n if (ownVersion === \"unknown\" || installed === ownVersion) return message;\n\n // Which side to move is the question the reader actually has, and it has a\n // right answer: the CLI is what evaluates this file, so the packages follow\n // it — unless the CLI is the older one, in which case following it would\n // move the project backwards.\n const cliIsNewer = compareVersions(ownVersion, installed) > 0;\n return cliIsNewer\n ? `${lead} ${specifier} ${installed} is installed; this CLI is ${ownVersion}. `\n + `Run pnpm add ${specifier}@${ownVersion}`\n : `${lead} ${specifier} ${installed} is installed; this CLI is ${ownVersion}, which is older. `\n + `Update the CLI to ${installed}, or pin ${specifier} back to ${ownVersion}`;\n}\n\n/** -1 / 0 / 1 on the numeric parts, ignoring any prerelease tail. */\nfunction compareVersions(a: string, b: string): number {\n const parts = (v: string): number[] => v.split(\"-\")[0].split(\".\").map(n => Number(n) || 0);\n const [x, y] = [parts(a), parts(b)];\n for (let i = 0; i < 3; i++) {\n if ((x[i] ?? 0) !== (y[i] ?? 0)) return (x[i] ?? 0) < (y[i] ?? 0) ? -1 : 1;\n }\n return 0;\n}\n\n/** Files that declare resources, in the order they are loaded. */\nconst RESOURCE_ENTRY_NAMES = [\"resources.ts\", \"resources.js\", \"resources/index.ts\", \"resources/index.js\"];\n\n/** Collection files, which may also declare a resource beside the collection using it. */\nfunction collectionFiles(configDir: string): string[] {\n const dir = path.join(configDir, \"collections\");\n if (!fs.existsSync(dir) || !fs.statSync(dir).isDirectory()) return [];\n return fs.readdirSync(dir)\n .filter(f => /\\.(ts|js|mts|mjs)$/.test(f) && !/\\.(test|spec|d)\\./.test(f))\n .sort()\n .map(f => path.join(dir, f));\n}\n\n/**\n * Make the project's own TypeScript loader active in this process.\n *\n * `import()` below relies on tsx's resolver, and nothing had registered it:\n * `rebase build` runs from `node_modules/.bin/rebase`, which is plain Node.\n * On a modern Node that *looks* like it works — native type stripping imports\n * `collections/index.ts` happily — right up to the first relative specifier\n * inside it. The scaffolded template writes `import posts from \"./posts.js\"`,\n * which is the correct thing for TypeScript to emit and which native stripping\n * does not remap back to `posts.ts`. So `rebase build` failed on every\n * scaffolded project with `Cannot find module …/posts.js`, and the failure\n * looked like a broken template rather than a missing loader.\n *\n * Resolved from the project, not from this package, exactly as `resolveTsx`\n * does for the schema-generation subprocess: tsx is the project's dependency\n * (the scaffold declares it), and taking it from anywhere else would mean\n * evaluating a project's config with a loader it did not choose.\n *\n * Best-effort by design. A project with no tsx keeps the previous behaviour —\n * which is correct for one whose config is plain JavaScript, or whose imports\n * carry no extension — rather than failing on a dependency it never needed.\n * Registered once per process; `register` is cheap but not free, and deriving\n * twice in one process is an ordinary thing for a watch mode to do.\n */\nlet tsxRegistered = false;\n\nasync function registerProjectTsx(configDir: string): Promise<void> {\n if (tsxRegistered) return;\n tsxRegistered = true;\n\n // Where tsx might be, in the order most likely to find it. The scaffold\n // declares tsx in the *backend* workspace, and pnpm's isolated layout puts\n // it in `backend/node_modules` rather than hoisting it — so resolving only\n // from the config directory finds nothing on exactly the project shape this\n // exists to serve.\n const projectRoot = path.dirname(configDir);\n const bases = [configDir, projectRoot, path.join(projectRoot, \"backend\")];\n\n for (const base of bases) {\n try {\n // Resolved against a file *inside* the project so Node walks that\n // project's `node_modules`, not this package's.\n const requireFromProject = createRequire(path.join(base, \"noop.js\"));\n const api = await import(pathToFileURL(requireFromProject.resolve(\"tsx/esm/api\")).href) as\n { register?: () => unknown; default?: { register?: () => unknown } };\n\n // tsx's own `register`, not `module.register`. `tsx/esm/api` is the\n // API module; the loader is `tsx/esm`. Handing the API module to\n // `module.register` registers something with no hooks and reports\n // success — a worse failure than throwing, because the import below\n // then resolves exactly as it did before and the bug looks unfixed.\n const register = api.register ?? api.default?.register;\n if (typeof register === \"function\") {\n register();\n return;\n }\n } catch {\n // Not here. Try the next base.\n }\n }\n\n // No tsx anywhere in the project. The import below still runs; it simply\n // resolves the way Node alone would, which is correct for a config written\n // in plain JavaScript or whose imports carry no extension.\n}\n\n/**\n * Import a module for its side effects.\n *\n * Plain `import()` so tsx's loader hooks resolve `.ts` and workspace\n * specifiers, exactly as the collection loader does.\n */\nasync function evaluate(filePath: string): Promise<Record<string, unknown>> {\n return await import(pathToFileURL(filePath).href) as Record<string, unknown>;\n}\n\nexport interface DeriveOptions {\n /** Absolute path to the project's config directory. */\n configDir: string;\n /**\n * Whether to evaluate collection files too. Resources are conventionally\n * declared in `resources.ts`, but nothing stops a bucket being declared\n * beside the collection that stores into it, and a graph that missed it\n * would under-report what the project needs.\n */\n includeCollections?: boolean;\n /**\n * Absolute path to the crons directory. Each file is evaluated through the\n * same loader the runtime uses, which declares the cron under the same id\n * the scheduler runs it as. Absent, or missing on disk: no crons.\n */\n cronsDir?: string;\n /**\n * Absolute path to the functions directory. Read statically — the\n * bundler's portability analysis — never evaluated: a function's module\n * scope belongs to a running backend, not a build.\n */\n functionsDir?: string;\n /** The project root, for the paths a graph records. Defaults to the config directory's parent. */\n projectRoot?: string;\n}\n\n/** The derive options for a backend app, from its manifest entry. */\nexport function deriveOptionsFor(projectRoot: string, app: RebaseBackendAppConfig): DeriveOptions {\n const paths = resolveBackendPaths(app, projectRoot);\n return {\n projectRoot,\n configDir: path.join(projectRoot, paths.config),\n cronsDir: path.join(projectRoot, paths.crons),\n functionsDir: path.join(projectRoot, paths.functions)\n };\n}\n\n/** Whether a module's default export looks like a collection. */\nfunction isCollectionLike(value: unknown): value is CollectionConfig {\n return typeof value === \"object\" && value !== null\n && typeof (value as { slug?: unknown }).slug === \"string\"\n && typeof (value as { properties?: unknown }).properties === \"object\";\n}\n\n/**\n * The resources a collection reaches: its database, and every bucket a\n * storage property stores into. Only resources the graph declares get an\n * edge — the implicit default database is not in the graph, so a collection\n * that names no `dataSource` records nothing.\n */\nfunction collectionEdges(collection: CollectionConfig, declared: Set<string>): Array<[string, string]> {\n const edges: Array<[string, string]> = [];\n const from = `collection:${collection.slug}`;\n const databaseId = resourceId(\"database\", collection.dataSource ?? DEFAULT_RESOURCE_KEY);\n if (declared.has(databaseId)) edges.push([databaseId, from]);\n\n const walk = (properties: Record<string, unknown> | undefined, prefix: string): void => {\n for (const [name, raw] of Object.entries(properties ?? {})) {\n const property = raw as {\n storage?: { storageSource?: unknown };\n of?: unknown;\n properties?: Record<string, unknown>;\n };\n if (!property || typeof property !== \"object\") continue;\n if (property.storage) {\n const key = property.storage.storageSource === undefined\n ? DEFAULT_RESOURCE_KEY\n : resourceKeyOf(property.storage.storageSource as string);\n const bucketId = resourceId(\"bucket\", key);\n if (declared.has(bucketId)) edges.push([bucketId, `property:${collection.slug}.${prefix}${name}`]);\n }\n if (property.properties) walk(property.properties, `${prefix}${name}.`);\n if (property.of && typeof property.of === \"object\") walk({ [name]: property.of }, `${prefix}`);\n }\n };\n walk(collection.properties as Record<string, unknown>, \"\");\n return edges;\n}\n\n/**\n * A cron file that would not import, said in terms somebody can act on.\n *\n * The graph is derived by evaluating each cron module, in a process with none\n * of the deployment's environment — so a cron that reaches a module validating\n * `DATABASE_URL` at import fails here and works perfectly at boot. The raw\n * message is a Zod error about a variable, which reads as \"the environment is\n * wrong\" when the environment is not the point.\n */\nfunction cronLoadIssue(problem: string): string {\n return `${summariseCronProblem(problem)}\\n` +\n \" `rebase resources` evaluates each cron file to read its schedule, and it is a build \" +\n \"step: no .env, no secrets. Move work that needs the deployment's environment inside the \" +\n \"handler — `const { x } = await import(\\\"…\\\")` — so the module scope imports nothing that \" +\n \"reads configuration.\";\n}\n\n/**\n * The cron loader's `<file> (threw: <message>)`, as one line.\n *\n * The message is whatever the module threw, and the common case is a schema\n * validator: `loadEnv()` at module scope with no `.env` present throws a\n * `ZodError`, whose `.message` is the JSON array of its issues. That arrived\n * here verbatim — ten lines of `{ \"expected\": \"string\", \"code\":\n * \"invalid_type\", … }` — with the sentence that says what to do underneath it,\n * so the reader met \"Invalid input: expected string, received undefined\" twice\n * before reaching the explanation.\n *\n * A validator's own sentences are the useful half of that object, so they are\n * kept and the serialisation is dropped.\n */\nfunction summariseCronProblem(problem: string): string {\n const match = problem.match(/^(.*) \\(threw: ([\\s\\S]*)\\)$/);\n if (!match) return problem.split(\"\\n\")[0];\n const [, file, thrown] = match;\n return `${file}: ${summariseThrownMessage(thrown)}`;\n}\n\n/** One sentence out of a thrown message, whatever shape it arrived in. */\nfunction summariseThrownMessage(message: string): string {\n const trimmed = message.trim();\n if (trimmed.startsWith(\"[\") || trimmed.startsWith(\"{\")) {\n try {\n const parsed: unknown = JSON.parse(trimmed);\n const issues = Array.isArray(parsed) ? parsed : [parsed];\n const sentences = [...new Set(\n issues\n .map(issue => (issue as { message?: unknown } | null)?.message)\n .filter((m): m is string => typeof m === \"string\" && m.length > 0)\n )];\n if (sentences.length > 0) return sentences.join(\"; \");\n } catch {\n // Not JSON after all — fall through to the first line.\n }\n }\n return trimmed.split(\"\\n\")[0];\n}\n\n/**\n * Evaluate a project's config and return the graph it declares.\n *\n * Clears any previously registered declarations first, so deriving twice in one\n * process — a watch mode, a test — describes the project rather than the union\n * of every project seen so far.\n */\nexport async function deriveResourceGraph(options: DeriveOptions): Promise<{ graph: ResourceGraph; issues: ResourceIssue[] }> {\n const { configDir, includeCollections = true, cronsDir, functionsDir } = options;\n const projectRoot = options.projectRoot ?? path.dirname(configDir);\n // Before anything is evaluated: the loader has to be in place for the very\n // first import, and the first import is a collection file.\n await registerProjectTsx(configDir);\n\n // All three registries, and the side lists are the easy ones to forget:\n // resetting only the resources leaves one project's handlers attached\n // while its topics are gone — every one of them then reads as orphaned\n // against the *next* project. Caught by deriving twice in one process,\n // which is what a watch mode does.\n resetDeclaredResources();\n resetDeclaredSubscriptions();\n resetDeclaredQueueConsumers();\n\n const issues: ResourceIssue[] = [];\n const usedBy = new Map<string, string[]>();\n // `recordUse`, not `use`: React 19 made `use` a hook name, and\n // `react-hooks/rules-of-hooks` refuses a call to one from a plain function\n // — a lint error on a file with no React in it.\n const recordUse = (resource: string, by: string): void => {\n const list = usedBy.get(resource) ?? [];\n if (!list.includes(by)) list.push(by);\n usedBy.set(resource, list);\n };\n\n // ── Declarations ─────────────────────────────────────────────────────\n // Which export name is which resource, so a function's `import { media }`\n // can be recorded as reaching `bucket:media` without evaluating the\n // function. The export name and the key are usually the same word and\n // nothing requires them to be.\n const handleExports = new Map<string, string>();\n for (const name of RESOURCE_ENTRY_NAMES) {\n const candidate = path.join(configDir, name);\n if (!fs.existsSync(candidate)) continue;\n try {\n const mod = await evaluate(candidate);\n for (const [exportName, value] of Object.entries(mod)) {\n if (isResourceHandle(value)) handleExports.set(exportName, resourceId(value.kind, value.key));\n }\n } catch (err) {\n issues.push({ path: path.relative(configDir, candidate), message: describeIssue(err, projectRoot) });\n }\n break;\n }\n\n // ── Collections ──────────────────────────────────────────────────────\n const collections: CollectionConfig[] = [];\n if (includeCollections) {\n for (const entry of collectionFiles(configDir)) {\n try {\n const mod = await evaluate(entry);\n // As the loader sees it at boot: a handle written where a key\n // belongs is its key here too.\n if (isCollectionLike(mod.default)) collections.push(resolveResourceRefs(mod.default));\n } catch (err) {\n // Named individually: \"could not derive the graph\" with no file in\n // it sends somebody reading every module in the directory.\n issues.push({\n path: path.relative(configDir, entry),\n message: describeIssue(err, projectRoot)\n });\n }\n }\n }\n\n // ── Crons ────────────────────────────────────────────────────────────\n // Through the runtime's own loader, so the id the graph records is the id\n // the scheduler runs — the filename — and a cron that would not load at\n // boot does not load here either.\n //\n // Which means the module is EVALUATED, in a build step that has none of the\n // deployment's environment. A cron whose module scope reaches something that\n // validates `DATABASE_URL` at import therefore derives on a laptop with a\n // `.env` and nowhere else. That is worth an explicit sentence rather than a\n // stack, so `cronLoadIssue` adds one.\n if (cronsDir && fs.existsSync(cronsDir)) {\n // The runtime's loader logs each job as it loads, to stdout, as JSON\n // — which is also where `rebase resources --json` writes the graph.\n // Its problems come back as a list, so the log adds nothing here.\n const previousLevel = process.env.LOG_LEVEL;\n process.env.LOG_LEVEL = \"error\";\n try {\n const { loadCronJobsWithDiagnostics } = await import(\"@rebasepro/server\");\n // This process's own `import()`, not the loader's `new Function`\n // one: tsx's hooks are registered here, and a test runner's module\n // sandbox answers the other with \"a dynamic import callback was\n // not specified\" — the same trap the split-roles e2e records.\n const { problems } = await loadCronJobsWithDiagnostics(cronsDir, url => import(url));\n for (const problem of problems) {\n issues.push({ path: path.relative(projectRoot, cronsDir), message: cronLoadIssue(problem) });\n }\n } catch (err) {\n issues.push({\n path: path.relative(projectRoot, cronsDir),\n message: cronLoadIssue(err instanceof Error ? err.message : String(err))\n });\n } finally {\n if (previousLevel === undefined) delete process.env.LOG_LEVEL;\n else process.env.LOG_LEVEL = previousLevel;\n }\n }\n\n // ── Functions ────────────────────────────────────────────────────────\n if (functionsDir && fs.existsSync(functionsDir)) {\n for (const report of analyseFunctionsDirectory(functionsDir, projectRoot)) {\n const requires = [...new Set(\n report.issues\n .filter(issue => issue.kind === \"node-builtin\" || issue.kind === \"node-only-package\")\n .map(issue => issue.message)\n )];\n try {\n declareFunction(report.name, {\n file: report.file,\n portable: report.portable,\n ...(requires.length > 0 ? { requires } : {})\n });\n } catch (err) {\n issues.push({ path: report.file, message: describeIssue(err, projectRoot) });\n }\n for (const imported of report.resourceImports) {\n const resource = handleExports.get(imported);\n if (resource) recordUse(resource, `function:${report.name}`);\n }\n }\n }\n\n // ── Edges ────────────────────────────────────────────────────────────\n const declared = new Set(declaredResources().map(r => resourceId(r.kind, r.key)));\n for (const collection of collections) {\n for (const [resource, by] of collectionEdges(collection, declared)) recordUse(resource, by);\n }\n for (const sub of declaredSubscriptions()) {\n const id = resourceId(\"topic\", sub.topic);\n if (declared.has(id)) recordUse(id, `subscription:${sub.topic}.${sub.name}`);\n }\n for (const consumer of declaredQueueConsumers()) {\n const id = resourceId(\"queue\", consumer.queue);\n if (declared.has(id)) recordUse(id, `handler:${consumer.queue}`);\n }\n\n const graph = buildResourceGraph(usedBy);\n\n // Two resources of a kind whose env suffixes collide would silently read\n // each other's configuration, which is indistinguishable from a\n // misconfigured deployment.\n const byKind = new Map<string, string[]>();\n for (const r of graph.resources) {\n byKind.set(r.kind, [...(byKind.get(r.kind) ?? []), r.key]);\n }\n for (const [kind, keys] of byKind) {\n const collision = findEnvSuffixCollision(keys);\n if (collision) {\n issues.push({\n path: `${kind}.${collision.b}`,\n message:\n `\"${collision.a}\" and \"${collision.b}\" both bind from ${collision.suffix}, so one would ` +\n \"read the other's configuration. Rename one.\"\n });\n }\n }\n\n // A subscription on a topic nobody declared is a handler that will never\n // run, and nothing else would ever say so.\n const topics = new Set(graph.resources.filter(r => r.kind === \"topic\").map(r => r.key));\n for (const sub of declaredSubscriptions()) {\n if (!topics.has(sub.topic)) {\n issues.push({\n path: `topic.${sub.topic}`,\n message:\n `Subscription \"${sub.name}\" is declared on topic \"${sub.topic}\", which nothing declares. ` +\n \"The handler would never run.\"\n });\n }\n }\n\n // A collection routed to a database nothing declares boots into a\n // refusal, and a property stored in a bucket nothing declares answers 501\n // on its first upload. Both are cheaper to hear about here.\n const databases = new Set(graph.resources.filter(r => r.kind === \"database\").map(r => r.key));\n const buckets = new Set(graph.resources.filter(r => r.kind === \"bucket\").map(r => r.key));\n for (const collection of collections) {\n if (collection.dataSource && collection.dataSource !== DEFAULT_RESOURCE_KEY && !databases.has(collection.dataSource)) {\n issues.push({\n path: `collection.${collection.slug}`,\n message:\n `routes to dataSource \"${collection.dataSource}\", which nothing declares. ` +\n `Add database(\"${collection.dataSource}\") to resources.ts, or import the handle and use it here.`\n });\n }\n for (const [name, raw] of Object.entries(collection.properties ?? {})) {\n const key = (raw as { storage?: { storageSource?: unknown } })?.storage?.storageSource;\n if (typeof key === \"string\" && key !== DEFAULT_RESOURCE_KEY && buckets.size > 0 && !buckets.has(key)) {\n issues.push({\n path: `collection.${collection.slug}.${name}`,\n message: `stores into bucket \"${key}\", which nothing declares. Add bucket(\"${key}\") to resources.ts.`\n });\n }\n }\n }\n\n return { graph, issues };\n}\n\n/** The graph as it is written to disk: stable key order, trailing newline. */\nexport function serializeResourceGraph(graph: ResourceGraph): string {\n const resources = graph.resources.map(r => ({\n kind: r.kind,\n key: r.key,\n engine: r.engine,\n transport: r.transport,\n ...(r.label !== undefined ? { label: r.label } : {}),\n ...(Object.keys(r.options).length > 0 ? { options: r.options } : {}),\n ...(r.usedBy && r.usedBy.length > 0 ? { usedBy: r.usedBy } : {})\n }));\n return JSON.stringify({\n $generated: `Generated from config by \\`rebase resources\\`. Edit the declarations, not this file.`,\n version: graph.version,\n resources\n }, null, 4) + \"\\n\";\n}\n\n/** Read the committed graph, or null when a project has none yet. */\nexport function readResourceGraphFile(projectRoot: string): string | null {\n const file = path.join(projectRoot, RESOURCE_GRAPH_FILENAME);\n return fs.existsSync(file) ? fs.readFileSync(file, \"utf-8\") : null;\n}\n\n/**\n * Write the graph, and say whether the file changed.\n *\n * The boolean is what a `--check` mode reports on: a project whose committed\n * graph disagrees with its config has a host reading one thing and a runtime\n * doing another.\n */\nexport function writeResourceGraphFile(projectRoot: string, graph: ResourceGraph): { changed: boolean; file: string } {\n const file = path.join(projectRoot, RESOURCE_GRAPH_FILENAME);\n const next = serializeResourceGraph(graph);\n const previous = fs.existsSync(file) ? fs.readFileSync(file, \"utf-8\") : null;\n if (previous === next) return { changed: false, file };\n fs.writeFileSync(file, next);\n return { changed: true, file };\n}\n\n/** Parse a committed graph file, tolerating the `$generated` banner. */\nexport function parseResourceGraph(contents: string): ResourceGraph {\n const raw = JSON.parse(contents) as { version?: number; resources?: unknown };\n if (raw.version !== 1) {\n throw new Error(\n `Unsupported resource graph version ${String(raw.version)}. ` +\n \"This project was generated by a newer Rebase; upgrade rather than provisioning half of it.\"\n );\n }\n const entries = Array.isArray(raw.resources) ? raw.resources as Record<string, unknown>[] : [];\n // `options` is omitted from the file when empty; a reader of the graph\n // expects it present, so it is restored here.\n return { version: 1, resources: entries.map(r => ({ options: {}, ...r })) as ResourceGraph[\"resources\"] };\n}\n\n/**\n * Add the resources a project has without declaring them.\n *\n * A backend has a database whether or not anyone said so, and a project that\n * declares no buckets still gets one default storage source from the plain\n * unsuffixed variables. Both are load-bearing defaults and both are invisible\n * in the graph, so a status view built only from declarations would show an\n * empty screen to the majority of projects — the ones that most need to be told\n * which variable their one database reads.\n */\nexport function withImplicitDefaults(graph: ResourceGraph): {\n declaration: ResourceDeclaration;\n implicit: boolean;\n}[] {\n const entries = graph.resources.map(declaration => ({ declaration, implicit: false }));\n const has = (kind: string) => graph.resources.some(r => r.kind === kind);\n\n if (!has(\"database\")) {\n entries.unshift({\n declaration: {\n kind: \"database\",\n key: DEFAULT_RESOURCE_KEY,\n engine: \"postgres\",\n transport: \"server\",\n options: {}\n },\n implicit: true\n });\n }\n if (!has(\"bucket\")) {\n entries.push({\n declaration: {\n kind: \"bucket\",\n key: DEFAULT_RESOURCE_KEY,\n // `local` is what an unconfigured default source resolves to,\n // and naming it here keeps the row honest about what a project\n // with no S3 variables actually gets: a directory that a\n // container erases on restart, which production then drops.\n engine: \"local\",\n transport: \"server\",\n options: {}\n },\n implicit: true\n });\n }\n return entries;\n}\n\n/**\n * The resources a project *has*, which is what a person is asking about.\n *\n * One projection, because two of them disagreed. `rebase status` built its\n * rows from {@link withImplicitDefaults} and listed `buckets ✓ (default) local\n * · implicit`; `rebase resources` and `rebase resources --json` built theirs\n * from the raw graph and listed only the database and the function. Two\n * commands whose whole job is to answer \"what does this project need\", on the\n * same stock scaffold, giving different answers.\n *\n * The implicit entries are marked rather than hidden, because the distinction\n * is real and load-bearing: a declared resource is recorded in\n * `rebase.resources.json` for a host to provision, and an implicit one is a\n * default the runtime supplies whether or not anyone wrote it down. That file\n * still holds declarations only — it is a wire contract a host reads, and\n * putting defaults in it would ask for something to be provisioned that\n * nobody declared.\n */\nexport type ProjectedResource = ResourceDeclaration & { implicit?: true };\n\nexport function projectResourceGraph(graph: ResourceGraph): ProjectedResource[] {\n return withImplicitDefaults(graph).map(({ declaration, implicit }) =>\n (implicit ? { ...declaration, implicit: true as const } : declaration));\n}\n"],"mappings":"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"}
|