@donotdev/cli 0.0.16 → 0.0.17
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/dependencies-matrix.json +33 -129
- package/dist/bin/commands/bump.js +9 -2
- package/dist/bin/commands/create-app.js +195 -40
- package/dist/bin/commands/create-project.js +195 -40
- package/dist/bin/commands/deploy.js +51 -20
- package/dist/bin/commands/doctor.js +249 -56
- package/dist/bin/commands/emu.js +18 -20
- package/dist/bin/commands/make-admin.js +30 -10
- package/dist/bin/commands/setup.js +512 -122
- package/dist/bin/commands/type-check.d.ts.map +1 -1
- package/dist/bin/commands/type-check.js +7 -3
- package/dist/bin/commands/type-check.js.map +1 -1
- package/dist/bin/donotdev.js +26 -14
- package/dist/index.js +264 -80
- package/package.json +1 -1
- package/templates/root-consumer/guides/dndev/ENV_SETUP.md.example +6 -6
- package/templates/root-consumer/guides/dndev/INDEX.md.example +2 -2
- package/templates/root-consumer/guides/dndev/SETUP_AUTH.md.example +13 -6
- package/templates/root-consumer/guides/dndev/SETUP_CRUD.md.example +149 -1086
- package/templates/root-consumer/guides/dndev/SETUP_FIREBASE.md.example +68 -16
- package/templates/root-consumer/guides/dndev/SETUP_FUNCTIONS.md.example +6 -111
- package/templates/root-consumer/guides/dndev/SETUP_SUPABASE.md.example +123 -32
- package/templates/root-consumer/guides/dndev/SETUP_VERCEL.md.example +108 -91
- package/templates/root-consumer/guides/dndev/advanced/EMULATORS.md.example +2 -2
- package/dist/bin/commands/firebase-setup.d.ts +0 -6
- package/dist/bin/commands/firebase-setup.d.ts.map +0 -1
- package/dist/bin/commands/firebase-setup.js +0 -7
- package/dist/bin/commands/firebase-setup.js.map +0 -1
- package/dist/bin/commands/supabase-setup.d.ts +0 -6
- package/dist/bin/commands/supabase-setup.d.ts.map +0 -1
- package/dist/bin/commands/supabase-setup.js +0 -7
- package/dist/bin/commands/supabase-setup.js.map +0 -1
package/dist/index.js
CHANGED
|
@@ -8469,7 +8469,14 @@ function readServiceAccountKey(filePath) {
|
|
|
8469
8469
|
throw new DoNotDevError(
|
|
8470
8470
|
`Invalid service account key: missing required fields`,
|
|
8471
8471
|
DO_NOT_DEV_ERROR_CODES.CONFIG_INVALID,
|
|
8472
|
-
{
|
|
8472
|
+
{
|
|
8473
|
+
context: {
|
|
8474
|
+
filePath,
|
|
8475
|
+
missingFields: ["project_id", "private_key", "client_email"].filter(
|
|
8476
|
+
(f) => !key[f]
|
|
8477
|
+
)
|
|
8478
|
+
}
|
|
8479
|
+
}
|
|
8473
8480
|
);
|
|
8474
8481
|
}
|
|
8475
8482
|
return content;
|
|
@@ -8876,9 +8883,7 @@ function getCLIInstallInstructions(tool) {
|
|
|
8876
8883
|
"Or: winget install Supabase.CLI",
|
|
8877
8884
|
"Or download from: https://github.com/supabase/cli/releases"
|
|
8878
8885
|
],
|
|
8879
|
-
darwin: [
|
|
8880
|
-
"brew install supabase/tap/supabase"
|
|
8881
|
-
],
|
|
8886
|
+
darwin: ["brew install supabase/tap/supabase"],
|
|
8882
8887
|
linux: [
|
|
8883
8888
|
"brew install supabase/tap/supabase",
|
|
8884
8889
|
"Or see: https://supabase.com/docs/guides/cli"
|
|
@@ -8886,7 +8891,11 @@ function getCLIInstallInstructions(tool) {
|
|
|
8886
8891
|
},
|
|
8887
8892
|
[CLI_TOOLS.VERCEL]: {
|
|
8888
8893
|
win32: ["npm install -g vercel", "Or: npx vercel (no install)"],
|
|
8889
|
-
darwin: [
|
|
8894
|
+
darwin: [
|
|
8895
|
+
"npm install -g vercel",
|
|
8896
|
+
"Or: brew install vercel",
|
|
8897
|
+
"Or: npx vercel"
|
|
8898
|
+
],
|
|
8890
8899
|
linux: ["npm install -g vercel", "Or: npx vercel"]
|
|
8891
8900
|
},
|
|
8892
8901
|
[CLI_TOOLS.SENTRY_CLI]: {
|
|
@@ -17034,7 +17043,9 @@ async function deploySupabaseFunctions(appDir, config) {
|
|
|
17034
17043
|
const supabaseDir = joinPath(appDir, "supabase");
|
|
17035
17044
|
const functionsDir = joinPath(supabaseDir, "functions");
|
|
17036
17045
|
if (!pathExists(functionsDir)) {
|
|
17037
|
-
log.warn(
|
|
17046
|
+
log.warn(
|
|
17047
|
+
"No supabase/functions/ directory found. Skipping Supabase functions deployment."
|
|
17048
|
+
);
|
|
17038
17049
|
return;
|
|
17039
17050
|
}
|
|
17040
17051
|
requireCLI(
|
|
@@ -17050,10 +17061,14 @@ async function deploySupabaseFunctions(appDir, config) {
|
|
|
17050
17061
|
return pathExists(indexPath);
|
|
17051
17062
|
});
|
|
17052
17063
|
if (functionDirs.length === 0) {
|
|
17053
|
-
log.warn(
|
|
17064
|
+
log.warn(
|
|
17065
|
+
"No Edge Functions found in supabase/functions/. Skipping deployment."
|
|
17066
|
+
);
|
|
17054
17067
|
return;
|
|
17055
17068
|
}
|
|
17056
|
-
log.info(
|
|
17069
|
+
log.info(
|
|
17070
|
+
`Found ${functionDirs.length} Edge Function(s): ${functionDirs.join(", ")}`
|
|
17071
|
+
);
|
|
17057
17072
|
const s = Y2();
|
|
17058
17073
|
for (const functionName of functionDirs) {
|
|
17059
17074
|
s.start(`Deploying Edge Function: ${functionName}...`);
|
|
@@ -17072,7 +17087,12 @@ async function deploySupabaseFunctions(appDir, config) {
|
|
|
17072
17087
|
throw new DoNotDevError(
|
|
17073
17088
|
`Failed to deploy Supabase Edge Function: ${functionName}`,
|
|
17074
17089
|
"deployment-failed",
|
|
17075
|
-
{
|
|
17090
|
+
{
|
|
17091
|
+
context: {
|
|
17092
|
+
functionName,
|
|
17093
|
+
error: error2 instanceof Error ? error2.message : String(error2)
|
|
17094
|
+
}
|
|
17095
|
+
}
|
|
17076
17096
|
);
|
|
17077
17097
|
}
|
|
17078
17098
|
}
|
|
@@ -17892,10 +17912,7 @@ FIREBASE_AUTH_EMULATOR_HOST=${authEmulatorHost}
|
|
|
17892
17912
|
// packages/tooling/src/apps/emu-supabase.ts
|
|
17893
17913
|
init_utils();
|
|
17894
17914
|
init_cli_output();
|
|
17895
|
-
import {
|
|
17896
|
-
spawn as spawn3,
|
|
17897
|
-
execSync as execSync5
|
|
17898
|
-
} from "node:child_process";
|
|
17915
|
+
import { spawn as spawn3, execSync as execSync5 } from "node:child_process";
|
|
17899
17916
|
import { platform as platform3 } from "node:os";
|
|
17900
17917
|
function isDockerRunning() {
|
|
17901
17918
|
try {
|
|
@@ -17945,7 +17962,10 @@ async function startSupabase(app, _projectRoot) {
|
|
|
17945
17962
|
if (!childProcess.pid) return;
|
|
17946
17963
|
if (isWindows) {
|
|
17947
17964
|
try {
|
|
17948
|
-
execSync5(`taskkill /F /T /PID ${childProcess.pid}`, {
|
|
17965
|
+
execSync5(`taskkill /F /T /PID ${childProcess.pid}`, {
|
|
17966
|
+
stdio: "ignore",
|
|
17967
|
+
timeout: 2e3
|
|
17968
|
+
});
|
|
17949
17969
|
} catch {
|
|
17950
17970
|
}
|
|
17951
17971
|
} else {
|
|
@@ -17975,10 +17995,7 @@ async function startSupabase(app, _projectRoot) {
|
|
|
17975
17995
|
// packages/tooling/src/apps/emu-vercel.ts
|
|
17976
17996
|
init_utils();
|
|
17977
17997
|
init_cli_output();
|
|
17978
|
-
import {
|
|
17979
|
-
spawn as spawn4,
|
|
17980
|
-
execSync as execSync6
|
|
17981
|
-
} from "node:child_process";
|
|
17998
|
+
import { spawn as spawn4, execSync as execSync6 } from "node:child_process";
|
|
17982
17999
|
import { platform as platform4 } from "node:os";
|
|
17983
18000
|
async function startVercel(app, _projectRoot) {
|
|
17984
18001
|
log.info(`Starting Vercel dev server for ${app.name}...
|
|
@@ -17991,20 +18008,19 @@ async function startVercel(app, _projectRoot) {
|
|
|
17991
18008
|
return 1;
|
|
17992
18009
|
}
|
|
17993
18010
|
const isWindows = platform4() === "win32";
|
|
17994
|
-
const childProcess = spawn4(
|
|
17995
|
-
"
|
|
17996
|
-
|
|
17997
|
-
|
|
17998
|
-
|
|
17999
|
-
cwd: app.path,
|
|
18000
|
-
shell: isWindows
|
|
18001
|
-
}
|
|
18002
|
-
);
|
|
18011
|
+
const childProcess = spawn4("vercel", ["dev"], {
|
|
18012
|
+
stdio: "inherit",
|
|
18013
|
+
cwd: app.path,
|
|
18014
|
+
shell: isWindows
|
|
18015
|
+
});
|
|
18003
18016
|
const cleanup = () => {
|
|
18004
18017
|
if (!childProcess.pid) return;
|
|
18005
18018
|
if (isWindows) {
|
|
18006
18019
|
try {
|
|
18007
|
-
execSync6(`taskkill /F /T /PID ${childProcess.pid}`, {
|
|
18020
|
+
execSync6(`taskkill /F /T /PID ${childProcess.pid}`, {
|
|
18021
|
+
stdio: "ignore",
|
|
18022
|
+
timeout: 2e3
|
|
18023
|
+
});
|
|
18008
18024
|
} catch {
|
|
18009
18025
|
}
|
|
18010
18026
|
} else {
|
|
@@ -18051,7 +18067,9 @@ async function main3(options) {
|
|
|
18051
18067
|
switch (app.platform) {
|
|
18052
18068
|
case "firebase": {
|
|
18053
18069
|
if (!app.hasFunctions) {
|
|
18054
|
-
log.error(
|
|
18070
|
+
log.error(
|
|
18071
|
+
`App "${app.name}" has Firebase config but no functions directory.`
|
|
18072
|
+
);
|
|
18055
18073
|
log.error('Use "dndev dev" for apps without backend functions.\n');
|
|
18056
18074
|
return 1;
|
|
18057
18075
|
}
|
|
@@ -18518,7 +18536,11 @@ function getMatrixPath(mode) {
|
|
|
18518
18536
|
const executionMode = mode || detectExecutionMode();
|
|
18519
18537
|
if (executionMode === "development") {
|
|
18520
18538
|
const templatesRoot = getTemplatesRoot();
|
|
18521
|
-
const devPath = normalizePath(
|
|
18539
|
+
const devPath = normalizePath(
|
|
18540
|
+
templatesRoot,
|
|
18541
|
+
"..",
|
|
18542
|
+
"dependencies-matrix.json"
|
|
18543
|
+
);
|
|
18522
18544
|
if (pathExists(devPath)) {
|
|
18523
18545
|
return devPath;
|
|
18524
18546
|
}
|
|
@@ -18689,7 +18711,12 @@ async function runQuestionnaire(questions, initialAnswers, showWIP, askFor) {
|
|
|
18689
18711
|
async function deployFrontend(appDir, serviceAccountPath, projectId, config) {
|
|
18690
18712
|
const s = Y2();
|
|
18691
18713
|
s.start("Deploying frontend to Firebase Hosting...");
|
|
18692
|
-
const args = buildFirebaseDeployArgs(
|
|
18714
|
+
const args = buildFirebaseDeployArgs(
|
|
18715
|
+
"hosting",
|
|
18716
|
+
projectId,
|
|
18717
|
+
config.debug,
|
|
18718
|
+
config.force ?? true
|
|
18719
|
+
);
|
|
18693
18720
|
const result = executeFirebaseCommand(args, {
|
|
18694
18721
|
cwd: appDir,
|
|
18695
18722
|
serviceAccountPath,
|
|
@@ -18736,7 +18763,9 @@ async function deployVercelFrontend(appDir, _config) {
|
|
|
18736
18763
|
if (result.status !== 0) {
|
|
18737
18764
|
s.stop("Vercel deployment failed");
|
|
18738
18765
|
const errOutput = result.stderr?.trim();
|
|
18739
|
-
throw new Error(
|
|
18766
|
+
throw new Error(
|
|
18767
|
+
errOutput || `Vercel deploy exited with code ${result.status}`
|
|
18768
|
+
);
|
|
18740
18769
|
}
|
|
18741
18770
|
s.stop("Frontend deployed to Vercel");
|
|
18742
18771
|
} catch (err) {
|
|
@@ -19193,7 +19222,12 @@ async function deployRules(appDir, serviceAccountPath, projectId, config, option
|
|
|
19193
19222
|
const targetNames = targets.join(", ");
|
|
19194
19223
|
const s = Y2();
|
|
19195
19224
|
s.start(`Deploying ${targetNames}...`);
|
|
19196
|
-
const args = buildFirebaseDeployArgs(
|
|
19225
|
+
const args = buildFirebaseDeployArgs(
|
|
19226
|
+
targets,
|
|
19227
|
+
projectId,
|
|
19228
|
+
config.debug,
|
|
19229
|
+
config.force ?? true
|
|
19230
|
+
);
|
|
19197
19231
|
const result = executeFirebaseCommand(args, {
|
|
19198
19232
|
cwd: appDir,
|
|
19199
19233
|
serviceAccountPath,
|
|
@@ -19567,13 +19601,17 @@ async function main6(options = {}) {
|
|
|
19567
19601
|
const availableApps = detectAvailableApps();
|
|
19568
19602
|
if (availableApps.length === 0) {
|
|
19569
19603
|
if (providerInfo.hasSupabase && !providerInfo.hasFirebase) {
|
|
19570
|
-
log.info(
|
|
19604
|
+
log.info(
|
|
19605
|
+
"Supabase project detected. Deploying Supabase resources..."
|
|
19606
|
+
);
|
|
19571
19607
|
} else {
|
|
19572
19608
|
log.info("No apps with firebase.json or supabase/ directory found.");
|
|
19573
19609
|
log.info(
|
|
19574
19610
|
"To deploy, ensure your app has a firebase.json or supabase/ configuration."
|
|
19575
19611
|
);
|
|
19576
|
-
log.info(
|
|
19612
|
+
log.info(
|
|
19613
|
+
"Run from a DoNotDev project, or create one with: dndev init"
|
|
19614
|
+
);
|
|
19577
19615
|
return;
|
|
19578
19616
|
}
|
|
19579
19617
|
}
|
|
@@ -19853,12 +19891,7 @@ async function main6(options = {}) {
|
|
|
19853
19891
|
}
|
|
19854
19892
|
}
|
|
19855
19893
|
}
|
|
19856
|
-
await deployFunctions(
|
|
19857
|
-
appDir,
|
|
19858
|
-
serviceAccountPath,
|
|
19859
|
-
config.project,
|
|
19860
|
-
config
|
|
19861
|
-
);
|
|
19894
|
+
await deployFunctions(appDir, serviceAccountPath, config.project, config);
|
|
19862
19895
|
}
|
|
19863
19896
|
if (shouldDeployFirebaseRules && serviceAccountPath && config.project && appProviderInfo.firebaseConfig) {
|
|
19864
19897
|
await deployRules(appDir, serviceAccountPath, config.project, config, {
|
|
@@ -20082,18 +20115,102 @@ init_pathResolver();
|
|
|
20082
20115
|
// packages/tooling/src/scaffolding/scaffold-matrix.ts
|
|
20083
20116
|
init_utils();
|
|
20084
20117
|
var MATRIX = [
|
|
20085
|
-
{
|
|
20086
|
-
|
|
20087
|
-
|
|
20088
|
-
|
|
20089
|
-
|
|
20090
|
-
|
|
20091
|
-
|
|
20092
|
-
|
|
20093
|
-
{
|
|
20094
|
-
|
|
20095
|
-
|
|
20096
|
-
|
|
20118
|
+
{
|
|
20119
|
+
builder: "vite",
|
|
20120
|
+
backend: "none",
|
|
20121
|
+
baseTemplate: "app-vite",
|
|
20122
|
+
overlay: null,
|
|
20123
|
+
deployConfig: null,
|
|
20124
|
+
functionsTemplate: null
|
|
20125
|
+
},
|
|
20126
|
+
{
|
|
20127
|
+
builder: "vite",
|
|
20128
|
+
backend: "firebase",
|
|
20129
|
+
baseTemplate: "app-vite",
|
|
20130
|
+
overlay: "overlay-firebase",
|
|
20131
|
+
deployConfig: "firebase",
|
|
20132
|
+
functionsTemplate: "functions-firebase"
|
|
20133
|
+
},
|
|
20134
|
+
{
|
|
20135
|
+
builder: "vite",
|
|
20136
|
+
backend: "supabase",
|
|
20137
|
+
baseTemplate: "app-vite",
|
|
20138
|
+
overlay: "overlay-supabase",
|
|
20139
|
+
deployConfig: "vercel-supabase",
|
|
20140
|
+
functionsTemplate: "functions-supabase"
|
|
20141
|
+
},
|
|
20142
|
+
{
|
|
20143
|
+
builder: "vite",
|
|
20144
|
+
backend: "vercel",
|
|
20145
|
+
baseTemplate: "app-vite",
|
|
20146
|
+
overlay: "overlay-vercel",
|
|
20147
|
+
deployConfig: "vercel-vercel",
|
|
20148
|
+
functionsTemplate: "functions-vercel"
|
|
20149
|
+
},
|
|
20150
|
+
{
|
|
20151
|
+
builder: "nextjs",
|
|
20152
|
+
backend: "none",
|
|
20153
|
+
baseTemplate: "app-next",
|
|
20154
|
+
overlay: null,
|
|
20155
|
+
deployConfig: null,
|
|
20156
|
+
functionsTemplate: null
|
|
20157
|
+
},
|
|
20158
|
+
{
|
|
20159
|
+
builder: "nextjs",
|
|
20160
|
+
backend: "firebase",
|
|
20161
|
+
baseTemplate: "app-next",
|
|
20162
|
+
overlay: "overlay-firebase",
|
|
20163
|
+
deployConfig: "firebase",
|
|
20164
|
+
functionsTemplate: "functions-firebase"
|
|
20165
|
+
},
|
|
20166
|
+
{
|
|
20167
|
+
builder: "nextjs",
|
|
20168
|
+
backend: "supabase",
|
|
20169
|
+
baseTemplate: "app-next",
|
|
20170
|
+
overlay: "overlay-supabase",
|
|
20171
|
+
deployConfig: "vercel-supabase",
|
|
20172
|
+
functionsTemplate: "functions-supabase"
|
|
20173
|
+
},
|
|
20174
|
+
{
|
|
20175
|
+
builder: "nextjs",
|
|
20176
|
+
backend: "vercel",
|
|
20177
|
+
baseTemplate: "app-next",
|
|
20178
|
+
overlay: "overlay-vercel",
|
|
20179
|
+
deployConfig: "vercel-vercel",
|
|
20180
|
+
functionsTemplate: "functions-vercel"
|
|
20181
|
+
},
|
|
20182
|
+
{
|
|
20183
|
+
builder: "expo",
|
|
20184
|
+
backend: "none",
|
|
20185
|
+
baseTemplate: "app-expo",
|
|
20186
|
+
overlay: null,
|
|
20187
|
+
deployConfig: null,
|
|
20188
|
+
functionsTemplate: null
|
|
20189
|
+
},
|
|
20190
|
+
{
|
|
20191
|
+
builder: "expo",
|
|
20192
|
+
backend: "firebase",
|
|
20193
|
+
baseTemplate: "app-expo",
|
|
20194
|
+
overlay: "overlay-firebase",
|
|
20195
|
+
deployConfig: null,
|
|
20196
|
+
functionsTemplate: "functions-firebase"
|
|
20197
|
+
},
|
|
20198
|
+
{
|
|
20199
|
+
builder: "expo",
|
|
20200
|
+
backend: "supabase",
|
|
20201
|
+
baseTemplate: "app-expo",
|
|
20202
|
+
overlay: "overlay-supabase",
|
|
20203
|
+
deployConfig: null,
|
|
20204
|
+
functionsTemplate: "functions-supabase"
|
|
20205
|
+
},
|
|
20206
|
+
{
|
|
20207
|
+
builder: "demo",
|
|
20208
|
+
backend: "none",
|
|
20209
|
+
baseTemplate: "app-demo",
|
|
20210
|
+
overlay: null,
|
|
20211
|
+
deployConfig: null,
|
|
20212
|
+
functionsTemplate: null
|
|
20213
|
+
}
|
|
20097
20214
|
];
|
|
20098
20215
|
function comboKey(builder, backend) {
|
|
20099
20216
|
return `${builder}-${backend}`;
|
|
@@ -20106,7 +20223,9 @@ function getScaffoldRow(builder, backend) {
|
|
|
20106
20223
|
const key = comboKey(builder, backend);
|
|
20107
20224
|
const row = ROWS_BY_KEY.get(key);
|
|
20108
20225
|
if (!row) {
|
|
20109
|
-
throw new Error(
|
|
20226
|
+
throw new Error(
|
|
20227
|
+
`Unsupported scaffold combo: ${key}. Supported: ${[...ROWS_BY_KEY.keys()].join(", ")}`
|
|
20228
|
+
);
|
|
20110
20229
|
}
|
|
20111
20230
|
return row;
|
|
20112
20231
|
}
|
|
@@ -20262,13 +20381,16 @@ async function createApp(appName, appConfig, workspaceRoot, templatesRoot) {
|
|
|
20262
20381
|
const variantFile = `src/config/providers.${appTemplate}.ts.example`;
|
|
20263
20382
|
if (overlayFiles.includes(variantFile)) continue;
|
|
20264
20383
|
}
|
|
20265
|
-
const providersVariant = file.match(
|
|
20384
|
+
const providersVariant = file.match(
|
|
20385
|
+
/^src\/config\/providers\.(\w+)\.ts\.example$/
|
|
20386
|
+
);
|
|
20266
20387
|
if (providersVariant) {
|
|
20267
20388
|
if (providersVariant[1] !== appTemplate) continue;
|
|
20268
20389
|
const destPath2 = joinPath(appDir, "src/config/providers.ts");
|
|
20269
20390
|
await ensureDir(getDirname(destPath2));
|
|
20270
20391
|
await copy(joinPath(overlayDir, file), destPath2, { overwrite: true });
|
|
20271
|
-
if (await isTextFile(destPath2))
|
|
20392
|
+
if (await isTextFile(destPath2))
|
|
20393
|
+
await replacePlaceholders(destPath2, replacements);
|
|
20272
20394
|
continue;
|
|
20273
20395
|
}
|
|
20274
20396
|
const sourcePath = joinPath(overlayDir, file);
|
|
@@ -20293,16 +20415,28 @@ async function createApp(appName, appConfig, workspaceRoot, templatesRoot) {
|
|
|
20293
20415
|
}
|
|
20294
20416
|
if (deployConfig === "vercel-supabase") {
|
|
20295
20417
|
const vercelPath = joinPath(appDir, "vercel.json");
|
|
20296
|
-
const headersFragmentPath = joinPath(
|
|
20418
|
+
const headersFragmentPath = joinPath(
|
|
20419
|
+
overlayDir,
|
|
20420
|
+
"vercel.headers.example"
|
|
20421
|
+
);
|
|
20297
20422
|
const fullVercelPath = joinPath(overlayDir, "vercel.json.example");
|
|
20298
20423
|
if (pathExists(vercelPath) && pathExists(headersFragmentPath)) {
|
|
20299
20424
|
const vercelJson = readSync(vercelPath, { format: "json" });
|
|
20300
|
-
const headersFragment = readSync(headersFragmentPath, {
|
|
20301
|
-
|
|
20302
|
-
|
|
20425
|
+
const headersFragment = readSync(headersFragmentPath, {
|
|
20426
|
+
format: "json"
|
|
20427
|
+
});
|
|
20428
|
+
vercelJson.headers = [
|
|
20429
|
+
...vercelJson.headers ?? [],
|
|
20430
|
+
...headersFragment
|
|
20431
|
+
];
|
|
20432
|
+
await write(vercelPath, vercelJson, {
|
|
20433
|
+
format: "json",
|
|
20434
|
+
overwrite: true
|
|
20435
|
+
});
|
|
20303
20436
|
} else if (pathExists(fullVercelPath)) {
|
|
20304
20437
|
await copy(fullVercelPath, vercelPath);
|
|
20305
|
-
if (await isTextFile(vercelPath))
|
|
20438
|
+
if (await isTextFile(vercelPath))
|
|
20439
|
+
await replacePlaceholders(vercelPath, replacements);
|
|
20306
20440
|
}
|
|
20307
20441
|
}
|
|
20308
20442
|
}
|
|
@@ -20322,9 +20456,13 @@ async function createApp(appName, appConfig, workspaceRoot, templatesRoot) {
|
|
|
20322
20456
|
});
|
|
20323
20457
|
if (row.functionsTemplate) {
|
|
20324
20458
|
const functionsTemplateName = row.functionsTemplate;
|
|
20325
|
-
const functionsTemplateExists = pathExists(
|
|
20459
|
+
const functionsTemplateExists = pathExists(
|
|
20460
|
+
joinPath(templatesRoot, functionsTemplateName)
|
|
20461
|
+
);
|
|
20326
20462
|
if (!functionsTemplateExists) {
|
|
20327
|
-
log.warn(
|
|
20463
|
+
log.warn(
|
|
20464
|
+
`Functions template "${functionsTemplateName}" not found \u2014 skipping functions scaffolding.`
|
|
20465
|
+
);
|
|
20328
20466
|
} else {
|
|
20329
20467
|
const isSupabaseFunctions = functionsTemplateName === "functions-supabase";
|
|
20330
20468
|
const functionsRootDir = isSupabaseFunctions ? appDir : joinPath(appDir, "functions");
|
|
@@ -20332,7 +20470,10 @@ async function createApp(appName, appConfig, workspaceRoot, templatesRoot) {
|
|
|
20332
20470
|
const functionsPackageJson = generatePackageJson(
|
|
20333
20471
|
functionsTemplateName,
|
|
20334
20472
|
executionMode,
|
|
20335
|
-
{
|
|
20473
|
+
{
|
|
20474
|
+
appName,
|
|
20475
|
+
platform: row.functionsTemplate.replace("functions-", "")
|
|
20476
|
+
}
|
|
20336
20477
|
);
|
|
20337
20478
|
const packageJsonPath2 = joinPath(functionsRootDir, "package.json");
|
|
20338
20479
|
await ensureDir(functionsRootDir);
|
|
@@ -20368,13 +20509,19 @@ async function createApp(appName, appConfig, workspaceRoot, templatesRoot) {
|
|
|
20368
20509
|
}
|
|
20369
20510
|
const deploymentTemplateDir = joinPath(templatesRoot, "root-consumer");
|
|
20370
20511
|
if (deployConfig === "firebase") {
|
|
20371
|
-
const firebaseJsonSource = joinPath(
|
|
20512
|
+
const firebaseJsonSource = joinPath(
|
|
20513
|
+
deploymentTemplateDir,
|
|
20514
|
+
"firebase.json.example"
|
|
20515
|
+
);
|
|
20372
20516
|
if (pathExists(firebaseJsonSource)) {
|
|
20373
20517
|
await copy(firebaseJsonSource, joinPath(appDir, "firebase.json"));
|
|
20374
20518
|
const firebaseJsonDest = joinPath(appDir, "firebase.json");
|
|
20375
|
-
if (await isTextFile(firebaseJsonDest))
|
|
20519
|
+
if (await isTextFile(firebaseJsonDest))
|
|
20520
|
+
await replacePlaceholders(firebaseJsonDest, replacements);
|
|
20376
20521
|
if (appTemplate === "nextjs") {
|
|
20377
|
-
const firebaseJson = readSync(firebaseJsonDest, {
|
|
20522
|
+
const firebaseJson = readSync(firebaseJsonDest, {
|
|
20523
|
+
format: "json"
|
|
20524
|
+
});
|
|
20378
20525
|
if (firebaseJson.hosting?.rewrites) {
|
|
20379
20526
|
firebaseJson.hosting.rewrites = firebaseJson.hosting.rewrites.filter(
|
|
20380
20527
|
(r2) => r2.destination !== "/index.html"
|
|
@@ -20383,17 +20530,28 @@ async function createApp(appName, appConfig, workspaceRoot, templatesRoot) {
|
|
|
20383
20530
|
if (firebaseJson.hosting) {
|
|
20384
20531
|
firebaseJson.hosting.public = "out";
|
|
20385
20532
|
}
|
|
20386
|
-
await write(firebaseJsonDest, firebaseJson, {
|
|
20533
|
+
await write(firebaseJsonDest, firebaseJson, {
|
|
20534
|
+
format: "json",
|
|
20535
|
+
overwrite: true
|
|
20536
|
+
});
|
|
20387
20537
|
}
|
|
20388
20538
|
}
|
|
20389
|
-
const firebasercSource = joinPath(
|
|
20539
|
+
const firebasercSource = joinPath(
|
|
20540
|
+
deploymentTemplateDir,
|
|
20541
|
+
".firebaserc.example"
|
|
20542
|
+
);
|
|
20390
20543
|
if (pathExists(firebasercSource)) {
|
|
20391
20544
|
await copy(firebasercSource, joinPath(appDir, ".firebaserc"));
|
|
20392
20545
|
const firebasercDest = joinPath(appDir, ".firebaserc");
|
|
20393
|
-
if (await isTextFile(firebasercDest))
|
|
20546
|
+
if (await isTextFile(firebasercDest))
|
|
20547
|
+
await replacePlaceholders(firebasercDest, replacements);
|
|
20394
20548
|
}
|
|
20395
20549
|
if (row.functionsTemplate === "functions-firebase") {
|
|
20396
|
-
for (const example of [
|
|
20550
|
+
for (const example of [
|
|
20551
|
+
"firestore.rules.example",
|
|
20552
|
+
"firestore.indexes.json.example",
|
|
20553
|
+
"storage.rules.example"
|
|
20554
|
+
]) {
|
|
20397
20555
|
const src = joinPath(deploymentTemplateDir, example);
|
|
20398
20556
|
if (pathExists(src)) {
|
|
20399
20557
|
await copy(src, joinPath(appDir, example.replace(".example", "")));
|
|
@@ -20409,11 +20567,22 @@ async function createApp(appName, appConfig, workspaceRoot, templatesRoot) {
|
|
|
20409
20567
|
{
|
|
20410
20568
|
source: "functions",
|
|
20411
20569
|
codebase: "default",
|
|
20412
|
-
ignore: [
|
|
20570
|
+
ignore: [
|
|
20571
|
+
"node_modules",
|
|
20572
|
+
".git",
|
|
20573
|
+
"firebase-debug.log",
|
|
20574
|
+
"firebase-debug.*.log",
|
|
20575
|
+
"**/.*",
|
|
20576
|
+
"**/*.test.ts",
|
|
20577
|
+
"**/__tests__/**"
|
|
20578
|
+
],
|
|
20413
20579
|
runtime: "nodejs22"
|
|
20414
20580
|
}
|
|
20415
20581
|
],
|
|
20416
|
-
firestore: {
|
|
20582
|
+
firestore: {
|
|
20583
|
+
rules: "firestore.rules",
|
|
20584
|
+
indexes: "firestore.indexes.json"
|
|
20585
|
+
},
|
|
20417
20586
|
storage: { rules: "storage.rules" },
|
|
20418
20587
|
emulators: {
|
|
20419
20588
|
auth: { port: 9099 },
|
|
@@ -20423,15 +20592,26 @@ async function createApp(appName, appConfig, workspaceRoot, templatesRoot) {
|
|
|
20423
20592
|
ui: { enabled: true, port: 4e3 }
|
|
20424
20593
|
}
|
|
20425
20594
|
};
|
|
20426
|
-
await write(firebaseJsonPath, expoFirebaseJson, {
|
|
20595
|
+
await write(firebaseJsonPath, expoFirebaseJson, {
|
|
20596
|
+
format: "json",
|
|
20597
|
+
overwrite: true
|
|
20598
|
+
});
|
|
20427
20599
|
}
|
|
20428
|
-
const firebasercSource = joinPath(
|
|
20600
|
+
const firebasercSource = joinPath(
|
|
20601
|
+
deploymentTemplateDir,
|
|
20602
|
+
".firebaserc.example"
|
|
20603
|
+
);
|
|
20429
20604
|
const firebasercDest = joinPath(appDir, ".firebaserc");
|
|
20430
20605
|
if (pathExists(firebasercSource) && !pathExists(firebasercDest)) {
|
|
20431
20606
|
await copy(firebasercSource, firebasercDest);
|
|
20432
|
-
if (await isTextFile(firebasercDest))
|
|
20433
|
-
|
|
20434
|
-
|
|
20607
|
+
if (await isTextFile(firebasercDest))
|
|
20608
|
+
await replacePlaceholders(firebasercDest, replacements);
|
|
20609
|
+
}
|
|
20610
|
+
for (const example of [
|
|
20611
|
+
"firestore.rules.example",
|
|
20612
|
+
"firestore.indexes.json.example",
|
|
20613
|
+
"storage.rules.example"
|
|
20614
|
+
]) {
|
|
20435
20615
|
const src = joinPath(deploymentTemplateDir, example);
|
|
20436
20616
|
const dest = joinPath(appDir, example.replace(".example", ""));
|
|
20437
20617
|
if (pathExists(src) && !pathExists(dest)) {
|
|
@@ -20440,11 +20620,15 @@ async function createApp(appName, appConfig, workspaceRoot, templatesRoot) {
|
|
|
20440
20620
|
}
|
|
20441
20621
|
}
|
|
20442
20622
|
if (deployConfig === "vercel-vercel") {
|
|
20443
|
-
const vercelJsonSource = joinPath(
|
|
20623
|
+
const vercelJsonSource = joinPath(
|
|
20624
|
+
deploymentTemplateDir,
|
|
20625
|
+
"vercel.json.example"
|
|
20626
|
+
);
|
|
20444
20627
|
if (pathExists(vercelJsonSource)) {
|
|
20445
20628
|
await copy(vercelJsonSource, joinPath(appDir, "vercel.json"));
|
|
20446
20629
|
const vercelJsonDest = joinPath(appDir, "vercel.json");
|
|
20447
|
-
if (await isTextFile(vercelJsonDest))
|
|
20630
|
+
if (await isTextFile(vercelJsonDest))
|
|
20631
|
+
await replacePlaceholders(vercelJsonDest, replacements);
|
|
20448
20632
|
}
|
|
20449
20633
|
}
|
|
20450
20634
|
const backendInfo = row.functionsTemplate ? ` with ${row.functionsTemplate.replace("functions-", "")} functions` : "";
|
package/package.json
CHANGED
|
@@ -9,8 +9,8 @@ If you haven’t run `dndev init` yet, see [AGENT_START_HERE.md](./AGENT_START_H
|
|
|
9
9
|
## The Flow
|
|
10
10
|
|
|
11
11
|
```
|
|
12
|
-
bun install
|
|
13
|
-
|
|
12
|
+
bun install → install dependencies
|
|
13
|
+
dndev dev -> start app, read the homepage setup guide
|
|
14
14
|
dndev setup firebase -> configure Firebase project + .env
|
|
15
15
|
-- or --
|
|
16
16
|
dndev setup supabase -> configure Supabase project + .env
|
|
@@ -24,7 +24,7 @@ dndev deploy -> deploy to production
|
|
|
24
24
|
|
|
25
25
|
```bash
|
|
26
26
|
bun install
|
|
27
|
-
|
|
27
|
+
dndev dev
|
|
28
28
|
```
|
|
29
29
|
|
|
30
30
|
Open the app. The homepage shows every setup step with instructions.
|
|
@@ -73,7 +73,7 @@ Asks for your **public** project URL and anon key (both safe to share -- shipped
|
|
|
73
73
|
|
|
74
74
|
Get them from: https://supabase.com/dashboard -> your project -> Settings -> API
|
|
75
75
|
|
|
76
|
-
See [SETUP_SUPABASE.md](./SETUP_SUPABASE.md) for full details (tables, RLS, `
|
|
76
|
+
See [SETUP_SUPABASE.md](./SETUP_SUPABASE.md) for full details (tables, RLS, `dndev generate sql`). See [Secrets Philosophy](#secrets-philosophy) for how we handle secret keys.
|
|
77
77
|
|
|
78
78
|
---
|
|
79
79
|
|
|
@@ -186,7 +186,7 @@ my-project/
|
|
|
186
186
|
|
|
187
187
|
| Command | What It Does |
|
|
188
188
|
|---------|-------------|
|
|
189
|
-
| `
|
|
189
|
+
| `dndev dev` | Start dev server |
|
|
190
190
|
| `dndev emu start` | Start Firebase emulators |
|
|
191
191
|
| `dndev setup firebase` | Configure Firebase project + .env |
|
|
192
192
|
| `dndev setup supabase` | Configure Supabase project + .env |
|
|
@@ -215,4 +215,4 @@ my-project/
|
|
|
215
215
|
|
|
216
216
|
---
|
|
217
217
|
|
|
218
|
-
**Start here: `bun install &&
|
|
218
|
+
**Start here: `bun install && dndev dev`. The homepage tells you everything else.**
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
- [ENV_SETUP.md](./ENV_SETUP.md) — After `dndev init`: env, Firebase/Supabase, deploy
|
|
13
13
|
- [GOTCHAS.md](./GOTCHAS.md) - **Common mistakes & pitfalls** (phase-tagged, read before coding)
|
|
14
14
|
- [SETUP_FIREBASE.md](./SETUP_FIREBASE.md) - Firebase project setup (`dndev setup firebase`)
|
|
15
|
-
- [SETUP_SUPABASE.md](./SETUP_SUPABASE.md) - Supabase project setup (`dndev setup supabase`, `
|
|
15
|
+
- [SETUP_SUPABASE.md](./SETUP_SUPABASE.md) - Supabase project setup (`dndev setup supabase`, `dndev generate sql`)
|
|
16
16
|
- [SETUP_TESTING.md](./SETUP_TESTING.md) - Test generation (Phase 4)
|
|
17
17
|
|
|
18
18
|
---
|
|
@@ -36,7 +36,7 @@
|
|
|
36
36
|
- [SETUP_BILLING.md](./SETUP_BILLING.md) - Stripe subscriptions (pre-configured)
|
|
37
37
|
- [SETUP_BLOG.md](./SETUP_BLOG.md) - Convention-based markdown blog with i18n
|
|
38
38
|
- [SETUP_PWA.md](./SETUP_PWA.md) - Progressive Web App setup
|
|
39
|
-
- [SETUP_FUNCTIONS.md](./SETUP_FUNCTIONS.md) -
|
|
39
|
+
- [SETUP_FUNCTIONS.md](./SETUP_FUNCTIONS.md) - Functions overview (redirects to provider guides)
|
|
40
40
|
|
|
41
41
|
---
|
|
42
42
|
|