@cloudwerk/cli 0.15.0 → 0.15.2

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.
Files changed (2) hide show
  1. package/dist/index.js +494 -23
  2. package/package.json +4 -4
package/dist/index.js CHANGED
@@ -358,26 +358,6 @@ async function build(pathArg, options) {
358
358
  logger.debug(`Found ${serviceManifest.services.length} service(s)`);
359
359
  }
360
360
  }
361
- const renderer = cloudwerkConfig.ui?.renderer ?? "hono-jsx";
362
- const serverEntryCode = generateServerEntry(manifest, scanResult, {
363
- appDir,
364
- routesDir,
365
- config: cloudwerkConfig,
366
- serverEntry: null,
367
- clientEntry: null,
368
- verbose,
369
- hydrationEndpoint: "/__cloudwerk",
370
- renderer,
371
- publicDir: cloudwerkConfig.publicDir ?? "public",
372
- root: cwd,
373
- isProduction: true
374
- }, {
375
- queueManifest,
376
- serviceManifest
377
- });
378
- const tempEntryPath = path2.join(tempDir, "_server-entry.ts");
379
- fs2.writeFileSync(tempEntryPath, serverEntryCode);
380
- logger.debug(`Generated temp entry: ${tempEntryPath}`);
381
361
  logger.debug(`Building client assets...`);
382
362
  const baseClientConfig = {
383
363
  root: cwd,
@@ -391,11 +371,14 @@ async function build(pathArg, options) {
391
371
  emptyOutDir: true,
392
372
  minify: minify ? "esbuild" : false,
393
373
  sourcemap,
374
+ manifest: true,
375
+ // Generate manifest.json for asset mapping
394
376
  rollupOptions: {
395
377
  input: "virtual:cloudwerk/client-entry",
396
378
  output: {
397
- entryFileNames: "__cloudwerk/client.js",
379
+ entryFileNames: "__cloudwerk/client-[hash].js",
398
380
  chunkFileNames: "__cloudwerk/[name]-[hash].js",
381
+ // Use hashed names for CSS to enable caching
399
382
  assetFileNames: "__cloudwerk/[name]-[hash][extname]"
400
383
  }
401
384
  }
@@ -410,14 +393,41 @@ async function build(pathArg, options) {
410
393
  clientConfig.plugins = [...userPlugins, ...baseClientConfig.plugins ?? []];
411
394
  }
412
395
  }
396
+ let assetManifest = null;
413
397
  try {
414
398
  await viteBuild(clientConfig);
415
399
  logger.debug(`Client assets built successfully`);
400
+ const manifestPath = path2.join(outputDir, "static", ".vite", "manifest.json");
401
+ if (fs2.existsSync(manifestPath)) {
402
+ assetManifest = JSON.parse(fs2.readFileSync(manifestPath, "utf-8"));
403
+ logger.debug(`Loaded asset manifest with ${Object.keys(assetManifest).length} entries`);
404
+ }
416
405
  } catch (error) {
417
406
  if (verbose) {
418
407
  logger.debug(`Client build skipped or failed: ${error instanceof Error ? error.message : String(error)}`);
419
408
  }
420
409
  }
410
+ const renderer = cloudwerkConfig.ui?.renderer ?? "hono-jsx";
411
+ const serverEntryCode = generateServerEntry(manifest, scanResult, {
412
+ appDir,
413
+ routesDir,
414
+ config: cloudwerkConfig,
415
+ serverEntry: null,
416
+ clientEntry: null,
417
+ verbose,
418
+ hydrationEndpoint: "/__cloudwerk",
419
+ renderer,
420
+ publicDir: cloudwerkConfig.publicDir ?? "public",
421
+ root: cwd,
422
+ isProduction: true
423
+ }, {
424
+ queueManifest,
425
+ serviceManifest,
426
+ assetManifest
427
+ });
428
+ const tempEntryPath = path2.join(tempDir, "_server-entry.ts");
429
+ fs2.writeFileSync(tempEntryPath, serverEntryCode);
430
+ logger.debug(`Generated temp entry: ${tempEntryPath}`);
421
431
  logger.debug(`Building server bundle...`);
422
432
  const baseServerConfig = {
423
433
  root: cwd,
@@ -435,12 +445,16 @@ async function build(pathArg, options) {
435
445
  minify: minify ? "esbuild" : false,
436
446
  sourcemap,
437
447
  ssr: true,
448
+ // Emit CSS and other assets from SSR build (CSS imported in layouts, etc.)
449
+ ssrEmitAssets: true,
438
450
  rollupOptions: {
439
451
  input: tempEntryPath,
440
452
  // Externalize Node.js builtins (polyfilled by nodejs_compat in Workers)
441
453
  external: [...builtinModules, /^node:/],
442
454
  output: {
443
- entryFileNames: "index.js"
455
+ entryFileNames: "index.js",
456
+ // Put assets in static directory to be served by Cloudflare
457
+ assetFileNames: "static/assets/[name]-[hash][extname]"
444
458
  }
445
459
  }
446
460
  },
@@ -465,6 +479,12 @@ async function build(pathArg, options) {
465
479
  }
466
480
  await viteBuild(serverConfig);
467
481
  logger.debug(`Server bundle built successfully`);
482
+ const headersContent = `/__cloudwerk/*
483
+ Cache-Control: public, max-age=31536000, immutable
484
+ `;
485
+ const headersPath = path2.join(outputDir, "static", "_headers");
486
+ fs2.writeFileSync(headersPath, headersContent);
487
+ logger.debug(`Generated _headers file for static asset caching`);
468
488
  let ssgPaths = [];
469
489
  if (options.ssg) {
470
490
  logger.info(`Generating static pages...`);
@@ -1585,6 +1605,10 @@ function removeBinding(cwd, bindingName, env) {
1585
1605
  found = true;
1586
1606
  }
1587
1607
  }
1608
+ if (cfg.images && cfg.images.binding === bindingName) {
1609
+ delete cfg.images;
1610
+ found = true;
1611
+ }
1588
1612
  return found;
1589
1613
  };
1590
1614
  if (env && config.env?.[env]) {
@@ -1698,6 +1722,12 @@ function extractBindingsFromConfig(config) {
1698
1722
  });
1699
1723
  }
1700
1724
  }
1725
+ if (config.images) {
1726
+ bindings2.push({
1727
+ type: "images",
1728
+ name: config.images.binding
1729
+ });
1730
+ }
1701
1731
  return bindings2;
1702
1732
  }
1703
1733
  function getBindingTypeName(type) {
@@ -1722,6 +1752,8 @@ function getBindingTypeName(type) {
1722
1752
  return "Vectorize";
1723
1753
  case "hyperdrive":
1724
1754
  return "Hyperdrive";
1755
+ case "images":
1756
+ return "Images";
1725
1757
  default:
1726
1758
  return type;
1727
1759
  }
@@ -1826,7 +1858,8 @@ var TYPE_MAPPINGS = {
1826
1858
  secret: "string",
1827
1859
  ai: "Ai",
1828
1860
  vectorize: "VectorizeIndex",
1829
- hyperdrive: "Hyperdrive"
1861
+ hyperdrive: "Hyperdrive",
1862
+ images: 'import("@cloudwerk/core/bindings").CloudflareImagesBinding'
1830
1863
  };
1831
1864
  function getTypeForBinding(type) {
1832
1865
  return TYPE_MAPPINGS[type] || "unknown";
@@ -1857,6 +1890,7 @@ function generateEnvTypes(cwd, bindings2, options = {}) {
1857
1890
  "ai",
1858
1891
  "vectorize",
1859
1892
  "hyperdrive",
1893
+ "images",
1860
1894
  "secret"
1861
1895
  ];
1862
1896
  const resultBindings = [];
@@ -1911,6 +1945,8 @@ function getSectionName(type) {
1911
1945
  return "Vectorize Indexes";
1912
1946
  case "hyperdrive":
1913
1947
  return "Hyperdrive";
1948
+ case "images":
1949
+ return "Images";
1914
1950
  default:
1915
1951
  return "Other";
1916
1952
  }
@@ -3005,6 +3041,7 @@ function generateBindingsDts(bindings2, includeTimestamp) {
3005
3041
  "ai",
3006
3042
  "vectorize",
3007
3043
  "hyperdrive",
3044
+ "images",
3008
3045
  "secret"
3009
3046
  ];
3010
3047
  let firstSection = true;
@@ -3056,6 +3093,7 @@ function generateContextDts(bindings2, includeTimestamp) {
3056
3093
  "ai",
3057
3094
  "vectorize",
3058
3095
  "hyperdrive",
3096
+ "images",
3059
3097
  "secret"
3060
3098
  ];
3061
3099
  let firstSection = true;
@@ -3130,6 +3168,8 @@ function getSectionName2(type) {
3130
3168
  return "Vectorize Indexes";
3131
3169
  case "hyperdrive":
3132
3170
  return "Hyperdrive";
3171
+ case "images":
3172
+ return "Images";
3133
3173
  default:
3134
3174
  return "Other";
3135
3175
  }
@@ -5782,6 +5822,435 @@ async function servicesStatus(options = {}) {
5782
5822
  }
5783
5823
  }
5784
5824
 
5825
+ // src/commands/auth.ts
5826
+ import pc25 from "picocolors";
5827
+ import { loadConfig as loadConfig21 } from "@cloudwerk/core/build";
5828
+
5829
+ // src/utils/auth-scanner.ts
5830
+ import * as fs18 from "fs";
5831
+ import * as path20 from "path";
5832
+ import { pathToFileURL } from "url";
5833
+ import { build as build2 } from "esbuild";
5834
+ import {
5835
+ scanAuth,
5836
+ loadConfig as loadConfig20
5837
+ } from "@cloudwerk/core/build";
5838
+ async function compileTypeScriptModule(filePath) {
5839
+ const result = await build2({
5840
+ entryPoints: [filePath],
5841
+ bundle: true,
5842
+ write: false,
5843
+ format: "esm",
5844
+ platform: "node",
5845
+ target: "node20",
5846
+ // Externalize all packages - they'll be resolved from node_modules at runtime
5847
+ packages: "external"
5848
+ });
5849
+ const fileDir = path20.dirname(filePath);
5850
+ const fileName = path20.basename(filePath, path20.extname(filePath));
5851
+ const tempPath = path20.join(fileDir, `.${fileName}-${Date.now()}.mjs`);
5852
+ fs18.writeFileSync(tempPath, result.outputFiles[0].text);
5853
+ return tempPath;
5854
+ }
5855
+ async function loadTypeScriptModule(filePath) {
5856
+ let tempFile = null;
5857
+ try {
5858
+ if (filePath.endsWith(".ts") || filePath.endsWith(".tsx")) {
5859
+ tempFile = await compileTypeScriptModule(filePath);
5860
+ const module = await import(pathToFileURL(tempFile).href);
5861
+ return module;
5862
+ }
5863
+ return await import(pathToFileURL(filePath).href);
5864
+ } finally {
5865
+ if (tempFile) {
5866
+ try {
5867
+ fs18.unlinkSync(tempFile);
5868
+ } catch {
5869
+ }
5870
+ }
5871
+ }
5872
+ }
5873
+ async function scanAuthProviders(appDir) {
5874
+ const config = await loadConfig20(process.cwd());
5875
+ const scanResult = await scanAuth(appDir, { extensions: config.extensions });
5876
+ if (scanResult.providerFiles.length === 0) {
5877
+ return [];
5878
+ }
5879
+ const providers = [];
5880
+ for (const file of scanResult.providerFiles) {
5881
+ const entry = {
5882
+ id: file.providerId ?? file.name,
5883
+ type: "oauth",
5884
+ // Default
5885
+ filePath: file.absolutePath,
5886
+ name: file.name,
5887
+ disabled: false
5888
+ };
5889
+ try {
5890
+ const module = await loadTypeScriptModule(file.absolutePath);
5891
+ const exported = module?.default;
5892
+ if (exported && typeof exported === "object") {
5893
+ const obj = exported;
5894
+ if ("provider" in obj && "id" in obj && "type" in obj) {
5895
+ providers.push({
5896
+ id: obj.id,
5897
+ type: obj.type,
5898
+ filePath: file.absolutePath
5899
+ });
5900
+ continue;
5901
+ }
5902
+ if ("id" in obj && "type" in obj) {
5903
+ providers.push({
5904
+ id: obj.id,
5905
+ type: obj.type,
5906
+ filePath: file.absolutePath
5907
+ });
5908
+ continue;
5909
+ }
5910
+ }
5911
+ providers.push({
5912
+ id: entry.id,
5913
+ type: entry.type,
5914
+ filePath: entry.filePath
5915
+ });
5916
+ } catch (error) {
5917
+ console.warn(`Failed to load provider module: ${file.absolutePath}`, error);
5918
+ providers.push({
5919
+ id: entry.id,
5920
+ type: entry.type,
5921
+ filePath: entry.filePath
5922
+ });
5923
+ }
5924
+ }
5925
+ return providers;
5926
+ }
5927
+ async function loadAuthConfig(appDir) {
5928
+ const config = await loadConfig20(process.cwd());
5929
+ const scanResult = await scanAuth(appDir, { extensions: config.extensions });
5930
+ if (!scanResult.configFile) {
5931
+ return null;
5932
+ }
5933
+ try {
5934
+ const module = await loadTypeScriptModule(scanResult.configFile.absolutePath);
5935
+ const authConfig = module?.default;
5936
+ if (!authConfig) {
5937
+ return null;
5938
+ }
5939
+ const session = authConfig.session;
5940
+ return {
5941
+ basePath: authConfig.basePath,
5942
+ session: session?.strategy ? { strategy: session.strategy } : void 0,
5943
+ debug: authConfig.debug
5944
+ };
5945
+ } catch {
5946
+ return null;
5947
+ }
5948
+ }
5949
+ function generateUsersTableSQL() {
5950
+ return `-- Users table for authentication
5951
+ CREATE TABLE IF NOT EXISTS users (
5952
+ id TEXT PRIMARY KEY,
5953
+ email TEXT UNIQUE,
5954
+ email_verified TEXT,
5955
+ name TEXT,
5956
+ image TEXT,
5957
+ created_at TEXT NOT NULL DEFAULT (datetime('now')),
5958
+ updated_at TEXT NOT NULL DEFAULT (datetime('now'))
5959
+ );
5960
+
5961
+ CREATE INDEX IF NOT EXISTS idx_users_email ON users(email);
5962
+ `;
5963
+ }
5964
+ function generateAccountsTableSQL() {
5965
+ return `-- Accounts table for OAuth provider links
5966
+ CREATE TABLE IF NOT EXISTS accounts (
5967
+ id TEXT PRIMARY KEY,
5968
+ user_id TEXT NOT NULL,
5969
+ type TEXT NOT NULL,
5970
+ provider TEXT NOT NULL,
5971
+ provider_account_id TEXT NOT NULL,
5972
+ refresh_token TEXT,
5973
+ access_token TEXT,
5974
+ expires_at INTEGER,
5975
+ token_type TEXT,
5976
+ scope TEXT,
5977
+ id_token TEXT,
5978
+ FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE,
5979
+ UNIQUE(provider, provider_account_id)
5980
+ );
5981
+
5982
+ CREATE INDEX IF NOT EXISTS idx_accounts_user_id ON accounts(user_id);
5983
+ CREATE INDEX IF NOT EXISTS idx_accounts_provider ON accounts(provider, provider_account_id);
5984
+ `;
5985
+ }
5986
+ function generateSessionsTableSQL() {
5987
+ return `-- Sessions table for database session strategy
5988
+ CREATE TABLE IF NOT EXISTS sessions (
5989
+ id TEXT PRIMARY KEY,
5990
+ user_id TEXT NOT NULL,
5991
+ session_token TEXT UNIQUE NOT NULL,
5992
+ expires_at TEXT NOT NULL,
5993
+ created_at TEXT NOT NULL DEFAULT (datetime('now')),
5994
+ updated_at TEXT NOT NULL DEFAULT (datetime('now')),
5995
+ data TEXT,
5996
+ FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
5997
+ );
5998
+
5999
+ CREATE INDEX IF NOT EXISTS idx_sessions_user_id ON sessions(user_id);
6000
+ CREATE INDEX IF NOT EXISTS idx_sessions_token ON sessions(session_token);
6001
+ CREATE INDEX IF NOT EXISTS idx_sessions_expires ON sessions(expires_at);
6002
+ `;
6003
+ }
6004
+ function generateWebAuthnCredentialsTableSQL() {
6005
+ return `-- WebAuthn credentials table for passkey authentication
6006
+ CREATE TABLE IF NOT EXISTS webauthn_credentials (
6007
+ id TEXT PRIMARY KEY,
6008
+ user_id TEXT NOT NULL,
6009
+ public_key TEXT NOT NULL,
6010
+ counter INTEGER NOT NULL DEFAULT 0,
6011
+ aaguid TEXT,
6012
+ transports TEXT,
6013
+ backed_up INTEGER NOT NULL DEFAULT 0,
6014
+ device_type TEXT NOT NULL DEFAULT 'singleDevice',
6015
+ created_at TEXT NOT NULL DEFAULT (datetime('now')),
6016
+ last_used_at TEXT,
6017
+ name TEXT,
6018
+ FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
6019
+ );
6020
+
6021
+ CREATE INDEX IF NOT EXISTS idx_credentials_user_id ON webauthn_credentials(user_id);
6022
+ `;
6023
+ }
6024
+ function generateVerificationTokensTableSQL() {
6025
+ return `-- Verification tokens table for email verification and password reset
6026
+ CREATE TABLE IF NOT EXISTS verification_tokens (
6027
+ identifier TEXT NOT NULL,
6028
+ token TEXT NOT NULL,
6029
+ expires_at TEXT NOT NULL,
6030
+ PRIMARY KEY (identifier, token)
6031
+ );
6032
+
6033
+ CREATE INDEX IF NOT EXISTS idx_verification_tokens_token ON verification_tokens(token);
6034
+ `;
6035
+ }
6036
+ function generateUserRolesTableSQL() {
6037
+ return `-- User roles junction table for RBAC
6038
+ CREATE TABLE IF NOT EXISTS user_roles (
6039
+ user_id TEXT NOT NULL,
6040
+ role_id TEXT NOT NULL,
6041
+ assigned_at TEXT NOT NULL DEFAULT (datetime('now')),
6042
+ PRIMARY KEY (user_id, role_id),
6043
+ FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
6044
+ );
6045
+
6046
+ CREATE INDEX IF NOT EXISTS idx_user_roles_user_id ON user_roles(user_id);
6047
+ CREATE INDEX IF NOT EXISTS idx_user_roles_role_id ON user_roles(role_id);
6048
+ `;
6049
+ }
6050
+
6051
+ // src/commands/auth.ts
6052
+ async function auth(options = {}) {
6053
+ const verbose = options.verbose ?? false;
6054
+ const logger = createLogger(verbose);
6055
+ try {
6056
+ const cwd = process.cwd();
6057
+ logger.debug("Loading configuration...");
6058
+ const config = await loadConfig21(cwd);
6059
+ const appDir = config.appDir;
6060
+ logger.debug(`Scanning for auth providers in ${appDir}/auth/providers/...`);
6061
+ const providers = await scanAuthProviders(appDir);
6062
+ logger.debug(`Loading auth config from ${appDir}/auth/config.ts...`);
6063
+ const authConfig = await loadAuthConfig(appDir);
6064
+ console.log();
6065
+ console.log(pc25.bold("Cloudwerk Authentication"));
6066
+ console.log();
6067
+ console.log(pc25.dim(` Found ${providers.length} provider(s):`));
6068
+ if (providers.length === 0) {
6069
+ console.log(pc25.dim(" (none)"));
6070
+ } else {
6071
+ for (const provider of providers) {
6072
+ const typeColor = provider.type === "oauth" ? pc25.blue : provider.type === "passkey" ? pc25.yellow : provider.type === "credentials" ? pc25.green : pc25.cyan;
6073
+ console.log(
6074
+ ` ${pc25.cyan(provider.id)} ${pc25.dim("(")}${typeColor(provider.type)}${pc25.dim(")")}`
6075
+ );
6076
+ }
6077
+ }
6078
+ console.log();
6079
+ const sessionStrategy = authConfig?.session?.strategy ?? "jwt";
6080
+ console.log(pc25.dim(" Session strategy:"));
6081
+ console.log(
6082
+ ` ${sessionStrategy === "database" ? pc25.yellow("database") : pc25.green("jwt")} ${pc25.dim(sessionStrategy === "database" ? "(requires D1)" : "(stateless)")}`
6083
+ );
6084
+ console.log();
6085
+ console.log(pc25.bold("Commands:"));
6086
+ console.log();
6087
+ console.log(
6088
+ pc25.dim(" cloudwerk auth migrations ") + "Generate D1 migration files"
6089
+ );
6090
+ console.log();
6091
+ if (providers.length === 0) {
6092
+ console.log(pc25.bold("Quick Start:"));
6093
+ console.log();
6094
+ console.log(pc25.dim(" Create a provider at app/auth/providers/github.ts:"));
6095
+ console.log();
6096
+ console.log(
6097
+ pc25.cyan(" import { defineProvider, github } from '@cloudwerk/auth/convention'")
6098
+ );
6099
+ console.log();
6100
+ console.log(pc25.cyan(" export default defineProvider("));
6101
+ console.log(pc25.cyan(" github({"));
6102
+ console.log(pc25.cyan(" clientId: process.env.GITHUB_CLIENT_ID!,"));
6103
+ console.log(pc25.cyan(" clientSecret: process.env.GITHUB_CLIENT_SECRET!,"));
6104
+ console.log(pc25.cyan(" })"));
6105
+ console.log(pc25.cyan(" )"));
6106
+ console.log();
6107
+ }
6108
+ } catch (error) {
6109
+ handleCommandError(error, verbose);
6110
+ }
6111
+ }
6112
+
6113
+ // src/commands/auth/migrations.ts
6114
+ import * as fs19 from "fs";
6115
+ import * as path21 from "path";
6116
+ import pc26 from "picocolors";
6117
+ import { loadConfig as loadConfig22, scanAuth as scanAuth2 } from "@cloudwerk/core/build";
6118
+ async function authMigrations(options = {}) {
6119
+ const verbose = options.verbose ?? false;
6120
+ const dryRun = options.dryRun ?? false;
6121
+ const logger = createLogger(verbose);
6122
+ try {
6123
+ const cwd = process.cwd();
6124
+ logger.debug("Loading configuration...");
6125
+ const config = await loadConfig22(cwd);
6126
+ const appDir = config.appDir;
6127
+ logger.debug(`Scanning for auth providers in ${appDir}/auth/providers/...`);
6128
+ const providers = await scanAuthProviders(appDir);
6129
+ logger.debug(`Loading auth config from ${appDir}/auth/config.ts...`);
6130
+ const authConfig = await loadAuthConfig(appDir);
6131
+ console.log();
6132
+ console.log(pc26.bold("Auth Migrations Generator"));
6133
+ console.log();
6134
+ const tables = [];
6135
+ tables.push({
6136
+ name: "users",
6137
+ reason: "Required for all auth",
6138
+ sql: generateUsersTableSQL()
6139
+ });
6140
+ const hasOAuth = providers.some((p) => p.type === "oauth" || p.type === "oidc");
6141
+ const hasPasskey = providers.some((p) => p.type === "passkey");
6142
+ const hasEmail = providers.some((p) => p.type === "email");
6143
+ if (hasOAuth) {
6144
+ const oauthProviders = providers.filter((p) => p.type === "oauth" || p.type === "oidc").map((p) => p.id).join(", ");
6145
+ tables.push({
6146
+ name: "accounts",
6147
+ reason: `Required for OAuth providers (${oauthProviders})`,
6148
+ sql: generateAccountsTableSQL()
6149
+ });
6150
+ }
6151
+ const sessionStrategy = authConfig?.session?.strategy ?? "jwt";
6152
+ if (sessionStrategy === "database") {
6153
+ tables.push({
6154
+ name: "sessions",
6155
+ reason: "Required for database session strategy",
6156
+ sql: generateSessionsTableSQL()
6157
+ });
6158
+ }
6159
+ if (hasPasskey) {
6160
+ const passkeyProvider = providers.find((p) => p.type === "passkey");
6161
+ tables.push({
6162
+ name: "webauthn_credentials",
6163
+ reason: `Required for passkey provider (${passkeyProvider?.id})`,
6164
+ sql: generateWebAuthnCredentialsTableSQL()
6165
+ });
6166
+ }
6167
+ if (hasEmail) {
6168
+ const emailProvider = providers.find((p) => p.type === "email");
6169
+ tables.push({
6170
+ name: "verification_tokens",
6171
+ reason: `Required for email provider (${emailProvider?.id})`,
6172
+ sql: generateVerificationTokensTableSQL()
6173
+ });
6174
+ }
6175
+ const authScanResult = await scanAuth2(appDir, { extensions: config.extensions });
6176
+ const hasRBAC = authScanResult.rbacFile !== void 0;
6177
+ if (hasRBAC) {
6178
+ tables.push({
6179
+ name: "user_roles",
6180
+ reason: "Required for role-based access control",
6181
+ sql: generateUserRolesTableSQL()
6182
+ });
6183
+ }
6184
+ console.log(pc26.dim(" Detected configuration:"));
6185
+ console.log(
6186
+ ` ${pc26.cyan("Providers")}: ${providers.length === 0 ? pc26.dim("(none)") : providers.map((p) => `${p.id} (${p.type})`).join(", ")}`
6187
+ );
6188
+ console.log(
6189
+ ` ${pc26.cyan("Session")}: ${sessionStrategy === "database" ? pc26.yellow("database") : pc26.green("jwt")}`
6190
+ );
6191
+ console.log(
6192
+ ` ${pc26.cyan("RBAC")}: ${hasRBAC ? pc26.green("enabled") : pc26.dim("(none)")}`
6193
+ );
6194
+ console.log();
6195
+ console.log(pc26.dim(" Tables to create:"));
6196
+ for (const table of tables) {
6197
+ console.log(` ${pc26.green("\u2713")} ${pc26.cyan(table.name)} ${pc26.dim(`- ${table.reason}`)}`);
6198
+ }
6199
+ console.log();
6200
+ const outputDir = options.output ?? path21.join(cwd, "migrations");
6201
+ const migrationName = "0001_auth_tables.sql";
6202
+ const migrationPath = path21.join(outputDir, migrationName);
6203
+ const migrationContent = [
6204
+ "-- Cloudwerk Auth Migration",
6205
+ "-- Generated by: cloudwerk auth migrations",
6206
+ `-- Date: ${(/* @__PURE__ */ new Date()).toISOString()}`,
6207
+ "--",
6208
+ "-- This migration creates the following tables:",
6209
+ ...tables.map((t) => `-- - ${t.name}: ${t.reason}`),
6210
+ "",
6211
+ ...tables.map((t) => t.sql)
6212
+ ].join("\n");
6213
+ if (dryRun) {
6214
+ console.log(pc26.bold("Dry run - Migration content:"));
6215
+ console.log();
6216
+ console.log(pc26.dim("\u2500".repeat(60)));
6217
+ console.log(migrationContent);
6218
+ console.log(pc26.dim("\u2500".repeat(60)));
6219
+ console.log();
6220
+ console.log(pc26.dim(` Would write to: ${migrationPath}`));
6221
+ console.log();
6222
+ return;
6223
+ }
6224
+ if (!fs19.existsSync(outputDir)) {
6225
+ fs19.mkdirSync(outputDir, { recursive: true });
6226
+ console.log(pc26.dim(` Created migrations directory: ${outputDir}`));
6227
+ }
6228
+ if (fs19.existsSync(migrationPath)) {
6229
+ console.log(pc26.yellow(` Migration already exists: ${migrationPath}`));
6230
+ console.log(pc26.dim(" Use --dry-run to preview the migration content"));
6231
+ console.log(pc26.dim(" Delete the existing file to regenerate"));
6232
+ console.log();
6233
+ return;
6234
+ }
6235
+ fs19.writeFileSync(migrationPath, migrationContent);
6236
+ console.log(pc26.green(` \u2713 Created migration: ${migrationPath}`));
6237
+ console.log();
6238
+ console.log(pc26.bold("Next steps:"));
6239
+ console.log();
6240
+ console.log(pc26.dim(" 1. Review the generated migration file"));
6241
+ console.log(pc26.dim(" 2. Apply with wrangler:"));
6242
+ console.log();
6243
+ console.log(pc26.cyan(" wrangler d1 migrations apply <DATABASE_NAME> --local"));
6244
+ console.log();
6245
+ console.log(pc26.dim(" 3. For production:"));
6246
+ console.log();
6247
+ console.log(pc26.cyan(" wrangler d1 migrations apply <DATABASE_NAME> --remote"));
6248
+ console.log();
6249
+ } catch (error) {
6250
+ handleCommandError(error, verbose);
6251
+ }
6252
+ }
6253
+
5785
6254
  // src/index.ts
5786
6255
  program.name("cloudwerk").description("Cloudwerk CLI - Build and deploy full-stack apps to Cloudflare").version(VERSION).enablePositionalOptions();
5787
6256
  program.command("dev [path]").description("Start development server").option("-p, --port <number>", "Port to listen on", String(DEFAULT_PORT)).option("-H, --host <host>", "Host to bind", DEFAULT_HOST).option("-c, --config <path>", "Path to config file").option("--verbose", "Enable verbose logging").action(dev);
@@ -5811,4 +6280,6 @@ servicesCmd.command("extract <name>").description("Extract service to separate W
5811
6280
  servicesCmd.command("inline <name>").description("Convert extracted service back to local mode").option("--verbose", "Enable verbose logging").action(servicesInline);
5812
6281
  servicesCmd.command("deploy <name>").description("Deploy extracted service").option("-e, --env <environment>", "Environment to deploy to").option("--verbose", "Enable verbose logging").action(servicesDeploy);
5813
6282
  servicesCmd.command("status").description("Show status of all services").option("--json", "Output as JSON").option("--verbose", "Enable verbose logging").action(servicesStatus);
6283
+ var authCmd = program.command("auth").description("Manage Cloudwerk authentication").enablePositionalOptions().passThroughOptions().option("--verbose", "Enable verbose logging").action(auth);
6284
+ authCmd.command("migrations").description("Generate D1 migration files for auth tables").option("-o, --output <dir>", "Output directory for migrations").option("--dry-run", "Preview migration without writing").option("--verbose", "Enable verbose logging").action(authMigrations);
5814
6285
  program.parse();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cloudwerk/cli",
3
- "version": "0.15.0",
3
+ "version": "0.15.2",
4
4
  "description": "Dev server, build, deploy commands for Cloudwerk",
5
5
  "repository": {
6
6
  "type": "git",
@@ -31,9 +31,9 @@
31
31
  "picocolors": "^1.1.0",
32
32
  "wrangler": "^4.0.0",
33
33
  "vite": "^6.0.0",
34
- "@cloudwerk/core": "^0.15.0",
35
- "@cloudwerk/vite-plugin": "^0.6.2",
36
- "@cloudwerk/ui": "^0.15.0"
34
+ "@cloudwerk/core": "^0.15.1",
35
+ "@cloudwerk/ui": "^0.15.1",
36
+ "@cloudwerk/vite-plugin": "^0.6.5"
37
37
  },
38
38
  "devDependencies": {
39
39
  "@types/node": "^20.0.0",