@rebasepro/cli 0.11.1-canary.g8caabf3 → 0.11.1-canary.g95e61c2
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/dist/bundle.d.ts +9 -2
- package/dist/index.es.js +136 -5
- package/dist/index.es.js.map +1 -1
- package/dist/manifest.d.ts +1 -1
- package/package.json +7 -7
- package/templates/eject/backend/src/index.ts +41 -27
- package/templates/template/.env.example +15 -0
- package/templates/template/README.md +22 -6
- package/templates/template/ai-instructions.md +1 -0
- package/templates/template/backend/functions/hello.ts +45 -14
- package/templates/template/backend/tsconfig.json +7 -1
package/dist/bundle.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type NativeDependency, type RebaseBundleManifest, type RebaseBackendAppConfig } from "@rebasepro/types";
|
|
1
|
+
import { type DeclaredStorageSources, type NativeDependency, type RebaseBundleManifest, type RebaseBackendAppConfig } from "@rebasepro/types";
|
|
2
2
|
export declare const DEFAULT_BUNDLE_DIR = "dist-bundle";
|
|
3
3
|
export interface BuildBundleOptions {
|
|
4
4
|
projectRoot: string;
|
|
@@ -8,6 +8,13 @@ export interface BuildBundleOptions {
|
|
|
8
8
|
outDir?: string;
|
|
9
9
|
/** Runtime range from the manifest, recorded for compatibility checks. */
|
|
10
10
|
runtimeRange: string;
|
|
11
|
+
/**
|
|
12
|
+
* The `storage` block of `rebase.json` — which buckets this project uses.
|
|
13
|
+
*
|
|
14
|
+
* Passed in rather than re-read here so `rebase.json` is parsed and validated
|
|
15
|
+
* once, by the command that owns it.
|
|
16
|
+
*/
|
|
17
|
+
storage?: DeclaredStorageSources;
|
|
11
18
|
/** Skip type checking. Faster, and strictly worse — for iteration only. */
|
|
12
19
|
skipTypeCheck?: boolean;
|
|
13
20
|
/** Skip regenerating the Drizzle schema from the collections. */
|
|
@@ -37,7 +44,7 @@ export interface BuildBundleResult {
|
|
|
37
44
|
* says exactly how to proceed, while a false positive would hand back the crash
|
|
38
45
|
* loop this exists to prevent.
|
|
39
46
|
*/
|
|
40
|
-
export declare function detectStorageAuthorize(compiledConfigDir: string): boolean;
|
|
47
|
+
export declare function detectStorageAuthorize(compiledConfigDir: string, depth?: number): boolean;
|
|
41
48
|
/**
|
|
42
49
|
* Detect native code in the dependency closure.
|
|
43
50
|
*
|
package/dist/index.es.js
CHANGED
|
@@ -12,7 +12,7 @@ import crypto from "crypto";
|
|
|
12
12
|
import { execSync, spawn, spawnSync } from "child_process";
|
|
13
13
|
import os from "os";
|
|
14
14
|
import { createRebaseClient } from "@rebasepro/client";
|
|
15
|
-
import { BUNDLE_FORMAT_VERSION, RUNTIME_CONTRACT_VERSION, computeSchemaVersion, deserializeCollections } from "@rebasepro/types";
|
|
15
|
+
import { BUNDLE_FORMAT_VERSION, RUNTIME_CONTRACT_VERSION, computeSchemaVersion, deserializeCollections, findStorageSuffixCollision, normalizeStorageSources, storageEnvSuffix } from "@rebasepro/types";
|
|
16
16
|
import { generateSDK } from "@rebasepro/codegen";
|
|
17
17
|
import { createRequire } from "module";
|
|
18
18
|
//#region src/utils/package-manager.ts
|
|
@@ -2338,17 +2338,90 @@ function validateManifest(raw) {
|
|
|
2338
2338
|
}
|
|
2339
2339
|
byPath.set(at, name);
|
|
2340
2340
|
}
|
|
2341
|
+
const storage = validateStorageSources(raw.storage, issues);
|
|
2341
2342
|
if (issues.length > 0) return { issues };
|
|
2342
2343
|
return {
|
|
2343
2344
|
manifest: {
|
|
2344
2345
|
$schema: typeof raw.$schema === "string" ? raw.$schema : void 0,
|
|
2345
2346
|
rebase: raw.rebase,
|
|
2346
|
-
apps
|
|
2347
|
+
apps,
|
|
2348
|
+
...storage ? { storage } : {}
|
|
2347
2349
|
},
|
|
2348
2350
|
issues
|
|
2349
2351
|
};
|
|
2350
2352
|
}
|
|
2351
2353
|
/**
|
|
2354
|
+
* Validate the `storage` block — which buckets this project uses.
|
|
2355
|
+
*
|
|
2356
|
+
* Absent means one default source, so `undefined` is a valid answer and not an
|
|
2357
|
+
* issue. Everything else is checked strictly, because each key here becomes the
|
|
2358
|
+
* suffix of a set of environment variables: a key that cannot become a variable
|
|
2359
|
+
* name, or two keys that become the *same* one, are failures worth catching
|
|
2360
|
+
* while someone is looking at the file rather than at a tenant serving one
|
|
2361
|
+
* bucket's files with another's credentials.
|
|
2362
|
+
*/
|
|
2363
|
+
function validateStorageSources(raw, issues) {
|
|
2364
|
+
if (raw === void 0) return void 0;
|
|
2365
|
+
if (!isRecord(raw)) {
|
|
2366
|
+
issues.push({
|
|
2367
|
+
path: "storage",
|
|
2368
|
+
message: "must be an object keyed by storage source name"
|
|
2369
|
+
});
|
|
2370
|
+
return;
|
|
2371
|
+
}
|
|
2372
|
+
const sources = {};
|
|
2373
|
+
for (const [key, value] of Object.entries(raw)) {
|
|
2374
|
+
if (!isRecord(value)) {
|
|
2375
|
+
issues.push({
|
|
2376
|
+
path: `storage.${key}`,
|
|
2377
|
+
message: "must be an object"
|
|
2378
|
+
});
|
|
2379
|
+
continue;
|
|
2380
|
+
}
|
|
2381
|
+
if (typeof value.engine !== "string" || value.engine.trim() === "") {
|
|
2382
|
+
issues.push({
|
|
2383
|
+
path: `storage.${key}.engine`,
|
|
2384
|
+
message: "is required, e.g. \"s3\", \"gcs\" or \"local\""
|
|
2385
|
+
});
|
|
2386
|
+
continue;
|
|
2387
|
+
}
|
|
2388
|
+
if (value.transport !== void 0 && value.transport !== "server" && value.transport !== "direct") {
|
|
2389
|
+
issues.push({
|
|
2390
|
+
path: `storage.${key}.transport`,
|
|
2391
|
+
message: "must be \"server\" or \"direct\""
|
|
2392
|
+
});
|
|
2393
|
+
continue;
|
|
2394
|
+
}
|
|
2395
|
+
if (value.label !== void 0 && typeof value.label !== "string") {
|
|
2396
|
+
issues.push({
|
|
2397
|
+
path: `storage.${key}.label`,
|
|
2398
|
+
message: "must be a string"
|
|
2399
|
+
});
|
|
2400
|
+
continue;
|
|
2401
|
+
}
|
|
2402
|
+
try {
|
|
2403
|
+
storageEnvSuffix(key);
|
|
2404
|
+
} catch {
|
|
2405
|
+
issues.push({
|
|
2406
|
+
path: `storage.${key}`,
|
|
2407
|
+
message: "name cannot become an environment variable suffix — use a name containing at least one letter or digit"
|
|
2408
|
+
});
|
|
2409
|
+
continue;
|
|
2410
|
+
}
|
|
2411
|
+
sources[key] = {
|
|
2412
|
+
engine: value.engine,
|
|
2413
|
+
...value.transport !== void 0 ? { transport: value.transport } : {},
|
|
2414
|
+
...value.label !== void 0 ? { label: value.label } : {}
|
|
2415
|
+
};
|
|
2416
|
+
}
|
|
2417
|
+
const collision = findStorageSuffixCollision(Object.keys(sources));
|
|
2418
|
+
if (collision) issues.push({
|
|
2419
|
+
path: `storage.${collision.b}`,
|
|
2420
|
+
message: `maps to the same environment variable suffix ("${collision.suffix || "(none)"}") as "${collision.a}", so the two would read each other's configuration — rename one of them`
|
|
2421
|
+
});
|
|
2422
|
+
return Object.keys(sources).length > 0 ? sources : void 0;
|
|
2423
|
+
}
|
|
2424
|
+
/**
|
|
2352
2425
|
* Infer a manifest from a directory that does not have one.
|
|
2353
2426
|
*
|
|
2354
2427
|
* This mirrors exactly what the template scaffolds, which is what makes adopting
|
|
@@ -3185,16 +3258,27 @@ async function writeBundleTsconfig(projectRoot, outDir, includes, skipTypeCheck)
|
|
|
3185
3258
|
* says exactly how to proceed, while a false positive would hand back the crash
|
|
3186
3259
|
* loop this exists to prevent.
|
|
3187
3260
|
*/
|
|
3188
|
-
function detectStorageAuthorize(compiledConfigDir) {
|
|
3261
|
+
function detectStorageAuthorize(compiledConfigDir, depth = 0) {
|
|
3189
3262
|
const indexPath = [
|
|
3190
3263
|
".js",
|
|
3191
3264
|
".mjs",
|
|
3192
3265
|
".ts"
|
|
3193
3266
|
].map((ext) => path.join(compiledConfigDir, `index${ext}`)).find((candidate) => fs.existsSync(candidate));
|
|
3194
3267
|
if (!indexPath) return false;
|
|
3268
|
+
return moduleExportsStorageAuthorize(indexPath, depth);
|
|
3269
|
+
}
|
|
3270
|
+
/**
|
|
3271
|
+
* Whether one compiled module re-exports or defines `storageAuthorize`.
|
|
3272
|
+
*
|
|
3273
|
+
* Split out from {@link detectStorageAuthorize} so a wildcard re-export can be
|
|
3274
|
+
* followed. `export * from "./storage.js"` is an ordinary way to write a config
|
|
3275
|
+
* barrel, and treating it as "no hook" rejected deploys that were correct — with
|
|
3276
|
+
* a message telling the developer to add a hook they had already written.
|
|
3277
|
+
*/
|
|
3278
|
+
function moduleExportsStorageAuthorize(modulePath, depth) {
|
|
3195
3279
|
let source;
|
|
3196
3280
|
try {
|
|
3197
|
-
source = fs.readFileSync(
|
|
3281
|
+
source = fs.readFileSync(modulePath, "utf8");
|
|
3198
3282
|
} catch {
|
|
3199
3283
|
return false;
|
|
3200
3284
|
}
|
|
@@ -3203,9 +3287,48 @@ function detectStorageAuthorize(compiledConfigDir) {
|
|
|
3203
3287
|
const parts = entry.split(/\bas\b/);
|
|
3204
3288
|
return parts[parts.length - 1].trim();
|
|
3205
3289
|
}).includes("storageAuthorize")) return true;
|
|
3290
|
+
if (depth < 3) for (const clause of source.matchAll(/\bexport\s*\*\s*from\s*["']([^"']+)["']/g)) {
|
|
3291
|
+
const specifier = clause[1];
|
|
3292
|
+
if (!specifier.startsWith(".")) continue;
|
|
3293
|
+
const resolved = resolveRelativeModule(path.dirname(modulePath), specifier);
|
|
3294
|
+
if (resolved && moduleExportsStorageAuthorize(resolved, depth + 1)) return true;
|
|
3295
|
+
}
|
|
3206
3296
|
return false;
|
|
3207
3297
|
}
|
|
3208
3298
|
/**
|
|
3299
|
+
* Resolve a relative ESM specifier to a file on disk.
|
|
3300
|
+
*
|
|
3301
|
+
* Compiled output carries explicit `.js` extensions, but the same function reads
|
|
3302
|
+
* TypeScript sources during a source boot, where the specifier may be
|
|
3303
|
+
* extensionless or point at a directory index.
|
|
3304
|
+
*/
|
|
3305
|
+
function resolveRelativeModule(fromDir, specifier) {
|
|
3306
|
+
const base = path.resolve(fromDir, specifier);
|
|
3307
|
+
const candidates = [
|
|
3308
|
+
base,
|
|
3309
|
+
`${base}.js`,
|
|
3310
|
+
`${base}.mjs`,
|
|
3311
|
+
`${base}.ts`,
|
|
3312
|
+
path.join(base, "index.js"),
|
|
3313
|
+
path.join(base, "index.mjs"),
|
|
3314
|
+
path.join(base, "index.ts")
|
|
3315
|
+
];
|
|
3316
|
+
for (const candidate of candidates) try {
|
|
3317
|
+
if (fs.existsSync(candidate) && fs.statSync(candidate).isFile()) return candidate;
|
|
3318
|
+
} catch {
|
|
3319
|
+
continue;
|
|
3320
|
+
}
|
|
3321
|
+
if (/\.js$/.test(base)) {
|
|
3322
|
+
const asTs = base.replace(/\.js$/, ".ts");
|
|
3323
|
+
try {
|
|
3324
|
+
if (fs.existsSync(asTs) && fs.statSync(asTs).isFile()) return asTs;
|
|
3325
|
+
} catch {
|
|
3326
|
+
return null;
|
|
3327
|
+
}
|
|
3328
|
+
}
|
|
3329
|
+
return null;
|
|
3330
|
+
}
|
|
3331
|
+
/**
|
|
3209
3332
|
* Detect native code in the dependency closure.
|
|
3210
3333
|
*
|
|
3211
3334
|
* Walks declared runtime dependencies breadth-first through `node_modules`,
|
|
@@ -3559,6 +3682,9 @@ async function buildBundle(options) {
|
|
|
3559
3682
|
const declared = collectDeclaredDependencies(projectRoot);
|
|
3560
3683
|
const nativeModules = detectNativeDependencies(projectRoot, declared);
|
|
3561
3684
|
const declaresStorageAuthorize = detectStorageAuthorize(path.join(outDir, paths.config));
|
|
3685
|
+
const storageSources = normalizeStorageSources(options.storage, void 0);
|
|
3686
|
+
const collision = findStorageSuffixCollision(storageSources.map((s) => s.key));
|
|
3687
|
+
if (collision) throw new Error(`Storage sources "${collision.a}" and "${collision.b}" in rebase.json both map to the environment variable suffix "${collision.suffix || "(none)"}", so they would read each other's configuration. Rename one of them.`);
|
|
3562
3688
|
const schemaOut = paths.schema.replace(/\.ts$/, ".js");
|
|
3563
3689
|
const relative = (target) => fs.existsSync(path.join(outDir, target)) ? target : void 0;
|
|
3564
3690
|
const manifest = {
|
|
@@ -3584,7 +3710,10 @@ async function buildBundle(options) {
|
|
|
3584
3710
|
native: nativeModules.length > 0,
|
|
3585
3711
|
nativeModules: nativeModules.length > 0 ? nativeModules : void 0
|
|
3586
3712
|
},
|
|
3587
|
-
storage: {
|
|
3713
|
+
storage: {
|
|
3714
|
+
authorize: declaresStorageAuthorize,
|
|
3715
|
+
...storageSources.length > 0 ? { sources: storageSources } : {}
|
|
3716
|
+
},
|
|
3588
3717
|
deps: { declared },
|
|
3589
3718
|
build: {
|
|
3590
3719
|
cli: resolveCliVersion(),
|
|
@@ -4040,6 +4169,7 @@ async function buildCommand(rawArgs = []) {
|
|
|
4040
4169
|
app,
|
|
4041
4170
|
outDir: args["--out"],
|
|
4042
4171
|
runtimeRange: manifest.rebase,
|
|
4172
|
+
storage: manifest.storage,
|
|
4043
4173
|
skipTypeCheck: args["--skip-type-check"],
|
|
4044
4174
|
skipSchema: args["--skip-schema"]
|
|
4045
4175
|
});
|
|
@@ -5976,6 +6106,7 @@ async function deployBundle(opts) {
|
|
|
5976
6106
|
appName: backend.name,
|
|
5977
6107
|
app: backend.app,
|
|
5978
6108
|
runtimeRange: loaded.manifest.rebase,
|
|
6109
|
+
storage: loaded.manifest.storage,
|
|
5979
6110
|
skipTypeCheck: opts.skipTypeCheck,
|
|
5980
6111
|
log: (m) => console.log(chalk.gray(m))
|
|
5981
6112
|
})).outDir;
|