@rebasepro/cli 0.24.0 → 0.24.1-canary.g19596ef
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/bin/rebase.js +3 -0
- package/dist/agent-setup-B31F00DC.js +324 -0
- package/dist/agent-setup-B31F00DC.js.map +1 -0
- package/dist/api-keys-Bl1I9q7M.js +525 -0
- package/dist/api-keys-Bl1I9q7M.js.map +1 -0
- package/dist/apps-CNgQJjg5.js +340 -0
- package/dist/apps-CNgQJjg5.js.map +1 -0
- package/dist/args-BnmsEXvu.js +109 -0
- package/dist/args-BnmsEXvu.js.map +1 -0
- package/dist/atlas-dev-database-D_LP_Woq.js +62 -0
- package/dist/atlas-dev-database-D_LP_Woq.js.map +1 -0
- package/dist/auth-aySHEF45.js +414 -0
- package/dist/auth-aySHEF45.js.map +1 -0
- package/dist/branch-pointer-DvSHBjjz.js +2 -0
- package/dist/build-DIHWIqO4.js +336 -0
- package/dist/build-DIHWIqO4.js.map +1 -0
- package/dist/bundle-CqURVN-K.js +1621 -0
- package/dist/bundle-CqURVN-K.js.map +1 -0
- package/dist/bundle.d.ts +6 -1
- package/dist/cli.d.ts +2 -2
- package/dist/cloud-DjByQlkW.js +9667 -0
- package/dist/cloud-DjByQlkW.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/api-keys.d.ts +2 -0
- package/dist/commands/console.d.ts +12 -0
- package/dist/commands/db.d.ts +64 -24
- package/dist/commands/dev.d.ts +103 -6
- package/dist/commands/generate_sdk.d.ts +26 -0
- package/dist/commands/init.d.ts +58 -0
- package/dist/commands/skills.d.ts +22 -7
- package/dist/commands/start.d.ts +15 -0
- package/dist/commands/storage.d.ts +46 -0
- package/dist/commands/upgrade.d.ts +1 -0
- package/dist/consent-ClRHmcxf.js +76 -0
- package/dist/consent-ClRHmcxf.js.map +1 -0
- package/dist/console-D6SJf8k_.js +102 -0
- package/dist/console-D6SJf8k_.js.map +1 -0
- package/dist/context-BqhRydBg.js +1234 -0
- package/dist/context-BqhRydBg.js.map +1 -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/daemon-pzsQwDHa.js +2 -0
- package/dist/db-B7RuNuaQ.js +1312 -0
- package/dist/db-B7RuNuaQ.js.map +1 -0
- package/dist/derive-Dl0z2OWi.js +484 -0
- package/dist/derive-Dl0z2OWi.js.map +1 -0
- package/dist/dev-CYr_WXan.js +1599 -0
- package/dist/dev-CYr_WXan.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/last-dev-database.d.ts +41 -0
- package/dist/dev-db/prepare.d.ts +19 -3
- package/dist/dev-db/pull.d.ts +90 -5
- package/dist/dev-db/resolve.d.ts +26 -2
- package/dist/dev-preflight-B-LYyBf1.js +2 -0
- package/dist/dev-server-DG8Tt-uf.js +96 -0
- package/dist/dev-server-DG8Tt-uf.js.map +1 -0
- package/dist/doctor-BP8cZ-7F.js +771 -0
- package/dist/doctor-BP8cZ-7F.js.map +1 -0
- package/dist/eject-DizdoecV.js +335 -0
- package/dist/eject-DizdoecV.js.map +1 -0
- package/dist/fold-static-BC9CYicw.js +201 -0
- package/dist/fold-static-BC9CYicw.js.map +1 -0
- package/dist/function-portability-BIvlvR9q.js +265 -0
- package/dist/function-portability-BIvlvR9q.js.map +1 -0
- package/dist/generate_sdk-BPqdJc3Q.js +489 -0
- package/dist/generate_sdk-BPqdJc3Q.js.map +1 -0
- package/dist/index.es.js +151 -24674
- package/dist/index.es.js.map +1 -1
- package/dist/init-Utstvh2Q.js +1149 -0
- package/dist/init-Utstvh2Q.js.map +1 -0
- package/dist/last-dev-database-B58g89uA.js +2 -0
- package/dist/manifest-DRp0UVTj.js +880 -0
- package/dist/manifest-DRp0UVTj.js.map +1 -0
- package/dist/manifest.d.ts +15 -3
- package/dist/normalize-imports-X0Tgze9y.js +107 -0
- package/dist/normalize-imports-X0Tgze9y.js.map +1 -0
- package/dist/{package-manager-CXQevsE1.js → package-manager-DSEOwTqM.js} +2 -2
- package/dist/{package-manager-CXQevsE1.js.map → package-manager-DSEOwTqM.js.map} +1 -1
- package/dist/path-flags-BFgG9V6y.js +91 -0
- package/dist/path-flags-BFgG9V6y.js.map +1 -0
- package/dist/precompress.d.ts +17 -0
- package/dist/prepare-CDN6lSpz.js +1236 -0
- package/dist/prepare-CDN6lSpz.js.map +1 -0
- package/dist/{project-DmaGLmH2.js → project-Wp2zoyeN.js} +33 -4
- package/dist/project-Wp2zoyeN.js.map +1 -0
- package/dist/pull-CrGYMEsy.js +458 -0
- package/dist/pull-CrGYMEsy.js.map +1 -0
- package/dist/resolve-B2O-wKKk.js +2 -0
- package/dist/resources-FD7AfZx8.js +183 -0
- package/dist/resources-FD7AfZx8.js.map +1 -0
- package/dist/schema-DMdr431S.js +125 -0
- package/dist/schema-DMdr431S.js.map +1 -0
- package/dist/skills-DTFibT1m.js +642 -0
- package/dist/skills-DTFibT1m.js.map +1 -0
- package/dist/start-DEa-hOBh.js +267 -0
- package/dist/start-DEa-hOBh.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-DxnJTekv.js +352 -0
- package/dist/status-DxnJTekv.js.map +1 -0
- package/dist/storage-DyVq-f_F.js +303 -0
- package/dist/storage-DyVq-f_F.js.map +1 -0
- package/dist/telemetry/identity.d.ts +10 -1
- package/dist/telemetry-C8Uq8phb.js +129 -0
- package/dist/telemetry-C8Uq8phb.js.map +1 -0
- package/dist/telemetry-EMFRVRQi.js +351 -0
- package/dist/telemetry-EMFRVRQi.js.map +1 -0
- package/dist/unknown-command-DNwDcX3w.js +98 -0
- package/dist/unknown-command-DNwDcX3w.js.map +1 -0
- package/dist/upgrade-CtZtRkTE.js +1332 -0
- package/dist/upgrade-CtZtRkTE.js.map +1 -0
- package/dist/upgrade.d.ts +24 -3
- package/dist/utils/dev-preflight.d.ts +47 -5
- package/dist/utils/dev-server.d.ts +30 -0
- package/dist/utils/path-flags.d.ts +15 -2
- package/dist/utils/project.d.ts +16 -0
- package/dist/utils/temp-archive.d.ts +9 -0
- package/dist/{version-DsgBmP22.js → version-Buyyo1ut.js} +2 -2
- package/dist/{version-DsgBmP22.js.map → version-Buyyo1ut.js.map} +1 -1
- package/package.json +7 -7
- package/templates/eject/Dockerfile +4 -2
- package/templates/eject/backend/src/env.ts +1 -1
- package/templates/eject/backend/src/index.ts +29 -4
- package/templates/eject/docker-compose.custom.yml +8 -4
- package/templates/generated-schemas/blank.ts +40 -0
- package/templates/generated-schemas/blog.ts +127 -0
- package/templates/generated-schemas/ecommerce.ts +93 -0
- package/templates/overlays/baas/config/package.json +1 -0
- package/templates/overlays/baas/package.json +1 -0
- package/templates/template/.env.example +10 -4
- package/templates/template/README.md +11 -4
- package/templates/template/ai-instructions.md +3 -2
- package/templates/template/config/resources.ts +7 -6
- package/templates/template/docker-compose.yml +9 -3
- package/templates/template/frontend/vite.config.ts +13 -3
- package/templates/template/package.json +1 -0
- package/templates/template/scripts/example.ts +2 -1
- package/templates/template/tsconfig.json +25 -0
- package/dist/branch-pointer-kk9F5FBO.js +0 -111
- package/dist/branch-pointer-kk9F5FBO.js.map +0 -1
- package/dist/constraints-DKGbNfUW.js +0 -48
- package/dist/constraints-DKGbNfUW.js.map +0 -1
- package/dist/daemon-C8p32i-n.js +0 -357
- package/dist/daemon-C8p32i-n.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-DmaGLmH2.js.map +0 -1
- package/dist/pull-Dywm5FWP.js +0 -232
- package/dist/pull-Dywm5FWP.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
- package/templates/template/backend/src/schema.generated.ts +0 -13
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
import { u as compareMountOrder, v as staticAppAddress } from "./manifest-DRp0UVTj.js";
|
|
2
|
+
import { _ as foldStaticIntoBundle, b as toolStdio } from "./bundle-CqURVN-K.js";
|
|
3
|
+
import chalk from "chalk";
|
|
4
|
+
import fs from "fs";
|
|
5
|
+
import path from "path";
|
|
6
|
+
import { execa } from "execa";
|
|
7
|
+
//#region src/fold-static.ts
|
|
8
|
+
/**
|
|
9
|
+
* Folding a project's static apps into its backend bundle.
|
|
10
|
+
*
|
|
11
|
+
* Shared by `rebase build` and `rebase cloud deploy` deliberately. It lived in
|
|
12
|
+
* the build *command* first, and `deploy` rebuilds the bundle itself — so a
|
|
13
|
+
* deploy silently produced a bundle without the frontend, packed 164 KB where
|
|
14
|
+
* 39 MB was expected, and the site 404'd on the managed runtime exactly as if
|
|
15
|
+
* folding had never been written. Two callers building the same artefact must
|
|
16
|
+
* share the step that completes it.
|
|
17
|
+
*
|
|
18
|
+
* Why fold at all: `bootFromBundle` serves static apps from `entry.static`
|
|
19
|
+
* behind `REBASE_SERVE_STATIC` (default on). A managed tenant runs one pod, so
|
|
20
|
+
* putting the built assets in the bundle gives it the shape a custom container
|
|
21
|
+
* already had — site at `/`, admin at `/admin`, API at `/api` — which is the
|
|
22
|
+
* only honest baseline for calling the managed runtime a drop-in replacement.
|
|
23
|
+
*
|
|
24
|
+
* **Every** static app is folded, each at its declared path. Folding used to
|
|
25
|
+
* pick exactly one and refuse when it found two, which meant a project with a
|
|
26
|
+
* site and an admin panel deployed with neither.
|
|
27
|
+
*/
|
|
28
|
+
/**
|
|
29
|
+
* Every static app in the manifest, in mount order.
|
|
30
|
+
*
|
|
31
|
+
* Longest path first, so the `/`-rooted app is registered last — its catch-all
|
|
32
|
+
* would otherwise claim its siblings' URLs — and, at an equal path, the app with
|
|
33
|
+
* a hostname before the one without (see `compareMountOrder`). Pure, so the
|
|
34
|
+
* ordering is testable without a filesystem.
|
|
35
|
+
*/
|
|
36
|
+
function foldableApps(manifest) {
|
|
37
|
+
const apps = [];
|
|
38
|
+
const skipped = [];
|
|
39
|
+
for (const [name, app] of Object.entries(manifest.apps ?? {})) {
|
|
40
|
+
if (app?.type !== "static") continue;
|
|
41
|
+
if (!app.output) {
|
|
42
|
+
skipped.push({
|
|
43
|
+
name,
|
|
44
|
+
reason: `"${name}" declares no output directory — not folded in.`
|
|
45
|
+
});
|
|
46
|
+
continue;
|
|
47
|
+
}
|
|
48
|
+
const { host, path: appPath } = staticAppAddress(name, app);
|
|
49
|
+
apps.push({
|
|
50
|
+
name,
|
|
51
|
+
build: app.build,
|
|
52
|
+
output: app.output,
|
|
53
|
+
path: appPath,
|
|
54
|
+
...host ? { host } : {},
|
|
55
|
+
spa: app.spa ?? true,
|
|
56
|
+
cms: app.cms
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
apps.sort(compareMountOrder);
|
|
60
|
+
return {
|
|
61
|
+
apps,
|
|
62
|
+
skipped
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Assert a built app's assets are actually rooted at the path it is served from.
|
|
67
|
+
*
|
|
68
|
+
* An app mounted at `/admin` but built with Vite's default `base: "/"` emits
|
|
69
|
+
* `<script src="/assets/index-a1b2.js">`. The server serves `index.html` fine
|
|
70
|
+
* and 404s every asset: a blank page, no server error, nothing in the logs. It
|
|
71
|
+
* is the single most expensive silent failure in this design, so it is a build
|
|
72
|
+
* error rather than a runtime surprise.
|
|
73
|
+
*
|
|
74
|
+
* Only `<script src>` and `<link href>` are inspected — those are what a bundler
|
|
75
|
+
* rewrites through `base`. Author-written anchors and canonical URLs are not
|
|
76
|
+
* evidence of a misbuild.
|
|
77
|
+
*/
|
|
78
|
+
function assertBuiltForPath(indexHtml, basePath, appName) {
|
|
79
|
+
if (basePath === "/") return;
|
|
80
|
+
const offenders = [];
|
|
81
|
+
for (const match of indexHtml.matchAll(/<(?:script|link)\b[^>]*?\b(?:src|href)\s*=\s*["']([^"']+)["']/gi)) {
|
|
82
|
+
const ref = match[1];
|
|
83
|
+
if (!ref.startsWith("/")) continue;
|
|
84
|
+
if (ref === `${basePath}` || ref.startsWith(`${basePath}/`)) continue;
|
|
85
|
+
offenders.push(ref);
|
|
86
|
+
}
|
|
87
|
+
if (offenders.length === 0) return;
|
|
88
|
+
throw new Error(`"${appName}" is declared at ${basePath} but its build emitted assets rooted at /.\n index.html references: ${offenders.slice(0, 3).join(", ")}\n The app would load a blank page. Set \`base\` from REBASE_APP_BASE in its
|
|
89
|
+
build config — see docs/apps-and-runtimes.md §4.2.`);
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* The environment every static app is built with, wherever that build is driven from.
|
|
93
|
+
*
|
|
94
|
+
* Shared because there are two drivers — `foldFrontendIntoBundle` here, and
|
|
95
|
+
* `buildAssetApp` in `build.ts` for a standalone `type: "static"` app — and they
|
|
96
|
+
* had already drifted: the path variables were duplicated into both, so a fix
|
|
97
|
+
* applied to one shipped a bundle built the old way from the other. One function
|
|
98
|
+
* makes that impossible rather than merely unlikely.
|
|
99
|
+
*
|
|
100
|
+
* ## REBASE_APP_*
|
|
101
|
+
*
|
|
102
|
+
* The declared path is a build-time input, not only a serving concern: Vite
|
|
103
|
+
* reads `base` from REBASE_APP_BASE, and the trailing slash is that field's
|
|
104
|
+
* convention. See `assertBuiltForPath`.
|
|
105
|
+
*
|
|
106
|
+
* For an app declared at a URL these carry the path part only — `/` for
|
|
107
|
+
* `https://admin.example.com`. The hostname is where the platform routes the
|
|
108
|
+
* app, not something its assets should name: an absolute `base` would pin
|
|
109
|
+
* every script to production, so `vite preview` and any other hostname the app
|
|
110
|
+
* is opened on would load it from somewhere else.
|
|
111
|
+
*
|
|
112
|
+
* ## NODE_ENV
|
|
113
|
+
*
|
|
114
|
+
* A built app is a production artifact by construction, so build it as one. Not
|
|
115
|
+
* a formality: the scaffold's `.env` carries `NODE_ENV=development` for the dev
|
|
116
|
+
* backend, and Vite's `loadEnv` promotes a `NODE_ENV` found in an env file into
|
|
117
|
+
* the build unless the environment already sets one. So `rebase build` and
|
|
118
|
+
* `rebase cloud deploy` shipped a *development* bundle — `import.meta.env.DEV
|
|
119
|
+
* === true`, development React, dev-only branches live — from commands whose
|
|
120
|
+
* whole purpose is to produce something deployable. Setting it here is what
|
|
121
|
+
* closes it: Vite consults the env file's NODE_ENV only when `process.env`
|
|
122
|
+
* has none.
|
|
123
|
+
*
|
|
124
|
+
* ## VITE_API_URL
|
|
125
|
+
*
|
|
126
|
+
* An app served by the backend it talks to has its API on its own origin by
|
|
127
|
+
* construction, so a baked-in absolute URL can only be wrong. It was: that same
|
|
128
|
+
* `.env` carries `VITE_API_URL=http://localhost:3001` and
|
|
129
|
+
* `frontend/vite.config.ts` reads the project root via `envDir: ".."`, so a
|
|
130
|
+
* stock deploy shipped a site whose every request went to whoever ran the build
|
|
131
|
+
* — passing every server-side health check on the way out. Blanking it here
|
|
132
|
+
* fixes the bundle even for a project whose `.env` predates the `init` fix, or
|
|
133
|
+
* was written by hand. Empty is the right value rather than a missing one: the
|
|
134
|
+
* client falls back to `window.location.origin`, which keeps working when a
|
|
135
|
+
* custom domain is added.
|
|
136
|
+
*
|
|
137
|
+
* Vite prioritises `process.env.VITE_*` over `.env` files, so an explicit
|
|
138
|
+
* `VITE_API_URL=https://api.example.com rebase cloud deploy` still wins — the
|
|
139
|
+
* cross-origin escape hatch stays open, it just has to be deliberate. Nothing on
|
|
140
|
+
* this path loads the project `.env` into `process.env`, so a value inherited
|
|
141
|
+
* here really was set by the caller.
|
|
142
|
+
*/
|
|
143
|
+
function staticBuildEnv(appPath, appName) {
|
|
144
|
+
return {
|
|
145
|
+
REBASE_APP_PATH: appPath,
|
|
146
|
+
REBASE_APP_BASE: appPath === "/" ? "/" : `${appPath}/`,
|
|
147
|
+
REBASE_APP_NAME: appName,
|
|
148
|
+
NODE_ENV: "production",
|
|
149
|
+
VITE_API_URL: process.env.VITE_API_URL ?? ""
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
/**
|
|
153
|
+
* Build the project's static apps and fold them into the backend bundle.
|
|
154
|
+
*
|
|
155
|
+
* Throws rather than exiting, so the caller decides whether a missing frontend
|
|
156
|
+
* should fail its command — a `build` may reasonably want to stop, and so should
|
|
157
|
+
* a deploy, but that is not this function's call to make.
|
|
158
|
+
*/
|
|
159
|
+
async function foldFrontendIntoBundle(options) {
|
|
160
|
+
const { projectRoot, manifest, bundleDir, skipBuild } = options;
|
|
161
|
+
const log = options.log ?? ((m) => console.log(m));
|
|
162
|
+
const { apps, skipped } = foldableApps(manifest);
|
|
163
|
+
for (const { reason } of skipped) log(chalk.yellow(` ⚠ ${reason}`));
|
|
164
|
+
if (apps.length === 0) return [];
|
|
165
|
+
const outcomes = [];
|
|
166
|
+
for (const app of apps) {
|
|
167
|
+
if (app.build && !skipBuild && !options.builtApps?.has(app.name)) {
|
|
168
|
+
await execa(app.build, {
|
|
169
|
+
cwd: projectRoot,
|
|
170
|
+
stdio: toolStdio(options.quietStdout),
|
|
171
|
+
shell: true,
|
|
172
|
+
env: staticBuildEnv(app.path, app.name)
|
|
173
|
+
});
|
|
174
|
+
options.builtApps?.add(app.name);
|
|
175
|
+
}
|
|
176
|
+
const assetsDir = path.join(projectRoot, app.output);
|
|
177
|
+
if (!fs.existsSync(assetsDir)) throw new Error(`"${app.name}" declared output "${app.output}" does not exist after building — the bundle would ship without a frontend.`);
|
|
178
|
+
const indexHtml = path.join(assetsDir, "index.html");
|
|
179
|
+
if (fs.existsSync(indexHtml)) assertBuiltForPath(fs.readFileSync(indexHtml, "utf8"), app.path, app.name);
|
|
180
|
+
const { fileCount } = foldStaticIntoBundle({
|
|
181
|
+
bundleDir,
|
|
182
|
+
assetsDir,
|
|
183
|
+
appName: app.name,
|
|
184
|
+
path: app.path,
|
|
185
|
+
host: app.host,
|
|
186
|
+
spa: app.spa,
|
|
187
|
+
cms: app.cms
|
|
188
|
+
});
|
|
189
|
+
outcomes.push({
|
|
190
|
+
appName: app.name,
|
|
191
|
+
fileCount,
|
|
192
|
+
path: app.path,
|
|
193
|
+
...app.host ? { host: app.host } : {}
|
|
194
|
+
});
|
|
195
|
+
}
|
|
196
|
+
return outcomes;
|
|
197
|
+
}
|
|
198
|
+
//#endregion
|
|
199
|
+
export { staticBuildEnv as i, foldFrontendIntoBundle as n, foldableApps as r, assertBuiltForPath as t };
|
|
200
|
+
|
|
201
|
+
//# sourceMappingURL=fold-static-BC9CYicw.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fold-static-BC9CYicw.js","names":[],"sources":["../src/fold-static.ts"],"sourcesContent":["/**\n * Folding a project's static apps into its backend bundle.\n *\n * Shared by `rebase build` and `rebase cloud deploy` deliberately. It lived in\n * the build *command* first, and `deploy` rebuilds the bundle itself — so a\n * deploy silently produced a bundle without the frontend, packed 164 KB where\n * 39 MB was expected, and the site 404'd on the managed runtime exactly as if\n * folding had never been written. Two callers building the same artefact must\n * share the step that completes it.\n *\n * Why fold at all: `bootFromBundle` serves static apps from `entry.static`\n * behind `REBASE_SERVE_STATIC` (default on). A managed tenant runs one pod, so\n * putting the built assets in the bundle gives it the shape a custom container\n * already had — site at `/`, admin at `/admin`, API at `/api` — which is the\n * only honest baseline for calling the managed runtime a drop-in replacement.\n *\n * **Every** static app is folded, each at its declared path. Folding used to\n * pick exactly one and refuse when it found two, which meant a project with a\n * site and an admin panel deployed with neither.\n */\nimport fs from \"fs\";\nimport path from \"path\";\nimport { execa } from \"execa\";\nimport chalk from \"chalk\";\nimport { foldStaticIntoBundle } from \"./bundle\";\nimport { compareMountOrder, staticAppAddress } from \"./manifest\";\nimport { toolStdio } from \"./utils/tool-stdio\";\n\n/** The apps section of a project manifest, as much of it as folding needs. */\nexport interface FoldableManifest {\n apps?: Record<string, {\n type?: string;\n build?: string;\n output?: string;\n path?: string;\n spa?: boolean;\n cms?: string;\n }>;\n}\n\nexport interface FoldOptions {\n projectRoot: string;\n manifest: FoldableManifest;\n /** The backend bundle directory, already written. */\n bundleDir: string;\n /** Skip running each app's own build command; fold what is already built. */\n skipBuild?: boolean;\n /**\n * The static apps already built in this run, shared with `buildAssetApp`.\n * An app named here is not built again; an app built here is added. One\n * `rebase build` used to run the admin's `vite build && tsc` twice — once\n * to fold it, once for its own static bundle — with the same command, the\n * same environment and the same output directory.\n */\n builtApps?: Set<string>;\n log?: (message: string) => void;\n /** Send each app's build output to stderr: the caller's stdout carries a result. See `toolStdio`. */\n quietStdout?: boolean;\n}\n\nexport interface FoldOutcome {\n appName: string;\n fileCount: number;\n /** Public base path this app was folded in at. */\n path: string;\n /** The one hostname it answers on, when its `path` names one. */\n host?: string;\n}\n\n/** A static app as folding sees it, with the manifest's defaults applied. */\nexport interface FoldableApp {\n name: string;\n build?: string;\n output?: string;\n /**\n * Public base path, defaulted to `/`. Always a path: when the manifest's\n * `path` is a URL, this is the part after the hostname, which is what the\n * app is built for and mounted at.\n */\n path: string;\n /** The one hostname this app answers on, when its `path` is a URL. */\n host?: string;\n /** SPA fallback, defaulted to `true`. */\n spa: boolean;\n /** Where this app mounts the Rebase CMS, if it does. */\n cms?: string;\n}\n\n/**\n * Every static app in the manifest, in mount order.\n *\n * Longest path first, so the `/`-rooted app is registered last — its catch-all\n * would otherwise claim its siblings' URLs — and, at an equal path, the app with\n * a hostname before the one without (see `compareMountOrder`). Pure, so the\n * ordering is testable without a filesystem.\n */\nexport function foldableApps(manifest: FoldableManifest): {\n apps: FoldableApp[];\n /** Apps that cannot be folded, and why. */\n skipped: { name: string; reason: string }[];\n} {\n const apps: FoldableApp[] = [];\n const skipped: { name: string; reason: string }[] = [];\n\n for (const [name, app] of Object.entries(manifest.apps ?? {})) {\n if (app?.type !== \"static\") continue;\n if (!app.output) {\n skipped.push({ name,\nreason: `\"${name}\" declares no output directory — not folded in.` });\n continue;\n }\n const { host, path: appPath } = staticAppAddress(name, app);\n apps.push({\n name,\n build: app.build,\n output: app.output,\n path: appPath,\n ...(host ? { host } : {}),\n spa: app.spa ?? true,\n cms: app.cms\n });\n }\n\n apps.sort(compareMountOrder);\n return { apps,\nskipped };\n}\n\n/**\n * Assert a built app's assets are actually rooted at the path it is served from.\n *\n * An app mounted at `/admin` but built with Vite's default `base: \"/\"` emits\n * `<script src=\"/assets/index-a1b2.js\">`. The server serves `index.html` fine\n * and 404s every asset: a blank page, no server error, nothing in the logs. It\n * is the single most expensive silent failure in this design, so it is a build\n * error rather than a runtime surprise.\n *\n * Only `<script src>` and `<link href>` are inspected — those are what a bundler\n * rewrites through `base`. Author-written anchors and canonical URLs are not\n * evidence of a misbuild.\n */\nexport function assertBuiltForPath(\n indexHtml: string,\n basePath: string,\n appName: string\n): void {\n if (basePath === \"/\") return;\n\n const offenders: string[] = [];\n const pattern = /<(?:script|link)\\b[^>]*?\\b(?:src|href)\\s*=\\s*[\"']([^\"']+)[\"']/gi;\n for (const match of indexHtml.matchAll(pattern)) {\n const ref = match[1];\n if (!ref.startsWith(\"/\")) continue;\n if (ref === `${basePath}` || ref.startsWith(`${basePath}/`)) continue;\n offenders.push(ref);\n }\n\n if (offenders.length === 0) return;\n\n throw new Error(\n `\"${appName}\" is declared at ${basePath} but its build emitted assets rooted at /.\\n` +\n ` index.html references: ${offenders.slice(0, 3).join(\", \")}\\n` +\n \" The app would load a blank page. Set `base` from REBASE_APP_BASE in its\\n\" +\n \" build config — see docs/apps-and-runtimes.md §4.2.\"\n );\n}\n\n/**\n * The environment every static app is built with, wherever that build is driven from.\n *\n * Shared because there are two drivers — `foldFrontendIntoBundle` here, and\n * `buildAssetApp` in `build.ts` for a standalone `type: \"static\"` app — and they\n * had already drifted: the path variables were duplicated into both, so a fix\n * applied to one shipped a bundle built the old way from the other. One function\n * makes that impossible rather than merely unlikely.\n *\n * ## REBASE_APP_*\n *\n * The declared path is a build-time input, not only a serving concern: Vite\n * reads `base` from REBASE_APP_BASE, and the trailing slash is that field's\n * convention. See `assertBuiltForPath`.\n *\n * For an app declared at a URL these carry the path part only — `/` for\n * `https://admin.example.com`. The hostname is where the platform routes the\n * app, not something its assets should name: an absolute `base` would pin\n * every script to production, so `vite preview` and any other hostname the app\n * is opened on would load it from somewhere else.\n *\n * ## NODE_ENV\n *\n * A built app is a production artifact by construction, so build it as one. Not\n * a formality: the scaffold's `.env` carries `NODE_ENV=development` for the dev\n * backend, and Vite's `loadEnv` promotes a `NODE_ENV` found in an env file into\n * the build unless the environment already sets one. So `rebase build` and\n * `rebase cloud deploy` shipped a *development* bundle — `import.meta.env.DEV\n * === true`, development React, dev-only branches live — from commands whose\n * whole purpose is to produce something deployable. Setting it here is what\n * closes it: Vite consults the env file's NODE_ENV only when `process.env`\n * has none.\n *\n * ## VITE_API_URL\n *\n * An app served by the backend it talks to has its API on its own origin by\n * construction, so a baked-in absolute URL can only be wrong. It was: that same\n * `.env` carries `VITE_API_URL=http://localhost:3001` and\n * `frontend/vite.config.ts` reads the project root via `envDir: \"..\"`, so a\n * stock deploy shipped a site whose every request went to whoever ran the build\n * — passing every server-side health check on the way out. Blanking it here\n * fixes the bundle even for a project whose `.env` predates the `init` fix, or\n * was written by hand. Empty is the right value rather than a missing one: the\n * client falls back to `window.location.origin`, which keeps working when a\n * custom domain is added.\n *\n * Vite prioritises `process.env.VITE_*` over `.env` files, so an explicit\n * `VITE_API_URL=https://api.example.com rebase cloud deploy` still wins — the\n * cross-origin escape hatch stays open, it just has to be deliberate. Nothing on\n * this path loads the project `.env` into `process.env`, so a value inherited\n * here really was set by the caller.\n */\nexport function staticBuildEnv(appPath: string, appName: string): Record<string, string> {\n return {\n REBASE_APP_PATH: appPath,\n REBASE_APP_BASE: appPath === \"/\" ? \"/\" : `${appPath}/`,\n REBASE_APP_NAME: appName,\n NODE_ENV: \"production\",\n VITE_API_URL: process.env.VITE_API_URL ?? \"\"\n };\n}\n\n/**\n * Build the project's static apps and fold them into the backend bundle.\n *\n * Throws rather than exiting, so the caller decides whether a missing frontend\n * should fail its command — a `build` may reasonably want to stop, and so should\n * a deploy, but that is not this function's call to make.\n */\nexport async function foldFrontendIntoBundle(options: FoldOptions): Promise<FoldOutcome[]> {\n const { projectRoot, manifest, bundleDir, skipBuild } = options;\n const log = options.log ?? ((m: string) => console.log(m));\n\n const { apps, skipped } = foldableApps(manifest);\n for (const { reason } of skipped) log(chalk.yellow(` ⚠ ${reason}`));\n if (apps.length === 0) return [];\n\n const outcomes: FoldOutcome[] = [];\n\n for (const app of apps) {\n if (app.build && !skipBuild && !options.builtApps?.has(app.name)) {\n await execa(app.build, {\n cwd: projectRoot,\n stdio: toolStdio(options.quietStdout),\n shell: true,\n env: staticBuildEnv(app.path, app.name)\n });\n options.builtApps?.add(app.name);\n }\n\n const assetsDir = path.join(projectRoot, app.output as string);\n if (!fs.existsSync(assetsDir)) {\n // Exited 0 and produced nothing where the manifest says it should.\n // Folding that ships an empty site, which from the outside is\n // indistinguishable from a broken deploy.\n throw new Error(\n `\"${app.name}\" declared output \"${app.output}\" does not exist after building — ` +\n \"the bundle would ship without a frontend.\"\n );\n }\n\n const indexHtml = path.join(assetsDir, \"index.html\");\n if (fs.existsSync(indexHtml)) {\n assertBuiltForPath(fs.readFileSync(indexHtml, \"utf8\"), app.path, app.name);\n }\n\n const { fileCount } = foldStaticIntoBundle({\n bundleDir,\n assetsDir,\n appName: app.name,\n path: app.path,\n host: app.host,\n spa: app.spa,\n cms: app.cms\n });\n outcomes.push({ appName: app.name,\nfileCount,\npath: app.path,\n...(app.host ? { host: app.host } : {}) });\n }\n\n return outcomes;\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,265 @@
|
|
|
1
|
+
import * as fs$1 from "fs";
|
|
2
|
+
import * as path$1 from "path";
|
|
3
|
+
//#region src/function-portability.ts
|
|
4
|
+
/**
|
|
5
|
+
* What each of a project's custom functions depends on the *host* for.
|
|
6
|
+
*
|
|
7
|
+
* A custom function is a Hono app, and Hono runs on every JavaScript server
|
|
8
|
+
* runtime there is. So the question of whether a given function could run
|
|
9
|
+
* somewhere other than a Node process is not about the framework — it is
|
|
10
|
+
* entirely about what the function's own file imports and touches. This answers
|
|
11
|
+
* that, per file, from source.
|
|
12
|
+
*
|
|
13
|
+
* It is a **report, not a rule.** Nothing here fails a build. A function that
|
|
14
|
+
* opens a file, shells out, or runs raw SQL is a perfectly good function, and
|
|
15
|
+
* on a Node deployment — which is every deployment today — it costs nothing.
|
|
16
|
+
* The value is in knowing which ones those are *before* it matters, and in the
|
|
17
|
+
* one finding that is genuinely a latent bug on any host:
|
|
18
|
+
* `process.env` read at module scope.
|
|
19
|
+
*
|
|
20
|
+
* Why bother now: the alternative is discovering it per-file, later, across a
|
|
21
|
+
* codebase already written against the assumption. The scan is cheap, the
|
|
22
|
+
* output is short, and the habits it nudges — importing from
|
|
23
|
+
* `@rebasepro/server/functions`, reading configuration inside the handler — are
|
|
24
|
+
* better on Node too.
|
|
25
|
+
*
|
|
26
|
+
* @module
|
|
27
|
+
*/
|
|
28
|
+
/** Node built-ins, bare and `node:`-prefixed alike. */
|
|
29
|
+
var NODE_BUILTINS = /* @__PURE__ */ new Set([
|
|
30
|
+
"assert",
|
|
31
|
+
"async_hooks",
|
|
32
|
+
"buffer",
|
|
33
|
+
"child_process",
|
|
34
|
+
"cluster",
|
|
35
|
+
"console",
|
|
36
|
+
"constants",
|
|
37
|
+
"crypto",
|
|
38
|
+
"dgram",
|
|
39
|
+
"diagnostics_channel",
|
|
40
|
+
"dns",
|
|
41
|
+
"domain",
|
|
42
|
+
"events",
|
|
43
|
+
"fs",
|
|
44
|
+
"http",
|
|
45
|
+
"http2",
|
|
46
|
+
"https",
|
|
47
|
+
"inspector",
|
|
48
|
+
"module",
|
|
49
|
+
"net",
|
|
50
|
+
"os",
|
|
51
|
+
"path",
|
|
52
|
+
"perf_hooks",
|
|
53
|
+
"process",
|
|
54
|
+
"punycode",
|
|
55
|
+
"querystring",
|
|
56
|
+
"readline",
|
|
57
|
+
"repl",
|
|
58
|
+
"stream",
|
|
59
|
+
"string_decoder",
|
|
60
|
+
"sys",
|
|
61
|
+
"timers",
|
|
62
|
+
"tls",
|
|
63
|
+
"trace_events",
|
|
64
|
+
"tty",
|
|
65
|
+
"url",
|
|
66
|
+
"util",
|
|
67
|
+
"v8",
|
|
68
|
+
"vm",
|
|
69
|
+
"wasi",
|
|
70
|
+
"worker_threads",
|
|
71
|
+
"zlib"
|
|
72
|
+
]);
|
|
73
|
+
/**
|
|
74
|
+
* Packages that need Node, with the reason. Not exhaustive and cannot be —
|
|
75
|
+
* an unknown package is reported as unknown rather than guessed at, because
|
|
76
|
+
* claiming a dependency is portable when it is not is worse than saying nothing.
|
|
77
|
+
*/
|
|
78
|
+
var NODE_ONLY_PACKAGES = /* @__PURE__ */ new Map([
|
|
79
|
+
["@hono/node-server", "the Node HTTP adapter"],
|
|
80
|
+
["ws", "Node sockets"],
|
|
81
|
+
["jsonwebtoken", "node:crypto"],
|
|
82
|
+
["drizzle-orm", "a database TCP socket"],
|
|
83
|
+
["pg", "a database TCP socket"],
|
|
84
|
+
["postgres", "a database TCP socket"],
|
|
85
|
+
["mysql2", "a database TCP socket"],
|
|
86
|
+
["mongodb", "a database TCP socket"],
|
|
87
|
+
["nodemailer", "SMTP sockets"],
|
|
88
|
+
["sharp", "a native addon"],
|
|
89
|
+
["bcrypt", "a native addon"],
|
|
90
|
+
["puppeteer", "a browser subprocess"],
|
|
91
|
+
["fs-extra", "the filesystem"],
|
|
92
|
+
["chokidar", "the filesystem"],
|
|
93
|
+
["dotenv", "reading a file at import time"],
|
|
94
|
+
["archiver", "Node streams"],
|
|
95
|
+
["ioredis", "Node sockets"]
|
|
96
|
+
]);
|
|
97
|
+
/** Files the functions loader would serve. Mirrors `function-loader.ts`. */
|
|
98
|
+
function functionFiles(directory) {
|
|
99
|
+
if (!fs$1.existsSync(directory)) return [];
|
|
100
|
+
return fs$1.readdirSync(directory, { withFileTypes: true }).filter((entry) => entry.isFile()).map((entry) => entry.name).filter((name) => (name.endsWith(".ts") || name.endsWith(".js")) && !name.startsWith(".") && !name.includes(".test.") && !name.endsWith(".d.ts") && name !== "index.ts" && name !== "index.js").sort();
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* Blank out comments, preserving line structure so reported line numbers stay
|
|
104
|
+
* true. Strings are left alone: an import specifier *is* a string, and blanking
|
|
105
|
+
* them before reading imports is how a scan reports that a file imports `""`.
|
|
106
|
+
*/
|
|
107
|
+
function stripComments(source) {
|
|
108
|
+
return source.replace(/\/\*[\s\S]*?\*\//g, (match) => match.replace(/[^\n]/g, " ")).replace(/(^|[^:])\/\/[^\n]*/g, (match, lead) => lead + " ".repeat(match.length - lead.length));
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* Comments *and* string bodies, for scans that look for identifiers rather than
|
|
112
|
+
* specifiers — where `"process.env.FOO"` inside a message is not a read.
|
|
113
|
+
*/
|
|
114
|
+
function stripNonCode(source) {
|
|
115
|
+
return stripComments(source).replace(/`(?:[^`\\]|\\.)*`/g, (match) => match.replace(/[^\n]/g, " ")).replace(/"(?:[^"\\\n]|\\.)*"/g, (match) => `"${" ".repeat(Math.max(0, match.length - 2))}"`).replace(/'(?:[^'\\\n]|\\.)*'/g, (match) => `'${" ".repeat(Math.max(0, match.length - 2))}'`);
|
|
116
|
+
}
|
|
117
|
+
/** Import specifiers, keeping the raw line so the report can point at it. */
|
|
118
|
+
function imports(source) {
|
|
119
|
+
const found = [];
|
|
120
|
+
source.split("\n").forEach((line, index) => {
|
|
121
|
+
const match = /(?:^|[\s;}])(?:import|export)(\s+type)?\s[^;]*?from\s*["']([^"']+)["']/.exec(line) ?? /(?:^|[\s;}])import\s*["']([^"']+)["']/.exec(line);
|
|
122
|
+
if (!match) return;
|
|
123
|
+
const specifier = match[2] ?? match[1];
|
|
124
|
+
if (!specifier) return;
|
|
125
|
+
found.push({
|
|
126
|
+
specifier,
|
|
127
|
+
line: index + 1,
|
|
128
|
+
typeOnly: Boolean(match[1]) || /\bimport\s+type\b/.test(line)
|
|
129
|
+
});
|
|
130
|
+
});
|
|
131
|
+
return found;
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* The names a file imports from the project's resources module.
|
|
135
|
+
*
|
|
136
|
+
* Matched on the specifier's last path segment — `../config/resources`,
|
|
137
|
+
* `../../config/resources.js`, `@app/config/resources` — because that is the
|
|
138
|
+
* one convention `rebase resources` reads declarations from, and a function
|
|
139
|
+
* importing a handle from anywhere else has put its declaration somewhere
|
|
140
|
+
* the graph does not look. Type-only imports are skipped: they reach nothing
|
|
141
|
+
* at runtime.
|
|
142
|
+
*/
|
|
143
|
+
function resourceImports(source) {
|
|
144
|
+
const names = /* @__PURE__ */ new Set();
|
|
145
|
+
for (const line of source.split("\n")) {
|
|
146
|
+
const match = /(?:^|[\s;}])import\s+(?!type\b)\{([^}]*)\}\s*from\s*["']([^"']+)["']/.exec(line);
|
|
147
|
+
if (!match) continue;
|
|
148
|
+
const specifier = match[2].replace(/\.[cm]?[jt]s$/, "");
|
|
149
|
+
if (!/(?:^|\/)resources(?:\/index)?$/.test(specifier)) continue;
|
|
150
|
+
for (const part of match[1].split(",")) {
|
|
151
|
+
const name = part.trim().replace(/^type\s+/, "");
|
|
152
|
+
if (!name || part.trim().startsWith("type ")) continue;
|
|
153
|
+
names.add(name.split(/\s+as\s+/)[0].trim());
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
return [...names].sort();
|
|
157
|
+
}
|
|
158
|
+
/**
|
|
159
|
+
* `process.env` (and friends) read where the module is *evaluated*.
|
|
160
|
+
*
|
|
161
|
+
* Indentation is the heuristic for "inside something that runs later", which is
|
|
162
|
+
* exact for every formatter anyone uses on a function file and wrong only for
|
|
163
|
+
* code nobody writes. The cost of being wrong is one line of advisory output.
|
|
164
|
+
*/
|
|
165
|
+
function moduleScopeEnvReads(source) {
|
|
166
|
+
const lines = stripNonCode(source).split("\n");
|
|
167
|
+
const hits = [];
|
|
168
|
+
lines.forEach((line, index) => {
|
|
169
|
+
if (/^\s/.test(line) || line.trim() === "") return;
|
|
170
|
+
if (/\bprocess\.env\b/.test(line)) hits.push(index + 1);
|
|
171
|
+
});
|
|
172
|
+
return hits;
|
|
173
|
+
}
|
|
174
|
+
function packageOf(specifier) {
|
|
175
|
+
return specifier.startsWith("@") ? specifier.split("/").slice(0, 2).join("/") : specifier.split("/")[0];
|
|
176
|
+
}
|
|
177
|
+
/** Analyse one function file's source. Exported for testing. */
|
|
178
|
+
function analyseFunctionSource(source, name, file) {
|
|
179
|
+
const issues = [];
|
|
180
|
+
const withStrings = stripComments(source);
|
|
181
|
+
const code = stripNonCode(source);
|
|
182
|
+
for (const { specifier, line, typeOnly } of imports(withStrings)) {
|
|
183
|
+
if (typeOnly) continue;
|
|
184
|
+
if (specifier.startsWith(".") || specifier.startsWith("/")) continue;
|
|
185
|
+
const bare = specifier.replace(/^node:/, "");
|
|
186
|
+
if (specifier.startsWith("node:") || NODE_BUILTINS.has(bare.split("/")[0])) {
|
|
187
|
+
issues.push({
|
|
188
|
+
kind: "node-builtin",
|
|
189
|
+
line,
|
|
190
|
+
message: `imports the Node built-in "${specifier}"`,
|
|
191
|
+
actionable: false
|
|
192
|
+
});
|
|
193
|
+
continue;
|
|
194
|
+
}
|
|
195
|
+
const pkg = packageOf(specifier);
|
|
196
|
+
if (specifier === "@rebasepro/server") {
|
|
197
|
+
issues.push({
|
|
198
|
+
kind: "root-barrel-import",
|
|
199
|
+
line,
|
|
200
|
+
message: "imports from \"@rebasepro/server\" — the whole framework, Node-only. Use \"@rebasepro/server/functions\" for the authoring surface.",
|
|
201
|
+
actionable: false
|
|
202
|
+
});
|
|
203
|
+
continue;
|
|
204
|
+
}
|
|
205
|
+
const why = NODE_ONLY_PACKAGES.get(pkg);
|
|
206
|
+
if (why) issues.push({
|
|
207
|
+
kind: "node-only-package",
|
|
208
|
+
line,
|
|
209
|
+
message: `imports "${specifier}", which needs ${why}`,
|
|
210
|
+
actionable: false
|
|
211
|
+
});
|
|
212
|
+
}
|
|
213
|
+
for (const line of moduleScopeEnvReads(code)) issues.push({
|
|
214
|
+
kind: "module-scope-env",
|
|
215
|
+
line,
|
|
216
|
+
message: "reads process.env at module scope. If the variable is unset the whole file fails to import, and the loader reports that as a skipped function — read it inside the handler instead (`requireEnv(c, \"NAME\")`, or `lazyResource`).",
|
|
217
|
+
actionable: true
|
|
218
|
+
});
|
|
219
|
+
return {
|
|
220
|
+
name,
|
|
221
|
+
file,
|
|
222
|
+
issues,
|
|
223
|
+
portable: issues.every((issue) => issue.kind === "root-barrel-import"),
|
|
224
|
+
resourceImports: resourceImports(withStrings)
|
|
225
|
+
};
|
|
226
|
+
}
|
|
227
|
+
/**
|
|
228
|
+
* Analyse every function in a directory.
|
|
229
|
+
*
|
|
230
|
+
* @param functionsDir Absolute path to the functions directory.
|
|
231
|
+
* @param projectRoot For rendering paths people recognise.
|
|
232
|
+
*/
|
|
233
|
+
function analyseFunctionsDirectory(functionsDir, projectRoot) {
|
|
234
|
+
return functionFiles(functionsDir).map((fileName) => {
|
|
235
|
+
const absolute = path$1.join(functionsDir, fileName);
|
|
236
|
+
return analyseFunctionSource(fs$1.readFileSync(absolute, "utf8"), path$1.basename(fileName, path$1.extname(fileName)), path$1.relative(projectRoot, absolute));
|
|
237
|
+
});
|
|
238
|
+
}
|
|
239
|
+
/**
|
|
240
|
+
* The lines to print after a build, or nothing at all.
|
|
241
|
+
*
|
|
242
|
+
* Silent when every function is portable and nothing is actionable, because a
|
|
243
|
+
* report that always prints is a report nobody reads. Actionable findings are
|
|
244
|
+
* always shown; the rest collapse to a single count with a pointer.
|
|
245
|
+
*/
|
|
246
|
+
function summarisePortability(results) {
|
|
247
|
+
if (results.length === 0) return [];
|
|
248
|
+
const actionable = results.flatMap((result) => result.issues.filter((issue) => issue.actionable).map((issue) => ({
|
|
249
|
+
result,
|
|
250
|
+
issue
|
|
251
|
+
})));
|
|
252
|
+
const nodeOnly = results.filter((result) => !result.portable);
|
|
253
|
+
const lines = [];
|
|
254
|
+
for (const { result, issue } of actionable) lines.push(` ⚠ ${result.file}:${issue.line} — ${issue.message}`);
|
|
255
|
+
if (nodeOnly.length > 0) {
|
|
256
|
+
const names = nodeOnly.map((result) => result.name).join(", ");
|
|
257
|
+
lines.push(` ℹ ${nodeOnly.length} of ${results.length} function(s) depend on Node: ${names}`);
|
|
258
|
+
lines.push(" Fine for any Node deployment — recorded in the manifest so a host knows.");
|
|
259
|
+
}
|
|
260
|
+
return lines;
|
|
261
|
+
}
|
|
262
|
+
//#endregion
|
|
263
|
+
export { summarisePortability as n, analyseFunctionsDirectory as t };
|
|
264
|
+
|
|
265
|
+
//# sourceMappingURL=function-portability-BIvlvR9q.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"function-portability-BIvlvR9q.js","names":[],"sources":["../src/function-portability.ts"],"sourcesContent":["/**\n * What each of a project's custom functions depends on the *host* for.\n *\n * A custom function is a Hono app, and Hono runs on every JavaScript server\n * runtime there is. So the question of whether a given function could run\n * somewhere other than a Node process is not about the framework — it is\n * entirely about what the function's own file imports and touches. This answers\n * that, per file, from source.\n *\n * It is a **report, not a rule.** Nothing here fails a build. A function that\n * opens a file, shells out, or runs raw SQL is a perfectly good function, and\n * on a Node deployment — which is every deployment today — it costs nothing.\n * The value is in knowing which ones those are *before* it matters, and in the\n * one finding that is genuinely a latent bug on any host:\n * `process.env` read at module scope.\n *\n * Why bother now: the alternative is discovering it per-file, later, across a\n * codebase already written against the assumption. The scan is cheap, the\n * output is short, and the habits it nudges — importing from\n * `@rebasepro/server/functions`, reading configuration inside the handler — are\n * better on Node too.\n *\n * @module\n */\nimport * as fs from \"fs\";\nimport * as path from \"path\";\n\n/** Node built-ins, bare and `node:`-prefixed alike. */\nconst NODE_BUILTINS = new Set([\n \"assert\", \"async_hooks\", \"buffer\", \"child_process\", \"cluster\", \"console\",\n \"constants\", \"crypto\", \"dgram\", \"diagnostics_channel\", \"dns\", \"domain\",\n \"events\", \"fs\", \"http\", \"http2\", \"https\", \"inspector\", \"module\", \"net\",\n \"os\", \"path\", \"perf_hooks\", \"process\", \"punycode\", \"querystring\",\n \"readline\", \"repl\", \"stream\", \"string_decoder\", \"sys\", \"timers\", \"tls\",\n \"trace_events\", \"tty\", \"url\", \"util\", \"v8\", \"vm\", \"wasi\", \"worker_threads\",\n \"zlib\"\n]);\n\n/**\n * Packages that need Node, with the reason. Not exhaustive and cannot be —\n * an unknown package is reported as unknown rather than guessed at, because\n * claiming a dependency is portable when it is not is worse than saying nothing.\n */\nconst NODE_ONLY_PACKAGES = new Map<string, string>([\n [\"@hono/node-server\", \"the Node HTTP adapter\"],\n [\"ws\", \"Node sockets\"],\n [\"jsonwebtoken\", \"node:crypto\"],\n [\"drizzle-orm\", \"a database TCP socket\"],\n [\"pg\", \"a database TCP socket\"],\n [\"postgres\", \"a database TCP socket\"],\n [\"mysql2\", \"a database TCP socket\"],\n [\"mongodb\", \"a database TCP socket\"],\n [\"nodemailer\", \"SMTP sockets\"],\n [\"sharp\", \"a native addon\"],\n [\"bcrypt\", \"a native addon\"],\n [\"puppeteer\", \"a browser subprocess\"],\n [\"fs-extra\", \"the filesystem\"],\n [\"chokidar\", \"the filesystem\"],\n [\"dotenv\", \"reading a file at import time\"],\n [\"archiver\", \"Node streams\"],\n [\"ioredis\", \"Node sockets\"]\n]);\n\n/** What a single finding is about. */\nexport type PortabilityIssueKind =\n | \"node-builtin\"\n | \"node-only-package\"\n | \"module-scope-env\"\n | \"root-barrel-import\"\n | \"unknown-package\";\n\nexport interface PortabilityIssue {\n kind: PortabilityIssueKind;\n /** 1-based line in the function file. */\n line: number;\n /** One sentence, already phrased for a terminal. */\n message: string;\n /**\n * Whether this is a problem *today*, on Node, rather than only a constraint\n * on where the function could run. Exactly one kind is:\n * `module-scope-env`, which is a latent crash on any host where a variable\n * happens to be unset at import time, and takes every other function in the\n * same directory down with it — the loader reports a file that throws on\n * import as simply \"skipped\".\n */\n actionable: boolean;\n}\n\nexport interface FunctionPortability {\n /** Function name — the filename without extension, as it mounts. */\n name: string;\n /** Path relative to the project root. */\n file: string;\n /** Empty when the function depends on nothing host-specific. */\n issues: PortabilityIssue[];\n /** True when `issues` contains nothing that pins it to Node. */\n portable: boolean;\n /**\n * Names imported from the project's resources module — `import { media }\n * from \"../../config/resources\"` — so the graph can record that this\n * function reaches `media` without evaluating it. Export names, not keys:\n * the derive step maps them through the evaluated module.\n */\n resourceImports: string[];\n}\n\n/** Files the functions loader would serve. Mirrors `function-loader.ts`. */\nfunction functionFiles(directory: string): string[] {\n if (!fs.existsSync(directory)) return [];\n return fs.readdirSync(directory, { withFileTypes: true })\n .filter(entry => entry.isFile())\n .map(entry => entry.name)\n .filter(name =>\n (name.endsWith(\".ts\") || name.endsWith(\".js\"))\n && !name.startsWith(\".\")\n && !name.includes(\".test.\")\n && !name.endsWith(\".d.ts\")\n && name !== \"index.ts\"\n && name !== \"index.js\")\n .sort();\n}\n\n/**\n * Blank out comments, preserving line structure so reported line numbers stay\n * true. Strings are left alone: an import specifier *is* a string, and blanking\n * them before reading imports is how a scan reports that a file imports `\"\"`.\n */\nfunction stripComments(source: string): string {\n return source\n .replace(/\\/\\*[\\s\\S]*?\\*\\//g, match => match.replace(/[^\\n]/g, \" \"))\n .replace(/(^|[^:])\\/\\/[^\\n]*/g, (match, lead) => lead + \" \".repeat(match.length - lead.length));\n}\n\n/**\n * Comments *and* string bodies, for scans that look for identifiers rather than\n * specifiers — where `\"process.env.FOO\"` inside a message is not a read.\n */\nfunction stripNonCode(source: string): string {\n return stripComments(source)\n .replace(/`(?:[^`\\\\]|\\\\.)*`/g, match => match.replace(/[^\\n]/g, \" \"))\n .replace(/\"(?:[^\"\\\\\\n]|\\\\.)*\"/g, match => `\"${\" \".repeat(Math.max(0, match.length - 2))}\"`)\n .replace(/'(?:[^'\\\\\\n]|\\\\.)*'/g, match => `'${\" \".repeat(Math.max(0, match.length - 2))}'`);\n}\n\n/** Import specifiers, keeping the raw line so the report can point at it. */\nfunction imports(source: string): Array<{ specifier: string; line: number; typeOnly: boolean }> {\n const found: Array<{ specifier: string; line: number; typeOnly: boolean }> = [];\n const lines = source.split(\"\\n\");\n\n lines.forEach((line, index) => {\n const match = /(?:^|[\\s;}])(?:import|export)(\\s+type)?\\s[^;]*?from\\s*[\"']([^\"']+)[\"']/.exec(line)\n ?? /(?:^|[\\s;}])import\\s*[\"']([^\"']+)[\"']/.exec(line);\n if (!match) return;\n const specifier = match[2] ?? match[1];\n if (!specifier) return;\n found.push({\n specifier,\n line: index + 1,\n // `import type` is erased before anything runs, so it constrains\n // nothing at runtime — flagging it would be noise that trains people\n // to ignore the report.\n typeOnly: Boolean(match[1]) || /\\bimport\\s+type\\b/.test(line)\n });\n });\n\n return found;\n}\n\n/**\n * The names a file imports from the project's resources module.\n *\n * Matched on the specifier's last path segment — `../config/resources`,\n * `../../config/resources.js`, `@app/config/resources` — because that is the\n * one convention `rebase resources` reads declarations from, and a function\n * importing a handle from anywhere else has put its declaration somewhere\n * the graph does not look. Type-only imports are skipped: they reach nothing\n * at runtime.\n */\nfunction resourceImports(source: string): string[] {\n const names = new Set<string>();\n for (const line of source.split(\"\\n\")) {\n const match = /(?:^|[\\s;}])import\\s+(?!type\\b)\\{([^}]*)\\}\\s*from\\s*[\"']([^\"']+)[\"']/.exec(line);\n if (!match) continue;\n const specifier = match[2].replace(/\\.[cm]?[jt]s$/, \"\");\n if (!/(?:^|\\/)resources(?:\\/index)?$/.test(specifier)) continue;\n for (const part of match[1].split(\",\")) {\n const name = part.trim().replace(/^type\\s+/, \"\");\n if (!name || part.trim().startsWith(\"type \")) continue;\n // `media as photos` imports `media`; the local alias is not the export.\n names.add(name.split(/\\s+as\\s+/)[0].trim());\n }\n }\n return [...names].sort();\n}\n\n/**\n * `process.env` (and friends) read where the module is *evaluated*.\n *\n * Indentation is the heuristic for \"inside something that runs later\", which is\n * exact for every formatter anyone uses on a function file and wrong only for\n * code nobody writes. The cost of being wrong is one line of advisory output.\n */\nfunction moduleScopeEnvReads(source: string): number[] {\n const lines = stripNonCode(source).split(\"\\n\");\n const hits: number[] = [];\n lines.forEach((line, index) => {\n if (/^\\s/.test(line) || line.trim() === \"\") return;\n if (/\\bprocess\\.env\\b/.test(line)) hits.push(index + 1);\n });\n return hits;\n}\n\nfunction packageOf(specifier: string): string {\n return specifier.startsWith(\"@\")\n ? specifier.split(\"/\").slice(0, 2).join(\"/\")\n : specifier.split(\"/\")[0];\n}\n\n/** Analyse one function file's source. Exported for testing. */\nexport function analyseFunctionSource(source: string, name: string, file: string): FunctionPortability {\n const issues: PortabilityIssue[] = [];\n // Two views of the same file: imports need their string literals intact,\n // identifier scans need them gone. Both keep line numbers.\n const withStrings = stripComments(source);\n const code = stripNonCode(source);\n\n for (const { specifier, line, typeOnly } of imports(withStrings)) {\n if (typeOnly) continue;\n if (specifier.startsWith(\".\") || specifier.startsWith(\"/\")) continue;\n\n const bare = specifier.replace(/^node:/, \"\");\n if (specifier.startsWith(\"node:\") || NODE_BUILTINS.has(bare.split(\"/\")[0])) {\n issues.push({\n kind: \"node-builtin\",\n line,\n message: `imports the Node built-in \"${specifier}\"`,\n actionable: false\n });\n continue;\n }\n\n const pkg = packageOf(specifier);\n\n // The exact specifier, not the package: `@rebasepro/server/functions`\n // is the portable subpath and is the thing being recommended.\n if (specifier === \"@rebasepro/server\") {\n // Not a portability problem in itself — the root barrel works\n // perfectly on Node — but it is the single edit that would let this\n // file move, and it is one line.\n issues.push({\n kind: \"root-barrel-import\",\n line,\n message: \"imports from \\\"@rebasepro/server\\\" — the whole framework, Node-only. \"\n + \"Use \\\"@rebasepro/server/functions\\\" for the authoring surface.\",\n actionable: false\n });\n continue;\n }\n\n const why = NODE_ONLY_PACKAGES.get(pkg);\n if (why) {\n issues.push({\n kind: \"node-only-package\",\n line,\n message: `imports \"${specifier}\", which needs ${why}`,\n actionable: false\n });\n }\n }\n\n for (const line of moduleScopeEnvReads(code)) {\n issues.push({\n kind: \"module-scope-env\",\n line,\n message: \"reads process.env at module scope. If the variable is unset the whole file \"\n + \"fails to import, and the loader reports that as a skipped function — read it \"\n + \"inside the handler instead (`requireEnv(c, \\\"NAME\\\")`, or `lazyResource`).\",\n actionable: true\n });\n }\n\n return {\n name,\n file,\n issues,\n // The advisory import-path finding does not make a function unportable —\n // it is the same code either way, reachable through a heavier door.\n portable: issues.every(issue => issue.kind === \"root-barrel-import\"),\n resourceImports: resourceImports(withStrings)\n };\n}\n\n/**\n * Analyse every function in a directory.\n *\n * @param functionsDir Absolute path to the functions directory.\n * @param projectRoot For rendering paths people recognise.\n */\nexport function analyseFunctionsDirectory(functionsDir: string, projectRoot: string): FunctionPortability[] {\n return functionFiles(functionsDir).map(fileName => {\n const absolute = path.join(functionsDir, fileName);\n const source = fs.readFileSync(absolute, \"utf8\");\n return analyseFunctionSource(\n source,\n path.basename(fileName, path.extname(fileName)),\n path.relative(projectRoot, absolute)\n );\n });\n}\n\n/**\n * The lines to print after a build, or nothing at all.\n *\n * Silent when every function is portable and nothing is actionable, because a\n * report that always prints is a report nobody reads. Actionable findings are\n * always shown; the rest collapse to a single count with a pointer.\n */\nexport function summarisePortability(results: FunctionPortability[]): string[] {\n if (results.length === 0) return [];\n\n const actionable = results.flatMap(result =>\n result.issues.filter(issue => issue.actionable).map(issue => ({ result, issue })));\n const nodeOnly = results.filter(result => !result.portable);\n\n const lines: string[] = [];\n\n for (const { result, issue } of actionable) {\n lines.push(` ⚠ ${result.file}:${issue.line} — ${issue.message}`);\n }\n\n if (nodeOnly.length > 0) {\n const names = nodeOnly.map(result => result.name).join(\", \");\n lines.push(\n ` ℹ ${nodeOnly.length} of ${results.length} function(s) depend on Node: ${names}`\n );\n lines.push(\" Fine for any Node deployment — recorded in the manifest so a host knows.\");\n }\n\n return lines;\n}\n"],"mappings":"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"}
|