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/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
- console.log("Azure Functions metadata created");
244
- console.log("\nInstalling minimal runtime dependencies...");
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("Runtime dependencies installed\n");
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...\n");
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\n");
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("Previous build cleaned\n");
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("\nOutput: .open-next/");
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)\n");
327
- console.log("Next: opennextjs-azure deploy\n");
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(`${colors.green}\u2713${colors.reset} Infrastructure ready
382
- `);
490
+ console.log(` ${greenCheck()} Infrastructure ready`);
383
491
  } else {
384
- console.log("Skipping infrastructure provisioning\n");
492
+ console.log("Skipping infrastructure provisioning");
385
493
  }
386
494
  console.log("Uploading static assets...");
387
495
  await uploadStaticAssets(appName, resourceGroup);
388
- console.log(`${colors.green}\u2713${colors.reset} Assets uploaded
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(`${colors.green}\u2713${colors.reset} Function App deployed
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
- ${colors.red}\u2717${colors.reset} Deployment failed: ${error.message}`);
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 = ["Microsoft.Web", "Microsoft.Storage", "Microsoft.Compute", "Microsoft.Quota"];
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(` Registering ${provider}...`);
547
+ console.log(`Registering ${provider}...`);
436
548
  await execAsync$1(`az provider register --namespace ${provider} --wait`);
437
- console.log(` ${colors.green}\u2713${colors.reset} ${provider} registered`);
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(` ${colors.green}\u2713${colors.reset} ${requiredSku.name}: ${requiredSku.quota} instances available`);
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(` ${colors.green}\u2713${colors.reset} Subscription: ${account.Name} (${account.State})`);
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(` ${colors.green}\u2713${colors.reset} Region: ${locations[0].DisplayName} (${location})`);
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(` ${colors.green}\u2713${colors.reset} Build output structure valid`);
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(` ${colors.green}\u2713${colors.reset} All required infrastructure exists`);
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...\n");
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(`${colors.green}\u2713${colors.reset} Deployment Complete!`);
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("\nInfrastructure:");
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("\nConfiguration:");
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
- ${colors.green}\u2713${colors.reset} Deployment completed, but some post-flight checks failed.`);
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.d619537c.mjs';
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.d619537c.js';
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,5 @@
1
+ import { ImageLoader } from '@opennextjs/aws/types/overrides.js';
2
+
3
+ declare const azureBlobImageLoader: ImageLoader;
4
+
5
+ export { azureBlobImageLoader as default };
@@ -0,0 +1,5 @@
1
+ import { ImageLoader } from '@opennextjs/aws/types/overrides.js';
2
+
3
+ declare const azureBlobImageLoader: ImageLoader;
4
+
5
+ export { azureBlobImageLoader as default };
@@ -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 };