@cmssy/cli 16.2.1 → 16.4.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 (2) hide show
  1. package/dist/index.js +558 -417
  2. package/package.json +2 -2
package/dist/index.js CHANGED
@@ -667,12 +667,12 @@ function hasFlag(args, name) {
667
667
  // src/init.ts
668
668
  import {
669
669
  copyFileSync as copyFileSync2,
670
- existsSync as existsSync4,
671
- mkdirSync as mkdirSync2,
672
- readFileSync as readFileSync4,
673
- writeFileSync as writeFileSync3
670
+ existsSync as existsSync5,
671
+ mkdirSync as mkdirSync3,
672
+ readFileSync as readFileSync5,
673
+ writeFileSync as writeFileSync4
674
674
  } from "fs";
675
- import { dirname as dirname2, join as join4, resolve as resolve2 } from "path";
675
+ import { dirname as dirname3, join as join5, resolve as resolve2 } from "path";
676
676
  import { fileURLToPath } from "url";
677
677
 
678
678
  // src/eslint-config.ts
@@ -940,6 +940,119 @@ function wireEslintConfig(root, pkg, asset) {
940
940
  return { patched: flat, dependency: true, notes: [] };
941
941
  }
942
942
 
943
+ // src/next-root.ts
944
+ import {
945
+ existsSync as existsSync4,
946
+ mkdirSync as mkdirSync2,
947
+ readFileSync as readFileSync4,
948
+ renameSync,
949
+ writeFileSync as writeFileSync3
950
+ } from "fs";
951
+ import { dirname as dirname2, join as join4, posix } from "path";
952
+ var BACKUP_DIR = ".cmssy-backup";
953
+ function existingFile(root, base) {
954
+ return ["tsx", "ts", "jsx", "js"].map((extension) => `${base}.${extension}`).find((candidate) => existsSync4(join4(root, candidate)));
955
+ }
956
+ function readMetadataBlock(source) {
957
+ const start = source.search(/^export const metadata\b/m);
958
+ if (start === -1) return null;
959
+ const open = source.indexOf("{", start);
960
+ if (open === -1) return null;
961
+ let depth = 0;
962
+ for (let index = open; index < source.length; index += 1) {
963
+ const char = source[index];
964
+ if (char === "{") depth += 1;
965
+ if (char === "}") {
966
+ depth -= 1;
967
+ if (depth === 0) {
968
+ const end = source.indexOf(";", index);
969
+ return source.slice(start, end === -1 ? index + 1 : end + 1);
970
+ }
971
+ }
972
+ }
973
+ return null;
974
+ }
975
+ function carryRootLayout(source) {
976
+ const cssImports = [
977
+ ...source.matchAll(/^import\s+["']([^"']+\.css)["'];?\s*$/gm)
978
+ ].map((match) => match[1]);
979
+ const metadata = readMetadataBlock(source);
980
+ return {
981
+ cssImports,
982
+ metadata,
983
+ needsMetadataType: metadata !== null && /\bMetadata\b/.test(metadata)
984
+ };
985
+ }
986
+ function rewriteImport(specifier, fromDir, toDir) {
987
+ if (!specifier.startsWith(".")) return specifier;
988
+ const rewritten = posix.relative(toDir, posix.join(fromDir, specifier));
989
+ return rewritten.startsWith(".") ? rewritten : `./${rewritten}`;
990
+ }
991
+ function applyCarriedLayout(layoutSource, carried, originalDir, targetDir) {
992
+ const lines = [];
993
+ if (carried.needsMetadataType) {
994
+ lines.push('import type { Metadata } from "next";');
995
+ }
996
+ for (const specifier of carried.cssImports) {
997
+ lines.push(`import "${rewriteImport(specifier, originalDir, targetDir)}";`);
998
+ }
999
+ if (lines.length === 0 && !carried.metadata) return layoutSource;
1000
+ const importBlock = layoutSource.match(/^(?:import[^\n]*\n)+/m);
1001
+ const cut = importBlock ? (importBlock.index ?? 0) + importBlock[0].length : 0;
1002
+ const carriedImports = lines.length > 0 ? `${lines.join("\n")}
1003
+ ` : "";
1004
+ const metadata = carried.metadata ? `
1005
+ ${carried.metadata}
1006
+ ` : "";
1007
+ return `${layoutSource.slice(0, cut)}${carriedImports}${metadata}${layoutSource.slice(cut)}`;
1008
+ }
1009
+ function relocateNextRootFiles(root, prefix) {
1010
+ const moved = [];
1011
+ let layout = null;
1012
+ for (const base of ["page", "layout"]) {
1013
+ const from = existingFile(root, `${prefix}app/${base}`);
1014
+ if (!from) continue;
1015
+ const to = posix.join(BACKUP_DIR, from);
1016
+ if (base === "layout") {
1017
+ layout = carryRootLayout(readFileSync4(join4(root, from), "utf8"));
1018
+ }
1019
+ mkdirSync2(dirname2(join4(root, to)), { recursive: true });
1020
+ renameSync(join4(root, from), join4(root, to));
1021
+ moved.push({ from, to });
1022
+ }
1023
+ return { moved, layout };
1024
+ }
1025
+ function patchCmssyLayout(root, target, carried, originalLayoutDir) {
1026
+ const path = join4(root, target);
1027
+ const source = readFileSync4(path, "utf8");
1028
+ const patched = applyCarriedLayout(
1029
+ source,
1030
+ carried,
1031
+ originalLayoutDir,
1032
+ posix.dirname(target)
1033
+ );
1034
+ if (patched === source) return false;
1035
+ writeFileSync3(path, patched);
1036
+ return true;
1037
+ }
1038
+ function describeRelocation(relocated, prefix) {
1039
+ const lines = relocated.moved.map(
1040
+ ({ from, to }) => `moved ${from} to ${to} - the cmssy catch-all now serves /`
1041
+ );
1042
+ const { layout } = relocated;
1043
+ if (!layout) return lines;
1044
+ const css = layout.cssImports.length;
1045
+ const carried = [
1046
+ css > 0 ? `${css} global CSS import${css === 1 ? "" : "s"}` : null,
1047
+ layout.metadata ? "the metadata export" : null
1048
+ ].filter((part) => part !== null);
1049
+ const verb = carried.length > 0 ? `carried ${carried.join(" and ")} into` : "nothing to carry into";
1050
+ lines.push(
1051
+ `${verb} both ${prefix}app/[[...path]]/layout.tsx and ${prefix}app/cmssy-edit/[[...path]]/layout.tsx - fonts, providers or anything else the old layout did are yours to port to BOTH`
1052
+ );
1053
+ return lines;
1054
+ }
1055
+
943
1056
  // src/init.ts
944
1057
  var ASSETS_DIR = fileURLToPath(new URL("../assets/init", import.meta.url));
945
1058
  var CLI_PACKAGE_JSON = fileURLToPath(
@@ -959,13 +1072,10 @@ function frameworkFiles(framework, root) {
959
1072
  purpose
960
1073
  }))
961
1074
  ];
962
- if (framework.name === "next" && existingFile(root, `${srcPrefix}app/layout`)) {
963
- return files.filter((file) => !file.target.endsWith("/layout.tsx"));
964
- }
965
1075
  return files;
966
1076
  }
967
1077
  function cliVersion() {
968
- const pkg = JSON.parse(readFileSync4(CLI_PACKAGE_JSON, "utf8"));
1078
+ const pkg = JSON.parse(readFileSync5(CLI_PACKAGE_JSON, "utf8"));
969
1079
  return pkg.version;
970
1080
  }
971
1081
  function sortedEntries(entries) {
@@ -993,47 +1103,29 @@ function addDependencies(root, pkg, framework, development) {
993
1103
  ...pinned(missingDev)
994
1104
  });
995
1105
  }
996
- writeFileSync3(
997
- join4(root, "package.json"),
1106
+ writeFileSync4(
1107
+ join5(root, "package.json"),
998
1108
  `${JSON.stringify(pkg, null, 2)}
999
1109
  `
1000
1110
  );
1001
1111
  return [...missing, ...missingDev].map((name) => `${name}@${range}`);
1002
1112
  }
1003
1113
  function detectInstallCommand(root) {
1004
- if (existsSync4(join4(root, "pnpm-lock.yaml")) || existsSync4(join4(root, "pnpm-workspace.yaml"))) {
1114
+ if (existsSync5(join5(root, "pnpm-lock.yaml")) || existsSync5(join5(root, "pnpm-workspace.yaml"))) {
1005
1115
  return "pnpm install";
1006
1116
  }
1007
- if (existsSync4(join4(root, "yarn.lock"))) return "yarn";
1008
- if (existsSync4(join4(root, "bun.lock")) || existsSync4(join4(root, "bun.lockb"))) {
1117
+ if (existsSync5(join5(root, "yarn.lock"))) return "yarn";
1118
+ if (existsSync5(join5(root, "bun.lock")) || existsSync5(join5(root, "bun.lockb"))) {
1009
1119
  return "bun install";
1010
1120
  }
1011
1121
  return "npm install";
1012
1122
  }
1013
- function existingFile(root, base) {
1014
- return ["tsx", "ts", "jsx", "js"].map((extension) => `${base}.${extension}`).find((candidate) => existsSync4(join4(root, candidate)));
1015
- }
1016
1123
  function frameworkNotes(framework, root, skipped) {
1017
1124
  const notes = [];
1018
1125
  if (framework.name === "next") {
1019
1126
  const prefix = nextSrcPrefix(root);
1020
- const home = existingFile(root, `${prefix}app/page`);
1021
- if (home) {
1022
- notes.push({
1023
- status: "unknown",
1024
- message: `${home} conflicts with the cmssy catch-all route - delete it and the cmssy page serves /`
1025
- });
1026
- }
1027
- const rootLayout = existingFile(root, `${prefix}app/layout`);
1028
- if (rootLayout) {
1029
- notes.push({
1030
- status: "fail",
1031
- message: `${rootLayout} outranks the cmssy root layouts, so they were NOT written and <html lang> stays whatever that file says`,
1032
- fix: `delete ${rootLayout} and rerun, then move its global CSS import and metadata into BOTH ${prefix}app/[[...path]]/layout.tsx and ${prefix}app/cmssy-edit/[[...path]]/layout.tsx - they are separate roots, and an editor preview with no CSS is the usual way to find out you only did one. Routes outside the cmssy catch-alls need a root layout of their own once it is gone: move them under a route group, e.g. ${prefix}app/(site)/layout.tsx, or the build fails with "doesn't have a root layout".`
1033
- });
1034
- }
1035
1127
  for (const layout of ["app/[[...path]]", "app/cmssy-edit/[[...path]]"]) {
1036
- if (!rootLayout && skipped.includes(`${prefix}${layout}/layout.tsx`)) {
1128
+ if (skipped.includes(`${prefix}${layout}/layout.tsx`)) {
1037
1129
  notes.push({
1038
1130
  status: "unknown",
1039
1131
  message: `${prefix}${layout}/layout.tsx already existed - set <html lang={await resolveCmssyLocale(cmssy, path)}> there yourself (import from @cmssy/core), or the site declares one language while rendering another`
@@ -1046,7 +1138,7 @@ function frameworkNotes(framework, root, skipped) {
1046
1138
  status: "unknown",
1047
1139
  message: "the cmssy wiring needs the React integration and a server adapter - run: npx astro add react node"
1048
1140
  });
1049
- if (existsSync4(join4(root, "src/pages/index.astro"))) {
1141
+ if (existsSync5(join5(root, "src/pages/index.astro"))) {
1050
1142
  notes.push({
1051
1143
  status: "unknown",
1052
1144
  message: "src/pages/index.astro shadows the cmssy catch-all for / - delete it and the cmssy page serves /"
@@ -1071,7 +1163,7 @@ function runInit(options, deps) {
1071
1163
  const { log } = deps;
1072
1164
  try {
1073
1165
  const root = resolve2(deps.cwd, options.dir ?? ".");
1074
- if (!existsSync4(root)) {
1166
+ if (!existsSync5(root)) {
1075
1167
  throw new CliError(
1076
1168
  `${root} does not exist`,
1077
1169
  "pass --dir with the app's directory, or run cmssy init inside it"
@@ -1085,11 +1177,15 @@ function runInit(options, deps) {
1085
1177
  message: `detected ${framework.label} - wiring cmssy into ${root}`
1086
1178
  })
1087
1179
  );
1180
+ const relocated = framework.name === "next" ? relocateNextRootFiles(root, nextSrcPrefix(root)) : { moved: [], layout: null };
1181
+ for (const line of describeRelocation(relocated, nextSrcPrefix(root))) {
1182
+ log(formatResult({ status: "ok", message: line }));
1183
+ }
1088
1184
  const written = [];
1089
1185
  const skipped = [];
1090
1186
  for (const file of frameworkFiles(framework, root)) {
1091
- const target = join4(root, file.target);
1092
- if (existsSync4(target) && !options.force) {
1187
+ const target = join5(root, file.target);
1188
+ if (existsSync5(target) && !options.force) {
1093
1189
  skipped.push(file.target);
1094
1190
  log(
1095
1191
  formatResult({
@@ -1099,16 +1195,24 @@ function runInit(options, deps) {
1099
1195
  );
1100
1196
  continue;
1101
1197
  }
1102
- mkdirSync2(dirname2(target), { recursive: true });
1103
- copyFileSync2(join4(ASSETS_DIR, framework.name, file.asset), target);
1198
+ mkdirSync3(dirname3(target), { recursive: true });
1199
+ copyFileSync2(join5(ASSETS_DIR, framework.name, file.asset), target);
1104
1200
  written.push(file.target);
1105
1201
  log(formatResult({ status: "ok", message: `wrote ${file.target}` }));
1106
1202
  log(` ${file.purpose}`);
1203
+ if (relocated.layout && file.target.endsWith("/layout.tsx")) {
1204
+ patchCmssyLayout(
1205
+ root,
1206
+ file.target,
1207
+ relocated.layout,
1208
+ `${nextSrcPrefix(root)}app`
1209
+ );
1210
+ }
1107
1211
  }
1108
1212
  const eslint = wireEslintConfig(
1109
1213
  root,
1110
1214
  pkg,
1111
- join4(ASSETS_DIR, ESLINT_CONFIG_FILE)
1215
+ join5(ASSETS_DIR, ESLINT_CONFIG_FILE)
1112
1216
  );
1113
1217
  if (eslint.written) {
1114
1218
  written.push(eslint.written);
@@ -1184,8 +1288,8 @@ function runInit(options, deps) {
1184
1288
  }
1185
1289
 
1186
1290
  // src/link.ts
1187
- import { existsSync as existsSync6, readFileSync as readFileSync6, writeFileSync as writeFileSync4 } from "fs";
1188
- import { join as join6 } from "path";
1291
+ import { existsSync as existsSync8, readFileSync as readFileSync7, writeFileSync as writeFileSync6 } from "fs";
1292
+ import { join as join9 } from "path";
1189
1293
  import {
1190
1294
  buildEditorUrl,
1191
1295
  checkDraftSecret,
@@ -1193,8 +1297,8 @@ import {
1193
1297
  } from "@cmssy/core/preflight";
1194
1298
 
1195
1299
  // src/env-load.ts
1196
- import { existsSync as existsSync5, readFileSync as readFileSync5 } from "fs";
1197
- import { join as join5 } from "path";
1300
+ import { existsSync as existsSync6, readFileSync as readFileSync6 } from "fs";
1301
+ import { join as join6 } from "path";
1198
1302
 
1199
1303
  // src/env-file.ts
1200
1304
  function parseEnvFile(content) {
@@ -1233,9 +1337,9 @@ function applyEnv(vars, env) {
1233
1337
  var ENV_FILES = [".env.local", ".env"];
1234
1338
  function loadEnvFiles(cwd, env) {
1235
1339
  for (const file of ENV_FILES) {
1236
- const path = join5(cwd, file);
1237
- if (!existsSync5(path)) continue;
1238
- applyEnv(parseEnvFile(readFileSync5(path, "utf8")), env);
1340
+ const path = join6(cwd, file);
1341
+ if (!existsSync6(path)) continue;
1342
+ applyEnv(parseEnvFile(readFileSync6(path, "utf8")), env);
1239
1343
  }
1240
1344
  }
1241
1345
 
@@ -1265,374 +1369,112 @@ function mergeEnvContent(existing, updates) {
1265
1369
  `;
1266
1370
  }
1267
1371
 
1268
- // src/link.ts
1269
- function resolveToken(options, deps) {
1270
- const token = options.token?.trim() || deps.env.CMSSY_API_TOKEN?.trim();
1271
- if (!token) {
1272
- throw new CliError(
1273
- "no API token given",
1274
- "create an API token in the cmssy dashboard (Settings \u2192 API Tokens), then run cmssy link --token cs_... or set CMSSY_API_TOKEN"
1275
- );
1372
+ // src/sync-manifest.ts
1373
+ import { existsSync as existsSync7 } from "fs";
1374
+ import { isAbsolute, join as join8 } from "path";
1375
+ import {
1376
+ buildBlockManifest
1377
+ } from "@cmssy/core";
1378
+
1379
+ // src/site-modules.ts
1380
+ import { mkdirSync as mkdirSync4, rmSync, writeFileSync as writeFileSync5 } from "fs";
1381
+ import { basename, join as join7, resolve as resolve3 } from "path";
1382
+ import { pathToFileURL } from "url";
1383
+ import { build } from "esbuild";
1384
+ var RUNTIME_ONLY_MODULES = ["server-only", "client-only"];
1385
+ var ASSET_EXTENSIONS = [
1386
+ ".module.css",
1387
+ ".module.scss",
1388
+ ".module.sass",
1389
+ ".module.less",
1390
+ ".css",
1391
+ ".scss",
1392
+ ".sass",
1393
+ ".less",
1394
+ ".png",
1395
+ ".jpg",
1396
+ ".jpeg",
1397
+ ".gif",
1398
+ ".svg",
1399
+ ".webp",
1400
+ ".avif",
1401
+ ".ico",
1402
+ ".woff",
1403
+ ".woff2",
1404
+ ".ttf",
1405
+ ".otf",
1406
+ ".mp4",
1407
+ ".webm",
1408
+ ".mp3",
1409
+ ".md",
1410
+ ".mdx"
1411
+ ];
1412
+ var assetLoaders = Object.fromEntries(
1413
+ ASSET_EXTENSIONS.map((extension) => [extension, "empty"])
1414
+ );
1415
+ var runtimeOnlyModules = {
1416
+ name: "cmssy-runtime-only-modules",
1417
+ setup(api) {
1418
+ const filter = new RegExp(`^(${RUNTIME_ONLY_MODULES.join("|")})$`);
1419
+ api.onResolve({ filter }, (args) => ({
1420
+ path: args.path,
1421
+ namespace: "cmssy-empty"
1422
+ }));
1423
+ api.onLoad({ filter: /.*/, namespace: "cmssy-empty" }, () => ({
1424
+ contents: "export {};",
1425
+ loader: "js"
1426
+ }));
1276
1427
  }
1277
- return token;
1278
- }
1279
- function describeWorkspace(workspace) {
1280
- const org = workspace.organizationSlug ?? "?";
1281
- return `${workspace.name} (${org}/${workspace.slug})`;
1282
- }
1283
- function draftRouteBase(previewUrl) {
1428
+ };
1429
+ async function loadSiteModule(cwd, entry) {
1430
+ const entryPath = resolve3(cwd, entry);
1431
+ let code;
1284
1432
  try {
1285
- const base = new URL(previewUrl);
1286
- const basePath = base.pathname.replace(/\/+$/, "");
1287
- return `${base.origin}${basePath}/api/draft`;
1288
- } catch {
1289
- return null;
1290
- }
1291
- }
1292
- function buildDraftPreviewUrls(previewUrl, draftSecret) {
1293
- const base = draftRouteBase(previewUrl);
1294
- if (!base) return null;
1295
- const params = new URLSearchParams({ secret: draftSecret, redirect: "/" });
1296
- return {
1297
- draftUrl: `${base}?${params.toString()}`,
1298
- exitUrl: `${base}?disable=1`
1299
- };
1300
- }
1301
- function draftRouteFix(cwd) {
1302
- const path = `${nextSrcPrefix(cwd)}app/api/draft/route.ts`;
1303
- return [
1304
- `add ${path} so Preview can enter draft mode:`,
1305
- ' import { createDraftRoute } from "@cmssy/next/server";',
1306
- ' import { cmssy } from "@/cmssy.config";',
1307
- " export const GET = createDraftRoute(cmssy);"
1308
- ].join("\n");
1309
- }
1310
- async function checkDraftRouteMounted(previewUrl, fetchImpl, cwd) {
1311
- const base = draftRouteBase(previewUrl);
1312
- if (!base) {
1313
- return {
1314
- status: "fail",
1315
- message: `the workspace preview URL ${previewUrl} is not a valid URL`,
1316
- fix: "set a valid deployed origin as the preview URL: cmssy link --preview-url https://your-site.com"
1317
- };
1433
+ const result = await build({
1434
+ absWorkingDir: cwd,
1435
+ entryPoints: [entryPath],
1436
+ bundle: true,
1437
+ platform: "node",
1438
+ format: "esm",
1439
+ target: "node20",
1440
+ write: false,
1441
+ packages: "external",
1442
+ jsx: "automatic",
1443
+ loader: assetLoaders,
1444
+ define: { "import.meta.env": "process.env" },
1445
+ plugins: [runtimeOnlyModules],
1446
+ logLevel: "silent"
1447
+ });
1448
+ code = result.outputFiles[0]?.text ?? "";
1449
+ } catch (error) {
1450
+ const messages = error && typeof error === "object" && "errors" in error ? error.errors.map(
1451
+ (issue) => issue.location ? `${issue.location.file}:${issue.location.line} ${issue.text}` : issue.text
1452
+ ) : [error instanceof Error ? error.message : String(error)];
1453
+ throw new CliError(`could not compile ${entry}`, messages.join("\n "));
1318
1454
  }
1319
- let status;
1455
+ const dir = join7(cwd, "node_modules", ".cache", "cmssy");
1456
+ const file = join7(
1457
+ dir,
1458
+ `${basename(entry).replace(/\.[^.]+$/, "")}.${process.pid}.${Date.now()}.mjs`
1459
+ );
1320
1460
  try {
1321
- status = (await fetchImpl(base, {
1322
- method: "HEAD",
1323
- signal: AbortSignal.timeout(5e3)
1324
- })).status;
1325
- } catch {
1326
- return {
1327
- status: "unknown",
1328
- message: `could not reach ${base} to check the /api/draft route`
1329
- };
1330
- }
1331
- if (status === 404) {
1332
- return {
1333
- status: "fail",
1334
- message: `the /api/draft route is not mounted at ${previewUrl} - Preview will 404`,
1335
- fix: draftRouteFix(cwd)
1336
- };
1337
- }
1338
- if (status === 500) {
1339
- return {
1340
- status: "fail",
1341
- message: `the /api/draft route is mounted but misconfigured at ${previewUrl} - the draft secret must be at least 16 characters`
1342
- };
1343
- }
1344
- return {
1345
- status: "ok",
1346
- message: `the /api/draft route is mounted at ${previewUrl}`
1347
- };
1348
- }
1349
- async function selectWorkspace(workspaces, options, deps) {
1350
- if (workspaces.length === 0) {
1351
- throw new CliError(
1352
- "the token's user has no workspaces",
1353
- "create a workspace in the cmssy dashboard, or use a token from a user who is a member of one"
1354
- );
1355
- }
1356
- const slugs = workspaces.map((workspace) => `${workspace.organizationSlug}/${workspace.slug}`).join(", ");
1357
- if (options.workspace) {
1358
- const wanted = options.workspace.trim();
1359
- const match = workspaces.find(
1360
- (workspace) => workspace.slug === wanted || `${workspace.organizationSlug}/${workspace.slug}` === wanted
1361
- );
1362
- if (!match) {
1363
- throw new CliError(
1364
- `no workspace named "${wanted}"`,
1365
- `pass one of: ${slugs}`
1366
- );
1367
- }
1368
- return match;
1369
- }
1370
- if (workspaces.length === 1) {
1371
- return workspaces[0];
1372
- }
1373
- if (!deps.isTty) {
1461
+ mkdirSync4(dir, { recursive: true });
1462
+ writeFileSync5(file, code);
1463
+ } catch (error) {
1374
1464
  throw new CliError(
1375
- "several workspaces are available and there is no terminal to ask in",
1376
- `pass --workspace <slug> - one of: ${slugs}`
1465
+ `could not write the compiled ${entry} to ${dir}`,
1466
+ error instanceof Error ? error.message : String(error)
1377
1467
  );
1378
1468
  }
1379
- deps.log("Which workspace should this app be linked to?");
1380
- workspaces.forEach((workspace, index) => {
1381
- deps.log(` ${index + 1}. ${describeWorkspace(workspace)}`);
1382
- });
1383
- const answer = await deps.ask(`Workspace [1-${workspaces.length}]: `);
1384
- const choice = Number(answer.trim());
1385
- const selected = Number.isInteger(choice) && choice >= 1 && choice <= workspaces.length ? workspaces[choice - 1] : void 0;
1386
- if (!selected) {
1469
+ try {
1470
+ return await import(pathToFileURL(file).href);
1471
+ } catch (error) {
1387
1472
  throw new CliError(
1388
- `"${answer.trim()}" is not a number between 1 and ${workspaces.length}`,
1389
- "run cmssy link again, or pass --workspace <slug>"
1473
+ `could not load ${entry}`,
1474
+ error instanceof Error ? error.message : String(error)
1390
1475
  );
1391
- }
1392
- return selected;
1393
- }
1394
- function resolvePreviewUrl(options) {
1395
- if (!options.previewUrl) return null;
1396
- let origin;
1397
- try {
1398
- origin = new URL(options.previewUrl).origin;
1399
- } catch {
1400
- throw new CliError(
1401
- `"${options.previewUrl}" is not a valid URL`,
1402
- "pass --preview-url with your DEPLOYED site origin, e.g. --preview-url https://example.com"
1403
- );
1404
- }
1405
- const { hostname } = new URL(origin);
1406
- if (hostname === "localhost" || hostname === "127.0.0.1") {
1407
- throw new CliError(
1408
- "the workspace preview URL is the DEPLOYED site every editor in the workspace previews - not your localhost",
1409
- "for local development, toggle dev mode in the cmssy editor and enter your local host there (per user, nothing shared)"
1410
- );
1411
- }
1412
- return origin;
1413
- }
1414
- function writeEnvLocal(cwd, updates) {
1415
- const path = join6(cwd, ".env.local");
1416
- const existing = existsSync6(path) ? readFileSync6(path, "utf8") : null;
1417
- writeFileSync4(path, mergeEnvContent(existing, updates));
1418
- }
1419
- async function runLink(options, deps) {
1420
- const { cwd, env, log } = deps;
1421
- try {
1422
- loadEnvFiles(cwd, env);
1423
- const token = resolveToken(options, deps);
1424
- const admin = {
1425
- token,
1426
- apiUrl: env.CMSSY_API_URL,
1427
- fetch: deps.fetch
1428
- };
1429
- const workspaces = await fetchMyWorkspaces(admin);
1430
- const workspace = await selectWorkspace(workspaces, options, deps);
1431
- const orgSlug = workspace.organizationSlug;
1432
- if (!orgSlug) {
1433
- throw new CliError(
1434
- `workspace ${workspace.slug} has no organization slug`,
1435
- "open the workspace in the cmssy dashboard once, then run cmssy link again"
1436
- );
1437
- }
1438
- log(
1439
- formatResult({
1440
- status: "ok",
1441
- message: `linking to ${describeWorkspace(workspace)}`
1442
- })
1443
- );
1444
- const draftSecret = await fetchDraftSecret({
1445
- ...admin,
1446
- workspaceId: workspace.id
1447
- });
1448
- log(formatResult({ status: "ok", message: "fetched the draft secret" }));
1449
- const previewUrl = resolvePreviewUrl(options);
1450
- if (previewUrl) {
1451
- await setPreviewUrl(previewUrl, { ...admin, workspaceId: workspace.id });
1452
- log(
1453
- formatResult({
1454
- status: "ok",
1455
- message: `set the workspace preview URL to ${previewUrl}`
1456
- })
1457
- );
1458
- } else {
1459
- log(
1460
- formatResult({
1461
- status: "unknown",
1462
- message: "preview URL left unchanged - pass --preview-url <deployed origin> to set it; for localhost use the editor dev-mode switch"
1463
- })
1464
- );
1465
- }
1466
- writeEnvLocal(cwd, {
1467
- CMSSY_ORG_SLUG: orgSlug,
1468
- CMSSY_WORKSPACE_SLUG: workspace.slug,
1469
- CMSSY_DRAFT_SECRET: draftSecret
1470
- });
1471
- log(
1472
- formatResult({
1473
- status: "ok",
1474
- message: "wrote CMSSY_ORG_SLUG, CMSSY_WORKSPACE_SLUG and CMSSY_DRAFT_SECRET to .env.local"
1475
- })
1476
- );
1477
- const preflight = {
1478
- apiUrl: env.CMSSY_API_URL,
1479
- org: orgSlug,
1480
- workspaceSlug: workspace.slug,
1481
- draftSecret,
1482
- fetch: deps.fetch
1483
- };
1484
- const reachable = await checkWorkspaceReachable(preflight);
1485
- log(formatResult(reachable));
1486
- const secretResult = await checkDraftSecret(preflight);
1487
- log(formatResult(secretResult));
1488
- let draftRouteResult = null;
1489
- if (reachable.previewUrl) {
1490
- draftRouteResult = await checkDraftRouteMounted(
1491
- reachable.previewUrl,
1492
- deps.fetch,
1493
- cwd
1494
- );
1495
- log(formatResult(draftRouteResult));
1496
- }
1497
- log(formatEditorLink(buildEditorUrl(preflight)));
1498
- if (reachable.previewUrl && secretResult.status !== "fail" && draftRouteResult?.status !== "fail") {
1499
- const draftUrls = buildDraftPreviewUrls(
1500
- reachable.previewUrl,
1501
- draftSecret
1502
- );
1503
- if (draftUrls) {
1504
- log(formatDraftPreviewLink(draftUrls.draftUrl, draftUrls.exitUrl));
1505
- }
1506
- }
1507
- const failed = reachable.status === "fail" || secretResult.status === "fail" || draftRouteResult?.status === "fail";
1508
- return failed ? 1 : 0;
1509
- } catch (error) {
1510
- if (error instanceof CliError) {
1511
- log(
1512
- formatResult({
1513
- status: "fail",
1514
- message: error.message,
1515
- fix: error.fix
1516
- })
1517
- );
1518
- return 1;
1519
- }
1520
- log(
1521
- formatResult({
1522
- status: "fail",
1523
- message: error instanceof Error ? error.message : String(error)
1524
- })
1525
- );
1526
- return 1;
1527
- }
1528
- }
1529
-
1530
- // src/sync-manifest.ts
1531
- import { existsSync as existsSync7 } from "fs";
1532
- import { isAbsolute, join as join8 } from "path";
1533
- import {
1534
- buildBlockManifest
1535
- } from "@cmssy/core";
1536
-
1537
- // src/site-modules.ts
1538
- import { mkdirSync as mkdirSync3, rmSync, writeFileSync as writeFileSync5 } from "fs";
1539
- import { basename, join as join7, resolve as resolve3 } from "path";
1540
- import { pathToFileURL } from "url";
1541
- import { build } from "esbuild";
1542
- var RUNTIME_ONLY_MODULES = ["server-only", "client-only"];
1543
- var ASSET_EXTENSIONS = [
1544
- ".module.css",
1545
- ".module.scss",
1546
- ".module.sass",
1547
- ".module.less",
1548
- ".css",
1549
- ".scss",
1550
- ".sass",
1551
- ".less",
1552
- ".png",
1553
- ".jpg",
1554
- ".jpeg",
1555
- ".gif",
1556
- ".svg",
1557
- ".webp",
1558
- ".avif",
1559
- ".ico",
1560
- ".woff",
1561
- ".woff2",
1562
- ".ttf",
1563
- ".otf",
1564
- ".mp4",
1565
- ".webm",
1566
- ".mp3",
1567
- ".md",
1568
- ".mdx"
1569
- ];
1570
- var assetLoaders = Object.fromEntries(
1571
- ASSET_EXTENSIONS.map((extension) => [extension, "empty"])
1572
- );
1573
- var runtimeOnlyModules = {
1574
- name: "cmssy-runtime-only-modules",
1575
- setup(api) {
1576
- const filter = new RegExp(`^(${RUNTIME_ONLY_MODULES.join("|")})$`);
1577
- api.onResolve({ filter }, (args) => ({
1578
- path: args.path,
1579
- namespace: "cmssy-empty"
1580
- }));
1581
- api.onLoad({ filter: /.*/, namespace: "cmssy-empty" }, () => ({
1582
- contents: "export {};",
1583
- loader: "js"
1584
- }));
1585
- }
1586
- };
1587
- async function loadSiteModule(cwd, entry) {
1588
- const entryPath = resolve3(cwd, entry);
1589
- let code;
1590
- try {
1591
- const result = await build({
1592
- absWorkingDir: cwd,
1593
- entryPoints: [entryPath],
1594
- bundle: true,
1595
- platform: "node",
1596
- format: "esm",
1597
- target: "node20",
1598
- write: false,
1599
- packages: "external",
1600
- jsx: "automatic",
1601
- loader: assetLoaders,
1602
- define: { "import.meta.env": "process.env" },
1603
- plugins: [runtimeOnlyModules],
1604
- logLevel: "silent"
1605
- });
1606
- code = result.outputFiles[0]?.text ?? "";
1607
- } catch (error) {
1608
- const messages = error && typeof error === "object" && "errors" in error ? error.errors.map(
1609
- (issue) => issue.location ? `${issue.location.file}:${issue.location.line} ${issue.text}` : issue.text
1610
- ) : [error instanceof Error ? error.message : String(error)];
1611
- throw new CliError(`could not compile ${entry}`, messages.join("\n "));
1612
- }
1613
- const dir = join7(cwd, "node_modules", ".cache", "cmssy");
1614
- const file = join7(
1615
- dir,
1616
- `${basename(entry).replace(/\.[^.]+$/, "")}.${process.pid}.${Date.now()}.mjs`
1617
- );
1618
- try {
1619
- mkdirSync3(dir, { recursive: true });
1620
- writeFileSync5(file, code);
1621
- } catch (error) {
1622
- throw new CliError(
1623
- `could not write the compiled ${entry} to ${dir}`,
1624
- error instanceof Error ? error.message : String(error)
1625
- );
1626
- }
1627
- try {
1628
- return await import(pathToFileURL(file).href);
1629
- } catch (error) {
1630
- throw new CliError(
1631
- `could not load ${entry}`,
1632
- error instanceof Error ? error.message : String(error)
1633
- );
1634
- } finally {
1635
- rmSync(file, { force: true });
1476
+ } finally {
1477
+ rmSync(file, { force: true });
1636
1478
  }
1637
1479
  }
1638
1480
 
@@ -1656,6 +1498,11 @@ var SYNC_MANIFEST_USAGE = [
1656
1498
  " cmssy sync-manifest [--blocks <path>] [--config <path>] [--token <cs_...>]",
1657
1499
  " [--org <slug>] [--workspace <slug>] [--dry-run] [--help]"
1658
1500
  ];
1501
+ function hasBlocksModule(cwd) {
1502
+ return BLOCKS_CANDIDATES.some(
1503
+ (candidate) => existsSync7(join8(cwd, candidate))
1504
+ );
1505
+ }
1659
1506
  function findModule(cwd, requested, candidates, flag) {
1660
1507
  if (requested?.trim()) {
1661
1508
  const path = requested.trim();
@@ -1725,7 +1572,7 @@ function resolveSlug(flag, fromConfig, fromEnv, variable, flagName) {
1725
1572
  }
1726
1573
  return value;
1727
1574
  }
1728
- function resolveToken2(options, env) {
1575
+ function resolveToken(options, env) {
1729
1576
  const token = options.token?.trim() || env.CMSSY_API_TOKEN?.trim();
1730
1577
  if (!token) {
1731
1578
  throw new CliError(
@@ -1816,7 +1663,7 @@ async function runSyncManifest(options, deps) {
1816
1663
  deps.log(JSON.stringify(manifest, null, 2));
1817
1664
  return 0;
1818
1665
  }
1819
- const token = resolveToken2(options, deps.env);
1666
+ const token = resolveToken(options, deps.env);
1820
1667
  const org = resolveSlug(
1821
1668
  options.org,
1822
1669
  collected.org,
@@ -1864,9 +1711,303 @@ async function runSyncManifest(options, deps) {
1864
1711
  }
1865
1712
  }
1866
1713
 
1714
+ // src/link.ts
1715
+ function resolveToken2(options, deps) {
1716
+ const token = options.token?.trim() || deps.env.CMSSY_API_TOKEN?.trim();
1717
+ if (!token) {
1718
+ throw new CliError(
1719
+ "no API token given",
1720
+ "create an API token in the cmssy dashboard (Settings \u2192 API Tokens), then run cmssy link --token cs_... or set CMSSY_API_TOKEN"
1721
+ );
1722
+ }
1723
+ return token;
1724
+ }
1725
+ function describeWorkspace(workspace) {
1726
+ const org = workspace.organizationSlug ?? "?";
1727
+ return `${workspace.name} (${org}/${workspace.slug})`;
1728
+ }
1729
+ function draftRouteBase(previewUrl) {
1730
+ try {
1731
+ const base = new URL(previewUrl);
1732
+ const basePath = base.pathname.replace(/\/+$/, "");
1733
+ return `${base.origin}${basePath}/api/draft`;
1734
+ } catch {
1735
+ return null;
1736
+ }
1737
+ }
1738
+ function buildDraftPreviewUrls(previewUrl, draftSecret) {
1739
+ const base = draftRouteBase(previewUrl);
1740
+ if (!base) return null;
1741
+ const params = new URLSearchParams({ secret: draftSecret, redirect: "/" });
1742
+ return {
1743
+ draftUrl: `${base}?${params.toString()}`,
1744
+ exitUrl: `${base}?disable=1`
1745
+ };
1746
+ }
1747
+ function draftRouteFix(cwd) {
1748
+ const path = `${nextSrcPrefix(cwd)}app/api/draft/route.ts`;
1749
+ return [
1750
+ `add ${path} so Preview can enter draft mode:`,
1751
+ ' import { createDraftRoute } from "@cmssy/next/server";',
1752
+ ' import { cmssy } from "@/cmssy.config";',
1753
+ " export const GET = createDraftRoute(cmssy);"
1754
+ ].join("\n");
1755
+ }
1756
+ async function checkDraftRouteMounted(previewUrl, fetchImpl, cwd) {
1757
+ const base = draftRouteBase(previewUrl);
1758
+ if (!base) {
1759
+ return {
1760
+ status: "fail",
1761
+ message: `the workspace preview URL ${previewUrl} is not a valid URL`,
1762
+ fix: "set a valid deployed origin as the preview URL: cmssy link --preview-url https://your-site.com"
1763
+ };
1764
+ }
1765
+ let status;
1766
+ try {
1767
+ status = (await fetchImpl(base, {
1768
+ method: "HEAD",
1769
+ signal: AbortSignal.timeout(5e3)
1770
+ })).status;
1771
+ } catch {
1772
+ return {
1773
+ status: "unknown",
1774
+ message: `could not reach ${base} to check the /api/draft route`
1775
+ };
1776
+ }
1777
+ if (status === 404) {
1778
+ return {
1779
+ status: "fail",
1780
+ message: `the /api/draft route is not mounted at ${previewUrl} - Preview will 404`,
1781
+ fix: draftRouteFix(cwd)
1782
+ };
1783
+ }
1784
+ if (status === 500) {
1785
+ return {
1786
+ status: "fail",
1787
+ message: `the /api/draft route is mounted but misconfigured at ${previewUrl} - the draft secret must be at least 16 characters`
1788
+ };
1789
+ }
1790
+ return {
1791
+ status: "ok",
1792
+ message: `the /api/draft route is mounted at ${previewUrl}`
1793
+ };
1794
+ }
1795
+ async function selectWorkspace(workspaces, options, deps) {
1796
+ if (workspaces.length === 0) {
1797
+ throw new CliError(
1798
+ "the token's user has no workspaces",
1799
+ "create a workspace in the cmssy dashboard, or use a token from a user who is a member of one"
1800
+ );
1801
+ }
1802
+ const slugs = workspaces.map((workspace) => `${workspace.organizationSlug}/${workspace.slug}`).join(", ");
1803
+ if (options.workspace) {
1804
+ const wanted = options.workspace.trim();
1805
+ const match = workspaces.find(
1806
+ (workspace) => workspace.slug === wanted || `${workspace.organizationSlug}/${workspace.slug}` === wanted
1807
+ );
1808
+ if (!match) {
1809
+ throw new CliError(
1810
+ `no workspace named "${wanted}"`,
1811
+ `pass one of: ${slugs}`
1812
+ );
1813
+ }
1814
+ return match;
1815
+ }
1816
+ if (workspaces.length === 1) {
1817
+ return workspaces[0];
1818
+ }
1819
+ if (!deps.isTty) {
1820
+ throw new CliError(
1821
+ "several workspaces are available and there is no terminal to ask in",
1822
+ `pass --workspace <slug> - one of: ${slugs}`
1823
+ );
1824
+ }
1825
+ deps.log("Which workspace should this app be linked to?");
1826
+ workspaces.forEach((workspace, index) => {
1827
+ deps.log(` ${index + 1}. ${describeWorkspace(workspace)}`);
1828
+ });
1829
+ const answer = await deps.ask(`Workspace [1-${workspaces.length}]: `);
1830
+ const choice = Number(answer.trim());
1831
+ const selected = Number.isInteger(choice) && choice >= 1 && choice <= workspaces.length ? workspaces[choice - 1] : void 0;
1832
+ if (!selected) {
1833
+ throw new CliError(
1834
+ `"${answer.trim()}" is not a number between 1 and ${workspaces.length}`,
1835
+ "run cmssy link again, or pass --workspace <slug>"
1836
+ );
1837
+ }
1838
+ return selected;
1839
+ }
1840
+ function isLocalHost(hostname) {
1841
+ return hostname === "localhost" || hostname === "127.0.0.1";
1842
+ }
1843
+ function resolvePreviewUrl(options) {
1844
+ if (!options.previewUrl) return { kind: "none" };
1845
+ let origin;
1846
+ try {
1847
+ origin = new URL(options.previewUrl).origin;
1848
+ } catch {
1849
+ throw new CliError(
1850
+ `"${options.previewUrl}" is not a valid URL`,
1851
+ "pass --preview-url with your DEPLOYED site origin, e.g. --preview-url https://example.com"
1852
+ );
1853
+ }
1854
+ return isLocalHost(new URL(origin).hostname) ? { kind: "local", origin } : { kind: "deployed", origin };
1855
+ }
1856
+ async function pushBlockManifest(workspace, admin, deps) {
1857
+ if (!hasBlocksModule(deps.cwd)) return null;
1858
+ try {
1859
+ const { manifest, blocksPath } = await collectManifest({}, deps);
1860
+ await saveBlockManifest(manifest, { ...admin, workspaceId: workspace.id });
1861
+ const count = manifest.blocks.length;
1862
+ return {
1863
+ status: "ok",
1864
+ message: `pushed the block manifest from ${blocksPath} (${count} block${count === 1 ? "" : "s"}) - the editor palette knows your blocks before the first deploy`
1865
+ };
1866
+ } catch (error) {
1867
+ if (error instanceof CliError) {
1868
+ const fix = error.fix ?? "run cmssy sync-manifest once the blocks module loads";
1869
+ return {
1870
+ status: "unknown",
1871
+ message: `block manifest not pushed: ${error.message} - ${fix}`
1872
+ };
1873
+ }
1874
+ throw error;
1875
+ }
1876
+ }
1877
+ function writeEnvLocal(cwd, updates) {
1878
+ const path = join9(cwd, ".env.local");
1879
+ const existing = existsSync8(path) ? readFileSync7(path, "utf8") : null;
1880
+ writeFileSync6(path, mergeEnvContent(existing, updates));
1881
+ }
1882
+ async function runLink(options, deps) {
1883
+ const { cwd, env, log } = deps;
1884
+ try {
1885
+ loadEnvFiles(cwd, env);
1886
+ const token = resolveToken2(options, deps);
1887
+ const admin = {
1888
+ token,
1889
+ apiUrl: env.CMSSY_API_URL,
1890
+ fetch: deps.fetch
1891
+ };
1892
+ const workspaces = await fetchMyWorkspaces(admin);
1893
+ const workspace = await selectWorkspace(workspaces, options, deps);
1894
+ const orgSlug = workspace.organizationSlug;
1895
+ if (!orgSlug) {
1896
+ throw new CliError(
1897
+ `workspace ${workspace.slug} has no organization slug`,
1898
+ "open the workspace in the cmssy dashboard once, then run cmssy link again"
1899
+ );
1900
+ }
1901
+ log(
1902
+ formatResult({
1903
+ status: "ok",
1904
+ message: `linking to ${describeWorkspace(workspace)}`
1905
+ })
1906
+ );
1907
+ const draftSecret = await fetchDraftSecret({
1908
+ ...admin,
1909
+ workspaceId: workspace.id
1910
+ });
1911
+ log(formatResult({ status: "ok", message: "fetched the draft secret" }));
1912
+ const previewUrl = resolvePreviewUrl(options);
1913
+ if (previewUrl.kind === "deployed") {
1914
+ await setPreviewUrl(previewUrl.origin, {
1915
+ ...admin,
1916
+ workspaceId: workspace.id
1917
+ });
1918
+ log(
1919
+ formatResult({
1920
+ status: "ok",
1921
+ message: `set the workspace preview URL to ${previewUrl.origin}`
1922
+ })
1923
+ );
1924
+ } else if (previewUrl.kind === "local") {
1925
+ log(
1926
+ formatResult({
1927
+ status: "unknown",
1928
+ message: `${previewUrl.origin} is your machine, not the DEPLOYED site every editor in the workspace previews - the shared preview URL was left unchanged; open the editor, toggle dev mode and enter ${previewUrl.origin} there (per user, nothing shared), and pass --preview-url <deployed origin> once the site is live`
1929
+ })
1930
+ );
1931
+ } else {
1932
+ log(
1933
+ formatResult({
1934
+ status: "unknown",
1935
+ message: "preview URL left unchanged - pass --preview-url <deployed origin> to set it; for localhost use the editor dev-mode switch"
1936
+ })
1937
+ );
1938
+ }
1939
+ writeEnvLocal(cwd, {
1940
+ CMSSY_ORG_SLUG: orgSlug,
1941
+ CMSSY_WORKSPACE_SLUG: workspace.slug,
1942
+ CMSSY_DRAFT_SECRET: draftSecret
1943
+ });
1944
+ env.CMSSY_ORG_SLUG = orgSlug;
1945
+ env.CMSSY_WORKSPACE_SLUG = workspace.slug;
1946
+ env.CMSSY_DRAFT_SECRET = draftSecret;
1947
+ log(
1948
+ formatResult({
1949
+ status: "ok",
1950
+ message: "wrote CMSSY_ORG_SLUG, CMSSY_WORKSPACE_SLUG and CMSSY_DRAFT_SECRET to .env.local"
1951
+ })
1952
+ );
1953
+ const manifestResult = await pushBlockManifest(workspace, admin, deps);
1954
+ if (manifestResult) log(formatResult(manifestResult));
1955
+ const preflight = {
1956
+ apiUrl: env.CMSSY_API_URL,
1957
+ org: orgSlug,
1958
+ workspaceSlug: workspace.slug,
1959
+ draftSecret,
1960
+ fetch: deps.fetch
1961
+ };
1962
+ const reachable = await checkWorkspaceReachable(preflight);
1963
+ log(formatResult(reachable));
1964
+ const secretResult = await checkDraftSecret(preflight);
1965
+ log(formatResult(secretResult));
1966
+ let draftRouteResult = null;
1967
+ if (reachable.previewUrl) {
1968
+ draftRouteResult = await checkDraftRouteMounted(
1969
+ reachable.previewUrl,
1970
+ deps.fetch,
1971
+ cwd
1972
+ );
1973
+ log(formatResult(draftRouteResult));
1974
+ }
1975
+ log(formatEditorLink(buildEditorUrl(preflight)));
1976
+ if (reachable.previewUrl && secretResult.status !== "fail" && draftRouteResult?.status !== "fail") {
1977
+ const draftUrls = buildDraftPreviewUrls(
1978
+ reachable.previewUrl,
1979
+ draftSecret
1980
+ );
1981
+ if (draftUrls) {
1982
+ log(formatDraftPreviewLink(draftUrls.draftUrl, draftUrls.exitUrl));
1983
+ }
1984
+ }
1985
+ const failed = reachable.status === "fail" || secretResult.status === "fail" || draftRouteResult?.status === "fail";
1986
+ return failed ? 1 : 0;
1987
+ } catch (error) {
1988
+ if (error instanceof CliError) {
1989
+ log(
1990
+ formatResult({
1991
+ status: "fail",
1992
+ message: error.message,
1993
+ fix: error.fix
1994
+ })
1995
+ );
1996
+ return 1;
1997
+ }
1998
+ log(
1999
+ formatResult({
2000
+ status: "fail",
2001
+ message: error instanceof Error ? error.message : String(error)
2002
+ })
2003
+ );
2004
+ return 1;
2005
+ }
2006
+ }
2007
+
1867
2008
  // src/types-command.ts
1868
- import { mkdirSync as mkdirSync4, readFileSync as readFileSync7, readdirSync, writeFileSync as writeFileSync6 } from "fs";
1869
- import { dirname as dirname3, relative, resolve as resolvePath } from "path";
2009
+ import { mkdirSync as mkdirSync5, readFileSync as readFileSync8, readdirSync, writeFileSync as writeFileSync7 } from "fs";
2010
+ import { dirname as dirname4, relative, resolve as resolvePath } from "path";
1870
2011
  import { DEFAULT_CMSSY_API_URL } from "@cmssy/core";
1871
2012
 
1872
2013
  // src/model-types.ts
@@ -2209,7 +2350,7 @@ function existingOperationNames(cwd, skipPath) {
2209
2350
  if (full === skipPath) continue;
2210
2351
  let source;
2211
2352
  try {
2212
- source = readFileSync7(full, "utf8");
2353
+ source = readFileSync8(full, "utf8");
2213
2354
  } catch {
2214
2355
  continue;
2215
2356
  }
@@ -2229,7 +2370,7 @@ function syncOperations(options, deps) {
2229
2370
  const source = generateOperationsFile();
2230
2371
  let previous = null;
2231
2372
  try {
2232
- previous = readFileSync7(outPath, "utf8");
2373
+ previous = readFileSync8(outPath, "utf8");
2233
2374
  } catch {
2234
2375
  previous = null;
2235
2376
  }
@@ -2256,8 +2397,8 @@ function syncOperations(options, deps) {
2256
2397
  return 1;
2257
2398
  }
2258
2399
  try {
2259
- mkdirSync4(dirname3(outPath), { recursive: true });
2260
- writeFileSync6(outPath, source);
2400
+ mkdirSync5(dirname4(outPath), { recursive: true });
2401
+ writeFileSync7(outPath, source);
2261
2402
  } catch (error) {
2262
2403
  throw new CliError(
2263
2404
  `could not write ${shown}: ${error.message}`,
@@ -2304,7 +2445,7 @@ async function runTypes(options, deps) {
2304
2445
  const source = generateModelTypes(models, { workspace });
2305
2446
  let previous = null;
2306
2447
  try {
2307
- previous = readFileSync7(outPath, "utf8");
2448
+ previous = readFileSync8(outPath, "utf8");
2308
2449
  } catch {
2309
2450
  previous = null;
2310
2451
  }
@@ -2322,8 +2463,8 @@ async function runTypes(options, deps) {
2322
2463
  deps.log(" run `cmssy types` and commit the result");
2323
2464
  return 1;
2324
2465
  }
2325
- mkdirSync4(dirname3(outPath), { recursive: true });
2326
- writeFileSync6(outPath, source);
2466
+ mkdirSync5(dirname4(outPath), { recursive: true });
2467
+ writeFileSync7(outPath, source);
2327
2468
  const fieldCount = models.reduce(
2328
2469
  (total, model) => total + model.fields.length,
2329
2470
  0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cmssy/cli",
3
- "version": "16.2.1",
3
+ "version": "16.4.0",
4
4
  "description": "The cmssy CLI: `cmssy init` wires cmssy into an existing Next.js, Astro or React Router app; `cmssy link` connects it to a workspace and verifies the editor wiring.",
5
5
  "keywords": [
6
6
  "cmssy",
@@ -28,7 +28,7 @@
28
28
  ],
29
29
  "dependencies": {
30
30
  "esbuild": "^0.27.7",
31
- "@cmssy/core": "16.2.1"
31
+ "@cmssy/core": "16.4.0"
32
32
  },
33
33
  "devDependencies": {
34
34
  "@types/node": "^20.0.0",