@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.
- package/dist/index.js +494 -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,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.
|
|
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.
|
|
35
|
-
"@cloudwerk/
|
|
36
|
-
"@cloudwerk/
|
|
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",
|