opennextjs-azure 0.1.1 → 0.1.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +24 -2
- package/dist/adapters/image-optimization.d.mts +20 -0
- package/dist/adapters/image-optimization.d.ts +20 -0
- package/dist/adapters/image-optimization.js +11 -0
- package/dist/adapters/wrappers/azure-functions.js +7 -4
- package/dist/adapters/wrappers/azure-image-optimization.d.mts +10 -0
- package/dist/adapters/wrappers/azure-image-optimization.d.ts +10 -0
- package/dist/adapters/wrappers/azure-image-optimization.js +151 -0
- package/dist/cli/index.js +486 -5
- package/dist/config/index.d.mts +1 -1
- package/dist/config/index.d.ts +1 -1
- package/dist/config/index.js +12 -0
- package/dist/deploy.js +182 -54
- package/dist/index.d.mts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/infrastructure/main.bicep +12 -0
- package/dist/overrides/imageLoader/azure-blob.d.mts +5 -0
- package/dist/overrides/imageLoader/azure-blob.d.ts +5 -0
- package/dist/overrides/imageLoader/azure-blob.js +37 -0
- package/dist/overrides/imageOptimization/azure-cached.d.mts +6 -0
- package/dist/overrides/imageOptimization/azure-cached.d.ts +6 -0
- package/dist/overrides/imageOptimization/azure-cached.js +115 -0
- package/dist/shared/{opennextjs-azure.d619537c.d.mts → opennextjs-azure.279bd73d.d.mts} +3 -1
- package/dist/shared/{opennextjs-azure.d619537c.d.ts → opennextjs-azure.279bd73d.d.ts} +3 -1
- package/infrastructure/main.bicep +12 -0
- package/package.json +2 -3
package/dist/deploy.js
CHANGED
|
@@ -4,7 +4,7 @@ import { fileURLToPath } from 'node:url';
|
|
|
4
4
|
import { exec } from 'node:child_process';
|
|
5
5
|
import { promisify } from 'node:util';
|
|
6
6
|
import { build as build$1 } from '@opennextjs/aws/build.js';
|
|
7
|
-
import fs$1 from 'node:fs';
|
|
7
|
+
import fs$1, { existsSync } from 'node:fs';
|
|
8
8
|
import readline from 'node:readline';
|
|
9
9
|
|
|
10
10
|
const execAsync$3 = promisify(exec);
|
|
@@ -61,6 +61,42 @@ async function scaffoldProject(targetDir, options = {}) {
|
|
|
61
61
|
});
|
|
62
62
|
await execAsync$3("pnpm install", { cwd: targetDir });
|
|
63
63
|
console.log("Dependencies installed\n");
|
|
64
|
+
const pagePath = path.join(targetDir, srcDir ? "src/app/page.tsx" : "app/page.tsx");
|
|
65
|
+
let pageContent = await fs.readFile(pagePath, "utf-8");
|
|
66
|
+
pageContent = pageContent.replace(
|
|
67
|
+
/<Image className="dark:invert" src="\/next\.svg" alt="Next\.js logo" width=\{180\} height=\{38\} priority \/>/,
|
|
68
|
+
`<div className="flex items-center gap-4">
|
|
69
|
+
<Image className="dark:invert" src="/next.svg" alt="Next.js logo" width={180} height={38} priority />
|
|
70
|
+
<span className="text-2xl text-gray-400 dark:text-gray-600">+</span>
|
|
71
|
+
<Image src="/azure.png" alt="Azure logo" width={38} height={38} priority />
|
|
72
|
+
</div>`
|
|
73
|
+
);
|
|
74
|
+
pageContent = pageContent.replace(
|
|
75
|
+
/href="https:\/\/vercel\.com\/new[^"]*"/,
|
|
76
|
+
'href="https://github.com/zpg6/opennextjs-azure"'
|
|
77
|
+
);
|
|
78
|
+
pageContent = pageContent.replace(
|
|
79
|
+
/className="rounded-full border border-solid border-transparent transition-colors flex items-center justify-center bg-foreground text-background/,
|
|
80
|
+
'className="rounded-full border border-solid border-blue-400 transition-colors flex items-center justify-center bg-blue-500/10'
|
|
81
|
+
);
|
|
82
|
+
pageContent = pageContent.replace(/hover:bg-\[#383838\] dark:hover:bg-\[#ccc\]/, "hover:bg-blue-500/20");
|
|
83
|
+
pageContent = pageContent.replace(
|
|
84
|
+
/<Image className="dark:invert" src="\/vercel\.svg" alt="Vercel logomark" width=\{20\} height=\{20\} \/>/,
|
|
85
|
+
'<Image src="/azure.png" alt="Azure logomark" width={20} height={20} />'
|
|
86
|
+
);
|
|
87
|
+
pageContent = pageContent.replace(/>Deploy now</, ">Deploy to Azure<");
|
|
88
|
+
await fs.writeFile(pagePath, pageContent);
|
|
89
|
+
const publicDir = path.join(targetDir, "public");
|
|
90
|
+
const azureLogoSource = path.join(
|
|
91
|
+
path.dirname(new URL(import.meta.url).pathname),
|
|
92
|
+
"../../examples/basic-app/public/azure.png"
|
|
93
|
+
);
|
|
94
|
+
const azureLogoDest = path.join(publicDir, "azure.png");
|
|
95
|
+
try {
|
|
96
|
+
await fs.copyFile(azureLogoSource, azureLogoDest);
|
|
97
|
+
} catch (error) {
|
|
98
|
+
console.warn("Warning: Could not copy Azure logo. You can add it manually to public/azure.png");
|
|
99
|
+
}
|
|
64
100
|
console.log("Creating open-next.config.ts...");
|
|
65
101
|
const openNextConfig = `// @ts-nocheck
|
|
66
102
|
export default {
|
|
@@ -78,6 +114,17 @@ export default {
|
|
|
78
114
|
middleware: {
|
|
79
115
|
external: false,
|
|
80
116
|
},
|
|
117
|
+
imageOptimization: {
|
|
118
|
+
loader: () => import("./node_modules/opennextjs-azure/dist/overrides/imageLoader/azure-blob.js").then(m => m.default),
|
|
119
|
+
override: {
|
|
120
|
+
wrapper: () => import("./node_modules/opennextjs-azure/dist/adapters/wrappers/azure-image-optimization.js").then(m => m.default),
|
|
121
|
+
converter: () => import("./node_modules/opennextjs-azure/dist/adapters/converters/azure-http.js").then(m => m.default),
|
|
122
|
+
},
|
|
123
|
+
install: {
|
|
124
|
+
packages: ["@img/sharp-linux-x64@0.33.5", "sharp@0.33.5"],
|
|
125
|
+
additionalArgs: "--force --ignore-scripts",
|
|
126
|
+
},
|
|
127
|
+
},
|
|
81
128
|
buildOutputPath: ".",
|
|
82
129
|
appPath: ".",
|
|
83
130
|
};
|
|
@@ -166,6 +213,19 @@ function getAzureConfigTemplate() {
|
|
|
166
213
|
`;
|
|
167
214
|
}
|
|
168
215
|
|
|
216
|
+
const colors$1 = {
|
|
217
|
+
green: "\x1B[32m",
|
|
218
|
+
red: "\x1B[31m",
|
|
219
|
+
yellow: "\x1B[33m",
|
|
220
|
+
reset: "\x1B[0m"
|
|
221
|
+
};
|
|
222
|
+
function greenCheck() {
|
|
223
|
+
return `${colors$1.green}\u2713${colors$1.reset}`;
|
|
224
|
+
}
|
|
225
|
+
function redX() {
|
|
226
|
+
return `${colors$1.red}\u2717${colors$1.reset}`;
|
|
227
|
+
}
|
|
228
|
+
|
|
169
229
|
const execAsync$2 = promisify(exec);
|
|
170
230
|
async function prepareFunctions() {
|
|
171
231
|
const functionsDir = path.join(process.cwd(), ".open-next/server-functions/default");
|
|
@@ -240,8 +300,49 @@ async function prepareFunctions() {
|
|
|
240
300
|
entryPoint: "handler"
|
|
241
301
|
};
|
|
242
302
|
await fs.writeFile(path.join(functionDir, "function.json"), JSON.stringify(functionJson, null, 2));
|
|
243
|
-
|
|
244
|
-
|
|
303
|
+
const imageOptDir = path.join(process.cwd(), ".open-next/image-optimization-function");
|
|
304
|
+
try {
|
|
305
|
+
await fs.access(imageOptDir);
|
|
306
|
+
console.log(" Adding image optimization function...");
|
|
307
|
+
const imageFunctionDir = path.join(functionsDir, "image-optimization");
|
|
308
|
+
await fs.mkdir(imageFunctionDir, { recursive: true });
|
|
309
|
+
const imageFunctionJson = {
|
|
310
|
+
bindings: [
|
|
311
|
+
{
|
|
312
|
+
authLevel: "anonymous",
|
|
313
|
+
type: "httpTrigger",
|
|
314
|
+
direction: "in",
|
|
315
|
+
name: "req",
|
|
316
|
+
methods: ["get", "head"],
|
|
317
|
+
route: "_next/image"
|
|
318
|
+
},
|
|
319
|
+
{
|
|
320
|
+
type: "http",
|
|
321
|
+
direction: "out",
|
|
322
|
+
name: "res"
|
|
323
|
+
}
|
|
324
|
+
],
|
|
325
|
+
scriptFile: "../index-image.mjs",
|
|
326
|
+
entryPoint: "handler"
|
|
327
|
+
};
|
|
328
|
+
await fs.writeFile(path.join(imageFunctionDir, "function.json"), JSON.stringify(imageFunctionJson, null, 2));
|
|
329
|
+
await fs.copyFile(path.join(imageOptDir, "index.mjs"), path.join(functionsDir, "index-image.mjs"));
|
|
330
|
+
await fs.cp(path.join(imageOptDir, ".next"), path.join(functionsDir, ".next"), {
|
|
331
|
+
recursive: true,
|
|
332
|
+
force: false
|
|
333
|
+
});
|
|
334
|
+
try {
|
|
335
|
+
await fs.copyFile(
|
|
336
|
+
path.join(imageOptDir, "open-next.config.mjs"),
|
|
337
|
+
path.join(functionsDir, "open-next.config.mjs")
|
|
338
|
+
);
|
|
339
|
+
} catch {
|
|
340
|
+
}
|
|
341
|
+
console.log(` ${greenCheck()} Image optimization function added`);
|
|
342
|
+
} catch {
|
|
343
|
+
}
|
|
344
|
+
console.log(` ${greenCheck()} Azure Functions metadata created`);
|
|
345
|
+
console.log("Installing minimal runtime dependencies...");
|
|
245
346
|
try {
|
|
246
347
|
const originalPackageJson = JSON.parse(await fs.readFile(path.join(functionsDir, "package.json"), "utf-8"));
|
|
247
348
|
const minimalPackageJson = {
|
|
@@ -260,22 +361,33 @@ async function prepareFunctions() {
|
|
|
260
361
|
await execAsync$2("npm install --production --no-package-lock --loglevel=error", {
|
|
261
362
|
cwd: functionsDir
|
|
262
363
|
});
|
|
263
|
-
console.log(
|
|
364
|
+
console.log(` ${greenCheck()} Runtime dependencies installed`);
|
|
264
365
|
} catch (error) {
|
|
265
366
|
console.error("Failed to install dependencies:", error.message);
|
|
266
367
|
throw error;
|
|
267
368
|
}
|
|
369
|
+
const imageOptDir2 = path.join(process.cwd(), ".open-next/image-optimization-function");
|
|
370
|
+
try {
|
|
371
|
+
await fs.access(imageOptDir2);
|
|
372
|
+
console.log("Installing Sharp with Linux x64 binaries for image optimization...");
|
|
373
|
+
await execAsync$2(
|
|
374
|
+
"npm install --force sharp@0.33.5 @img/sharp-linux-x64@0.33.5 @img/sharp-libvips-linux-x64@1.0.4",
|
|
375
|
+
{ cwd: functionsDir }
|
|
376
|
+
);
|
|
377
|
+
console.log(` ${greenCheck()} Sharp with Linux x64 binaries installed`);
|
|
378
|
+
} catch (error) {
|
|
379
|
+
}
|
|
268
380
|
}
|
|
269
381
|
|
|
270
382
|
async function build(configPath) {
|
|
271
|
-
console.log("Building Next.js app for Azure
|
|
383
|
+
console.log("Building Next.js app for Azure...");
|
|
272
384
|
const baseDir = process.cwd();
|
|
273
385
|
const userConfigPath = configPath || "open-next.config.ts";
|
|
274
386
|
const absoluteUserConfigPath = path.join(baseDir, userConfigPath);
|
|
275
387
|
let resolvedConfigPath = userConfigPath;
|
|
276
388
|
let tempConfigPath = null;
|
|
277
389
|
if (!fs$1.existsSync(absoluteUserConfigPath)) {
|
|
278
|
-
console.log("No open-next.config.ts found, using default Azure configuration
|
|
390
|
+
console.log("No open-next.config.ts found, using default Azure configuration");
|
|
279
391
|
const { createRequire } = await import('node:module');
|
|
280
392
|
const require = createRequire(import.meta.url);
|
|
281
393
|
const packagePath = path.dirname(require.resolve("opennextjs-azure/package.json"));
|
|
@@ -313,18 +425,17 @@ export default {
|
|
|
313
425
|
if (fs$1.existsSync(openNextPath)) {
|
|
314
426
|
console.log("Cleaning previous build output...");
|
|
315
427
|
fs$1.rmSync(openNextPath, { recursive: true, force: true });
|
|
316
|
-
console.log(
|
|
428
|
+
console.log(` ${greenCheck()} Previous build cleaned`);
|
|
317
429
|
}
|
|
318
430
|
console.log("Running OpenNext build...");
|
|
319
431
|
const externals = ["@opennextjs/aws"].join(",");
|
|
320
432
|
await build$1(resolvedConfigPath, externals);
|
|
321
|
-
console.log("OpenNext build complete\n");
|
|
322
433
|
await prepareFunctions();
|
|
323
434
|
console.log("Build completed successfully!");
|
|
324
|
-
console.log("
|
|
435
|
+
console.log("Output: .open-next/");
|
|
325
436
|
console.log(" \u251C\u2500\u2500 server-functions/default (Azure Functions app)");
|
|
326
|
-
console.log(" \u2514\u2500\u2500 assets (Static files)
|
|
327
|
-
console.log("Next: opennextjs-azure deploy
|
|
437
|
+
console.log(" \u2514\u2500\u2500 assets (Static files)");
|
|
438
|
+
console.log("Next: opennextjs-azure deploy");
|
|
328
439
|
} catch (error) {
|
|
329
440
|
console.error("Build failed:", error);
|
|
330
441
|
process.exit(1);
|
|
@@ -350,8 +461,7 @@ async function deploy$1(options) {
|
|
|
350
461
|
environment = "dev",
|
|
351
462
|
skipInfrastructure = false
|
|
352
463
|
} = options;
|
|
353
|
-
console.log(`Deploying ${appName} to Azure (${environment} environment)
|
|
354
|
-
`);
|
|
464
|
+
console.log(`Deploying ${appName} to Azure (${environment} environment)`);
|
|
355
465
|
try {
|
|
356
466
|
await checkAzureCLI();
|
|
357
467
|
await checkAzureLogin();
|
|
@@ -369,8 +479,7 @@ async function deploy$1(options) {
|
|
|
369
479
|
console.log("Provisioning Azure infrastructure...");
|
|
370
480
|
console.log(` Resource Group: ${resourceGroup}`);
|
|
371
481
|
console.log(` Location: ${location}`);
|
|
372
|
-
console.log(` Environment: ${environment}
|
|
373
|
-
`);
|
|
482
|
+
console.log(` Environment: ${environment}`);
|
|
374
483
|
deploymentOutputs = await provisionInfrastructure({
|
|
375
484
|
appName,
|
|
376
485
|
resourceGroup,
|
|
@@ -378,20 +487,17 @@ async function deploy$1(options) {
|
|
|
378
487
|
environment,
|
|
379
488
|
applicationInsights: options.applicationInsights ?? false
|
|
380
489
|
});
|
|
381
|
-
console.log(
|
|
382
|
-
`);
|
|
490
|
+
console.log(` ${greenCheck()} Infrastructure ready`);
|
|
383
491
|
} else {
|
|
384
|
-
console.log("Skipping infrastructure provisioning
|
|
492
|
+
console.log("Skipping infrastructure provisioning");
|
|
385
493
|
}
|
|
386
494
|
console.log("Uploading static assets...");
|
|
387
495
|
await uploadStaticAssets(appName, resourceGroup);
|
|
388
|
-
console.log(
|
|
389
|
-
`);
|
|
496
|
+
console.log(` ${greenCheck()} Assets uploaded`);
|
|
390
497
|
console.log("Deploying Function App...");
|
|
391
498
|
const functionAppName = deploymentOutputs?.functionApp || `${appName}-func-${environment}`;
|
|
392
499
|
await deployFunctionApp(functionAppName, resourceGroup);
|
|
393
|
-
console.log(
|
|
394
|
-
`);
|
|
500
|
+
console.log(` ${greenCheck()} Function App deployed`);
|
|
395
501
|
await performPostflightChecks(
|
|
396
502
|
resourceGroup,
|
|
397
503
|
functionAppName,
|
|
@@ -401,7 +507,7 @@ async function deploy$1(options) {
|
|
|
401
507
|
);
|
|
402
508
|
} catch (error) {
|
|
403
509
|
console.error(`
|
|
404
|
-
${
|
|
510
|
+
${redX()} Deployment failed: ${error.message}`);
|
|
405
511
|
process.exit(1);
|
|
406
512
|
}
|
|
407
513
|
}
|
|
@@ -421,7 +527,13 @@ async function checkAzureLogin() {
|
|
|
421
527
|
}
|
|
422
528
|
}
|
|
423
529
|
async function checkRequiredProviders(applicationInsights) {
|
|
424
|
-
const requiredProviders = [
|
|
530
|
+
const requiredProviders = [
|
|
531
|
+
"Microsoft.Web",
|
|
532
|
+
"Microsoft.Storage",
|
|
533
|
+
"Microsoft.Compute",
|
|
534
|
+
"Microsoft.Quota",
|
|
535
|
+
"Microsoft.ServiceLinker"
|
|
536
|
+
];
|
|
425
537
|
if (applicationInsights) {
|
|
426
538
|
requiredProviders.push("Microsoft.AlertsManagement");
|
|
427
539
|
}
|
|
@@ -432,9 +544,9 @@ async function checkRequiredProviders(applicationInsights) {
|
|
|
432
544
|
);
|
|
433
545
|
const state = stdout.trim();
|
|
434
546
|
if (state !== "Registered") {
|
|
435
|
-
console.log(`
|
|
547
|
+
console.log(`Registering ${provider}...`);
|
|
436
548
|
await execAsync$1(`az provider register --namespace ${provider} --wait`);
|
|
437
|
-
console.log(` ${
|
|
549
|
+
console.log(` ${greenCheck()} ${provider} registered`);
|
|
438
550
|
}
|
|
439
551
|
}
|
|
440
552
|
}
|
|
@@ -459,21 +571,16 @@ async function checkQuotaAvailability(location, environment) {
|
|
|
459
571
|
};
|
|
460
572
|
const requiredSku = skuMap[environment];
|
|
461
573
|
if (requiredSku.quota === 0) {
|
|
462
|
-
console.error(
|
|
463
|
-
|
|
464
|
-
${colors.red}\u2717${colors.reset} Quota Error: No quota available for ${environment} environment`
|
|
465
|
-
);
|
|
574
|
+
console.error(`
|
|
575
|
+
${redX()} Quota Error: No quota available for ${environment} environment`);
|
|
466
576
|
console.error(` Required: ${requiredSku.name}`);
|
|
467
|
-
console.error(` Current Limit: ${requiredSku.quota}
|
|
468
|
-
`);
|
|
577
|
+
console.error(` Current Limit: ${requiredSku.quota}`);
|
|
469
578
|
if (y1Limit > 0 && environment !== "dev") {
|
|
470
579
|
console.log(` Suggestion: Deploy to dev environment instead (has quota: ${y1Limit})`);
|
|
471
|
-
console.log(` Command: opennextjs-azure deploy --environment dev
|
|
472
|
-
`);
|
|
580
|
+
console.log(` Command: opennextjs-azure deploy --environment dev`);
|
|
473
581
|
} else if (ep1Limit > 0 && environment === "dev") {
|
|
474
582
|
console.log(` Suggestion: Deploy to prod environment instead (has quota: ${ep1Limit})`);
|
|
475
|
-
console.log(` Command: opennextjs-azure deploy --environment prod
|
|
476
|
-
`);
|
|
583
|
+
console.log(` Command: opennextjs-azure deploy --environment prod`);
|
|
477
584
|
} else {
|
|
478
585
|
console.log(` To request quota increase:`);
|
|
479
586
|
console.log(
|
|
@@ -486,7 +593,7 @@ ${colors.red}\u2717${colors.reset} Quota Error: No quota available for ${environ
|
|
|
486
593
|
}
|
|
487
594
|
throw new Error(`No ${requiredSku.type} quota available for ${environment} environment in ${location}`);
|
|
488
595
|
}
|
|
489
|
-
console.log(` ${
|
|
596
|
+
console.log(` ${greenCheck()} ${requiredSku.name}: ${requiredSku.quota} instances available`);
|
|
490
597
|
if (environment === "dev" && ep1Limit > 0) {
|
|
491
598
|
console.log(
|
|
492
599
|
` Premium tier also available (${ep1Limit} instances) - use --environment prod for better performance`
|
|
@@ -511,7 +618,8 @@ async function checkAzureSubscriptionPermissions() {
|
|
|
511
618
|
if (account.State !== "Enabled") {
|
|
512
619
|
throw new Error(`Subscription "${account.Name}" is not enabled (state: ${account.State})`);
|
|
513
620
|
}
|
|
514
|
-
console.log(` ${
|
|
621
|
+
console.log(` ${greenCheck()} Subscription: ${account.Name} (${account.State})
|
|
622
|
+
`);
|
|
515
623
|
} catch (error) {
|
|
516
624
|
throw new Error(`Failed to verify subscription permissions: ${error.message}`);
|
|
517
625
|
}
|
|
@@ -532,13 +640,14 @@ async function checkLocation(location) {
|
|
|
532
640
|
Available regions: ${available.trim().split("\n").slice(0, 10).join(", ")}`
|
|
533
641
|
);
|
|
534
642
|
}
|
|
535
|
-
console.log(` ${
|
|
643
|
+
console.log(` ${greenCheck()} Region: ${locations[0].DisplayName} (${location})`);
|
|
536
644
|
} catch (error) {
|
|
537
645
|
if (error.message.includes("Invalid location")) {
|
|
538
646
|
throw error;
|
|
539
647
|
}
|
|
540
648
|
console.warn(
|
|
541
|
-
` ${colors.yellow}Warning:${colors.reset} Could not validate location (continuing anyway): ${error.message}
|
|
649
|
+
` ${colors.yellow}Warning:${colors.reset} Could not validate location (continuing anyway): ${error.message}
|
|
650
|
+
`
|
|
542
651
|
);
|
|
543
652
|
}
|
|
544
653
|
}
|
|
@@ -562,7 +671,7 @@ Run 'opennextjs-azure build' to regenerate`
|
|
|
562
671
|
);
|
|
563
672
|
}
|
|
564
673
|
}
|
|
565
|
-
console.log(` ${
|
|
674
|
+
console.log(` ${greenCheck()} Build output structure valid`);
|
|
566
675
|
}
|
|
567
676
|
async function checkExistingInfrastructure(appName, resourceGroup, environment) {
|
|
568
677
|
console.log("Checking existing infrastructure...");
|
|
@@ -593,10 +702,10 @@ async function checkExistingInfrastructure(appName, resourceGroup, environment)
|
|
|
593
702
|
"Cannot skip infrastructure provisioning - required resources missing:\n" + errors.map((e) => ` \u2022 ${e}`).join("\n") + "\n\nRun without --skip-infrastructure to provision resources first."
|
|
594
703
|
);
|
|
595
704
|
}
|
|
596
|
-
console.log(` ${
|
|
705
|
+
console.log(` ${greenCheck()} All required infrastructure exists`);
|
|
597
706
|
}
|
|
598
707
|
async function performPostflightChecks(resourceGroup, functionAppName, location, environment, applicationInsights) {
|
|
599
|
-
console.log("Performing post-deployment verification
|
|
708
|
+
console.log("Performing post-deployment verification...");
|
|
600
709
|
try {
|
|
601
710
|
const { stdout: subIdStdout } = await execAsync$1("az account show --query id -o tsv");
|
|
602
711
|
const subscriptionId = subIdStdout.trim();
|
|
@@ -615,19 +724,19 @@ async function performPostflightChecks(resourceGroup, functionAppName, location,
|
|
|
615
724
|
const functionUrl = `https://${funcApp.DefaultHostName}`;
|
|
616
725
|
const assetsUrl = `https://${storage.Name}.blob.core.windows.net/assets`;
|
|
617
726
|
const portalUrl = `https://portal.azure.com/#@/resource/subscriptions/${subscriptionId}/resourceGroups/${resourceGroup}`;
|
|
727
|
+
console.log("\n\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550");
|
|
728
|
+
console.log(`${greenCheck()} Deployment Complete!`);
|
|
618
729
|
console.log("\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550");
|
|
619
|
-
console.log(
|
|
620
|
-
console.log("\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550");
|
|
621
|
-
console.log("\nApplication:");
|
|
730
|
+
console.log("Application:");
|
|
622
731
|
console.log(` App URL: ${functionUrl}`);
|
|
623
732
|
console.log(` Assets URL: ${assetsUrl}`);
|
|
624
733
|
console.log(` Status: ${funcApp.State}`);
|
|
625
734
|
console.log(` Type: ${funcApp.Kind}`);
|
|
626
|
-
console.log("
|
|
735
|
+
console.log("Infrastructure:");
|
|
627
736
|
console.log(` Resource Group: ${resourceGroup}`);
|
|
628
737
|
console.log(` Region: ${location}`);
|
|
629
738
|
console.log(` Environment: ${environment}`);
|
|
630
|
-
console.log("
|
|
739
|
+
console.log("Configuration:");
|
|
631
740
|
console.log(` App Service Plan: ${plan.Tier} (${plan.Sku})`);
|
|
632
741
|
console.log(` Storage Account: ${storage.Name} (${storage.Sku})`);
|
|
633
742
|
console.log(` Capacity: ${plan.Capacity || 1} instance(s)`);
|
|
@@ -642,9 +751,7 @@ async function performPostflightChecks(resourceGroup, functionAppName, location,
|
|
|
642
751
|
}
|
|
643
752
|
}
|
|
644
753
|
console.log("\nQuick Actions:");
|
|
645
|
-
console.log(
|
|
646
|
-
` View logs: az functionapp log tail --name ${functionAppName} --resource-group ${resourceGroup}`
|
|
647
|
-
);
|
|
754
|
+
console.log(` View logs: npx opennextjs-azure@latest tail`);
|
|
648
755
|
console.log(` Open in portal: ${portalUrl}`);
|
|
649
756
|
console.log("\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\n");
|
|
650
757
|
if (funcApp.State !== "Running") {
|
|
@@ -658,7 +765,7 @@ async function performPostflightChecks(resourceGroup, functionAppName, location,
|
|
|
658
765
|
`${colors.yellow}Warning:${colors.reset} Could not retrieve all deployment details: ${error.message}`
|
|
659
766
|
);
|
|
660
767
|
console.log(`
|
|
661
|
-
${
|
|
768
|
+
${greenCheck()} Deployment completed, but some post-flight checks failed.`);
|
|
662
769
|
console.log(` View resources: az resource list --resource-group ${resourceGroup} -o table
|
|
663
770
|
`);
|
|
664
771
|
}
|
|
@@ -682,15 +789,36 @@ async function provisionInfrastructure(options) {
|
|
|
682
789
|
);
|
|
683
790
|
return JSON.parse(stdout);
|
|
684
791
|
}
|
|
792
|
+
async function patchCSSForBlobStorage(assetsPath) {
|
|
793
|
+
const cssPath = path.join(assetsPath, "_next/static/css");
|
|
794
|
+
if (!existsSync(cssPath)) {
|
|
795
|
+
return;
|
|
796
|
+
}
|
|
797
|
+
const files = await fs.readdir(cssPath);
|
|
798
|
+
const cssFiles = files.filter((f) => f.endsWith(".css"));
|
|
799
|
+
for (const file of cssFiles) {
|
|
800
|
+
const filePath = path.join(cssPath, file);
|
|
801
|
+
let content = await fs.readFile(filePath, "utf-8");
|
|
802
|
+
content = content.replace(/url\(\s*(['"]?)(\/_next\/static\/media\/[^'")\s]+)\1\s*\)/g, "url($1/assets$2$1)");
|
|
803
|
+
await fs.writeFile(filePath, content, "utf-8");
|
|
804
|
+
}
|
|
805
|
+
}
|
|
685
806
|
async function uploadStaticAssets(appName, resourceGroup) {
|
|
686
807
|
const assetsPath = path.join(process.cwd(), ".open-next/assets");
|
|
808
|
+
await patchCSSForBlobStorage(assetsPath);
|
|
687
809
|
const { stdout } = await execAsync$1(
|
|
688
810
|
`az storage account list --resource-group ${resourceGroup} --query "[0].name" -o tsv`
|
|
689
811
|
);
|
|
690
812
|
const storageAccountName = stdout.trim();
|
|
691
813
|
await execAsync$1(
|
|
692
|
-
`az storage blob upload-batch --account-name ${storageAccountName} --destination assets --source ${assetsPath} --overwrite`
|
|
814
|
+
`az storage blob upload-batch --account-name ${storageAccountName} --destination assets --source ${assetsPath} --content-cache-control "public, max-age=0, must-revalidate" --overwrite`
|
|
693
815
|
);
|
|
816
|
+
const nextStaticPath = path.join(assetsPath, "_next/static");
|
|
817
|
+
if (existsSync(nextStaticPath)) {
|
|
818
|
+
await execAsync$1(
|
|
819
|
+
`az storage blob upload-batch --account-name ${storageAccountName} --destination assets --source ${nextStaticPath} --destination-path _next/static --content-cache-control "public, max-age=31536000, immutable" --overwrite`
|
|
820
|
+
);
|
|
821
|
+
}
|
|
694
822
|
}
|
|
695
823
|
async function deployFunctionApp(functionAppName, resourceGroup) {
|
|
696
824
|
const functionsPath = path.join(process.cwd(), ".open-next/server-functions/default");
|
|
@@ -832,4 +960,4 @@ async function getResourceGroupLocation(resourceGroup) {
|
|
|
832
960
|
}
|
|
833
961
|
}
|
|
834
962
|
|
|
835
|
-
export { build as b, deploy as d, init as i };
|
|
963
|
+
export { build as b, deploy as d, greenCheck as g, init as i, promptForInput as p, redX as r };
|
package/dist/index.d.mts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export { A as AzureConfig, a as AzureDeploymentTarget, d as defineAzureConfig } from './shared/opennextjs-azure.
|
|
1
|
+
export { A as AzureConfig, a as AzureDeploymentTarget, d as defineAzureConfig } from './shared/opennextjs-azure.279bd73d.mjs';
|
|
2
2
|
export { default as azureBlobCache } from './overrides/incrementalCache/azure-blob.mjs';
|
|
3
3
|
export { default as azureTableTagCache } from './overrides/tagCache/azure-table.mjs';
|
|
4
4
|
export { default as azureQueueRevalidation } from './overrides/queue/azure-queue.mjs';
|
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export { A as AzureConfig, a as AzureDeploymentTarget, d as defineAzureConfig } from './shared/opennextjs-azure.
|
|
1
|
+
export { A as AzureConfig, a as AzureDeploymentTarget, d as defineAzureConfig } from './shared/opennextjs-azure.279bd73d.js';
|
|
2
2
|
export { default as azureBlobCache } from './overrides/incrementalCache/azure-blob.js';
|
|
3
3
|
export { default as azureTableTagCache } from './overrides/tagCache/azure-table.js';
|
|
4
4
|
export { default as azureQueueRevalidation } from './overrides/queue/azure-queue.js';
|
|
@@ -96,6 +96,14 @@ resource storageAccount 'Microsoft.Storage/storageAccounts@2023-01-01' = {
|
|
|
96
96
|
publicAccess: 'Blob'
|
|
97
97
|
}
|
|
98
98
|
}
|
|
99
|
+
|
|
100
|
+
// Container for optimized images (public CDN access)
|
|
101
|
+
resource optimizedImagesContainer 'containers' = {
|
|
102
|
+
name: 'optimized-images'
|
|
103
|
+
properties: {
|
|
104
|
+
publicAccess: 'Blob'
|
|
105
|
+
}
|
|
106
|
+
}
|
|
99
107
|
}
|
|
100
108
|
|
|
101
109
|
// Table service for tag cache
|
|
@@ -194,6 +202,10 @@ resource functionApp 'Microsoft.Web/sites@2023-01-01' = {
|
|
|
194
202
|
name: 'AZURE_QUEUE_NAME'
|
|
195
203
|
value: queueName
|
|
196
204
|
}
|
|
205
|
+
{
|
|
206
|
+
name: 'AZURE_IMAGE_OPTIMIZATION_CACHE'
|
|
207
|
+
value: 'true'
|
|
208
|
+
}
|
|
197
209
|
{
|
|
198
210
|
name: 'NODE_ENV'
|
|
199
211
|
value: 'production'
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { Readable } from 'node:stream';
|
|
2
|
+
|
|
3
|
+
const { AZURE_STORAGE_ACCOUNT_NAME } = process.env;
|
|
4
|
+
const azureBlobImageLoader = {
|
|
5
|
+
name: "azure-blob",
|
|
6
|
+
load: async (key) => {
|
|
7
|
+
if (!AZURE_STORAGE_ACCOUNT_NAME) {
|
|
8
|
+
throw new Error("AZURE_STORAGE_ACCOUNT_NAME must be defined");
|
|
9
|
+
}
|
|
10
|
+
const cleanKey = key.replace(/^\//, "");
|
|
11
|
+
const blobUrl = `https://${AZURE_STORAGE_ACCOUNT_NAME}.blob.core.windows.net/assets/${cleanKey}`;
|
|
12
|
+
try {
|
|
13
|
+
const response = await fetch(blobUrl);
|
|
14
|
+
if (response.status === 404) {
|
|
15
|
+
throw new Error(`Image not found in blob storage: ${cleanKey}`);
|
|
16
|
+
}
|
|
17
|
+
if (!response.ok) {
|
|
18
|
+
throw new Error(`Failed to fetch image. Status: ${response.status}`);
|
|
19
|
+
}
|
|
20
|
+
if (!response.body) {
|
|
21
|
+
throw new Error("No body in fetch response");
|
|
22
|
+
}
|
|
23
|
+
const arrayBuffer = await response.arrayBuffer();
|
|
24
|
+
const buffer = Buffer.from(arrayBuffer);
|
|
25
|
+
const body = Readable.from(buffer);
|
|
26
|
+
return {
|
|
27
|
+
body,
|
|
28
|
+
contentType: response.headers.get("content-type") ?? void 0,
|
|
29
|
+
cacheControl: response.headers.get("cache-control") ?? void 0
|
|
30
|
+
};
|
|
31
|
+
} catch (error) {
|
|
32
|
+
throw new Error(`Failed to load image from Azure Blob: ${error.message}`);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
export { azureBlobImageLoader as default };
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { InternalEvent, InternalResult } from '@opennextjs/aws/types/open-next.js';
|
|
2
|
+
import { OpenNextHandler } from '@opennextjs/aws/types/overrides.js';
|
|
3
|
+
|
|
4
|
+
declare function createCachedImageOptimizationHandler(defaultHandler: OpenNextHandler<InternalEvent, InternalResult>): OpenNextHandler<InternalEvent, InternalResult>;
|
|
5
|
+
|
|
6
|
+
export { createCachedImageOptimizationHandler };
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { InternalEvent, InternalResult } from '@opennextjs/aws/types/open-next.js';
|
|
2
|
+
import { OpenNextHandler } from '@opennextjs/aws/types/overrides.js';
|
|
3
|
+
|
|
4
|
+
declare function createCachedImageOptimizationHandler(defaultHandler: OpenNextHandler<InternalEvent, InternalResult>): OpenNextHandler<InternalEvent, InternalResult>;
|
|
5
|
+
|
|
6
|
+
export { createCachedImageOptimizationHandler };
|