create-nextly-app 0.0.2-alpha.73 → 0.0.2-alpha.74

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/cli.cjs CHANGED
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env node
2
2
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
3
- const require_create_nextly = require("./create-nextly-Q62KZJib.cjs");
3
+ const require_create_nextly = require("./create-nextly-Decg8dye.cjs");
4
4
  // ../../node_modules/.pnpm/commander@14.0.2/node_modules/commander/lib/error.js
5
5
  var require_error = /* @__PURE__ */ require_create_nextly.__commonJSMin(((exports) => {
6
6
  /**
package/dist/cli.mjs CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { a as getAvailableTemplateNames, c as init, d as __require, f as __toESM, i as validateTemplateName, l as shutdown, n as resolveProjectArg, o as packageVersion, r as validateProjectName, s as capture, t as createNextly, u as __commonJSMin } from "./create-nextly-CuWn_ITx.mjs";
2
+ import { a as getAvailableTemplateNames, c as init, d as __require, f as __toESM, i as validateTemplateName, l as shutdown, n as resolveProjectArg, o as packageVersion, r as validateProjectName, s as capture, t as createNextly, u as __commonJSMin } from "./create-nextly-ChHdzG83.mjs";
3
3
  // ../../node_modules/.pnpm/commander@14.0.2/node_modules/commander/lib/error.js
4
4
  var require_error = /* @__PURE__ */ __commonJSMin(((exports) => {
5
5
  /**
@@ -45973,41 +45973,56 @@ const SEARCH_INDEX_SCRIPT = path.join("scripts", "build-search-index.mjs");
45973
45973
  async function projectHasSearchIndexScript(targetDir) {
45974
45974
  return import_lib$1.default.pathExists(path.join(targetDir, SEARCH_INDEX_SCRIPT));
45975
45975
  }
45976
- /**
45977
- * The plugins a Nextly config registers, by package name: each binding it
45978
- * imports from a `@nextlyhq/plugin-*` package (the SDK aside) that a
45979
- * `plugins: [...]` list in it names, however deep in an element. Read with
45980
- * the parser the Next config step uses, so nested brackets, either quote and
45981
- * default or aliased imports are read as the compiler reads them. First-party
45982
- * plugins register under their package name, which is what
45983
- * `nextly plugins install` takes. Throws for a config that does not parse,
45984
- * since every template's must.
45985
- *
45986
- * Exported for the test that holds every template's config to its build.
45987
- */
45988
- function configuredPlugins(configText, file = "nextly.config.ts") {
45989
- let program;
45976
+ /** Parses a config, naming the file when it does not parse. */
45977
+ function parseConfigProgram(configText, file) {
45990
45978
  try {
45991
- program = (0, import_lib.parse)(configText, {
45979
+ return (0, import_lib.parse)(configText, {
45992
45980
  sourceType: "module",
45993
45981
  plugins: ["typescript", "jsx"]
45994
45982
  }).program;
45995
45983
  } catch (error) {
45996
45984
  throw new Error(`${file} cannot be parsed (${error.message}), so the plugins its build must install cannot be read.`);
45997
45985
  }
45998
- const fromPackage = /* @__PURE__ */ new Map();
45986
+ }
45987
+ /** Each binding imported from a module `matches` accepts, to that module's name. */
45988
+ function bindingsImportedFrom(program, matches) {
45989
+ const bindings = /* @__PURE__ */ new Map();
45999
45990
  for (const statement of program.body) {
46000
45991
  if (statement.type !== "ImportDeclaration") continue;
46001
- const pkg = statement.source.value;
46002
- if (!pkg.startsWith("@nextlyhq/plugin-") || pkg === "@nextlyhq/plugin-sdk") continue;
46003
- for (const specifier of statement.specifiers) fromPackage.set(specifier.local.name, pkg);
45992
+ if (!matches(statement.source.value)) continue;
45993
+ for (const specifier of statement.specifiers) bindings.set(specifier.local.name, statement.source.value);
46004
45994
  }
46005
- const found = [];
45995
+ return bindings;
45996
+ }
45997
+ const isPluginPackage = (source) => source.startsWith("@nextlyhq/plugin-") && source !== "@nextlyhq/plugin-sdk";
45998
+ /** What a config's `plugins` lists hold: the packages, and whether a binding from its own source is among them. */
45999
+ function readPluginLists(configText, file) {
46000
+ const program = parseConfigProgram(configText, file);
46001
+ const fromPackage = bindingsImportedFrom(program, isPluginPackage);
46002
+ const packages = /* @__PURE__ */ new Set();
46006
46003
  for (const name of identifiersInPluginLists(program, file)) {
46007
46004
  const pkg = fromPackage.get(name);
46008
- if (pkg !== void 0 && !found.includes(pkg)) found.push(pkg);
46005
+ if (pkg !== void 0) packages.add(pkg);
46009
46006
  }
46010
- return found;
46007
+ const local = bindingsImportedFrom(program, (source) => source.startsWith("."));
46008
+ const ownPlugin = namesInPluginEntries(program, file).some((name) => local.has(name) && !fromPackage.has(name));
46009
+ return {
46010
+ packages: [...packages],
46011
+ ownPlugin
46012
+ };
46013
+ }
46014
+ /**
46015
+ * The `nextly plugins install` steps a project's build runs, read from its
46016
+ * config. A plugin package is installed by name, and the build says which. A
46017
+ * plugin the site wrote itself has no package name to give, and a production
46018
+ * boot refuses it like any other when nothing has installed it, so a config
46019
+ * that lists one installs with `--all`, which reads the names from the config.
46020
+ * Exported for the test that holds every template's config to its build.
46021
+ */
46022
+ function pluginInstallSteps(configText, file = "nextly.config.ts") {
46023
+ const { packages, ownPlugin } = readPluginLists(configText, file);
46024
+ if (ownPlugin) return ["nextly plugins install --all"];
46025
+ return packages.map((pkg) => `nextly plugins install ${pkg}`);
46011
46026
  }
46012
46027
  /** Every identifier inside the value of each `plugins: [...]` property, in source order. */
46013
46028
  function identifiersInPluginLists(program, file) {
@@ -46021,6 +46036,20 @@ function identifiersInPluginLists(program, file) {
46021
46036
  return names;
46022
46037
  }
46023
46038
  /**
46039
+ * The binding each entry of a `plugins: [...]` list is, as written: `seo` for
46040
+ * `seo` or `seo({ ... })`. Narrower than {@link identifiersInPluginLists},
46041
+ * which also sees the identifiers inside an entry's options.
46042
+ */
46043
+ function namesInPluginEntries(program, file) {
46044
+ const names = [];
46045
+ walkNodes(program, (node) => {
46046
+ if (!isPluginList(node, file) || node.value.type !== "ArrayExpression") return;
46047
+ for (const element of node.value.elements) if (element?.type === "Identifier") names.push(element.name);
46048
+ else if (element?.type === "CallExpression" && element.callee.type === "Identifier") names.push(element.callee.name);
46049
+ });
46050
+ return names;
46051
+ }
46052
+ /**
46024
46053
  * A `plugins: [...]` property, by an identifier or a string key. A `plugins`
46025
46054
  * property written any other way, such as a variable holding the list, a call
46026
46055
  * or a spread inside it, is
@@ -46052,16 +46081,15 @@ function isNode(value) {
46052
46081
  return typeof value === "object" && value !== null && typeof value.type === "string";
46053
46082
  }
46054
46083
  /**
46055
- * The plugins the scaffolded PROJECT's config registers, read from the
46056
- * finished project as {@link projectHasSearchIndexScript} is: a template
46057
- * offers more than one config (the blog's visual one registers none), so
46058
- * only the file the copy wrote can say. None when there is no config, or
46059
- * no project on disk to ask.
46084
+ * The plugin install steps the scaffolded PROJECT's build needs, read from
46085
+ * the finished project as {@link projectHasSearchIndexScript} is: a template
46086
+ * offers more than one config, so only the file the copy wrote can say. None
46087
+ * when there is no config, or no project on disk to ask.
46060
46088
  */
46061
- async function projectPlugins(targetDir) {
46089
+ async function projectPluginInstalls(targetDir) {
46062
46090
  if (targetDir === void 0) return [];
46063
46091
  const config = path.join(targetDir, "nextly.config.ts");
46064
- return await import_lib$1.default.pathExists(config) ? configuredPlugins(await import_lib$1.default.readFile(config, "utf-8"), config) : [];
46092
+ return await import_lib$1.default.pathExists(config) ? pluginInstallSteps(await import_lib$1.default.readFile(config, "utf-8"), config) : [];
46065
46093
  }
46066
46094
  /**
46067
46095
  * Every file under `root` that could carry an import specifier.
@@ -46654,7 +46682,7 @@ async function generatePackageJson(projectName, database, source = nextlySource(
46654
46682
  esbuild: PINNED_VERSIONS.esbuild
46655
46683
  };
46656
46684
  const shipsSearchIndex = targetDir ? await projectHasSearchIndexScript(targetDir) : false;
46657
- const plugins = await projectPlugins(targetDir);
46685
+ const pluginInstalls = await projectPluginInstalls(targetDir);
46658
46686
  if (shipsSearchIndex) devDependencies.pagefind = "^1.1.0";
46659
46687
  const pkg = {
46660
46688
  name: projectName,
@@ -46664,7 +46692,7 @@ async function generatePackageJson(projectName, database, source = nextlySource(
46664
46692
  dev: "next dev --turbopack",
46665
46693
  build: [
46666
46694
  "nextly migrate",
46667
- ...plugins.map((plugin) => `nextly plugins install ${plugin}`),
46695
+ ...pluginInstalls,
46668
46696
  "next build",
46669
46697
  ...shipsSearchIndex ? ["node scripts/build-search-index.mjs"] : []
46670
46698
  ].join(" && "),
@@ -47320,6 +47348,15 @@ const ADMIN_LAYOUT = {
47320
47348
  path: "admin/[[...params]]/layout.tsx",
47321
47349
  importsConfig: true
47322
47350
  };
47351
+ /**
47352
+ * The admin component map's two generated files, under the app's source root,
47353
+ * as the base template commits them: the admin layout imports the list, and
47354
+ * the admin page imports the map that registers the components. Read with the
47355
+ * routes, so an app Nextly is added to starts from the template's own empty
47356
+ * map rather than a second one written out here, and `next dev` rewrites them
47357
+ * as plugins are added.
47358
+ */
47359
+ const ADMIN_COMPONENT_MAP_FILES = ["types/generated/plugin-admin-components.generated.ts", "types/generated/plugin-admin-imports.generated.ts"];
47323
47360
  /** Every file the existing-app path writes from the templates, read together. */
47324
47361
  const ALL_ROUTES = [
47325
47362
  ...ROUTES,
@@ -47425,6 +47462,15 @@ async function readMediaRouteTemplates(templatesDir = resolveTemplatePath()) {
47425
47462
  if (route.importsConfig && !CONFIG_IMPORT.test(text)) throw new Error(`The template's ${route.path} does not import the Nextly config, so no route that finds it can be written. Nothing was installed or written.`);
47426
47463
  texts.set(route.path, text);
47427
47464
  }
47465
+ for (const file of ADMIN_COMPONENT_MAP_FILES) {
47466
+ const relative = `base/src/${file}`;
47467
+ try {
47468
+ texts.set(file, await import_lib$1.default.readFile(path.join(templatesDir, relative), "utf-8"));
47469
+ } catch (error) {
47470
+ const code = error.code ?? "unreadable";
47471
+ throw new Error(`The templates at ${templatesDir} have no readable ${relative} (${code}), so Nextly's admin component map cannot be added. Nothing was installed or written; check the --local-template path if one was given.`);
47472
+ }
47473
+ }
47428
47474
  return texts;
47429
47475
  }
47430
47476
  /**
@@ -47875,10 +47921,71 @@ function skipped(cwd, left, target, text, role, reason) {
47875
47921
  function toPosix(p) {
47876
47922
  return p.split(path.sep).join("/");
47877
47923
  }
47924
+ // src/generators/types.ts
47925
+ /**
47926
+ * Where the generated types file goes, relative to the project root: under
47927
+ * src/ when the app keeps its code there, at the root when it does not. The
47928
+ * config names this path and the types step creates it, so both read it here.
47929
+ */
47930
+ function typesOutputFile(projectInfo) {
47931
+ return `${projectInfo.srcDir ? "./src" : "."}/types/generated/nextly-types.ts`;
47932
+ }
47933
+ /**
47934
+ * The folder the generated files go in, relative to the project root, as
47935
+ * {@link typesOutputFile} names it: the admin layout and page import the
47936
+ * component map from here.
47937
+ */
47938
+ function typesOutputFolder(projectInfo) {
47939
+ return path.posix.dirname(path.posix.normalize(typesOutputFile(projectInfo)));
47940
+ }
47941
+ /**
47942
+ * Create the types directory structure for generated TypeScript types.
47943
+ * This mirrors what `nextly init` does - creates the directory and a placeholder file.
47944
+ * The actual types are generated when running `next dev` (which boots Nextly
47945
+ * and emits types lazily) or `npx nextly generate:types`.
47946
+ */
47947
+ async function generateTypesDirectory(cwd, projectInfo, templates) {
47948
+ const files = nothingYet();
47949
+ const typesFile = path.posix.normalize(typesOutputFile(projectInfo));
47950
+ const typesDir = typesOutputFolder(projectInfo);
47951
+ await writeNew(cwd, `${typesDir}/.gitkeep`, "", files, { why: "it is already there" });
47952
+ await writeNew(cwd, typesFile, `/**
47953
+ * Nextly Generated Types
47954
+ *
47955
+ * This file will be auto-generated when you run:
47956
+ * next dev (Nextly emits types on first boot)
47957
+ * npx nextly generate:types
47958
+ *
47959
+ * Do not edit this file manually - it will be overwritten.
47960
+ */
47961
+
47962
+ // Types will be generated here after running next dev
47963
+ export {};
47964
+ `, files, { why: "it is already there; Nextly writes the types into it when they are generated" });
47965
+ for (const file of ADMIN_COMPONENT_MAP_FILES) {
47966
+ const text = templates.get(file);
47967
+ if (text === void 0) throw new Error(`The templates read for this app hold no ${file}, so Nextly's admin component map cannot be written.`);
47968
+ await writeNew(cwd, `${typesDir}/${path.posix.basename(file)}`, text, files, { why: "it is already there; `next dev` and `nextly generate:types` keep it current" });
47969
+ }
47970
+ return files;
47971
+ }
47878
47972
  // src/generators/admin.ts
47879
- const ADMIN_PAGE_TEMPLATE = `"use client";
47973
+ /**
47974
+ * The generated component map's two files, by the name a module imports them
47975
+ * as: the list the layout reads, and the map the page loads to register the
47976
+ * components.
47977
+ */
47978
+ const COMPONENT_LIST = "plugin-admin-components.generated";
47979
+ const COMPONENT_MAP = "plugin-admin-imports.generated";
47980
+ /** How the file at `target` imports `name` from the app's generated types folder. */
47981
+ function generatedImport(cwd, target, projectInfo, name) {
47982
+ const relative = path.relative(path.dirname(target), path.join(cwd, typesOutputFolder(projectInfo), name)).split(path.sep).join("/");
47983
+ return relative.startsWith(".") ? relative : `./${relative}`;
47984
+ }
47985
+ const adminPageTemplate = (componentMap) => `"use client";
47880
47986
 
47881
47987
  import "@nextlyhq/admin/style.css";
47988
+ import "${componentMap}";
47882
47989
  import { RootLayout, QueryProvider, ErrorBoundary } from "@nextlyhq/admin";
47883
47990
 
47884
47991
  export default function AdminPage() {
@@ -47897,14 +48004,16 @@ export default function AdminPage() {
47897
48004
  `;
47898
48005
  /**
47899
48006
  * The admin layout, as the base template holds it, with its config import
47900
- * pointing at the project's `nextly.config` from where the layout is written.
47901
- * One source with a new project's layout, so an app Nextly is added to gets
47902
- * the same per-request admin.
48007
+ * pointing at the project's `nextly.config`, and its component-list import at
48008
+ * the app's generated types folder, from where the layout is written. One
48009
+ * source with a new project's layout, so an app Nextly is added to gets the
48010
+ * same per-request admin.
47903
48011
  */
47904
- function adminLayout(cwd, adminDir, templates) {
48012
+ function adminLayout(cwd, adminDir, projectInfo, templates) {
47905
48013
  const text = templates.get(ADMIN_LAYOUT.path);
47906
48014
  if (text === void 0) throw new Error(`The templates read for this app hold no ${ADMIN_LAYOUT.path}, so Nextly's admin layout cannot be written.`);
47907
- return withRelativeConfigImport(text, cwd, path.join(cwd, adminDir, "layout.tsx"), ADMIN_LAYOUT.path);
48015
+ const target = path.join(cwd, adminDir, "layout.tsx");
48016
+ return withRelativeConfigImport(text, cwd, target, ADMIN_LAYOUT.path).replace(new RegExp(`"[^"]*/${COMPONENT_LIST.replaceAll(".", "\\.")}"`), `"${generatedImport(cwd, target, projectInfo, COMPONENT_LIST)}"`);
47908
48017
  }
47909
48018
  /**
47910
48019
  * Generate the admin panel page and layout.
@@ -47913,56 +48022,23 @@ function adminLayout(cwd, adminDir, templates) {
47913
48022
  async function generateAdminPage(cwd, projectInfo, templates) {
47914
48023
  const files = nothingYet();
47915
48024
  const adminDir = `${projectInfo.appDir}/admin/[[...params]]`;
47916
- const layout = adminLayout(cwd, adminDir, templates);
48025
+ const layout = adminLayout(cwd, adminDir, projectInfo, templates);
48026
+ const page = adminPageTemplate(generatedImport(cwd, path.join(cwd, adminDir, "page.tsx"), projectInfo, COMPONENT_MAP));
47917
48027
  const pages = await appPageExtensions(cwd);
47918
48028
  if (pages.extensions !== "absent") {
47919
48029
  const { configName, extensions } = pages;
47920
- for (const [name, text] of [["page", ADMIN_PAGE_TEMPLATE], ["layout", layout]]) files.skipped.push(pageExtensionsSkip(`${adminDir}/${name}`, text, {
48030
+ for (const [name, text] of [["page", page], ["layout", layout]]) files.skipped.push(pageExtensionsSkip(`${adminDir}/${name}`, text, {
47921
48031
  configName,
47922
48032
  extensions
47923
48033
  }, TSX_FILE));
47924
48034
  return files;
47925
48035
  }
47926
- for (const [name, text] of [["page.tsx", ADMIN_PAGE_TEMPLATE], ["layout.tsx", layout]]) await writeNew(cwd, `${adminDir}/${name}`, text, files, {
48036
+ for (const [name, text] of [["page.tsx", page], ["layout.tsx", layout]]) await writeNew(cwd, `${adminDir}/${name}`, text, files, {
47927
48037
  why: `the app already has one; Nextly's admin needs its own ${name}, which is`,
47928
48038
  toAdd: text
47929
48039
  });
47930
48040
  return files;
47931
48041
  }
47932
- // src/generators/types.ts
47933
- /**
47934
- * Where the generated types file goes, relative to the project root: under
47935
- * src/ when the app keeps its code there, at the root when it does not. The
47936
- * config names this path and the types step creates it, so both read it here.
47937
- */
47938
- function typesOutputFile(projectInfo) {
47939
- return `${projectInfo.srcDir ? "./src" : "."}/types/generated/nextly-types.ts`;
47940
- }
47941
- /**
47942
- * Create the types directory structure for generated TypeScript types.
47943
- * This mirrors what `nextly init` does - creates the directory and a placeholder file.
47944
- * The actual types are generated when running `next dev` (which boots Nextly
47945
- * and emits types lazily) or `npx nextly generate:types`.
47946
- */
47947
- async function generateTypesDirectory(cwd, projectInfo) {
47948
- const files = nothingYet();
47949
- const typesFile = path.posix.normalize(typesOutputFile(projectInfo));
47950
- await writeNew(cwd, `${path.posix.dirname(typesFile)}/.gitkeep`, "", files, { why: "it is already there" });
47951
- await writeNew(cwd, typesFile, `/**
47952
- * Nextly Generated Types
47953
- *
47954
- * This file will be auto-generated when you run:
47955
- * next dev (Nextly emits types on first boot)
47956
- * npx nextly generate:types
47957
- *
47958
- * Do not edit this file manually - it will be overwritten.
47959
- */
47960
-
47961
- // Types will be generated here after running next dev
47962
- export {};
47963
- `, files, { why: "it is already there; Nextly writes the types into it when they are generated" });
47964
- return files;
47965
- }
47966
48042
  // src/generators/config.ts
47967
48043
  /**
47968
48044
  * Base templates for project types that use inline config generation.
@@ -48195,7 +48271,7 @@ async function generateIntoExistingApp(cwd, projectType, projectInfo, database,
48195
48271
  await generateMediaRoutes(cwd, projectInfo, templates),
48196
48272
  await generateUploadsRoute(cwd, projectInfo, templates),
48197
48273
  await generateInstrumentation(cwd, projectInfo),
48198
- await generateTypesDirectory(cwd, projectInfo),
48274
+ await generateTypesDirectory(cwd, projectInfo, templates),
48199
48275
  nextConfigFiles(await patchNextConfig(cwd, database)),
48200
48276
  cacheComponentsFiles(await turnOnCacheComponents(cwd))
48201
48277
  ];
@@ -52996,4 +53072,4 @@ async function createNextly(options = {}) {
52996
53072
  }
52997
53073
  export { getAvailableTemplateNames as a, init as c, __require as d, __toESM$1 as f, validateTemplateName as i, shutdown as l, resolveProjectArg as n, packageVersion as o, validateProjectName as r, capture as s, createNextly as t, __commonJSMin$1 as u };
52998
53074
 
52999
- //# sourceMappingURL=create-nextly-CuWn_ITx.mjs.map
53075
+ //# sourceMappingURL=create-nextly-ChHdzG83.mjs.map