storyshelf 0.3.2 → 0.5.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -1
- package/dist/client.js +20 -5
- package/dist/commands/build.js +31 -0
- package/dist/commands/connection.js +29 -0
- package/dist/commands/create.js +1 -1
- package/dist/commands/doctor.js +150 -0
- package/dist/commands/init.js +2 -2
- package/dist/commands/purge.js +1 -1
- package/dist/commands/retry.js +1 -1
- package/dist/commands/server/docker.js +76 -11
- package/dist/commands/server/init.js +495 -45
- package/dist/commands/server/prompts.js +410 -35
- package/dist/commands/server/serve.js +123 -0
- package/dist/commands/server/terraform-aws.js +458 -0
- package/dist/commands/server/terraform-azure.js +462 -0
- package/dist/commands/server/terraform-gcp.js +475 -0
- package/dist/commands/shared/detect-adapters.js +104 -0
- package/dist/commands/storybook-build.js +62 -0
- package/dist/commands/upload.js +106 -50
- package/dist/commands/whoami.js +22 -0
- package/dist/commands/worker/init.js +254 -0
- package/dist/commands/worker/serve.js +105 -0
- package/dist/config.js +396 -23
- package/dist/index.js +49 -17
- package/dist/output.js +10 -0
- package/package.json +21 -13
- package/schema/storyshelf-config.json +58 -0
|
@@ -2,86 +2,234 @@
|
|
|
2
2
|
import "node:path";
|
|
3
3
|
import "node:url";
|
|
4
4
|
import.meta.url;
|
|
5
|
+
import { detectPackageRunner, installCommand, startCommand } from "../../config.js";
|
|
5
6
|
import { printError, printLine } from "../../output.js";
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
7
|
+
import { detectInstalledAdapters } from "../shared/detect-adapters.js";
|
|
8
|
+
import { generateComposeYaml, generateComposeYamlWithWorker, generateDockerfile, generateDockerignore, generateWorkerDockerfile } from "./docker.js";
|
|
9
|
+
import { ADVANCED_ADAPTERS_PROMPT, AWS_INFRA_PROMPTS, AZURE_INFRA_PROMPTS, DEPLOY_TARGET_PROMPT, GCP_INFRA_PROMPTS, PROJECT_PROMPTS, localInfraPrompts } from "./prompts.js";
|
|
10
|
+
import { generateAwsTerraformFiles } from "./terraform-aws.js";
|
|
11
|
+
import { generateAzureTerraformFiles } from "./terraform-azure.js";
|
|
12
|
+
import { generateGcpTerraformFiles } from "./terraform-gcp.js";
|
|
9
13
|
import { join, resolve } from "node:path";
|
|
14
|
+
import { mkdir, writeFile } from "node:fs/promises";
|
|
10
15
|
import prompts from "prompts";
|
|
11
16
|
//#region src/commands/server/init.ts
|
|
17
|
+
/** Azure SDK pins (must match @storyshelf/queue-azure peerDependencies). */
|
|
18
|
+
const AZURE_STORAGE_QUEUE_SDK = "^12.31.0";
|
|
19
|
+
const AZURE_SERVICE_BUS_SDK = "^7.9.5";
|
|
20
|
+
/** Pub/Sub SDK pin (must match @storyshelf/queue-gcp peerDependencies). */
|
|
21
|
+
const GCP_PUBSUB_SDK = "^6.1.0";
|
|
22
|
+
/** DB peer SDK pins (must match db-* peerDependencies). */
|
|
23
|
+
const LIBSQL_CLIENT_SDK = "^0.15.0";
|
|
24
|
+
const BETTER_SQLITE3_SDK = "^12.0.0";
|
|
25
|
+
const PG_SDK = "^8.0.0";
|
|
26
|
+
const NEON_SDK = "^1.1.0";
|
|
27
|
+
const VERCEL_POSTGRES_SDK = "^0.10.0";
|
|
28
|
+
const PGLITE_SDK = "^0.5.8";
|
|
29
|
+
const MYSQL2_SDK = "^3.9.0";
|
|
30
|
+
const PLANETSCALE_SDK = "^1.19.0";
|
|
31
|
+
const TIDBCLOUD_SDK = "^0.1.0";
|
|
32
|
+
/** Email peer SDK pin (must match @storyshelf/notify-email peerDependencies). */
|
|
33
|
+
const NODEMAILER_SDK = "^10.0.12";
|
|
34
|
+
/** npm peer SDK for the chosen Azure queue backend. */ function azureSdkDep(queue) {
|
|
35
|
+
if (queue === "azure-service-bus") return {
|
|
36
|
+
name: "@azure/service-bus",
|
|
37
|
+
version: AZURE_SERVICE_BUS_SDK
|
|
38
|
+
};
|
|
39
|
+
return {
|
|
40
|
+
name: "@azure/storage-queue",
|
|
41
|
+
version: AZURE_STORAGE_QUEUE_SDK
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
/** Remote queues need a separately-assembled worker; memory runs in-process. */
|
|
45
|
+
function isRemoteQueue(queue) {
|
|
46
|
+
return queue !== "memory";
|
|
47
|
+
}
|
|
48
|
+
/** Normalize the deploy target, preserving pre-target answers. */
|
|
49
|
+
function resolveDeployTarget(answers) {
|
|
50
|
+
return answers.deployTarget ?? (answers.docker ? "docker" : "local");
|
|
51
|
+
}
|
|
12
52
|
const DB_PACKAGE = {
|
|
13
53
|
sqlite: "@storyshelf/db-sqlite",
|
|
14
|
-
turso: "@storyshelf/db-
|
|
54
|
+
turso: "@storyshelf/db-sqlite",
|
|
55
|
+
"better-sqlite3": "@storyshelf/db-sqlite",
|
|
56
|
+
"bun-sqlite": "@storyshelf/db-sqlite",
|
|
57
|
+
d1: "@storyshelf/db-sqlite",
|
|
58
|
+
postgres: "@storyshelf/db-postgres",
|
|
59
|
+
pg: "@storyshelf/db-postgres",
|
|
60
|
+
neon: "@storyshelf/db-postgres",
|
|
61
|
+
"neon-http": "@storyshelf/db-postgres",
|
|
62
|
+
vercel: "@storyshelf/db-postgres",
|
|
63
|
+
pglite: "@storyshelf/db-postgres",
|
|
64
|
+
mysql: "@storyshelf/db-mysql",
|
|
65
|
+
planetscale: "@storyshelf/db-mysql",
|
|
66
|
+
tidb: "@storyshelf/db-mysql"
|
|
15
67
|
};
|
|
16
68
|
const STORAGE_PACKAGE = {
|
|
17
69
|
local: "@storyshelf/storage-local",
|
|
18
|
-
s3: "@storyshelf/storage-s3"
|
|
70
|
+
s3: "@storyshelf/storage-s3",
|
|
71
|
+
azure: "@storyshelf/storage-azure",
|
|
72
|
+
gcs: "@storyshelf/storage-gcs"
|
|
19
73
|
};
|
|
20
74
|
const AUTH_PACKAGE = {
|
|
21
75
|
none: null,
|
|
22
|
-
password: "@storyshelf/auth
|
|
23
|
-
oauth: "@storyshelf/auth
|
|
76
|
+
password: "@storyshelf/auth",
|
|
77
|
+
oauth: "@storyshelf/auth"
|
|
24
78
|
};
|
|
25
79
|
const GIT_PACKAGE = {
|
|
26
80
|
none: null,
|
|
27
81
|
github: "@storyshelf/git-github",
|
|
28
82
|
gitlab: "@storyshelf/git-gitlab"
|
|
29
83
|
};
|
|
84
|
+
const QUEUE_PACKAGE = {
|
|
85
|
+
memory: null,
|
|
86
|
+
sqs: "@storyshelf/queue-sqs",
|
|
87
|
+
redis: "@storyshelf/queue-redis",
|
|
88
|
+
"azure-storage-queues": "@storyshelf/queue-azure",
|
|
89
|
+
"azure-service-bus": "@storyshelf/queue-azure",
|
|
90
|
+
"gcp-pubsub": "@storyshelf/queue-gcp"
|
|
91
|
+
};
|
|
30
92
|
const DB_IMPORT = {
|
|
31
93
|
sqlite: `import { createSqliteDatabase } from "@storyshelf/db-sqlite";`,
|
|
32
|
-
turso: `import { createTursoDatabase } from "@storyshelf/db-turso"
|
|
94
|
+
turso: `import { createTursoDatabase } from "@storyshelf/db-sqlite/turso";`,
|
|
95
|
+
"better-sqlite3": `import { createBetterSqlite3Database } from "@storyshelf/db-sqlite/better-sqlite3";`,
|
|
96
|
+
"bun-sqlite": `import { createBunSqliteDatabase } from "@storyshelf/db-sqlite/bun-sqlite";`,
|
|
97
|
+
d1: `import { createD1Database } from "@storyshelf/db-sqlite/d1";`,
|
|
98
|
+
postgres: `import { createPostgresDatabase } from "@storyshelf/db-postgres";`,
|
|
99
|
+
pg: `import { createPgDatabase } from "@storyshelf/db-postgres/pg";`,
|
|
100
|
+
neon: `import { createNeonDatabase } from "@storyshelf/db-postgres/neon";`,
|
|
101
|
+
"neon-http": `import { createNeonHttpDatabase } from "@storyshelf/db-postgres/neon-http";`,
|
|
102
|
+
vercel: `import { createVercelDatabase } from "@storyshelf/db-postgres/vercel";`,
|
|
103
|
+
pglite: `import { createPgliteDatabase } from "@storyshelf/db-postgres/pglite";`,
|
|
104
|
+
mysql: `import { createMysqlDatabase } from "@storyshelf/db-mysql";`,
|
|
105
|
+
planetscale: `import { createPlanetscaleDatabase } from "@storyshelf/db-mysql/planetscale";`,
|
|
106
|
+
tidb: `import { createTidbDatabase } from "@storyshelf/db-mysql/tidb";`
|
|
33
107
|
};
|
|
34
108
|
const STORAGE_IMPORT = {
|
|
35
109
|
local: `import { createLocalStorage } from "@storyshelf/storage-local";`,
|
|
36
|
-
s3: `import { createS3Storage } from "@storyshelf/storage-s3"
|
|
110
|
+
s3: `import { createS3Storage } from "@storyshelf/storage-s3";`,
|
|
111
|
+
azure: `import { createAzureStorage } from "@storyshelf/storage-azure";`,
|
|
112
|
+
gcs: `import { createGcsStorage } from "@storyshelf/storage-gcs";`
|
|
37
113
|
};
|
|
38
114
|
const DB_INIT = {
|
|
39
115
|
sqlite: `createSqliteDatabase(\`\${dataDir}/shelf.db\`)`,
|
|
40
|
-
turso: `createTursoDatabase({ url: process.env.TURSO_DATABASE_URL!, authToken: process.env.TURSO_AUTH_TOKEN })
|
|
116
|
+
turso: `createTursoDatabase({ url: process.env.TURSO_DATABASE_URL!, authToken: process.env.TURSO_AUTH_TOKEN })`,
|
|
117
|
+
"better-sqlite3": `createBetterSqlite3Database({ path: \`\${dataDir}/shelf.db\` })`,
|
|
118
|
+
"bun-sqlite": `createBunSqliteDatabase({ path: \`\${dataDir}/shelf.db\` })`,
|
|
119
|
+
d1: `createD1Database({ client: getD1Binding() })`,
|
|
120
|
+
postgres: `createPostgresDatabase({ url: process.env.DATABASE_URL! })`,
|
|
121
|
+
pg: `createPgDatabase({ url: process.env.DATABASE_URL! })`,
|
|
122
|
+
neon: `createNeonDatabase({ url: process.env.DATABASE_URL! })`,
|
|
123
|
+
"neon-http": `createNeonHttpDatabase({ url: process.env.DATABASE_URL! })`,
|
|
124
|
+
vercel: `createVercelDatabase()`,
|
|
125
|
+
pglite: `createPgliteDatabase()`,
|
|
126
|
+
mysql: `createMysqlDatabase({ url: process.env.DATABASE_URL! })`,
|
|
127
|
+
planetscale: `createPlanetscaleDatabase({ url: process.env.DATABASE_URL! })`,
|
|
128
|
+
tidb: `createTidbDatabase({ url: process.env.DATABASE_URL! })`
|
|
41
129
|
};
|
|
130
|
+
/** Extra scaffolding before `const database = ...` (D1 binding stub). */
|
|
131
|
+
function dbPrelude(database) {
|
|
132
|
+
if (database !== "d1") return [];
|
|
133
|
+
return [
|
|
134
|
+
`// TODO: provide your D1 binding (Cloudflare Workers: env.DB).`,
|
|
135
|
+
`function getD1Binding(): never {`,
|
|
136
|
+
` throw new Error("Provide your D1 database binding (env.DB).");`,
|
|
137
|
+
`}`
|
|
138
|
+
];
|
|
139
|
+
}
|
|
42
140
|
const STORAGE_INIT = {
|
|
43
141
|
local: `createLocalStorage(dataDir)`,
|
|
44
|
-
s3: `createS3Storage({ bucket: process.env.S3_BUCKET!, region: process.env.AWS_REGION })
|
|
142
|
+
s3: `createS3Storage({ bucket: process.env.S3_BUCKET!, region: process.env.AWS_REGION })`,
|
|
143
|
+
azure: `createAzureStorage({ container: "storybook", connectionString: process.env.AZURE_STORAGE_CONNECTION! })`,
|
|
144
|
+
gcs: `createGcsStorage({ bucket: process.env.GCS_BUCKET! })`
|
|
45
145
|
};
|
|
146
|
+
/** Whether the generated server wires notification providers. */
|
|
147
|
+
function wantsNotifications(answers) {
|
|
148
|
+
return answers.notifications ?? true;
|
|
149
|
+
}
|
|
46
150
|
function buildImports(answers) {
|
|
47
151
|
const imports = [
|
|
48
152
|
`import { serve } from "@hono/node-server";`,
|
|
49
|
-
`import {
|
|
153
|
+
`import { createShelfApp } from "@storyshelf/app";`,
|
|
50
154
|
`import { createShelfLogger } from "@storyshelf/core/logger";`,
|
|
155
|
+
`import { otelLogMixin } from "@storyshelf/observability";`,
|
|
156
|
+
`import { initObservabilityFromEnv } from "@storyshelf/observability/node";`,
|
|
51
157
|
DB_IMPORT[answers.database],
|
|
52
158
|
STORAGE_IMPORT[answers.storage]
|
|
53
159
|
];
|
|
54
|
-
if (answers.
|
|
160
|
+
if (answers.queue === "sqs") imports.push(`import { createSqsCaptureQueue } from "@storyshelf/queue-sqs";`);
|
|
161
|
+
if (answers.queue === "redis") imports.push(`import { createRedisCaptureQueue } from "@storyshelf/queue-redis";`);
|
|
162
|
+
if (answers.queue === "azure-storage-queues") imports.push(`import { createAzureStorageQueuesQueue } from "@storyshelf/queue-azure/storage-queues";`);
|
|
163
|
+
if (answers.queue === "azure-service-bus") imports.push(`import { createAzureServiceBusQueue } from "@storyshelf/queue-azure/service-bus";`);
|
|
164
|
+
if (answers.queue === "gcp-pubsub") imports.push(`import { createGcpPubSubQueue } from "@storyshelf/queue-gcp";`);
|
|
165
|
+
if (answers.auth === "password") imports.push(`import { createShelfAuth, ensurePasswordAdmin } from "@storyshelf/auth";`);
|
|
166
|
+
if (answers.auth === "oauth") {
|
|
167
|
+
imports.push(`import { createShelfAuth } from "@storyshelf/auth";`);
|
|
168
|
+
if (resolveDeployTarget(answers) === "aws") imports.push(`import { cognitoPreset } from "@storyshelf/auth";`);
|
|
169
|
+
else imports.push(`import { keycloakPreset } from "@storyshelf/auth";`);
|
|
170
|
+
}
|
|
55
171
|
if (answers.git !== "none") {
|
|
56
172
|
const host = answers.git === "github" ? "gitHubHost" : "gitLabHost";
|
|
57
173
|
imports.push(`import { ${host} } from "${GIT_PACKAGE[answers.git]}";`);
|
|
58
174
|
}
|
|
59
|
-
|
|
175
|
+
if (wantsNotifications(answers)) {
|
|
176
|
+
imports.push(`import { chatNotifiers } from "@storyshelf/notify-chat";`, `import { createEmailNotifier, smtpPresetFromEnv } from "@storyshelf/notify-email";`);
|
|
177
|
+
if (answers.auth !== "none") imports.push(`import { createAuthSystemHook } from "@storyshelf/app";`);
|
|
178
|
+
}
|
|
179
|
+
if (answers.queue === "memory") imports.push(`import { createPlaywrightCaptureRunner } from "@storyshelf/runner-playwright";`);
|
|
60
180
|
return imports;
|
|
61
181
|
}
|
|
62
182
|
function buildAdapterLines(answers) {
|
|
63
|
-
|
|
183
|
+
const lines = [
|
|
64
184
|
`// Adapters — swap these for your deployment`,
|
|
185
|
+
...dbPrelude(answers.database),
|
|
65
186
|
`const database = ${DB_INIT[answers.database]};`,
|
|
66
|
-
`const storage = ${STORAGE_INIT[answers.storage]}
|
|
67
|
-
`const captureRunner = createPlaywrightCaptureRunner();`,
|
|
68
|
-
`const logger = createShelfLogger({ level: process.env.LOG_LEVEL });`
|
|
187
|
+
`const storage = ${STORAGE_INIT[answers.storage]};`
|
|
69
188
|
];
|
|
189
|
+
if (answers.queue === "sqs") lines.push(`const captureQueue = createSqsCaptureQueue({ queueUrl: process.env.QUEUE_URL! });`);
|
|
190
|
+
if (answers.queue === "redis") lines.push(`const captureQueue = createRedisCaptureQueue({ url: process.env.REDIS_URL! });`);
|
|
191
|
+
if (answers.queue === "azure-storage-queues") lines.push(`const captureQueue = createAzureStorageQueuesQueue({ queueName: "capture-jobs", connectionString: process.env.AZURE_STORAGE_CONNECTION! });`);
|
|
192
|
+
if (answers.queue === "azure-service-bus") lines.push(`const captureQueue = createAzureServiceBusQueue({ queueName: "capture-jobs", connectionString: process.env.AZURE_SERVICE_BUS_CONNECTION! });`);
|
|
193
|
+
if (answers.queue === "gcp-pubsub") lines.push(`const captureQueue = createGcpPubSubQueue({ topic: "capture-jobs", subscription: "capture-jobs-worker", projectId: process.env.GOOGLE_CLOUD_PROJECT! });`);
|
|
194
|
+
if (answers.queue === "memory") lines.push(`const captureRunner = createPlaywrightCaptureRunner();`);
|
|
195
|
+
if (wantsNotifications(answers)) lines.push(`// Notifications: SMTP sender from SMTP_* env (undefined keeps invites out-of-band).`, `const emailSender = smtpPresetFromEnv(process.env);`, `const emailNotifiers = emailSender ? [createEmailNotifier(emailSender, { from: process.env.SMTP_FROM })] : [];`);
|
|
196
|
+
return lines;
|
|
70
197
|
}
|
|
71
198
|
function buildRouterLines(answers) {
|
|
72
199
|
const lines = [
|
|
73
|
-
|
|
200
|
+
`// Observability (noop unless OTEL_EXPORTER_OTLP_ENDPOINT is set).`,
|
|
201
|
+
`const observability = await initObservabilityFromEnv();`,
|
|
202
|
+
`const shelfLogger = createShelfLogger({ mixin: otelLogMixin });`,
|
|
203
|
+
``,
|
|
204
|
+
`const app = createShelfApp({`,
|
|
74
205
|
` database,`,
|
|
75
|
-
` storage
|
|
76
|
-
` captureRunner,`,
|
|
77
|
-
` logger,`
|
|
206
|
+
` storage,`
|
|
78
207
|
];
|
|
79
|
-
if (answers.
|
|
208
|
+
if (isRemoteQueue(answers.queue)) lines.push(` captureQueue,`);
|
|
209
|
+
else lines.push(` captureRunner,`);
|
|
210
|
+
if (wantsNotifications(answers)) lines.push(` notifiers: [...chatNotifiers, ...emailNotifiers],`);
|
|
211
|
+
if (answers.auth === "password") {
|
|
212
|
+
const notifyArgs = wantsNotifications(answers) ? `, emailSender, fromEmail: process.env.SMTP_FROM, onAuthSystemEvent: createAuthSystemHook()` : ``;
|
|
213
|
+
lines.push(` auth: createShelfAuth({ db: database, secret: process.env.SECRET!, baseURL: process.env.PUBLIC_BASE_URL!${notifyArgs} }).adapter,`);
|
|
214
|
+
}
|
|
215
|
+
if (answers.auth === "oauth" && resolveDeployTarget(answers) === "aws") lines.push(` auth: createShelfAuth({`, ` db: database,`, ` secret: process.env.SECRET!,`, ` baseURL: process.env.PUBLIC_BASE_URL!,`, ...wantsNotifications(answers) ? [
|
|
216
|
+
` emailSender,`,
|
|
217
|
+
` fromEmail: process.env.SMTP_FROM,`,
|
|
218
|
+
` onAuthSystemEvent: createAuthSystemHook(),`
|
|
219
|
+
] : [], ` social: [`, ` cognitoPreset({`, ` domain: process.env.COGNITO_DOMAIN!,`, ` region: process.env.COGNITO_REGION!,`, ` userPoolId: process.env.COGNITO_USER_POOL_ID!,`, ` clientId: process.env.OIDC_CLIENT_ID!,`, ` clientSecret: process.env.OIDC_CLIENT_SECRET,`, ` }),`, ` ],`, ` }).adapter,`);
|
|
220
|
+
if (answers.auth === "oauth" && resolveDeployTarget(answers) !== "aws") lines.push(` auth: createShelfAuth({`, ` db: database,`, ` secret: process.env.SECRET!,`, ` baseURL: process.env.PUBLIC_BASE_URL!,`, ...wantsNotifications(answers) ? [
|
|
221
|
+
` emailSender,`,
|
|
222
|
+
` fromEmail: process.env.SMTP_FROM,`,
|
|
223
|
+
` onAuthSystemEvent: createAuthSystemHook(),`
|
|
224
|
+
] : [], ` oauth: [`, ` keycloakPreset(process.env.OIDC_ISSUER!, {`, ` clientId: process.env.OIDC_CLIENT_ID!,`, ` clientSecret: process.env.OIDC_CLIENT_SECRET,`, ` }),`, ` ],`, ` }).adapter,`);
|
|
80
225
|
if (answers.git !== "none") {
|
|
81
226
|
const host = answers.git === "github" ? "gitHubHost" : "gitLabHost";
|
|
82
227
|
lines.push(` gitHosts: [${host}],`);
|
|
83
228
|
}
|
|
84
|
-
lines.push(` config: {`, ` secret: process.env.SECRET,`, `
|
|
229
|
+
if (answers.queue === "memory") lines.push(` logger: shelfLogger,`, ` observability,`, ` config: {`, ` secret: process.env.SECRET,`, ` // Rotation: keep the old SECRET here until credentials are re-encrypted.`, ` previousSecret: process.env.SECRET_PREVIOUS || undefined,`, ` migrateCredentialsOnBoot: process.env.SECRET_MIGRATE === "true",`, ` scratchDir: dataDir,`, ` },`);
|
|
230
|
+
else lines.push(` logger: shelfLogger,`, ` observability,`, ` config: {`, ` secret: process.env.SECRET,`, ` // Rotation: keep the old SECRET here until credentials are re-encrypted.`, ` previousSecret: process.env.SECRET_PREVIOUS || undefined,`, ` migrateCredentialsOnBoot: process.env.SECRET_MIGRATE === "true",`, ` },`);
|
|
231
|
+
lines.push(`});`, ``, `await app.lifecycle.setup();`, `const logger = app.lifecycle.logger;`);
|
|
232
|
+
if (answers.auth === "password") lines.push(``, `// Provision the env-driven local admin (AUTH_PASSWORD, at least 12 chars).`, `await ensurePasswordAdmin(database, {`, ` email: process.env.AUTH_EMAIL ?? "admin@local",`, ` password: process.env.AUTH_PASSWORD!,`, `});`);
|
|
85
233
|
return lines;
|
|
86
234
|
}
|
|
87
235
|
function generateServer(answers) {
|
|
@@ -100,7 +248,7 @@ function generateServer(answers) {
|
|
|
100
248
|
`});`,
|
|
101
249
|
``,
|
|
102
250
|
`const shutdown = async () => {`,
|
|
103
|
-
` await app.lifecycle.
|
|
251
|
+
` await app.lifecycle.teardown();`,
|
|
104
252
|
` server.close();`,
|
|
105
253
|
`};`,
|
|
106
254
|
`process.on("SIGTERM", () => {`,
|
|
@@ -112,19 +260,122 @@ function generateServer(answers) {
|
|
|
112
260
|
``
|
|
113
261
|
].join("\n");
|
|
114
262
|
}
|
|
263
|
+
function workerQueueImport(queue) {
|
|
264
|
+
if (queue === "redis") return `import { createRedisCaptureQueue } from "@storyshelf/queue-redis";`;
|
|
265
|
+
if (queue === "azure-storage-queues") return `import { createAzureStorageQueuesQueue } from "@storyshelf/queue-azure/storage-queues";`;
|
|
266
|
+
if (queue === "azure-service-bus") return `import { createAzureServiceBusQueue } from "@storyshelf/queue-azure/service-bus";`;
|
|
267
|
+
if (queue === "gcp-pubsub") return `import { createGcpPubSubQueue } from "@storyshelf/queue-gcp";`;
|
|
268
|
+
return `import { createSqsCaptureQueue } from "@storyshelf/queue-sqs";`;
|
|
269
|
+
}
|
|
270
|
+
function workerQueueInit(queue) {
|
|
271
|
+
if (queue === "redis") return `const queue = createRedisCaptureQueue({ url: process.env.REDIS_URL! });`;
|
|
272
|
+
if (queue === "azure-storage-queues") return `const queue = createAzureStorageQueuesQueue({ queueName: "capture-jobs", connectionString: process.env.AZURE_STORAGE_CONNECTION! });`;
|
|
273
|
+
if (queue === "azure-service-bus") return `const queue = createAzureServiceBusQueue({ queueName: "capture-jobs", connectionString: process.env.AZURE_SERVICE_BUS_CONNECTION! });`;
|
|
274
|
+
if (queue === "gcp-pubsub") return `const queue = createGcpPubSubQueue({ topic: "capture-jobs", subscription: "capture-jobs-worker", projectId: process.env.GOOGLE_CLOUD_PROJECT! });`;
|
|
275
|
+
return `const queue = createSqsCaptureQueue({ queueUrl: process.env.QUEUE_URL! });`;
|
|
276
|
+
}
|
|
277
|
+
function generateWorkerFile(answers) {
|
|
278
|
+
const queueImport = workerQueueImport(answers.queue);
|
|
279
|
+
const queueInit = workerQueueInit(answers.queue);
|
|
280
|
+
return [
|
|
281
|
+
queueImport,
|
|
282
|
+
`import { createCaptureWorker } from "@storyshelf/worker";`,
|
|
283
|
+
`import { initObservabilityFromEnv } from "@storyshelf/observability/node";`,
|
|
284
|
+
`import { createPlaywrightCaptureRunner } from "@storyshelf/runner-playwright";`,
|
|
285
|
+
DB_IMPORT[answers.database],
|
|
286
|
+
STORAGE_IMPORT[answers.storage],
|
|
287
|
+
``,
|
|
288
|
+
`// Observability (noop unless OTEL_EXPORTER_OTLP_ENDPOINT is set).`,
|
|
289
|
+
`const observability = await initObservabilityFromEnv();`,
|
|
290
|
+
``,
|
|
291
|
+
`const dataDir = process.env.DATA_DIR || "./data";`,
|
|
292
|
+
...dbPrelude(answers.database),
|
|
293
|
+
`const database = ${DB_INIT[answers.database]};`,
|
|
294
|
+
`const storage = ${STORAGE_INIT[answers.storage]};`,
|
|
295
|
+
queueInit,
|
|
296
|
+
`const runner = createPlaywrightCaptureRunner();`,
|
|
297
|
+
``,
|
|
298
|
+
`const worker = createCaptureWorker({`,
|
|
299
|
+
` queue,`,
|
|
300
|
+
` db: database,`,
|
|
301
|
+
` storage,`,
|
|
302
|
+
` runner,`,
|
|
303
|
+
` scratchDir: dataDir,`,
|
|
304
|
+
` config: { concurrency: Number(process.env.WORKER_CONCURRENCY) || 2 },`,
|
|
305
|
+
`});`,
|
|
306
|
+
``,
|
|
307
|
+
`await worker.start();`,
|
|
308
|
+
``,
|
|
309
|
+
`const shutdown = async () => {`,
|
|
310
|
+
` await worker.stop();`,
|
|
311
|
+
` await observability.shutdown();`,
|
|
312
|
+
`};`,
|
|
313
|
+
`process.on("SIGTERM", () => { shutdown().catch(() => {}); });`,
|
|
314
|
+
`process.on("SIGINT", () => { shutdown().catch(() => {}); });`,
|
|
315
|
+
``
|
|
316
|
+
].join("\n");
|
|
317
|
+
}
|
|
115
318
|
function buildDeps(answers) {
|
|
116
319
|
const deps = {
|
|
117
320
|
"@hono/node-server": "^1.17.0",
|
|
118
|
-
"@storyshelf/core": "0.
|
|
119
|
-
"@storyshelf/
|
|
120
|
-
|
|
121
|
-
|
|
321
|
+
"@storyshelf/core": "0.5.4",
|
|
322
|
+
"@storyshelf/app": "0.5.4",
|
|
323
|
+
"@storyshelf/observability": "0.5.4",
|
|
324
|
+
[DB_PACKAGE[answers.database]]: "0.5.4"
|
|
122
325
|
};
|
|
123
|
-
if (answers.
|
|
124
|
-
if (answers.
|
|
125
|
-
if (answers.
|
|
326
|
+
if (answers.database === "turso") deps["@libsql/client"] = LIBSQL_CLIENT_SDK;
|
|
327
|
+
if (answers.database === "better-sqlite3") deps["better-sqlite3"] = BETTER_SQLITE3_SDK;
|
|
328
|
+
if (answers.database === "pg") deps["pg"] = PG_SDK;
|
|
329
|
+
if (answers.database === "neon" || answers.database === "neon-http") deps["@neondatabase/serverless"] = NEON_SDK;
|
|
330
|
+
if (answers.database === "vercel") deps["@vercel/postgres"] = VERCEL_POSTGRES_SDK;
|
|
331
|
+
if (answers.database === "pglite") deps["@electric-sql/pglite"] = PGLITE_SDK;
|
|
332
|
+
if (answers.database === "mysql") deps["mysql2"] = MYSQL2_SDK;
|
|
333
|
+
if (answers.database === "planetscale") deps["@planetscale/database"] = PLANETSCALE_SDK;
|
|
334
|
+
if (answers.database === "tidb") deps["@tidbcloud/serverless"] = TIDBCLOUD_SDK;
|
|
335
|
+
if (answers.storage !== "local") deps[STORAGE_PACKAGE[answers.storage]] = "0.5.4";
|
|
336
|
+
if (answers.queue === "sqs" || answers.queue === "redis") {
|
|
337
|
+
const queuePkg = answers.queue === "redis" ? "@storyshelf/queue-redis" : "@storyshelf/queue-sqs";
|
|
338
|
+
deps[queuePkg] = "0.5.4";
|
|
339
|
+
if (answers.includeWorker) {
|
|
340
|
+
deps["@storyshelf/worker"] = "0.5.4";
|
|
341
|
+
deps["@storyshelf/runner-playwright"] = "0.5.4";
|
|
342
|
+
}
|
|
343
|
+
} else if (answers.queue === "azure-storage-queues" || answers.queue === "azure-service-bus") {
|
|
344
|
+
deps["@storyshelf/queue-azure"] = "0.5.4";
|
|
345
|
+
const sdk = azureSdkDep(answers.queue);
|
|
346
|
+
deps[sdk.name] = sdk.version;
|
|
347
|
+
if (answers.includeWorker) {
|
|
348
|
+
deps["@storyshelf/worker"] = "0.5.4";
|
|
349
|
+
deps["@storyshelf/runner-playwright"] = "0.5.4";
|
|
350
|
+
}
|
|
351
|
+
} else if (answers.queue === "gcp-pubsub") {
|
|
352
|
+
deps["@storyshelf/queue-gcp"] = "0.5.4";
|
|
353
|
+
deps["@google-cloud/pubsub"] = GCP_PUBSUB_SDK;
|
|
354
|
+
if (answers.includeWorker) {
|
|
355
|
+
deps["@storyshelf/worker"] = "0.5.4";
|
|
356
|
+
deps["@storyshelf/runner-playwright"] = "0.5.4";
|
|
357
|
+
}
|
|
358
|
+
} else deps["@storyshelf/runner-playwright"] = "0.5.4";
|
|
359
|
+
if (answers.auth !== "none") deps[AUTH_PACKAGE[answers.auth] ?? ""] = "0.5.4";
|
|
360
|
+
if (answers.git !== "none") deps[GIT_PACKAGE[answers.git] ?? ""] = "0.5.4";
|
|
361
|
+
if (wantsNotifications(answers)) {
|
|
362
|
+
deps["@storyshelf/notify-chat"] = "0.5.4";
|
|
363
|
+
deps["@storyshelf/notify-email"] = "0.5.4";
|
|
364
|
+
deps["nodemailer"] = NODEMAILER_SDK;
|
|
365
|
+
}
|
|
366
|
+
if (answers.queue !== "memory") {
|
|
367
|
+
const queuePkg = QUEUE_PACKAGE[answers.queue];
|
|
368
|
+
if (queuePkg) deps[queuePkg] = "0.5.4";
|
|
369
|
+
}
|
|
126
370
|
return deps;
|
|
127
371
|
}
|
|
372
|
+
function addInfraScripts(scripts) {
|
|
373
|
+
scripts["infra:init"] = "terraform -chdir=terraform init";
|
|
374
|
+
scripts["infra:plan"] = "terraform -chdir=terraform plan -out=tfplan";
|
|
375
|
+
scripts["infra:apply"] = "terraform -chdir=terraform apply tfplan";
|
|
376
|
+
scripts["infra:destroy"] = "terraform -chdir=terraform destroy";
|
|
377
|
+
scripts["infra:outputs"] = "terraform -chdir=terraform output -json";
|
|
378
|
+
}
|
|
128
379
|
function generatePackageJson(answers) {
|
|
129
380
|
const pkg = {
|
|
130
381
|
name: answers.name,
|
|
@@ -139,50 +390,249 @@ function generatePackageJson(answers) {
|
|
|
139
390
|
dependencies: buildDeps(answers),
|
|
140
391
|
devDependencies: { typescript: "^7.0.2" }
|
|
141
392
|
};
|
|
393
|
+
if (answers.includeWorker) {
|
|
394
|
+
pkg.scripts["worker"] = "node --experimental-transform-types worker.ts";
|
|
395
|
+
pkg.scripts["worker:dev"] = "node --experimental-transform-types --watch worker.ts";
|
|
396
|
+
}
|
|
397
|
+
if (resolveDeployTarget(answers) === "docker") {
|
|
398
|
+
const scripts = pkg.scripts;
|
|
399
|
+
scripts["docker:build"] = "docker compose build";
|
|
400
|
+
scripts["docker:up"] = "docker compose up --build";
|
|
401
|
+
scripts["docker:down"] = "docker compose down";
|
|
402
|
+
scripts["docker:logs"] = "docker compose logs -f";
|
|
403
|
+
}
|
|
404
|
+
if (resolveDeployTarget(answers) === "aws" || resolveDeployTarget(answers) === "azure" || resolveDeployTarget(answers) === "gcp") addInfraScripts(pkg.scripts);
|
|
142
405
|
return JSON.stringify(pkg, null, 2);
|
|
143
406
|
}
|
|
144
407
|
async function writeFiles(outDir, answers) {
|
|
145
408
|
const serverCode = generateServer(answers);
|
|
146
409
|
await writeFile(join(outDir, "server.ts"), serverCode);
|
|
147
410
|
printLine(`Created server.ts`);
|
|
411
|
+
if (answers.includeWorker) {
|
|
412
|
+
const workerCode = generateWorkerFile(answers);
|
|
413
|
+
await writeFile(join(outDir, "worker.ts"), workerCode);
|
|
414
|
+
printLine(`Created worker.ts`);
|
|
415
|
+
}
|
|
148
416
|
const pkgCode = generatePackageJson(answers);
|
|
149
417
|
await writeFile(join(outDir, "package.json"), pkgCode);
|
|
150
418
|
printLine(`Created package.json`);
|
|
151
|
-
await
|
|
419
|
+
if (resolveDeployTarget(answers) === "aws") await writeTerraformFiles(outDir, answers);
|
|
420
|
+
if (resolveDeployTarget(answers) === "azure") await writeAzureTerraformFiles(outDir, answers);
|
|
421
|
+
if (resolveDeployTarget(answers) === "gcp") await writeGcpTerraformFiles(outDir, answers);
|
|
422
|
+
await writeDockerFiles(outDir, answers);
|
|
423
|
+
}
|
|
424
|
+
/** Write the GCP Terraform reference stack (`terraform/*.tf` + README). */
|
|
425
|
+
async function writeGcpTerraformFiles(outDir, answers) {
|
|
426
|
+
const files = generateGcpTerraformFiles({
|
|
427
|
+
project: answers.name,
|
|
428
|
+
gcpProjectId: answers.gcpProjectId ?? "",
|
|
429
|
+
location: answers.gcpLocation ?? "us-central1",
|
|
430
|
+
...answers.domainName ? { domainName: answers.domainName } : {},
|
|
431
|
+
...answers.identityTenant ? { identityTenant: answers.identityTenant } : {}
|
|
432
|
+
});
|
|
433
|
+
await mkdir(join(outDir, "terraform"), { recursive: true });
|
|
434
|
+
for (const [relative, contents] of Object.entries(files)) await writeFile(join(outDir, relative), contents);
|
|
435
|
+
printLine(`Created terraform/ (${Object.keys(files).length} files)`);
|
|
436
|
+
}
|
|
437
|
+
/** Write the Azure Terraform reference stack (`terraform/*.tf` + README). */
|
|
438
|
+
async function writeAzureTerraformFiles(outDir, answers) {
|
|
439
|
+
const files = generateAzureTerraformFiles({
|
|
440
|
+
project: answers.name,
|
|
441
|
+
location: answers.azureLocation ?? "eastus",
|
|
442
|
+
queueBackend: answers.azureQueueBackend ?? "storage-queues",
|
|
443
|
+
...answers.domainName ? { domainName: answers.domainName } : {},
|
|
444
|
+
...answers.entraTenantId ? { entraTenantId: answers.entraTenantId } : {}
|
|
445
|
+
});
|
|
446
|
+
await mkdir(join(outDir, "terraform"), { recursive: true });
|
|
447
|
+
for (const [relative, contents] of Object.entries(files)) await writeFile(join(outDir, relative), contents);
|
|
448
|
+
printLine(`Created terraform/ (${Object.keys(files).length} files)`);
|
|
152
449
|
}
|
|
153
|
-
|
|
154
|
-
|
|
450
|
+
/** Write the AWS Terraform reference stack (`terraform/*.tf` + README). */
|
|
451
|
+
async function writeTerraformFiles(outDir, answers) {
|
|
452
|
+
const files = generateAwsTerraformFiles({
|
|
453
|
+
project: answers.name,
|
|
454
|
+
region: answers.awsRegion ?? "us-east-1",
|
|
455
|
+
dbEngine: answers.dbEngine ?? "rds",
|
|
456
|
+
...answers.domainName ? { domainName: answers.domainName } : {},
|
|
457
|
+
...answers.samlMetadataUrl ? { samlMetadataUrl: answers.samlMetadataUrl } : {}
|
|
458
|
+
});
|
|
459
|
+
await mkdir(join(outDir, "terraform"), { recursive: true });
|
|
460
|
+
for (const [relative, contents] of Object.entries(files)) await writeFile(join(outDir, relative), contents);
|
|
461
|
+
printLine(`Created terraform/ (${Object.keys(files).length} files)`);
|
|
462
|
+
}
|
|
463
|
+
async function writeDockerFiles(outDir, answers) {
|
|
464
|
+
if (!answers.docker) return;
|
|
465
|
+
if (isRemoteQueue(answers.queue) && answers.includeWorker) {
|
|
466
|
+
const slimDockerfile = [
|
|
467
|
+
"FROM node:lts-alpine",
|
|
468
|
+
"WORKDIR /app",
|
|
469
|
+
"COPY package.json ./",
|
|
470
|
+
"RUN npm install --omit=dev",
|
|
471
|
+
"COPY server.ts ./",
|
|
472
|
+
"RUN npx esbuild server.ts --bundle --platform=node --format=esm --outfile=dist/server.mjs",
|
|
473
|
+
"EXPOSE 3000",
|
|
474
|
+
"CMD [\"node\", \"dist/server.mjs\"]"
|
|
475
|
+
].join("\n");
|
|
476
|
+
await writeFile(join(outDir, "Dockerfile"), slimDockerfile);
|
|
477
|
+
await writeFile(join(outDir, "Dockerfile.worker"), generateWorkerDockerfile());
|
|
478
|
+
await writeFile(join(outDir, ".dockerignore"), generateDockerignore());
|
|
479
|
+
await writeFile(join(outDir, "compose.yaml"), generateComposeYamlWithWorker(answers.database));
|
|
480
|
+
printLine(`Created Dockerfile, Dockerfile.worker, .dockerignore, compose.yaml`);
|
|
481
|
+
return;
|
|
482
|
+
}
|
|
155
483
|
await writeFile(join(outDir, "Dockerfile"), generateDockerfile());
|
|
156
484
|
await writeFile(join(outDir, ".dockerignore"), generateDockerignore());
|
|
157
|
-
await writeFile(join(outDir, "compose.yaml"), generateComposeYaml());
|
|
485
|
+
await writeFile(join(outDir, "compose.yaml"), generateComposeYaml(answers.database));
|
|
158
486
|
printLine(`Created Dockerfile, .dockerignore, compose.yaml`);
|
|
159
487
|
}
|
|
160
|
-
function printNextSteps(answers) {
|
|
488
|
+
function printNextSteps(answers, runner) {
|
|
161
489
|
printLine(`\nScaffolded ${answers.name} in ${resolve(answers.dir)}`);
|
|
162
490
|
printLine(`\nNext steps:`);
|
|
163
491
|
printLine(` cd ${answers.dir}`);
|
|
164
|
-
|
|
165
|
-
|
|
492
|
+
const target = resolveDeployTarget(answers);
|
|
493
|
+
if (target === "aws") {
|
|
494
|
+
printLine(` terraform -chdir=terraform init`);
|
|
495
|
+
printLine(` terraform -chdir=terraform plan -out=tfplan # review before applying`);
|
|
496
|
+
printLine(` terraform -chdir=terraform apply tfplan # or: npm run infra:apply`);
|
|
497
|
+
printLine(` # wire outputs into env (terraform output -json / npm run infra:outputs):`);
|
|
498
|
+
printLine(` # S3_BUCKET / AWS_REGION, QUEUE_URL, DATABASE_URL, COGNITO_* credentials`);
|
|
499
|
+
printLine(` npm install`);
|
|
500
|
+
printLine(` npm start`);
|
|
501
|
+
return;
|
|
502
|
+
}
|
|
503
|
+
if (target === "azure") {
|
|
504
|
+
printLine(` az login`);
|
|
505
|
+
printLine(` terraform -chdir=terraform init`);
|
|
506
|
+
printLine(` terraform -chdir=terraform plan -out=tfplan # review before applying`);
|
|
507
|
+
printLine(` terraform -chdir=terraform apply tfplan # or: npm run infra:apply`);
|
|
508
|
+
printLine(` # wire outputs into env (terraform output -json / npm run infra:outputs):`);
|
|
509
|
+
printLine(` # AZURE_STORAGE_CONNECTION (+ AZURE_SERVICE_BUS_CONNECTION), DATABASE_URL, OIDC_* credentials`);
|
|
166
510
|
printLine(` npm install`);
|
|
167
511
|
printLine(` npm start`);
|
|
512
|
+
return;
|
|
513
|
+
}
|
|
514
|
+
if (target === "gcp") {
|
|
515
|
+
printLine(` gcloud auth login`);
|
|
516
|
+
printLine(` terraform -chdir=terraform init`);
|
|
517
|
+
printLine(` terraform -chdir=terraform plan -out=tfplan # review before applying`);
|
|
518
|
+
printLine(` terraform -chdir=terraform apply tfplan # or: npm run infra:apply`);
|
|
519
|
+
printLine(` # wire outputs into env (terraform output -json / npm run infra:outputs):`);
|
|
520
|
+
printLine(` # GCS_BUCKET + GOOGLE_CLOUD_PROJECT, DATABASE_URL, OIDC_* credentials`);
|
|
521
|
+
printLine(` npm install`);
|
|
522
|
+
printLine(` npm start`);
|
|
523
|
+
return;
|
|
524
|
+
}
|
|
525
|
+
if (target === "docker") {
|
|
526
|
+
printLine(` npm run docker:up # builds + starts app (+ worker)`);
|
|
527
|
+
printLine(` npm run docker:logs # follow logs`);
|
|
528
|
+
printLine(` npm run docker:down # tear down`);
|
|
529
|
+
if (answers.includeWorker) {
|
|
530
|
+
printLine(` # or separately:`);
|
|
531
|
+
printLine(` # docker compose up storyshelf worker`);
|
|
532
|
+
}
|
|
533
|
+
return;
|
|
534
|
+
}
|
|
535
|
+
printLine(` ${installCommand(runner)}`);
|
|
536
|
+
printLine(` ${startCommand(runner)}`);
|
|
537
|
+
if (answers.includeWorker) {
|
|
538
|
+
printLine(` # in another terminal:`);
|
|
539
|
+
printLine(` npx storyshelf worker serve --dir .`);
|
|
168
540
|
}
|
|
169
541
|
}
|
|
542
|
+
function promptInitial(choices, value) {
|
|
543
|
+
if (value === void 0 || value === "") return;
|
|
544
|
+
const idx = choices.findIndex((c) => c.value === value);
|
|
545
|
+
if (idx === -1) return void 0;
|
|
546
|
+
return idx;
|
|
547
|
+
}
|
|
170
548
|
/**
|
|
171
|
-
* Scaffold a new StoryShelf server project.
|
|
549
|
+
* Scaffold a new StoryShelf server project via interactive prompts.
|
|
550
|
+
*
|
|
551
|
+
* Asks for project name, database, storage, auth, and git provider choices,
|
|
552
|
+
* then generates `server.ts`, `package.json`, and optional Docker files in
|
|
553
|
+
* the chosen directory.
|
|
172
554
|
*
|
|
173
|
-
* @param _options -
|
|
555
|
+
* @param _options - Reserved for future CLI flags; currently prompts for all choices interactively
|
|
174
556
|
*/
|
|
175
557
|
async function runServerInit(_options) {
|
|
176
|
-
const
|
|
177
|
-
|
|
558
|
+
const cwdPkg = detectInstalledAdapters(process.cwd());
|
|
559
|
+
const phase1 = await prompts([...PROJECT_PROMPTS, DEPLOY_TARGET_PROMPT]) ?? {};
|
|
560
|
+
if (!phase1["name"] || !phase1["dir"]) {
|
|
178
561
|
printError("Cancelled.");
|
|
179
562
|
return;
|
|
180
563
|
}
|
|
181
|
-
const answers =
|
|
564
|
+
const answers = { ...phase1 };
|
|
565
|
+
const target = resolveDeployTarget(answers);
|
|
566
|
+
if (target === "local" || target === "docker") {
|
|
567
|
+
const advPhase = await prompts(ADVANCED_ADAPTERS_PROMPT) ?? {};
|
|
568
|
+
const advanced = answers.advancedAdapters ?? advPhase["advancedAdapters"] ?? false;
|
|
569
|
+
answers.advancedAdapters = advanced;
|
|
570
|
+
const localPrompts = localInfraPrompts(advanced).map((prompt) => {
|
|
571
|
+
if (prompt.type !== "select") return prompt;
|
|
572
|
+
const legacy = answers[prompt.name];
|
|
573
|
+
const detected = cwdPkg[prompt.name];
|
|
574
|
+
const initial = promptInitial(prompt.choices, legacy ?? detected);
|
|
575
|
+
if (initial !== void 0) return {
|
|
576
|
+
...prompt,
|
|
577
|
+
initial
|
|
578
|
+
};
|
|
579
|
+
return prompt;
|
|
580
|
+
});
|
|
581
|
+
const phase2 = await prompts(localPrompts) ?? {};
|
|
582
|
+
Object.assign(answers, phase2);
|
|
583
|
+
answers.advancedAdapters = advanced;
|
|
584
|
+
answers.deployTarget ??= target;
|
|
585
|
+
} else {
|
|
586
|
+
const legacyInfra = { ...phase1 };
|
|
587
|
+
Object.assign(answers, legacyInfra);
|
|
588
|
+
}
|
|
589
|
+
if (!answers.queue) answers.queue = "memory";
|
|
590
|
+
if (resolveDeployTarget(answers) === "aws") {
|
|
591
|
+
answers.database = "postgres";
|
|
592
|
+
answers.storage = "s3";
|
|
593
|
+
answers.queue = "sqs";
|
|
594
|
+
answers.includeWorker = true;
|
|
595
|
+
answers.docker = true;
|
|
596
|
+
const awsAnswers = await prompts(AWS_INFRA_PROMPTS) ?? {};
|
|
597
|
+
answers.awsRegion = awsAnswers["awsRegion"] ?? "us-east-1";
|
|
598
|
+
answers.dbEngine = awsAnswers["dbEngine"] ?? "rds";
|
|
599
|
+
answers.domainName = awsAnswers["domainName"] ?? void 0;
|
|
600
|
+
answers.samlMetadataUrl = awsAnswers["samlMetadataUrl"] ?? void 0;
|
|
601
|
+
}
|
|
602
|
+
if (resolveDeployTarget(answers) === "azure") {
|
|
603
|
+
answers.database = "postgres";
|
|
604
|
+
answers.storage = "azure";
|
|
605
|
+
answers.includeWorker = true;
|
|
606
|
+
answers.docker = true;
|
|
607
|
+
const azureAnswers = await prompts(AZURE_INFRA_PROMPTS) ?? {};
|
|
608
|
+
answers.azureLocation = azureAnswers["azureLocation"] ?? "eastus";
|
|
609
|
+
answers.azureQueueBackend = azureAnswers["azureQueueBackend"] ?? "storage-queues";
|
|
610
|
+
answers.queue = answers.azureQueueBackend === "service-bus" ? "azure-service-bus" : "azure-storage-queues";
|
|
611
|
+
answers.domainName = azureAnswers["domainName"] ?? void 0;
|
|
612
|
+
answers.entraTenantId = azureAnswers["entraTenantId"] ?? void 0;
|
|
613
|
+
}
|
|
614
|
+
if (resolveDeployTarget(answers) === "gcp") {
|
|
615
|
+
answers.database = "postgres";
|
|
616
|
+
answers.storage = "gcs";
|
|
617
|
+
answers.queue = "gcp-pubsub";
|
|
618
|
+
answers.includeWorker = true;
|
|
619
|
+
answers.docker = true;
|
|
620
|
+
const gcpAnswers = await prompts(GCP_INFRA_PROMPTS) ?? {};
|
|
621
|
+
answers.gcpProjectId = gcpAnswers["gcpProjectId"] ?? void 0;
|
|
622
|
+
answers.gcpLocation = gcpAnswers["gcpLocation"] ?? "us-central1";
|
|
623
|
+
answers.domainName = gcpAnswers["domainName"] ?? void 0;
|
|
624
|
+
answers.identityTenant = gcpAnswers["identityTenant"] ?? void 0;
|
|
625
|
+
}
|
|
626
|
+
if (resolveDeployTarget(answers) !== "aws" && resolveDeployTarget(answers) !== "azure" && resolveDeployTarget(answers) !== "gcp" && isRemoteQueue(answers.queue)) answers.includeWorker = (await prompts({
|
|
627
|
+
type: "confirm",
|
|
628
|
+
name: "includeWorker",
|
|
629
|
+
message: "Generate worker service alongside server?",
|
|
630
|
+
initial: true
|
|
631
|
+
}) ?? {})["includeWorker"] ?? true;
|
|
182
632
|
const outDir = resolve(answers.dir);
|
|
183
633
|
await mkdir(outDir, { recursive: true });
|
|
184
634
|
await writeFiles(outDir, answers);
|
|
185
|
-
printNextSteps(answers);
|
|
635
|
+
printNextSteps(answers, await detectPackageRunner(process.cwd()));
|
|
186
636
|
}
|
|
187
637
|
//#endregion
|
|
188
638
|
export { runServerInit };
|