nuxt-og-image 6.0.6 → 6.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (48) hide show
  1. package/dist/chunks/tw4.cjs +4 -4
  2. package/dist/chunks/tw4.mjs +4 -4
  3. package/dist/chunks/uno.cjs +2 -2
  4. package/dist/chunks/uno.mjs +2 -2
  5. package/dist/cli.cjs +471 -13
  6. package/dist/cli.mjs +473 -15
  7. package/dist/client/200.html +1 -1
  8. package/dist/client/404.html +1 -1
  9. package/dist/client/_nuxt/{DQ3A3fXi.js → BMN50qUN.js} +1 -1
  10. package/dist/client/_nuxt/Benf5wAR.js +1 -0
  11. package/dist/client/_nuxt/{BWSU2rm-.js → CUHt7c2i.js} +1 -1
  12. package/dist/client/_nuxt/D8rANzxA.js +1 -0
  13. package/dist/client/_nuxt/DDjmUBWb.js +1 -0
  14. package/dist/client/_nuxt/DVjjsoab.js +3860 -0
  15. package/dist/client/_nuxt/Dp-uF0ca.js +3 -0
  16. package/dist/client/_nuxt/IFrameLoader.HTWy4e5S.css +1 -0
  17. package/dist/client/_nuxt/builds/latest.json +1 -1
  18. package/dist/client/_nuxt/builds/meta/d1aecffe-1f20-4eff-ae9c-8206db4e03f8.json +1 -0
  19. package/dist/client/_nuxt/entry.CjxjoX5J.css +2 -0
  20. package/dist/client/_nuxt/error-404.CvxVKLRw.css +1 -0
  21. package/dist/client/_nuxt/error-500.9lyObXc1.css +1 -0
  22. package/dist/client/_nuxt/p4iQRQB2.js +2 -0
  23. package/dist/client/_nuxt/{pages.JfmTAseD.css → pages.CVzrVadZ.css} +1 -1
  24. package/dist/client/_nuxt/xGJq_Sw1.js +181 -0
  25. package/dist/client/debug/index.html +1 -1
  26. package/dist/client/docs/index.html +1 -1
  27. package/dist/client/index.html +1 -1
  28. package/dist/client/templates/index.html +1 -1
  29. package/dist/module.cjs +2 -2
  30. package/dist/module.json +1 -1
  31. package/dist/module.mjs +2 -2
  32. package/dist/runtime/app/utils.js +8 -1
  33. package/dist/runtime/shared/urlEncoding.js +2 -1
  34. package/dist/shared/{nuxt-og-image.BM_Slo30.mjs → nuxt-og-image.8sPZ_PD5.mjs} +143 -8
  35. package/dist/shared/{nuxt-og-image.BuMzAC1A.cjs → nuxt-og-image.CQzMjYCR.cjs} +144 -9
  36. package/package.json +7 -7
  37. package/dist/client/_nuxt/BG25oXSp.js +0 -3
  38. package/dist/client/_nuxt/CAkieT7G.js +0 -1
  39. package/dist/client/_nuxt/CeFUJIya.js +0 -3860
  40. package/dist/client/_nuxt/CpLhajTF.js +0 -181
  41. package/dist/client/_nuxt/DxQS3Z2e.js +0 -1
  42. package/dist/client/_nuxt/HkJuN7bp.js +0 -1
  43. package/dist/client/_nuxt/IFrameLoader.D3_OtGCw.css +0 -1
  44. package/dist/client/_nuxt/PRAc7tB1.js +0 -2
  45. package/dist/client/_nuxt/builds/meta/6b729f7b-1f0a-4b8a-9835-558aeb415439.json +0 -1
  46. package/dist/client/_nuxt/entry.Cm_2Fdxn.css +0 -2
  47. package/dist/client/_nuxt/error-404.D7HOwyLG.css +0 -1
  48. package/dist/client/_nuxt/error-500.DpzjKi44.css +0 -1
@@ -1,10 +1,11 @@
1
1
  'use strict';
2
2
 
3
3
  const promises = require('node:fs/promises');
4
+ const node_url = require('node:url');
4
5
  const exsolve = require('exsolve');
5
6
  const pathe = require('pathe');
6
7
  const cssProvider = require('./css-provider.cjs');
7
- const module$1 = require('../shared/nuxt-og-image.BuMzAC1A.cjs');
8
+ const module$1 = require('../shared/nuxt-og-image.CQzMjYCR.cjs');
8
9
  require('node:fs');
9
10
  require('@nuxt/kit');
10
11
  require('defu');
@@ -21,7 +22,6 @@ require('@nuxt/devtools-kit');
21
22
  require('unstorage');
22
23
  require('unstorage/drivers/fs-lite');
23
24
  require('../../dist/runtime/server/og-image/utils/css.js');
24
- require('node:url');
25
25
  require('magic-string');
26
26
  require('strip-literal');
27
27
  require('ufo');
@@ -56,11 +56,11 @@ function createModuleLoader(baseDir) {
56
56
  return async (id, base) => {
57
57
  const resolved = exsolve.resolveModulePath(id, { from: base || baseDir });
58
58
  if (resolved) {
59
- const module2 = await import(resolved).then((m) => m.default || m);
59
+ const module2 = await import(node_url.pathToFileURL(resolved).href).then((m) => m.default || m);
60
60
  return { path: resolved, base: pathe.dirname(resolved), module: module2 };
61
61
  }
62
62
  const relativePath = pathe.join(base || baseDir, id);
63
- const module = await import(relativePath).then((m) => m.default || m).catch(() => ({}));
63
+ const module = await import(node_url.pathToFileURL(relativePath).href).then((m) => m.default || m).catch(() => ({}));
64
64
  return { path: relativePath, base: pathe.dirname(relativePath), module };
65
65
  };
66
66
  }
@@ -1,8 +1,9 @@
1
1
  import { readFile } from 'node:fs/promises';
2
+ import { pathToFileURL } from 'node:url';
2
3
  import { resolveModulePath } from 'exsolve';
3
4
  import { dirname, join } from 'pathe';
4
5
  import { extractVariantBaseClasses, resolveVariantPrefixes } from './css-provider.mjs';
5
- import { g as resolveVarsDeep, s as simplifyCss, p as postProcessStyles, l as loadLightningCss, e as extractCssVars, b as extractUniversalVars, c as extractPerClassVars, d as extractClassStyles } from '../shared/nuxt-og-image.BM_Slo30.mjs';
6
+ import { g as resolveVarsDeep, s as simplifyCss, p as postProcessStyles, l as loadLightningCss, e as extractCssVars, b as extractUniversalVars, c as extractPerClassVars, d as extractClassStyles } from '../shared/nuxt-og-image.8sPZ_PD5.mjs';
6
7
  import 'node:fs';
7
8
  import '@nuxt/kit';
8
9
  import 'defu';
@@ -19,7 +20,6 @@ import '@nuxt/devtools-kit';
19
20
  import 'unstorage';
20
21
  import 'unstorage/drivers/fs-lite';
21
22
  import '../../dist/runtime/server/og-image/utils/css.js';
22
- import 'node:url';
23
23
  import 'magic-string';
24
24
  import 'strip-literal';
25
25
  import 'ufo';
@@ -54,11 +54,11 @@ function createModuleLoader(baseDir) {
54
54
  return async (id, base) => {
55
55
  const resolved = resolveModulePath(id, { from: base || baseDir });
56
56
  if (resolved) {
57
- const module2 = await import(resolved).then((m) => m.default || m);
57
+ const module2 = await import(pathToFileURL(resolved).href).then((m) => m.default || m);
58
58
  return { path: resolved, base: dirname(resolved), module: module2 };
59
59
  }
60
60
  const relativePath = join(base || baseDir, id);
61
- const module = await import(relativePath).then((m) => m.default || m).catch(() => ({}));
61
+ const module = await import(pathToFileURL(relativePath).href).then((m) => m.default || m).catch(() => ({}));
62
62
  return { path: relativePath, base: dirname(relativePath), module };
63
63
  };
64
64
  }
@@ -4,9 +4,10 @@ const promises = require('node:fs/promises');
4
4
  const defu = require('defu');
5
5
  const logger_js = require('../../dist/runtime/logger.js');
6
6
  const cssProvider = require('./css-provider.cjs');
7
- const module$1 = require('../shared/nuxt-og-image.BuMzAC1A.cjs');
7
+ const module$1 = require('../shared/nuxt-og-image.CQzMjYCR.cjs');
8
8
  require('exsolve');
9
9
  require('node:fs');
10
+ require('node:url');
10
11
  require('@nuxt/kit');
11
12
  require('nuxt-site-config/kit');
12
13
  require('ohash');
@@ -21,7 +22,6 @@ require('@nuxt/devtools-kit');
21
22
  require('unstorage');
22
23
  require('unstorage/drivers/fs-lite');
23
24
  require('../../dist/runtime/server/og-image/utils/css.js');
24
- require('node:url');
25
25
  require('magic-string');
26
26
  require('strip-literal');
27
27
  require('ufo');
@@ -2,9 +2,10 @@ import { readFile } from 'node:fs/promises';
2
2
  import { defu } from 'defu';
3
3
  import { logger } from '../../dist/runtime/logger.js';
4
4
  import { extractVariantBaseClasses, resolveVariantPrefixes } from './css-provider.mjs';
5
- import { s as simplifyCss, e as extractCssVars, a as extractPropertyInitialValues, b as extractUniversalVars, c as extractPerClassVars, d as extractClassStyles, p as postProcessStyles, f as extractVarsFromCss, r as resolveExtractedVars } from '../shared/nuxt-og-image.BM_Slo30.mjs';
5
+ import { s as simplifyCss, e as extractCssVars, a as extractPropertyInitialValues, b as extractUniversalVars, c as extractPerClassVars, d as extractClassStyles, p as postProcessStyles, f as extractVarsFromCss, r as resolveExtractedVars } from '../shared/nuxt-og-image.8sPZ_PD5.mjs';
6
6
  import 'exsolve';
7
7
  import 'node:fs';
8
+ import 'node:url';
8
9
  import '@nuxt/kit';
9
10
  import 'nuxt-site-config/kit';
10
11
  import 'ohash';
@@ -19,7 +20,6 @@ import '@nuxt/devtools-kit';
19
20
  import 'unstorage';
20
21
  import 'unstorage/drivers/fs-lite';
21
22
  import '../../dist/runtime/server/og-image/utils/css.js';
22
- import 'node:url';
23
23
  import 'magic-string';
24
24
  import 'strip-literal';
25
25
  import 'ufo';
package/dist/cli.cjs CHANGED
@@ -40,6 +40,10 @@ const RE_EXCLUDE_OUTPUT = /\.output/;
40
40
  const RE_EXCLUDE_DATA = /\.data/;
41
41
  const RE_EXCLUDE_DIST = /dist/;
42
42
  const RE_VUE_OR_SCRIPT = /\.(?:vue|ts|tsx|js|jsx)$/;
43
+ const RE_PASCAL_CASE_STRICT = /^[A-Z]\w*$/;
44
+ const RE_PASCAL_CASE_START = /^[A-Z]/;
45
+ const RE_SCRIPT_SETUP = /<script\s+setup[^>]*>\r?\n?/;
46
+ const RE_PATH_SEPARATOR = /[\\/]|\.\./;
43
47
  const DEPRECATED_COMPOSABLE_NAMES = /* @__PURE__ */ new Set([
44
48
  "defineOgImageComponent",
45
49
  "defineOgImageStatic",
@@ -204,10 +208,13 @@ function hasChromiumSuffix(filename) {
204
208
  return RE_CHROMIUM_SUFFIX.test(filename);
205
209
  }
206
210
  function listTemplates() {
207
- const templates = fs.readdirSync(communityDir).filter((f) => f.endsWith(".vue")).map(getBaseName);
208
- console.log("\nAvailable community templates:");
209
- templates.forEach((t) => console.log(` - ${t}`));
210
- console.log("\nUsage: npx nuxt-og-image eject <template-name>\n");
211
+ const templates = [...new Set(
212
+ fs.readdirSync(communityDir).filter((f) => f.endsWith(".vue")).map(getBaseName)
213
+ )];
214
+ p__namespace.intro("Community Templates");
215
+ p__namespace.note(templates.map((t) => `\u2022 ${t}`).join("\n"), "Available");
216
+ p__namespace.log.info("Usage: npx nuxt-og-image eject <template-name>");
217
+ p__namespace.outro("");
211
218
  }
212
219
  function findTemplateFile(name) {
213
220
  const files = fs.readdirSync(communityDir).filter((f) => f.endsWith(".vue"));
@@ -799,6 +806,439 @@ async function runMigrate(args2) {
799
806
  }
800
807
  p__namespace.outro(dryRun ? "Dry run complete" : "Migration complete!");
801
808
  }
809
+ async function runSwitch(args2) {
810
+ const dryRun = args2.includes("--dry-run") || args2.includes("-d");
811
+ const skipConfirm = args2.includes("--yes") || args2.includes("-y");
812
+ const fromIdx = args2.indexOf("--from");
813
+ const toIdx = args2.indexOf("--to");
814
+ const cliFrom = fromIdx !== -1 ? args2[fromIdx + 1] : null;
815
+ const cliTo = toIdx !== -1 ? args2[toIdx + 1] : null;
816
+ const validRenderers = RENDERERS.map((r) => r.name);
817
+ if (cliFrom && !validRenderers.includes(cliFrom)) {
818
+ p__namespace.log.error(`Invalid --from renderer: ${cliFrom}. Must be ${validRenderers.join(", ")}`);
819
+ process.exit(1);
820
+ }
821
+ if (cliTo && !validRenderers.includes(cliTo)) {
822
+ p__namespace.log.error(`Invalid --to renderer: ${cliTo}. Must be ${validRenderers.join(", ")}`);
823
+ process.exit(1);
824
+ }
825
+ p__namespace.intro("Switch OG Image renderer");
826
+ const cwd = process.cwd();
827
+ const dirs = [cwd];
828
+ if (fs.existsSync(pathe.join(cwd, "app")))
829
+ dirs.push(pathe.join(cwd, "app"));
830
+ const layersDir = pathe.join(cwd, "layers");
831
+ if (fs.existsSync(layersDir)) {
832
+ const layerDirs = fs.readdirSync(layersDir, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => pathe.join(layersDir, d.name));
833
+ dirs.push(...layerDirs);
834
+ }
835
+ const allComponents = [];
836
+ for (const dir of dirs) {
837
+ const components = findOgImageComponents(dir);
838
+ for (const filepath of components) {
839
+ const match = pathe.basename(filepath).match(RE_RENDERER_SUFFIX);
840
+ if (match) {
841
+ allComponents.push({ path: filepath, renderer: match[1] });
842
+ }
843
+ }
844
+ }
845
+ if (allComponents.length === 0) {
846
+ p__namespace.log.warn("No OG image components with renderer suffixes found.");
847
+ p__namespace.outro("Nothing to do");
848
+ return;
849
+ }
850
+ const currentRenderers = [...new Set(allComponents.map((c) => c.renderer))];
851
+ p__namespace.log.info(`Found ${allComponents.length} component(s) using: ${currentRenderers.join(", ")}`);
852
+ let fromRenderer;
853
+ if (cliFrom) {
854
+ fromRenderer = cliFrom;
855
+ } else if (skipConfirm) {
856
+ if (currentRenderers.length === 1) {
857
+ fromRenderer = currentRenderers[0];
858
+ } else {
859
+ p__namespace.log.error("Multiple renderers detected. Use --from to specify which to migrate.");
860
+ process.exit(1);
861
+ }
862
+ } else {
863
+ const fromSelection = await p__namespace.select({
864
+ message: "Which renderer do you want to migrate from?",
865
+ options: currentRenderers.map((r) => ({
866
+ value: r,
867
+ label: RENDERERS.find((rd) => rd.name === r)?.label || r
868
+ })),
869
+ initialValue: currentRenderers[0]
870
+ });
871
+ if (p__namespace.isCancel(fromSelection)) {
872
+ p__namespace.cancel("Cancelled");
873
+ process.exit(0);
874
+ }
875
+ fromRenderer = fromSelection;
876
+ }
877
+ let toRenderer;
878
+ if (cliTo) {
879
+ toRenderer = cliTo;
880
+ } else if (skipConfirm) {
881
+ toRenderer = fromRenderer === "takumi" ? "satori" : "takumi";
882
+ } else {
883
+ const targetOptions = RENDERERS.filter((r) => r.name !== fromRenderer);
884
+ const toSelection = await p__namespace.select({
885
+ message: "Which renderer do you want to switch to?",
886
+ options: targetOptions.map((r) => ({
887
+ value: r.name,
888
+ label: r.label,
889
+ hint: r.description
890
+ })),
891
+ initialValue: targetOptions[0]?.name
892
+ });
893
+ if (p__namespace.isCancel(toSelection)) {
894
+ p__namespace.cancel("Cancelled");
895
+ process.exit(0);
896
+ }
897
+ toRenderer = toSelection;
898
+ }
899
+ if (fromRenderer === toRenderer) {
900
+ p__namespace.log.warn(`Source and target renderer are the same (${fromRenderer}).`);
901
+ p__namespace.outro("Nothing to do");
902
+ return;
903
+ }
904
+ const toMigrate = allComponents.filter((c) => c.renderer === fromRenderer);
905
+ if (toMigrate.length === 0) {
906
+ p__namespace.log.warn(`No components using ${fromRenderer} renderer found.`);
907
+ p__namespace.outro("Nothing to do");
908
+ return;
909
+ }
910
+ p__namespace.note(
911
+ toMigrate.map((c) => `${pathe.relative(cwd, c.path)} \u2192 ${pathe.basename(c.path).replace(`.${fromRenderer}.vue`, `.${toRenderer}.vue`)}`).join("\n"),
912
+ `Renaming ${toMigrate.length} component(s): ${fromRenderer} \u2192 ${toRenderer}`
913
+ );
914
+ if (dryRun) {
915
+ p__namespace.log.warn("[Dry run mode, no changes will be made]");
916
+ p__namespace.outro("Dry run complete");
917
+ return;
918
+ }
919
+ if (!skipConfirm) {
920
+ const confirmed = await p__namespace.confirm({
921
+ message: `Rename ${toMigrate.length} component(s) from ${fromRenderer} to ${toRenderer}?`,
922
+ initialValue: true
923
+ });
924
+ if (p__namespace.isCancel(confirmed) || !confirmed) {
925
+ p__namespace.cancel("Cancelled");
926
+ process.exit(0);
927
+ }
928
+ }
929
+ const conflicts = [];
930
+ for (const component of toMigrate) {
931
+ const newPath = component.path.replace(`.${fromRenderer}.vue`, `.${toRenderer}.vue`);
932
+ if (fs.existsSync(newPath))
933
+ conflicts.push(`${pathe.basename(component.path)} \u2192 ${pathe.basename(newPath)} (already exists)`);
934
+ }
935
+ if (conflicts.length > 0) {
936
+ p__namespace.log.error("Conflicts detected, aborting:");
937
+ for (const c of conflicts) p__namespace.log.error(` ${c}`);
938
+ process.exit(1);
939
+ }
940
+ for (const component of toMigrate) {
941
+ const newPath = component.path.replace(`.${fromRenderer}.vue`, `.${toRenderer}.vue`);
942
+ fs.renameSync(component.path, newPath);
943
+ p__namespace.log.success(`${pathe.basename(component.path)} \u2192 ${pathe.basename(newPath)}`);
944
+ }
945
+ const hasPkg = fs.existsSync(pathe.join(cwd, "package.json"));
946
+ if (hasPkg) {
947
+ const noFromComponentsRemaining = allComponents.filter((c) => c.renderer === fromRenderer).length === toMigrate.length;
948
+ const detectedPreset = await detectDeploymentTarget(cwd);
949
+ const isEdge = detectedPreset ? EDGE_PRESETS.includes(detectedPreset) : false;
950
+ if (detectedPreset)
951
+ p__namespace.log.info(`Detected deployment target: ${detectedPreset}`);
952
+ const manageDeps = skipConfirm || await p__namespace.confirm({
953
+ message: `Manage dependencies? (install ${toRenderer}, ${noFromComponentsRemaining ? `remove ${fromRenderer}` : "keep existing"})`,
954
+ initialValue: true
955
+ }).then((v) => !p__namespace.isCancel(v) && v);
956
+ if (manageDeps) {
957
+ if (noFromComponentsRemaining) {
958
+ const oldDeps = [.../* @__PURE__ */ new Set([...getRendererDeps(fromRenderer, false), ...getRendererDeps(fromRenderer, true)])];
959
+ const newDeps = /* @__PURE__ */ new Set([...getRendererDeps(toRenderer, false), ...getRendererDeps(toRenderer, true)]);
960
+ const toRemove = oldDeps.filter((d) => !newDeps.has(d));
961
+ if (toRemove.length > 0) {
962
+ const pm = await nypm.detectPackageManager(cwd);
963
+ const spinner2 = p__namespace.spinner();
964
+ spinner2.start(`Removing ${fromRenderer} dependencies...`);
965
+ for (const dep of toRemove) {
966
+ await nypm.removeDependency(dep, { cwd }).catch(() => p__namespace.log.warn(`Could not remove ${dep}, remove manually: ${pm?.name || "npm"} remove ${dep}`));
967
+ }
968
+ spinner2.stop(`Removed ${fromRenderer} dependencies`);
969
+ }
970
+ }
971
+ await installRendererDeps([toRenderer], isEdge);
972
+ }
973
+ const spinner = p__namespace.spinner();
974
+ spinner.start("Running nuxt prepare to update types...");
975
+ const { exec } = await import('tinyexec');
976
+ try {
977
+ await exec("npx", ["nuxi", "prepare"], { nodeOptions: { cwd } });
978
+ spinner.stop("Types updated");
979
+ } catch {
980
+ spinner.stop("Failed to run nuxt prepare");
981
+ p__namespace.log.warn("Run manually: npx nuxt prepare");
982
+ }
983
+ }
984
+ p__namespace.outro("Renderer switch complete!");
985
+ }
986
+ function readPackageJson(cwd) {
987
+ const pkgPath = pathe.join(cwd, "package.json");
988
+ if (!fs.existsSync(pkgPath))
989
+ return null;
990
+ try {
991
+ return JSON.parse(fs.readFileSync(pkgPath, "utf-8"));
992
+ } catch {
993
+ return null;
994
+ }
995
+ }
996
+ function inferRenderer(cwd) {
997
+ const dirs = [cwd];
998
+ if (fs.existsSync(pathe.join(cwd, "app")))
999
+ dirs.push(pathe.join(cwd, "app"));
1000
+ for (const dir of dirs) {
1001
+ const components = findOgImageComponents(dir);
1002
+ for (const filepath of components) {
1003
+ const match = pathe.basename(filepath).match(RE_RENDERER_SUFFIX);
1004
+ if (match)
1005
+ return match[1];
1006
+ }
1007
+ }
1008
+ const pkg = readPackageJson(cwd);
1009
+ if (pkg) {
1010
+ const allDeps = { ...pkg.dependencies, ...pkg.devDependencies };
1011
+ if (allDeps["@takumi-rs/core"] || allDeps["@takumi-rs/wasm"])
1012
+ return "takumi";
1013
+ if (allDeps.satori)
1014
+ return "satori";
1015
+ }
1016
+ return null;
1017
+ }
1018
+ function detectCssFramework(cwd) {
1019
+ const pkg = readPackageJson(cwd);
1020
+ if (!pkg)
1021
+ return "none";
1022
+ const allDeps = { ...pkg.dependencies, ...pkg.devDependencies };
1023
+ if (allDeps["@unocss/nuxt"] || allDeps.unocss)
1024
+ return "unocss";
1025
+ if (allDeps["@nuxtjs/tailwindcss"] || allDeps.tailwindcss || allDeps["@nuxt/ui"])
1026
+ return "tailwind";
1027
+ return "none";
1028
+ }
1029
+ const SCRIPT_BLOCK = `<script setup lang="ts">
1030
+ const { title = 'My Page', description = '' } = defineProps<{
1031
+ title?: string
1032
+ description?: string
1033
+ }>()
1034
+ <\/script>`;
1035
+ function getStarterTemplate(renderer, css) {
1036
+ if (renderer === "satori") {
1037
+ return `${SCRIPT_BLOCK}
1038
+
1039
+ <template>
1040
+ <div :style="{ display: 'flex', flexDirection: 'column', justifyContent: 'center', alignItems: 'center', width: '100%', height: '100%', padding: '60px', backgroundColor: 'white', color: '#171717' }">
1041
+ <h1 :style="{ fontSize: '72px', fontWeight: 'bold', margin: 0, lineHeight: 1.1, textAlign: 'center' }">
1042
+ {{ title }}
1043
+ </h1>
1044
+ <p v-if="description" :style="{ fontSize: '32px', opacity: 0.6, marginTop: '16px', textAlign: 'center', maxWidth: '900px' }">
1045
+ {{ description }}
1046
+ </p>
1047
+ </div>
1048
+ </template>
1049
+ `;
1050
+ }
1051
+ if (css === "tailwind" || css === "unocss") {
1052
+ return `${SCRIPT_BLOCK}
1053
+
1054
+ <template>
1055
+ <div class="w-full h-full flex flex-col justify-center items-center p-[60px] bg-white text-neutral-900 dark:bg-neutral-900 dark:text-white">
1056
+ <h1 class="text-[72px] font-bold m-0 leading-tight text-center" style="text-wrap: balance;">
1057
+ {{ title }}
1058
+ </h1>
1059
+ <p v-if="description" class="text-[32px] opacity-60 mt-4 text-center max-w-[900px]">
1060
+ {{ description }}
1061
+ </p>
1062
+ </div>
1063
+ </template>
1064
+ `;
1065
+ }
1066
+ return `${SCRIPT_BLOCK}
1067
+
1068
+ <template>
1069
+ <div class="container">
1070
+ <h1>{{ title }}</h1>
1071
+ <p v-if="description">
1072
+ {{ description }}
1073
+ </p>
1074
+ </div>
1075
+ </template>
1076
+
1077
+ <style scoped>
1078
+ .container {
1079
+ width: 100%;
1080
+ height: 100%;
1081
+ display: flex;
1082
+ flex-direction: column;
1083
+ justify-content: center;
1084
+ align-items: center;
1085
+ padding: 60px;
1086
+ background: white;
1087
+ color: #171717;
1088
+ }
1089
+ h1 {
1090
+ font-size: 72px;
1091
+ font-weight: bold;
1092
+ margin: 0;
1093
+ line-height: 1.1;
1094
+ text-align: center;
1095
+ text-wrap: balance;
1096
+ }
1097
+ p {
1098
+ font-size: 32px;
1099
+ opacity: 0.6;
1100
+ margin-top: 16px;
1101
+ text-align: center;
1102
+ max-width: 900px;
1103
+ }
1104
+ </style>
1105
+ `;
1106
+ }
1107
+ function insertDefineOgImage(filePath, componentName) {
1108
+ if (!fs.existsSync(filePath))
1109
+ return false;
1110
+ const content = fs.readFileSync(filePath, "utf-8");
1111
+ if (content.includes("defineOgImage"))
1112
+ return false;
1113
+ const call = `defineOgImage('${componentName}', { title: 'Hello' })`;
1114
+ const scriptSetupMatch = content.match(RE_SCRIPT_SETUP);
1115
+ if (scriptSetupMatch) {
1116
+ const insertPos = scriptSetupMatch.index + scriptSetupMatch[0].length;
1117
+ const updated2 = `${content.slice(0, insertPos)}${call}
1118
+ ${content.slice(insertPos)}`;
1119
+ fs.writeFileSync(filePath, updated2, "utf-8");
1120
+ return true;
1121
+ }
1122
+ const updated = `<script setup lang="ts">
1123
+ ${call}
1124
+ <\/script>
1125
+
1126
+ ${content}`;
1127
+ fs.writeFileSync(filePath, updated, "utf-8");
1128
+ return true;
1129
+ }
1130
+ async function runCreate(name, args2) {
1131
+ const cwd = process.cwd();
1132
+ const rendererIdx = args2.indexOf("--renderer");
1133
+ const cliRenderer = rendererIdx !== -1 ? args2[rendererIdx + 1] : null;
1134
+ if (cliRenderer && !RENDERERS.some((r) => r.name === cliRenderer)) {
1135
+ p__namespace.log.error(`Invalid renderer: ${cliRenderer}. Must be ${RENDERERS.map((r) => r.name).join(", ")}`);
1136
+ process.exit(1);
1137
+ }
1138
+ const pathIdx = args2.indexOf("--path");
1139
+ const cliPath = pathIdx !== -1 ? args2[pathIdx + 1] : null;
1140
+ p__namespace.intro("Create OG Image Component");
1141
+ const validateName = (v) => {
1142
+ const trimmed = v?.trim();
1143
+ if (!trimmed)
1144
+ return "Name is required";
1145
+ if (RE_PATH_SEPARATOR.test(trimmed))
1146
+ return 'Name must not contain path separators or ".."';
1147
+ if (!RE_PASCAL_CASE_STRICT.test(trimmed))
1148
+ return "Must be PascalCase (e.g. MyOgImage)";
1149
+ };
1150
+ let componentName = name;
1151
+ if (!componentName) {
1152
+ const input = await p__namespace.text({
1153
+ message: "Component name:",
1154
+ placeholder: "MyOgImage",
1155
+ validate: validateName
1156
+ });
1157
+ if (p__namespace.isCancel(input)) {
1158
+ p__namespace.cancel("Cancelled");
1159
+ process.exit(0);
1160
+ }
1161
+ componentName = input.trim();
1162
+ } else {
1163
+ componentName = componentName.trim();
1164
+ if (!RE_PASCAL_CASE_START.test(componentName))
1165
+ componentName = componentName.charAt(0).toUpperCase() + componentName.slice(1);
1166
+ const err = validateName(componentName);
1167
+ if (err) {
1168
+ p__namespace.log.error(err);
1169
+ process.exit(1);
1170
+ }
1171
+ }
1172
+ let renderer;
1173
+ if (cliRenderer) {
1174
+ renderer = cliRenderer;
1175
+ } else {
1176
+ const inferred = inferRenderer(cwd);
1177
+ if (inferred) {
1178
+ renderer = inferred;
1179
+ p__namespace.log.info(`Inferred renderer: ${renderer}`);
1180
+ } else {
1181
+ const selection = await p__namespace.select({
1182
+ message: "Which renderer?",
1183
+ options: RENDERERS.map((r) => ({
1184
+ value: r.name,
1185
+ label: r.label,
1186
+ hint: r.description
1187
+ })),
1188
+ initialValue: "takumi"
1189
+ });
1190
+ if (p__namespace.isCancel(selection)) {
1191
+ p__namespace.cancel("Cancelled");
1192
+ process.exit(0);
1193
+ }
1194
+ renderer = selection;
1195
+ }
1196
+ }
1197
+ const css = detectCssFramework(cwd);
1198
+ if (css !== "none")
1199
+ p__namespace.log.info(`Detected CSS framework: ${css}`);
1200
+ const baseDir = fs.existsSync(pathe.join(cwd, "app")) ? pathe.join(cwd, "app") : cwd;
1201
+ const outputDir = cliPath ? pathe.resolve(cwd, cliPath) : pathe.resolve(baseDir, "components", "OgImage");
1202
+ if (!outputDir.startsWith(cwd)) {
1203
+ p__namespace.log.error("Output path must be within the project directory");
1204
+ process.exit(1);
1205
+ }
1206
+ if (!fs.existsSync(outputDir))
1207
+ fs.mkdirSync(outputDir, { recursive: true });
1208
+ const filename = `${componentName}.${renderer}.vue`;
1209
+ const outputPath = pathe.join(outputDir, filename);
1210
+ if (fs.existsSync(outputPath)) {
1211
+ p__namespace.log.error(`File already exists: ${pathe.relative(cwd, outputPath)}`);
1212
+ process.exit(1);
1213
+ }
1214
+ const template = getStarterTemplate(renderer, css);
1215
+ fs.writeFileSync(outputPath, template, "utf-8");
1216
+ p__namespace.log.success(`Created ${pathe.relative(cwd, outputPath)}`);
1217
+ const pagesDir = fs.existsSync(pathe.join(baseDir, "pages")) ? pathe.join(baseDir, "pages") : null;
1218
+ if (pagesDir) {
1219
+ const pageInput = await p__namespace.text({
1220
+ message: "Add defineOgImage to a page? (relative path from pages/, leave empty to skip)",
1221
+ placeholder: "index.vue"
1222
+ });
1223
+ if (!p__namespace.isCancel(pageInput) && pageInput && pageInput.trim()) {
1224
+ let pagePath = pageInput.trim();
1225
+ if (!pagePath.endsWith(".vue"))
1226
+ pagePath += ".vue";
1227
+ const fullPagePath = pathe.resolve(pagesDir, pagePath);
1228
+ if (!fullPagePath.startsWith(pagesDir)) {
1229
+ p__namespace.log.warn("Page path must be within the pages directory");
1230
+ } else if (insertDefineOgImage(fullPagePath, componentName)) {
1231
+ p__namespace.log.success(`Added defineOgImage('${componentName}') to ${pathe.relative(cwd, fullPagePath)}`);
1232
+ } else if (!fs.existsSync(fullPagePath)) {
1233
+ p__namespace.log.warn(`Page not found: ${pathe.relative(cwd, fullPagePath)}`);
1234
+ } else {
1235
+ p__namespace.log.info("Page already has defineOgImage, skipped");
1236
+ }
1237
+ }
1238
+ }
1239
+ p__namespace.log.info(`Usage: defineOgImage('${componentName}', { title: 'Hello' })`);
1240
+ p__namespace.outro("");
1241
+ }
802
1242
  async function runEnable(renderer, args2) {
803
1243
  const def = RENDERERS.find((r) => r.name === renderer);
804
1244
  if (!def) {
@@ -824,9 +1264,30 @@ async function runEnable(renderer, args2) {
824
1264
  await installRendererDeps([renderer], isEdge);
825
1265
  p__namespace.outro("Done");
826
1266
  }
1267
+ function showHelp() {
1268
+ p__namespace.intro("nuxt-og-image CLI");
1269
+ p__namespace.note([
1270
+ "create [name] Scaffold a new OG image component",
1271
+ " Options: --renderer <renderer>, --path <dir>",
1272
+ "list List available community templates",
1273
+ "eject <name> Eject a community template to your project",
1274
+ "switch Switch components between renderers",
1275
+ " Options: --from <renderer>, --to <renderer>, --dry-run, --yes",
1276
+ "enable <renderer> Install dependencies for a renderer (satori, browser, takumi)",
1277
+ " Options: --edge (install wasm versions for edge runtimes)",
1278
+ "migrate v6 Migrate to v6 (component suffixes + new API)",
1279
+ " Options: --dry-run, --yes, --renderer <renderer>"
1280
+ ].join("\n"), "Commands");
1281
+ p__namespace.outro("");
1282
+ }
827
1283
  const args = process.argv.slice(2);
828
1284
  const command = args[0];
829
- if (command === "eject") {
1285
+ const hasHelp = args.includes("--help") || args.includes("-h");
1286
+ if (hasHelp || !command) {
1287
+ showHelp();
1288
+ } else if (command === "create") {
1289
+ runCreate(args[1], args.slice(1));
1290
+ } else if (command === "eject") {
830
1291
  const templateName = args[1];
831
1292
  if (!templateName) {
832
1293
  p__namespace.log.error("Please specify a template name.");
@@ -845,6 +1306,8 @@ if (command === "eject") {
845
1306
  process.exit(1);
846
1307
  }
847
1308
  runMigrate(args);
1309
+ } else if (command === "switch") {
1310
+ runSwitch(args.slice(1));
848
1311
  } else if (command === "enable") {
849
1312
  const renderer = args[1];
850
1313
  if (!renderer) {
@@ -854,12 +1317,7 @@ if (command === "eject") {
854
1317
  }
855
1318
  runEnable(renderer, args);
856
1319
  } else {
857
- console.log("nuxt-og-image CLI\n");
858
- console.log("Commands:");
859
- console.log(" list List available community templates");
860
- console.log(" eject <name> Eject a community template to your project");
861
- console.log(" migrate v6 Migrate to v6 (component suffixes + new API)");
862
- console.log(" Options: --dry-run, --yes, --renderer <satori|browser|takumi>");
863
- console.log(" enable <renderer> Install dependencies for a renderer (satori, browser, takumi)");
864
- console.log(" Options: --edge (install wasm versions for edge runtimes)");
1320
+ p__namespace.log.error(`Unknown command: ${command}`);
1321
+ showHelp();
1322
+ process.exit(1);
865
1323
  }