@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.
- package/dist/index.js +511 -23
- 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.
|
|
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.
|
|
35
|
-
"@cloudwerk/vite-plugin": "^0.6.
|
|
36
|
-
"@cloudwerk/ui": "^0.15.
|
|
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",
|