@cloudwerk/cli 0.15.0 → 0.15.6

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 +511 -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,20 @@ 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",
378
+ onwarn(warning, defaultHandler) {
379
+ if (warning.code === "MODULE_LEVEL_DIRECTIVE" && warning.message.includes('"use client"')) {
380
+ return;
381
+ }
382
+ defaultHandler(warning);
383
+ },
396
384
  output: {
397
- entryFileNames: "__cloudwerk/client.js",
385
+ entryFileNames: "__cloudwerk/client-[hash].js",
398
386
  chunkFileNames: "__cloudwerk/[name]-[hash].js",
387
+ // Use hashed names for CSS to enable caching
399
388
  assetFileNames: "__cloudwerk/[name]-[hash][extname]"
400
389
  }
401
390
  }
@@ -410,14 +399,41 @@ async function build(pathArg, options) {
410
399
  clientConfig.plugins = [...userPlugins, ...baseClientConfig.plugins ?? []];
411
400
  }
412
401
  }
402
+ let assetManifest = null;
413
403
  try {
414
404
  await viteBuild(clientConfig);
415
405
  logger.debug(`Client assets built successfully`);
406
+ const manifestPath = path2.join(outputDir, "static", ".vite", "manifest.json");
407
+ if (fs2.existsSync(manifestPath)) {
408
+ assetManifest = JSON.parse(fs2.readFileSync(manifestPath, "utf-8"));
409
+ logger.debug(`Loaded asset manifest with ${Object.keys(assetManifest).length} entries`);
410
+ }
416
411
  } catch (error) {
417
412
  if (verbose) {
418
413
  logger.debug(`Client build skipped or failed: ${error instanceof Error ? error.message : String(error)}`);
419
414
  }
420
415
  }
416
+ const renderer = cloudwerkConfig.ui?.renderer ?? "hono-jsx";
417
+ const serverEntryCode = generateServerEntry(manifest, scanResult, {
418
+ appDir,
419
+ routesDir,
420
+ config: cloudwerkConfig,
421
+ serverEntry: null,
422
+ clientEntry: null,
423
+ verbose,
424
+ hydrationEndpoint: "/__cloudwerk",
425
+ renderer,
426
+ publicDir: cloudwerkConfig.publicDir ?? "public",
427
+ root: cwd,
428
+ isProduction: true
429
+ }, {
430
+ queueManifest,
431
+ serviceManifest,
432
+ assetManifest
433
+ });
434
+ const tempEntryPath = path2.join(tempDir, "_server-entry.ts");
435
+ fs2.writeFileSync(tempEntryPath, serverEntryCode);
436
+ logger.debug(`Generated temp entry: ${tempEntryPath}`);
421
437
  logger.debug(`Building server bundle...`);
422
438
  const baseServerConfig = {
423
439
  root: cwd,
@@ -434,13 +450,28 @@ async function build(pathArg, options) {
434
450
  // Don't clear - client assets are already there
435
451
  minify: minify ? "esbuild" : false,
436
452
  sourcemap,
453
+ // Use esnext target to support top-level await (used by React renderer init)
454
+ target: "esnext",
437
455
  ssr: true,
456
+ // Emit CSS and other assets from SSR build (CSS imported in layouts, etc.)
457
+ ssrEmitAssets: true,
438
458
  rollupOptions: {
439
459
  input: tempEntryPath,
440
460
  // Externalize Node.js builtins (polyfilled by nodejs_compat in Workers)
441
461
  external: [...builtinModules, /^node:/],
462
+ onwarn(warning, defaultHandler) {
463
+ if (warning.code === "MODULE_LEVEL_DIRECTIVE" && warning.message.includes('"use client"')) {
464
+ return;
465
+ }
466
+ if (warning.code === "MISSING_EXPORT") {
467
+ return;
468
+ }
469
+ defaultHandler(warning);
470
+ },
442
471
  output: {
443
- entryFileNames: "index.js"
472
+ entryFileNames: "index.js",
473
+ // Put assets in static directory to be served by Cloudflare
474
+ assetFileNames: "static/assets/[name]-[hash][extname]"
444
475
  }
445
476
  }
446
477
  },
@@ -465,6 +496,12 @@ async function build(pathArg, options) {
465
496
  }
466
497
  await viteBuild(serverConfig);
467
498
  logger.debug(`Server bundle built successfully`);
499
+ const headersContent = `/__cloudwerk/*
500
+ Cache-Control: public, max-age=31536000, immutable
501
+ `;
502
+ const headersPath = path2.join(outputDir, "static", "_headers");
503
+ fs2.writeFileSync(headersPath, headersContent);
504
+ logger.debug(`Generated _headers file for static asset caching`);
468
505
  let ssgPaths = [];
469
506
  if (options.ssg) {
470
507
  logger.info(`Generating static pages...`);
@@ -1585,6 +1622,10 @@ function removeBinding(cwd, bindingName, env) {
1585
1622
  found = true;
1586
1623
  }
1587
1624
  }
1625
+ if (cfg.images && cfg.images.binding === bindingName) {
1626
+ delete cfg.images;
1627
+ found = true;
1628
+ }
1588
1629
  return found;
1589
1630
  };
1590
1631
  if (env && config.env?.[env]) {
@@ -1698,6 +1739,12 @@ function extractBindingsFromConfig(config) {
1698
1739
  });
1699
1740
  }
1700
1741
  }
1742
+ if (config.images) {
1743
+ bindings2.push({
1744
+ type: "images",
1745
+ name: config.images.binding
1746
+ });
1747
+ }
1701
1748
  return bindings2;
1702
1749
  }
1703
1750
  function getBindingTypeName(type) {
@@ -1722,6 +1769,8 @@ function getBindingTypeName(type) {
1722
1769
  return "Vectorize";
1723
1770
  case "hyperdrive":
1724
1771
  return "Hyperdrive";
1772
+ case "images":
1773
+ return "Images";
1725
1774
  default:
1726
1775
  return type;
1727
1776
  }
@@ -1826,7 +1875,8 @@ var TYPE_MAPPINGS = {
1826
1875
  secret: "string",
1827
1876
  ai: "Ai",
1828
1877
  vectorize: "VectorizeIndex",
1829
- hyperdrive: "Hyperdrive"
1878
+ hyperdrive: "Hyperdrive",
1879
+ images: 'import("@cloudwerk/core/bindings").CloudflareImagesBinding'
1830
1880
  };
1831
1881
  function getTypeForBinding(type) {
1832
1882
  return TYPE_MAPPINGS[type] || "unknown";
@@ -1857,6 +1907,7 @@ function generateEnvTypes(cwd, bindings2, options = {}) {
1857
1907
  "ai",
1858
1908
  "vectorize",
1859
1909
  "hyperdrive",
1910
+ "images",
1860
1911
  "secret"
1861
1912
  ];
1862
1913
  const resultBindings = [];
@@ -1911,6 +1962,8 @@ function getSectionName(type) {
1911
1962
  return "Vectorize Indexes";
1912
1963
  case "hyperdrive":
1913
1964
  return "Hyperdrive";
1965
+ case "images":
1966
+ return "Images";
1914
1967
  default:
1915
1968
  return "Other";
1916
1969
  }
@@ -3005,6 +3058,7 @@ function generateBindingsDts(bindings2, includeTimestamp) {
3005
3058
  "ai",
3006
3059
  "vectorize",
3007
3060
  "hyperdrive",
3061
+ "images",
3008
3062
  "secret"
3009
3063
  ];
3010
3064
  let firstSection = true;
@@ -3056,6 +3110,7 @@ function generateContextDts(bindings2, includeTimestamp) {
3056
3110
  "ai",
3057
3111
  "vectorize",
3058
3112
  "hyperdrive",
3113
+ "images",
3059
3114
  "secret"
3060
3115
  ];
3061
3116
  let firstSection = true;
@@ -3130,6 +3185,8 @@ function getSectionName2(type) {
3130
3185
  return "Vectorize Indexes";
3131
3186
  case "hyperdrive":
3132
3187
  return "Hyperdrive";
3188
+ case "images":
3189
+ return "Images";
3133
3190
  default:
3134
3191
  return "Other";
3135
3192
  }
@@ -5782,6 +5839,435 @@ async function servicesStatus(options = {}) {
5782
5839
  }
5783
5840
  }
5784
5841
 
5842
+ // src/commands/auth.ts
5843
+ import pc25 from "picocolors";
5844
+ import { loadConfig as loadConfig21 } from "@cloudwerk/core/build";
5845
+
5846
+ // src/utils/auth-scanner.ts
5847
+ import * as fs18 from "fs";
5848
+ import * as path20 from "path";
5849
+ import { pathToFileURL } from "url";
5850
+ import { build as build2 } from "esbuild";
5851
+ import {
5852
+ scanAuth,
5853
+ loadConfig as loadConfig20
5854
+ } from "@cloudwerk/core/build";
5855
+ async function compileTypeScriptModule(filePath) {
5856
+ const result = await build2({
5857
+ entryPoints: [filePath],
5858
+ bundle: true,
5859
+ write: false,
5860
+ format: "esm",
5861
+ platform: "node",
5862
+ target: "node20",
5863
+ // Externalize all packages - they'll be resolved from node_modules at runtime
5864
+ packages: "external"
5865
+ });
5866
+ const fileDir = path20.dirname(filePath);
5867
+ const fileName = path20.basename(filePath, path20.extname(filePath));
5868
+ const tempPath = path20.join(fileDir, `.${fileName}-${Date.now()}.mjs`);
5869
+ fs18.writeFileSync(tempPath, result.outputFiles[0].text);
5870
+ return tempPath;
5871
+ }
5872
+ async function loadTypeScriptModule(filePath) {
5873
+ let tempFile = null;
5874
+ try {
5875
+ if (filePath.endsWith(".ts") || filePath.endsWith(".tsx")) {
5876
+ tempFile = await compileTypeScriptModule(filePath);
5877
+ const module = await import(pathToFileURL(tempFile).href);
5878
+ return module;
5879
+ }
5880
+ return await import(pathToFileURL(filePath).href);
5881
+ } finally {
5882
+ if (tempFile) {
5883
+ try {
5884
+ fs18.unlinkSync(tempFile);
5885
+ } catch {
5886
+ }
5887
+ }
5888
+ }
5889
+ }
5890
+ async function scanAuthProviders(appDir) {
5891
+ const config = await loadConfig20(process.cwd());
5892
+ const scanResult = await scanAuth(appDir, { extensions: config.extensions });
5893
+ if (scanResult.providerFiles.length === 0) {
5894
+ return [];
5895
+ }
5896
+ const providers = [];
5897
+ for (const file of scanResult.providerFiles) {
5898
+ const entry = {
5899
+ id: file.providerId ?? file.name,
5900
+ type: "oauth",
5901
+ // Default
5902
+ filePath: file.absolutePath,
5903
+ name: file.name,
5904
+ disabled: false
5905
+ };
5906
+ try {
5907
+ const module = await loadTypeScriptModule(file.absolutePath);
5908
+ const exported = module?.default;
5909
+ if (exported && typeof exported === "object") {
5910
+ const obj = exported;
5911
+ if ("provider" in obj && "id" in obj && "type" in obj) {
5912
+ providers.push({
5913
+ id: obj.id,
5914
+ type: obj.type,
5915
+ filePath: file.absolutePath
5916
+ });
5917
+ continue;
5918
+ }
5919
+ if ("id" in obj && "type" in obj) {
5920
+ providers.push({
5921
+ id: obj.id,
5922
+ type: obj.type,
5923
+ filePath: file.absolutePath
5924
+ });
5925
+ continue;
5926
+ }
5927
+ }
5928
+ providers.push({
5929
+ id: entry.id,
5930
+ type: entry.type,
5931
+ filePath: entry.filePath
5932
+ });
5933
+ } catch (error) {
5934
+ console.warn(`Failed to load provider module: ${file.absolutePath}`, error);
5935
+ providers.push({
5936
+ id: entry.id,
5937
+ type: entry.type,
5938
+ filePath: entry.filePath
5939
+ });
5940
+ }
5941
+ }
5942
+ return providers;
5943
+ }
5944
+ async function loadAuthConfig(appDir) {
5945
+ const config = await loadConfig20(process.cwd());
5946
+ const scanResult = await scanAuth(appDir, { extensions: config.extensions });
5947
+ if (!scanResult.configFile) {
5948
+ return null;
5949
+ }
5950
+ try {
5951
+ const module = await loadTypeScriptModule(scanResult.configFile.absolutePath);
5952
+ const authConfig = module?.default;
5953
+ if (!authConfig) {
5954
+ return null;
5955
+ }
5956
+ const session = authConfig.session;
5957
+ return {
5958
+ basePath: authConfig.basePath,
5959
+ session: session?.strategy ? { strategy: session.strategy } : void 0,
5960
+ debug: authConfig.debug
5961
+ };
5962
+ } catch {
5963
+ return null;
5964
+ }
5965
+ }
5966
+ function generateUsersTableSQL() {
5967
+ return `-- Users table for authentication
5968
+ CREATE TABLE IF NOT EXISTS users (
5969
+ id TEXT PRIMARY KEY,
5970
+ email TEXT UNIQUE,
5971
+ email_verified TEXT,
5972
+ name TEXT,
5973
+ image TEXT,
5974
+ created_at TEXT NOT NULL DEFAULT (datetime('now')),
5975
+ updated_at TEXT NOT NULL DEFAULT (datetime('now'))
5976
+ );
5977
+
5978
+ CREATE INDEX IF NOT EXISTS idx_users_email ON users(email);
5979
+ `;
5980
+ }
5981
+ function generateAccountsTableSQL() {
5982
+ return `-- Accounts table for OAuth provider links
5983
+ CREATE TABLE IF NOT EXISTS accounts (
5984
+ id TEXT PRIMARY KEY,
5985
+ user_id TEXT NOT NULL,
5986
+ type TEXT NOT NULL,
5987
+ provider TEXT NOT NULL,
5988
+ provider_account_id TEXT NOT NULL,
5989
+ refresh_token TEXT,
5990
+ access_token TEXT,
5991
+ expires_at INTEGER,
5992
+ token_type TEXT,
5993
+ scope TEXT,
5994
+ id_token TEXT,
5995
+ FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE,
5996
+ UNIQUE(provider, provider_account_id)
5997
+ );
5998
+
5999
+ CREATE INDEX IF NOT EXISTS idx_accounts_user_id ON accounts(user_id);
6000
+ CREATE INDEX IF NOT EXISTS idx_accounts_provider ON accounts(provider, provider_account_id);
6001
+ `;
6002
+ }
6003
+ function generateSessionsTableSQL() {
6004
+ return `-- Sessions table for database session strategy
6005
+ CREATE TABLE IF NOT EXISTS sessions (
6006
+ id TEXT PRIMARY KEY,
6007
+ user_id TEXT NOT NULL,
6008
+ session_token TEXT UNIQUE NOT NULL,
6009
+ expires_at TEXT NOT NULL,
6010
+ created_at TEXT NOT NULL DEFAULT (datetime('now')),
6011
+ updated_at TEXT NOT NULL DEFAULT (datetime('now')),
6012
+ data TEXT,
6013
+ FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
6014
+ );
6015
+
6016
+ CREATE INDEX IF NOT EXISTS idx_sessions_user_id ON sessions(user_id);
6017
+ CREATE INDEX IF NOT EXISTS idx_sessions_token ON sessions(session_token);
6018
+ CREATE INDEX IF NOT EXISTS idx_sessions_expires ON sessions(expires_at);
6019
+ `;
6020
+ }
6021
+ function generateWebAuthnCredentialsTableSQL() {
6022
+ return `-- WebAuthn credentials table for passkey authentication
6023
+ CREATE TABLE IF NOT EXISTS webauthn_credentials (
6024
+ id TEXT PRIMARY KEY,
6025
+ user_id TEXT NOT NULL,
6026
+ public_key TEXT NOT NULL,
6027
+ counter INTEGER NOT NULL DEFAULT 0,
6028
+ aaguid TEXT,
6029
+ transports TEXT,
6030
+ backed_up INTEGER NOT NULL DEFAULT 0,
6031
+ device_type TEXT NOT NULL DEFAULT 'singleDevice',
6032
+ created_at TEXT NOT NULL DEFAULT (datetime('now')),
6033
+ last_used_at TEXT,
6034
+ name TEXT,
6035
+ FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
6036
+ );
6037
+
6038
+ CREATE INDEX IF NOT EXISTS idx_credentials_user_id ON webauthn_credentials(user_id);
6039
+ `;
6040
+ }
6041
+ function generateVerificationTokensTableSQL() {
6042
+ return `-- Verification tokens table for email verification and password reset
6043
+ CREATE TABLE IF NOT EXISTS verification_tokens (
6044
+ identifier TEXT NOT NULL,
6045
+ token TEXT NOT NULL,
6046
+ expires_at TEXT NOT NULL,
6047
+ PRIMARY KEY (identifier, token)
6048
+ );
6049
+
6050
+ CREATE INDEX IF NOT EXISTS idx_verification_tokens_token ON verification_tokens(token);
6051
+ `;
6052
+ }
6053
+ function generateUserRolesTableSQL() {
6054
+ return `-- User roles junction table for RBAC
6055
+ CREATE TABLE IF NOT EXISTS user_roles (
6056
+ user_id TEXT NOT NULL,
6057
+ role_id TEXT NOT NULL,
6058
+ assigned_at TEXT NOT NULL DEFAULT (datetime('now')),
6059
+ PRIMARY KEY (user_id, role_id),
6060
+ FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
6061
+ );
6062
+
6063
+ CREATE INDEX IF NOT EXISTS idx_user_roles_user_id ON user_roles(user_id);
6064
+ CREATE INDEX IF NOT EXISTS idx_user_roles_role_id ON user_roles(role_id);
6065
+ `;
6066
+ }
6067
+
6068
+ // src/commands/auth.ts
6069
+ async function auth(options = {}) {
6070
+ const verbose = options.verbose ?? false;
6071
+ const logger = createLogger(verbose);
6072
+ try {
6073
+ const cwd = process.cwd();
6074
+ logger.debug("Loading configuration...");
6075
+ const config = await loadConfig21(cwd);
6076
+ const appDir = config.appDir;
6077
+ logger.debug(`Scanning for auth providers in ${appDir}/auth/providers/...`);
6078
+ const providers = await scanAuthProviders(appDir);
6079
+ logger.debug(`Loading auth config from ${appDir}/auth/config.ts...`);
6080
+ const authConfig = await loadAuthConfig(appDir);
6081
+ console.log();
6082
+ console.log(pc25.bold("Cloudwerk Authentication"));
6083
+ console.log();
6084
+ console.log(pc25.dim(` Found ${providers.length} provider(s):`));
6085
+ if (providers.length === 0) {
6086
+ console.log(pc25.dim(" (none)"));
6087
+ } else {
6088
+ for (const provider of providers) {
6089
+ const typeColor = provider.type === "oauth" ? pc25.blue : provider.type === "passkey" ? pc25.yellow : provider.type === "credentials" ? pc25.green : pc25.cyan;
6090
+ console.log(
6091
+ ` ${pc25.cyan(provider.id)} ${pc25.dim("(")}${typeColor(provider.type)}${pc25.dim(")")}`
6092
+ );
6093
+ }
6094
+ }
6095
+ console.log();
6096
+ const sessionStrategy = authConfig?.session?.strategy ?? "jwt";
6097
+ console.log(pc25.dim(" Session strategy:"));
6098
+ console.log(
6099
+ ` ${sessionStrategy === "database" ? pc25.yellow("database") : pc25.green("jwt")} ${pc25.dim(sessionStrategy === "database" ? "(requires D1)" : "(stateless)")}`
6100
+ );
6101
+ console.log();
6102
+ console.log(pc25.bold("Commands:"));
6103
+ console.log();
6104
+ console.log(
6105
+ pc25.dim(" cloudwerk auth migrations ") + "Generate D1 migration files"
6106
+ );
6107
+ console.log();
6108
+ if (providers.length === 0) {
6109
+ console.log(pc25.bold("Quick Start:"));
6110
+ console.log();
6111
+ console.log(pc25.dim(" Create a provider at app/auth/providers/github.ts:"));
6112
+ console.log();
6113
+ console.log(
6114
+ pc25.cyan(" import { defineProvider, github } from '@cloudwerk/auth/convention'")
6115
+ );
6116
+ console.log();
6117
+ console.log(pc25.cyan(" export default defineProvider("));
6118
+ console.log(pc25.cyan(" github({"));
6119
+ console.log(pc25.cyan(" clientId: process.env.GITHUB_CLIENT_ID!,"));
6120
+ console.log(pc25.cyan(" clientSecret: process.env.GITHUB_CLIENT_SECRET!,"));
6121
+ console.log(pc25.cyan(" })"));
6122
+ console.log(pc25.cyan(" )"));
6123
+ console.log();
6124
+ }
6125
+ } catch (error) {
6126
+ handleCommandError(error, verbose);
6127
+ }
6128
+ }
6129
+
6130
+ // src/commands/auth/migrations.ts
6131
+ import * as fs19 from "fs";
6132
+ import * as path21 from "path";
6133
+ import pc26 from "picocolors";
6134
+ import { loadConfig as loadConfig22, scanAuth as scanAuth2 } from "@cloudwerk/core/build";
6135
+ async function authMigrations(options = {}) {
6136
+ const verbose = options.verbose ?? false;
6137
+ const dryRun = options.dryRun ?? false;
6138
+ const logger = createLogger(verbose);
6139
+ try {
6140
+ const cwd = process.cwd();
6141
+ logger.debug("Loading configuration...");
6142
+ const config = await loadConfig22(cwd);
6143
+ const appDir = config.appDir;
6144
+ logger.debug(`Scanning for auth providers in ${appDir}/auth/providers/...`);
6145
+ const providers = await scanAuthProviders(appDir);
6146
+ logger.debug(`Loading auth config from ${appDir}/auth/config.ts...`);
6147
+ const authConfig = await loadAuthConfig(appDir);
6148
+ console.log();
6149
+ console.log(pc26.bold("Auth Migrations Generator"));
6150
+ console.log();
6151
+ const tables = [];
6152
+ tables.push({
6153
+ name: "users",
6154
+ reason: "Required for all auth",
6155
+ sql: generateUsersTableSQL()
6156
+ });
6157
+ const hasOAuth = providers.some((p) => p.type === "oauth" || p.type === "oidc");
6158
+ const hasPasskey = providers.some((p) => p.type === "passkey");
6159
+ const hasEmail = providers.some((p) => p.type === "email");
6160
+ if (hasOAuth) {
6161
+ const oauthProviders = providers.filter((p) => p.type === "oauth" || p.type === "oidc").map((p) => p.id).join(", ");
6162
+ tables.push({
6163
+ name: "accounts",
6164
+ reason: `Required for OAuth providers (${oauthProviders})`,
6165
+ sql: generateAccountsTableSQL()
6166
+ });
6167
+ }
6168
+ const sessionStrategy = authConfig?.session?.strategy ?? "jwt";
6169
+ if (sessionStrategy === "database") {
6170
+ tables.push({
6171
+ name: "sessions",
6172
+ reason: "Required for database session strategy",
6173
+ sql: generateSessionsTableSQL()
6174
+ });
6175
+ }
6176
+ if (hasPasskey) {
6177
+ const passkeyProvider = providers.find((p) => p.type === "passkey");
6178
+ tables.push({
6179
+ name: "webauthn_credentials",
6180
+ reason: `Required for passkey provider (${passkeyProvider?.id})`,
6181
+ sql: generateWebAuthnCredentialsTableSQL()
6182
+ });
6183
+ }
6184
+ if (hasEmail) {
6185
+ const emailProvider = providers.find((p) => p.type === "email");
6186
+ tables.push({
6187
+ name: "verification_tokens",
6188
+ reason: `Required for email provider (${emailProvider?.id})`,
6189
+ sql: generateVerificationTokensTableSQL()
6190
+ });
6191
+ }
6192
+ const authScanResult = await scanAuth2(appDir, { extensions: config.extensions });
6193
+ const hasRBAC = authScanResult.rbacFile !== void 0;
6194
+ if (hasRBAC) {
6195
+ tables.push({
6196
+ name: "user_roles",
6197
+ reason: "Required for role-based access control",
6198
+ sql: generateUserRolesTableSQL()
6199
+ });
6200
+ }
6201
+ console.log(pc26.dim(" Detected configuration:"));
6202
+ console.log(
6203
+ ` ${pc26.cyan("Providers")}: ${providers.length === 0 ? pc26.dim("(none)") : providers.map((p) => `${p.id} (${p.type})`).join(", ")}`
6204
+ );
6205
+ console.log(
6206
+ ` ${pc26.cyan("Session")}: ${sessionStrategy === "database" ? pc26.yellow("database") : pc26.green("jwt")}`
6207
+ );
6208
+ console.log(
6209
+ ` ${pc26.cyan("RBAC")}: ${hasRBAC ? pc26.green("enabled") : pc26.dim("(none)")}`
6210
+ );
6211
+ console.log();
6212
+ console.log(pc26.dim(" Tables to create:"));
6213
+ for (const table of tables) {
6214
+ console.log(` ${pc26.green("\u2713")} ${pc26.cyan(table.name)} ${pc26.dim(`- ${table.reason}`)}`);
6215
+ }
6216
+ console.log();
6217
+ const outputDir = options.output ?? path21.join(cwd, "migrations");
6218
+ const migrationName = "0001_auth_tables.sql";
6219
+ const migrationPath = path21.join(outputDir, migrationName);
6220
+ const migrationContent = [
6221
+ "-- Cloudwerk Auth Migration",
6222
+ "-- Generated by: cloudwerk auth migrations",
6223
+ `-- Date: ${(/* @__PURE__ */ new Date()).toISOString()}`,
6224
+ "--",
6225
+ "-- This migration creates the following tables:",
6226
+ ...tables.map((t) => `-- - ${t.name}: ${t.reason}`),
6227
+ "",
6228
+ ...tables.map((t) => t.sql)
6229
+ ].join("\n");
6230
+ if (dryRun) {
6231
+ console.log(pc26.bold("Dry run - Migration content:"));
6232
+ console.log();
6233
+ console.log(pc26.dim("\u2500".repeat(60)));
6234
+ console.log(migrationContent);
6235
+ console.log(pc26.dim("\u2500".repeat(60)));
6236
+ console.log();
6237
+ console.log(pc26.dim(` Would write to: ${migrationPath}`));
6238
+ console.log();
6239
+ return;
6240
+ }
6241
+ if (!fs19.existsSync(outputDir)) {
6242
+ fs19.mkdirSync(outputDir, { recursive: true });
6243
+ console.log(pc26.dim(` Created migrations directory: ${outputDir}`));
6244
+ }
6245
+ if (fs19.existsSync(migrationPath)) {
6246
+ console.log(pc26.yellow(` Migration already exists: ${migrationPath}`));
6247
+ console.log(pc26.dim(" Use --dry-run to preview the migration content"));
6248
+ console.log(pc26.dim(" Delete the existing file to regenerate"));
6249
+ console.log();
6250
+ return;
6251
+ }
6252
+ fs19.writeFileSync(migrationPath, migrationContent);
6253
+ console.log(pc26.green(` \u2713 Created migration: ${migrationPath}`));
6254
+ console.log();
6255
+ console.log(pc26.bold("Next steps:"));
6256
+ console.log();
6257
+ console.log(pc26.dim(" 1. Review the generated migration file"));
6258
+ console.log(pc26.dim(" 2. Apply with wrangler:"));
6259
+ console.log();
6260
+ console.log(pc26.cyan(" wrangler d1 migrations apply <DATABASE_NAME> --local"));
6261
+ console.log();
6262
+ console.log(pc26.dim(" 3. For production:"));
6263
+ console.log();
6264
+ console.log(pc26.cyan(" wrangler d1 migrations apply <DATABASE_NAME> --remote"));
6265
+ console.log();
6266
+ } catch (error) {
6267
+ handleCommandError(error, verbose);
6268
+ }
6269
+ }
6270
+
5785
6271
  // src/index.ts
5786
6272
  program.name("cloudwerk").description("Cloudwerk CLI - Build and deploy full-stack apps to Cloudflare").version(VERSION).enablePositionalOptions();
5787
6273
  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 +6297,6 @@ servicesCmd.command("extract <name>").description("Extract service to separate W
5811
6297
  servicesCmd.command("inline <name>").description("Convert extracted service back to local mode").option("--verbose", "Enable verbose logging").action(servicesInline);
5812
6298
  servicesCmd.command("deploy <name>").description("Deploy extracted service").option("-e, --env <environment>", "Environment to deploy to").option("--verbose", "Enable verbose logging").action(servicesDeploy);
5813
6299
  servicesCmd.command("status").description("Show status of all services").option("--json", "Output as JSON").option("--verbose", "Enable verbose logging").action(servicesStatus);
6300
+ var authCmd = program.command("auth").description("Manage Cloudwerk authentication").enablePositionalOptions().passThroughOptions().option("--verbose", "Enable verbose logging").action(auth);
6301
+ 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
6302
  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.6",
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.3",
35
+ "@cloudwerk/vite-plugin": "^0.6.8",
36
+ "@cloudwerk/ui": "^0.15.6"
37
37
  },
38
38
  "devDependencies": {
39
39
  "@types/node": "^20.0.0",