@tinacms/cli 2.4.4 → 2.5.1

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.
@@ -0,0 +1,4 @@
1
+ export declare const isDefaultAstroConfig: (source: string) => boolean;
2
+ export declare const findExistingPaths: (baseDir: string, relPaths: string[]) => string[];
3
+ export declare const parseAstroMajor: (version?: string) => number | undefined;
4
+ export declare const astroNodeAdapterDep: (astroMajor?: number) => string;
@@ -0,0 +1,8 @@
1
+ export type AstroSetupResult = {
2
+ configHandled: boolean;
3
+ demoScaffolded: boolean;
4
+ };
5
+ export declare const setupAstroVisualEditing: ({ baseDir, }: {
6
+ baseDir: string;
7
+ }) => AstroSetupResult;
8
+ export declare const logAstroConfigGuidance: () => void;
@@ -1,7 +1,7 @@
1
1
  import { CLICommand } from '../index';
2
2
  import { ContentFrontmatterFormat } from '@tinacms/schema-tools';
3
3
  export interface Framework {
4
- name: 'next' | 'hugo' | 'jekyll' | 'other';
4
+ name: 'next' | 'hugo' | 'jekyll' | 'astro' | 'other';
5
5
  reactive: boolean;
6
6
  }
7
7
  export type ReactiveFramework = 'next';
@@ -23,6 +23,8 @@ export type GeneratedFile = {
23
23
  };
24
24
  export type InitEnvironment = {
25
25
  hasTinaDeps: boolean;
26
+ hasReactDep: boolean;
27
+ astroMajor?: number;
26
28
  forestryConfigExists: boolean;
27
29
  frontMatterFormat: ContentFrontmatterFormat;
28
30
  gitIgnoreExists: boolean;
@@ -1 +1,2 @@
1
1
  export declare const helloWorldPost = "---\ntitle: Hello, World!\n---\n\n## Hello World!\n\nLorem ipsum dolor sit amet, consectetur adipiscing elit. Ut non lorem diam. Quisque vulputate nibh sodales eros pretium tincidunt. Aenean porttitor efficitur convallis. Nulla sagittis finibus convallis. Phasellus in fermentum quam, eu egestas tortor. Maecenas ac mollis leo. Integer maximus eu nisl vel sagittis.\n\nSuspendisse facilisis, mi ac scelerisque interdum, ligula ex imperdiet felis, a posuere eros justo nec sem. Nullam laoreet accumsan metus, sit amet tincidunt orci egestas nec. Pellentesque ut aliquet ante, at tristique nunc. Donec non massa nibh. Ut posuere lacus non aliquam laoreet. Fusce pharetra ligula a felis porttitor, at mollis ipsum maximus. Donec quam tortor, vehicula a magna sit amet, tincidunt dictum enim. In hac habitasse platea dictumst. Mauris sit amet ornare ligula, blandit consequat risus. Duis malesuada pellentesque lectus, non feugiat turpis eleifend a. Nullam tempus ante et diam pretium, ac faucibus ligula interdum.\n";
2
+ export declare const astroHelloWorldPost = "---\ntitle: The fastest way to build content-driven Astro sites\neyebrow: TinaCMS + Astro\nctaPrimary:\n label: Start editing\n href: /admin/index.html#/~/tinacms-demo\nctaSecondary:\n label: Read the docs\n href: https://tina.io/docs/frameworks/astro\n---\n\nAstro ships your pages with zero JavaScript by default. TinaCMS lets you edit them visually, backed by Git. Open this page in the CMS to edit this text live, then copy the pattern into your own pages.\n";
package/dist/index.js CHANGED
@@ -2,7 +2,7 @@
2
2
  import { Cli, Builtins } from "clipanion";
3
3
 
4
4
  // package.json
5
- var version = "2.4.4";
5
+ var version = "2.5.1";
6
6
 
7
7
  // src/next/commands/dev-command/index.ts
8
8
  import path10 from "path";
@@ -1564,7 +1564,7 @@ var BaseCommand = class extends Command {
1564
1564
  pathFilter
1565
1565
  });
1566
1566
  const tinaPathUpdates = modified.filter(
1567
- (path18) => path18.startsWith(".tina/__generated__/_schema.json") || path18.startsWith("tina/tina-lock.json")
1567
+ (path20) => path20.startsWith(".tina/__generated__/_schema.json") || path20.startsWith("tina/tina-lock.json")
1568
1568
  );
1569
1569
  if (tinaPathUpdates.length > 0) {
1570
1570
  res = await database.indexContent({
@@ -4302,26 +4302,48 @@ var AuditCommand = class extends Command4 {
4302
4302
  import { Command as Command6, Option as Option6 } from "clipanion";
4303
4303
 
4304
4304
  // src/cmds/init/detectEnvironment.ts
4305
- import fs11 from "fs-extra";
4305
+ import fs12 from "fs-extra";
4306
+ import path13 from "path";
4307
+
4308
+ // src/cmds/init/astro-config-detect.ts
4309
+ import fs11 from "fs";
4306
4310
  import path12 from "path";
4311
+ var isDefaultAstroConfig = (source) => {
4312
+ const stripped = source.replace(/\/\*[\s\S]*?\*\//g, "").replace(/\/\/.*$/gm, "").replace(/\s+/g, " ").trim();
4313
+ return /^import\s*\{\s*defineConfig\s*\}\s*from\s*['"]astro\/config['"]\s*;?\s*export\s+default\s+defineConfig\(\s*\{\s*\}\s*\)\s*;?$/.test(
4314
+ stripped
4315
+ );
4316
+ };
4317
+ var findExistingPaths = (baseDir, relPaths) => relPaths.filter((rel) => fs11.existsSync(path12.join(baseDir, rel)));
4318
+ var parseAstroMajor = (version2) => {
4319
+ const match = version2 ? String(version2).match(/(\d+)/) : null;
4320
+ return match ? Number(match[1]) : void 0;
4321
+ };
4322
+ var astroNodeAdapterDep = (astroMajor) => {
4323
+ const byMajor = { 5: "^9", 6: "^10" };
4324
+ const range = astroMajor ? byMajor[astroMajor] : void 0;
4325
+ return range ? `@astrojs/node@${range}` : "@astrojs/node";
4326
+ };
4327
+
4328
+ // src/cmds/init/detectEnvironment.ts
4307
4329
  var checkGitignoreForItem = async ({
4308
4330
  baseDir,
4309
4331
  line
4310
4332
  }) => {
4311
- const gitignoreContent = fs11.readFileSync(path12.join(baseDir, ".gitignore")).toString();
4333
+ const gitignoreContent = fs12.readFileSync(path13.join(baseDir, ".gitignore")).toString();
4312
4334
  return gitignoreContent.split("\n").some((item) => item === line);
4313
4335
  };
4314
4336
  var makeGeneratedFile = async (name2, generatedFileType, parentPath, opts) => {
4315
4337
  const result = {
4316
- fullPathTS: path12.join(
4338
+ fullPathTS: path13.join(
4317
4339
  parentPath,
4318
4340
  `${name2}.${opts?.typescriptSuffix || opts?.extensionOverride || "ts"}`
4319
4341
  ),
4320
- fullPathJS: path12.join(
4342
+ fullPathJS: path13.join(
4321
4343
  parentPath,
4322
4344
  `${name2}.${opts?.extensionOverride || "js"}`
4323
4345
  ),
4324
- fullPathOverride: opts?.extensionOverride ? path12.join(parentPath, `${name2}.${opts?.extensionOverride}`) : "",
4346
+ fullPathOverride: opts?.extensionOverride ? path13.join(parentPath, `${name2}.${opts?.extensionOverride}`) : "",
4325
4347
  generatedFileType,
4326
4348
  name: name2,
4327
4349
  parentPath,
@@ -4339,8 +4361,8 @@ var makeGeneratedFile = async (name2, generatedFileType, parentPath, opts) => {
4339
4361
  };
4340
4362
  }
4341
4363
  };
4342
- result.typescriptExists = await fs11.pathExists(result.fullPathTS);
4343
- result.javascriptExists = await fs11.pathExists(result.fullPathJS);
4364
+ result.typescriptExists = await fs12.pathExists(result.fullPathTS);
4365
+ result.javascriptExists = await fs12.pathExists(result.fullPathJS);
4344
4366
  return result;
4345
4367
  };
4346
4368
  var detectEnvironment = async ({
@@ -4349,21 +4371,21 @@ var detectEnvironment = async ({
4349
4371
  rootPath,
4350
4372
  debug = false
4351
4373
  }) => {
4352
- const hasForestryConfig = await fs11.pathExists(
4353
- path12.join(pathToForestryConfig, ".forestry", "settings.yml")
4374
+ const hasForestryConfig = await fs12.pathExists(
4375
+ path13.join(pathToForestryConfig, ".forestry", "settings.yml")
4354
4376
  );
4355
- const sampleContentPath = path12.join(
4377
+ const sampleContentPath = path13.join(
4356
4378
  baseDir,
4357
4379
  "content",
4358
4380
  "posts",
4359
4381
  "hello-world.md"
4360
4382
  );
4361
- const usingSrc = fs11.pathExistsSync(path12.join(baseDir, "src")) && (fs11.pathExistsSync(path12.join(baseDir, "src", "app")) || fs11.pathExistsSync(path12.join(baseDir, "src", "pages")));
4362
- const tinaFolder = path12.join(baseDir, "tina");
4383
+ const usingSrc = fs12.pathExistsSync(path13.join(baseDir, "src")) && (fs12.pathExistsSync(path13.join(baseDir, "src", "app")) || fs12.pathExistsSync(path13.join(baseDir, "src", "pages")));
4384
+ const tinaFolder = path13.join(baseDir, "tina");
4363
4385
  const tinaConfigExists = Boolean(
4364
4386
  // Does the tina folder exist?
4365
- await fs11.pathExists(tinaFolder) && // Does the tina folder contain a config file?
4366
- (await fs11.readdir(tinaFolder)).find((x) => x.includes("config"))
4387
+ await fs12.pathExists(tinaFolder) && // Does the tina folder contain a config file?
4388
+ (await fs12.readdir(tinaFolder)).find((x) => x.includes("config"))
4367
4389
  );
4368
4390
  const pagesDir = [baseDir, usingSrc ? "src" : false, "pages"].filter(
4369
4391
  Boolean
@@ -4375,12 +4397,12 @@ var detectEnvironment = async ({
4375
4397
  "next-api-handler": await makeGeneratedFile(
4376
4398
  "[...routes]",
4377
4399
  "next-api-handler",
4378
- path12.join(...pagesDir, "api", "tina")
4400
+ path13.join(...pagesDir, "api", "tina")
4379
4401
  ),
4380
4402
  "reactive-example": await makeGeneratedFile(
4381
4403
  "[filename]",
4382
4404
  "reactive-example",
4383
- path12.join(...pagesDir, "demo", "blog"),
4405
+ path13.join(...pagesDir, "demo", "blog"),
4384
4406
  {
4385
4407
  typescriptSuffix: "tsx"
4386
4408
  }
@@ -4388,22 +4410,24 @@ var detectEnvironment = async ({
4388
4410
  "users-json": await makeGeneratedFile(
4389
4411
  "index",
4390
4412
  "users-json",
4391
- path12.join(baseDir, "content", "users"),
4413
+ path13.join(baseDir, "content", "users"),
4392
4414
  { extensionOverride: "json" }
4393
4415
  ),
4394
4416
  "sample-content": await makeGeneratedFile(
4395
4417
  "hello-world",
4396
4418
  "sample-content",
4397
- path12.join(baseDir, "content", "posts"),
4419
+ path13.join(baseDir, "content", "posts"),
4398
4420
  { extensionOverride: "md" }
4399
4421
  )
4400
4422
  };
4401
- const hasSampleContent = await fs11.pathExists(sampleContentPath);
4402
- const hasPackageJSON = await fs11.pathExists("package.json");
4423
+ const hasSampleContent = await fs12.pathExists(sampleContentPath);
4424
+ const hasPackageJSON = await fs12.pathExists("package.json");
4403
4425
  let hasTinaDeps = false;
4426
+ let hasReactDep = false;
4427
+ let astroMajor;
4404
4428
  if (hasPackageJSON) {
4405
4429
  try {
4406
- const packageJSON = await fs11.readJSON("package.json");
4430
+ const packageJSON = await fs12.readJSON("package.json");
4407
4431
  const deps = [];
4408
4432
  if (packageJSON?.dependencies) {
4409
4433
  deps.push(...Object.keys(packageJSON.dependencies));
@@ -4414,13 +4438,19 @@ var detectEnvironment = async ({
4414
4438
  if (deps.includes("@tinacms/cli") && deps.includes("tinacms")) {
4415
4439
  hasTinaDeps = true;
4416
4440
  }
4441
+ if (deps.includes("react") && deps.includes("react-dom")) {
4442
+ hasReactDep = true;
4443
+ }
4444
+ astroMajor = parseAstroMajor(
4445
+ packageJSON?.dependencies?.astro || packageJSON?.devDependencies?.astro
4446
+ );
4417
4447
  } catch (e) {
4418
4448
  logger.error(
4419
4449
  "Error reading package.json assuming that no Tina dependencies are installed"
4420
4450
  );
4421
4451
  }
4422
4452
  }
4423
- const hasGitIgnore = await fs11.pathExists(path12.join(".gitignore"));
4453
+ const hasGitIgnore = await fs12.pathExists(path13.join(".gitignore"));
4424
4454
  const hasGitIgnoreNodeModules = hasGitIgnore && await checkGitignoreForItem({ baseDir, line: "node_modules" });
4425
4455
  const hasEnvTina = hasGitIgnore && await checkGitignoreForItem({ baseDir, line: ".env.tina" });
4426
4456
  const hasGitIgnoreEnv = hasGitIgnore && await checkGitignoreForItem({ baseDir, line: ".env" });
@@ -4430,9 +4460,9 @@ var detectEnvironment = async ({
4430
4460
  });
4431
4461
  let frontMatterFormat;
4432
4462
  if (hasForestryConfig) {
4433
- const hugoConfigPath = path12.join(rootPath, "config.toml");
4434
- if (await fs11.pathExists(hugoConfigPath)) {
4435
- const hugoConfig = await fs11.readFile(hugoConfigPath, "utf8");
4463
+ const hugoConfigPath = path13.join(rootPath, "config.toml");
4464
+ if (await fs12.pathExists(hugoConfigPath)) {
4465
+ const hugoConfig = await fs12.readFile(hugoConfigPath, "utf8");
4436
4466
  const metaDataFormat = hugoConfig.toString().match(/metaDataFormat = "(.*)"/)?.[1];
4437
4467
  if (metaDataFormat && (metaDataFormat === "yaml" || metaDataFormat === "toml" || metaDataFormat === "json")) {
4438
4468
  frontMatterFormat = metaDataFormat;
@@ -4453,7 +4483,9 @@ var detectEnvironment = async ({
4453
4483
  generatedFiles,
4454
4484
  usingSrc,
4455
4485
  tinaConfigExists,
4456
- hasTinaDeps
4486
+ hasTinaDeps,
4487
+ hasReactDep,
4488
+ astroMajor
4457
4489
  };
4458
4490
  if (debug) {
4459
4491
  console.log("Environment:");
@@ -4776,8 +4808,9 @@ var askCommonSetUp = async () => {
4776
4808
  {
4777
4809
  name: "framework",
4778
4810
  type: "select",
4779
- message: "What framework are you using?",
4811
+ message: "Which framework is your existing site built with?",
4780
4812
  choices: [
4813
+ { title: "Astro", value: { name: "astro", reactive: false } },
4781
4814
  { title: "Next.js", value: { name: "next", reactive: true } },
4782
4815
  { title: "Hugo", value: { name: "hugo", reactive: false } },
4783
4816
  { title: "Jekyll", value: { name: "jekyll", reactive: false } },
@@ -4951,6 +4984,23 @@ async function configure(env, opts) {
4951
4984
  process.exit(0);
4952
4985
  }
4953
4986
  const skipTinaSetupCommands = env.tinaConfigExists;
4987
+ if (!opts.isBackend && !env.packageJSONExists) {
4988
+ logger.warn(
4989
+ "No package.json found here \u2014 `tinacms init` adds TinaCMS to an existing site, it doesn't create one."
4990
+ );
4991
+ logger.info(
4992
+ `To start a new TinaCMS project from a template, run: ${cmdText(
4993
+ "npx create-tina-app@latest"
4994
+ )}`
4995
+ );
4996
+ process.exit(0);
4997
+ }
4998
+ if (!opts.isBackend) {
4999
+ logger.info("Setting up TinaCMS in your existing site.");
5000
+ logger.info(
5001
+ logText("(Starting a new project instead? Press Ctrl+C and run ") + cmdText("npx create-tina-app@latest") + logText(")")
5002
+ );
5003
+ }
4954
5004
  const { framework, packageManager } = await askCommonSetUp();
4955
5005
  const config2 = {
4956
5006
  envVars: [],
@@ -4961,7 +5011,7 @@ async function configure(env, opts) {
4961
5011
  // TODO: give this a better default
4962
5012
  typescript: false
4963
5013
  };
4964
- if (config2.framework.name === "next") {
5014
+ if (config2.framework.name === "next" || config2.framework.name === "astro") {
4965
5015
  config2.publicFolder = "public";
4966
5016
  } else if (config2.framework.name === "hugo") {
4967
5017
  config2.publicFolder = "static";
@@ -5046,19 +5096,19 @@ var CLICommand = class {
5046
5096
  };
5047
5097
 
5048
5098
  // src/cmds/init/apply.ts
5049
- import path16 from "path";
5099
+ import path18 from "path";
5050
5100
 
5051
5101
  // src/cmds/forestry-migrate/index.ts
5052
- import fs13 from "fs-extra";
5053
- import path14 from "path";
5102
+ import fs14 from "fs-extra";
5103
+ import path15 from "path";
5054
5104
  import yaml2 from "js-yaml";
5055
5105
  import pkg from "minimatch";
5056
5106
  import { parseFile, stringifyFile } from "@tinacms/graphql";
5057
5107
  import { CONTENT_FORMATS } from "@tinacms/schema-tools";
5058
5108
 
5059
5109
  // src/cmds/forestry-migrate/util/index.ts
5060
- import fs12 from "fs-extra";
5061
- import path13 from "path";
5110
+ import fs13 from "fs-extra";
5111
+ import path14 from "path";
5062
5112
  import yaml from "js-yaml";
5063
5113
  import z2 from "zod";
5064
5114
 
@@ -5500,7 +5550,7 @@ var transformForestryFieldsToTinaFields = ({
5500
5550
  return tinaFields;
5501
5551
  };
5502
5552
  var getFieldsFromTemplates = ({ tem, pathToForestryConfig, skipBlocks = false }) => {
5503
- const templatePath = path13.join(
5553
+ const templatePath = path14.join(
5504
5554
  pathToForestryConfig,
5505
5555
  ".forestry",
5506
5556
  "front_matter",
@@ -5509,7 +5559,7 @@ var getFieldsFromTemplates = ({ tem, pathToForestryConfig, skipBlocks = false })
5509
5559
  );
5510
5560
  let templateString = "";
5511
5561
  try {
5512
- templateString = fs12.readFileSync(templatePath).toString();
5562
+ templateString = fs13.readFileSync(templatePath).toString();
5513
5563
  } catch {
5514
5564
  throw new Error(
5515
5565
  `Could not find template ${tem} at ${templatePath}
@@ -5568,9 +5618,9 @@ function checkExt(ext) {
5568
5618
  var generateAllTemplates = async ({
5569
5619
  pathToForestryConfig
5570
5620
  }) => {
5571
- const allTemplates = (await fs13.readdir(
5572
- path14.join(pathToForestryConfig, ".forestry", "front_matter", "templates")
5573
- )).map((tem) => path14.basename(tem, ".yml"));
5621
+ const allTemplates = (await fs14.readdir(
5622
+ path15.join(pathToForestryConfig, ".forestry", "front_matter", "templates")
5623
+ )).map((tem) => path15.basename(tem, ".yml"));
5574
5624
  const templateMap = /* @__PURE__ */ new Map();
5575
5625
  const proms = allTemplates.map(async (tem) => {
5576
5626
  try {
@@ -5715,9 +5765,9 @@ var generateCollectionFromForestrySection = (args) => {
5715
5765
  return c;
5716
5766
  } else if (section.type === "document") {
5717
5767
  const filePath = section.path;
5718
- const extname = path14.extname(filePath);
5719
- const fileName = path14.basename(filePath, extname);
5720
- const dir = path14.dirname(filePath);
5768
+ const extname = path15.extname(filePath);
5769
+ const fileName = path15.basename(filePath, extname);
5770
+ const dir = path15.dirname(filePath);
5721
5771
  const ext = checkExt(extname);
5722
5772
  if (ext) {
5723
5773
  const fields = [];
@@ -5779,8 +5829,8 @@ var generateCollections = async ({
5779
5829
  templateMap,
5780
5830
  usingTypescript
5781
5831
  });
5782
- const forestryConfig = await fs13.readFile(
5783
- path14.join(pathToForestryConfig, ".forestry", "settings.yml")
5832
+ const forestryConfig = await fs14.readFile(
5833
+ path15.join(pathToForestryConfig, ".forestry", "settings.yml")
5784
5834
  );
5785
5835
  rewriteTemplateKeysInDocs({
5786
5836
  templateMap,
@@ -5810,12 +5860,12 @@ var rewriteTemplateKeysInDocs = (args) => {
5810
5860
  const { templateObj } = templateMap.get(templateKey);
5811
5861
  templateObj?.pages?.forEach((page) => {
5812
5862
  try {
5813
- const filePath = path14.join(page);
5814
- if (fs13.lstatSync(filePath).isDirectory()) {
5863
+ const filePath = path15.join(page);
5864
+ if (fs14.lstatSync(filePath).isDirectory()) {
5815
5865
  return;
5816
5866
  }
5817
- const extname = path14.extname(filePath);
5818
- const fileContent = fs13.readFileSync(filePath).toString();
5867
+ const extname = path15.extname(filePath);
5868
+ const fileContent = fs14.readFileSync(filePath).toString();
5819
5869
  const content = parseFile(
5820
5870
  fileContent,
5821
5871
  extname,
@@ -5826,7 +5876,7 @@ var rewriteTemplateKeysInDocs = (args) => {
5826
5876
  _template: stringifyLabel(templateKey),
5827
5877
  ...content
5828
5878
  };
5829
- fs13.writeFileSync(
5879
+ fs14.writeFileSync(
5830
5880
  filePath,
5831
5881
  stringifyFile(newContent, extname, true, markdownParseConfig)
5832
5882
  );
@@ -5841,12 +5891,12 @@ var rewriteTemplateKeysInDocs = (args) => {
5841
5891
 
5842
5892
  // src/cmds/init/apply.ts
5843
5893
  import { Telemetry as Telemetry3 } from "@tinacms/metrics";
5844
- import fs16 from "fs-extra";
5894
+ import fs18 from "fs-extra";
5845
5895
 
5846
5896
  // src/next/commands/codemod-command/index.ts
5847
5897
  import { Command as Command5, Option as Option5 } from "clipanion";
5848
- import fs14 from "fs-extra";
5849
- import path15 from "path";
5898
+ import fs15 from "fs-extra";
5899
+ import path16 from "path";
5850
5900
  var CodemodCommand = class extends Command5 {
5851
5901
  static paths = [["codemod"], ["codemod", "move-tina-folder"]];
5852
5902
  rootPath = Option5.String("--rootPath", {
@@ -5887,13 +5937,13 @@ var moveTinaFolder = async (rootPath = process.cwd()) => {
5887
5937
  logger.error(e.message);
5888
5938
  process.exit(1);
5889
5939
  }
5890
- const tinaDestination = path15.join(configManager.rootPath, "tina");
5891
- if (await fs14.existsSync(tinaDestination)) {
5940
+ const tinaDestination = path16.join(configManager.rootPath, "tina");
5941
+ if (await fs15.existsSync(tinaDestination)) {
5892
5942
  logger.info(
5893
5943
  `Folder already exists at ${tinaDestination}. Either delete this folder to complete the codemod, or ensure you have properly copied your config from the ".tina" folder.`
5894
5944
  );
5895
5945
  } else {
5896
- await fs14.moveSync(configManager.tinaFolderPath, tinaDestination);
5946
+ await fs15.moveSync(configManager.tinaFolderPath, tinaDestination);
5897
5947
  await writeGitignore(configManager.rootPath);
5898
5948
  logger.info(
5899
5949
  "Move to 'tina' folder complete. Be sure to update any imports of the autogenerated client!"
@@ -5901,8 +5951,8 @@ var moveTinaFolder = async (rootPath = process.cwd()) => {
5901
5951
  }
5902
5952
  };
5903
5953
  var writeGitignore = async (rootPath) => {
5904
- await fs14.outputFileSync(
5905
- path15.join(rootPath, "tina", ".gitignore"),
5954
+ await fs15.outputFileSync(
5955
+ path16.join(rootPath, "tina", ".gitignore"),
5906
5956
  "__generated__"
5907
5957
  );
5908
5958
  };
@@ -6157,6 +6207,44 @@ var baseFields = `[
6157
6207
  isBody: true,
6158
6208
  },
6159
6209
  ]`;
6210
+ var astroHeroFields = `[
6211
+ {
6212
+ type: 'string',
6213
+ name: 'eyebrow',
6214
+ label: 'Eyebrow',
6215
+ },
6216
+ {
6217
+ type: 'string',
6218
+ name: 'title',
6219
+ label: 'Headline',
6220
+ isTitle: true,
6221
+ required: true,
6222
+ },
6223
+ {
6224
+ type: 'rich-text',
6225
+ name: 'body',
6226
+ label: 'Tagline',
6227
+ isBody: true,
6228
+ },
6229
+ {
6230
+ type: 'object',
6231
+ name: 'ctaPrimary',
6232
+ label: 'Primary button',
6233
+ fields: [
6234
+ { type: 'string', name: 'label', label: 'Label' },
6235
+ { type: 'string', name: 'href', label: 'Link' },
6236
+ ],
6237
+ },
6238
+ {
6239
+ type: 'object',
6240
+ name: 'ctaSecondary',
6241
+ label: 'Secondary button',
6242
+ fields: [
6243
+ { type: 'string', name: 'label', label: 'Label' },
6244
+ { type: 'string', name: 'href', label: 'Link' },
6245
+ ],
6246
+ },
6247
+ ]`;
6160
6248
  var generateCollectionString = (args) => {
6161
6249
  if (args.collections) {
6162
6250
  return args.collections;
@@ -6187,9 +6275,25 @@ var generateCollectionString = (args) => {
6187
6275
  },
6188
6276
  },
6189
6277
  ]`;
6278
+ const astroExampleCollection = `[
6279
+ ${extraTinaCollections || ""}
6280
+ {
6281
+ name: 'post',
6282
+ label: 'Posts',
6283
+ path: 'content/posts',
6284
+ fields: ${astroHeroFields},
6285
+ ui: {
6286
+ // Opens the /tinacms-demo page for visual editing. Change or remove to fit your site.
6287
+ router: () => '/tinacms-demo',
6288
+ },
6289
+ },
6290
+ ]`;
6190
6291
  if (args.config?.framework?.name === "next") {
6191
6292
  return nextExampleCollection;
6192
6293
  }
6294
+ if (args.config?.framework?.name === "astro") {
6295
+ return astroExampleCollection;
6296
+ }
6193
6297
  return baseCollections;
6194
6298
  };
6195
6299
  var generateConfig = (args) => {
@@ -6314,6 +6418,19 @@ Lorem ipsum dolor sit amet, consectetur adipiscing elit. Ut non lorem diam. Quis
6314
6418
 
6315
6419
  Suspendisse facilisis, mi ac scelerisque interdum, ligula ex imperdiet felis, a posuere eros justo nec sem. Nullam laoreet accumsan metus, sit amet tincidunt orci egestas nec. Pellentesque ut aliquet ante, at tristique nunc. Donec non massa nibh. Ut posuere lacus non aliquam laoreet. Fusce pharetra ligula a felis porttitor, at mollis ipsum maximus. Donec quam tortor, vehicula a magna sit amet, tincidunt dictum enim. In hac habitasse platea dictumst. Mauris sit amet ornare ligula, blandit consequat risus. Duis malesuada pellentesque lectus, non feugiat turpis eleifend a. Nullam tempus ante et diam pretium, ac faucibus ligula interdum.
6316
6420
  `;
6421
+ var astroHelloWorldPost = `---
6422
+ title: The fastest way to build content-driven Astro sites
6423
+ eyebrow: TinaCMS + Astro
6424
+ ctaPrimary:
6425
+ label: Start editing
6426
+ href: /admin/index.html#/~/tinacms-demo
6427
+ ctaSecondary:
6428
+ label: Read the docs
6429
+ href: https://tina.io/docs/frameworks/astro
6430
+ ---
6431
+
6432
+ Astro ships your pages with zero JavaScript by default. TinaCMS lets you edit them visually, backed by Git. Open this page in the CMS to edit this text live, then copy the pattern into your own pages.
6433
+ `;
6317
6434
 
6318
6435
  // src/cmds/init/apply.ts
6319
6436
  import { format as format2 } from "prettier";
@@ -6337,10 +6454,329 @@ function extendNextScripts(scripts, opts) {
6337
6454
  }
6338
6455
  return result;
6339
6456
  }
6457
+ function extendAstroScripts(scripts) {
6458
+ return {
6459
+ ...scripts,
6460
+ dev: !scripts?.dev || scripts?.dev?.indexOf("tinacms dev -c") === -1 ? generateGqlScript(scripts?.dev || "astro dev") : scripts?.dev,
6461
+ build: !scripts?.build || !scripts?.build?.startsWith("tinacms build &&") ? `tinacms build && ${scripts?.build || "astro build"}` : scripts?.build
6462
+ };
6463
+ }
6464
+
6465
+ // src/cmds/init/astro-visual-editing.ts
6466
+ import fs16 from "fs-extra";
6467
+ import path17 from "path";
6468
+ var TS_NOCHECK = "// @ts-nocheck (generated types/client appear after your first tinacms dev run)\n";
6469
+ var DEMO_FILES = {
6470
+ "src/lib/tina/data.ts": `${TS_NOCHECK}import { requestWithMetadata } from '@tinacms/astro/data';
6471
+ import client from '../../../tina/__generated__/client';
6472
+
6473
+ export const getPost = (slug: string) =>
6474
+ requestWithMetadata(client.queries.post({ relativePath: slug + '.md' }), {
6475
+ priority: 'primary',
6476
+ });
6477
+ `,
6478
+ "src/lib/tina/islands.ts": `${TS_NOCHECK}import type { IslandRegistry } from '@tinacms/astro/experimental';
6479
+ import type { QueryResult } from '@tinacms/astro/data';
6480
+ import type { PostQuery } from '../../../tina/__generated__/types';
6481
+ import PostBody from '../../components/tina/PostBody.astro';
6482
+ import { getPost } from './data';
6483
+
6484
+ export const islands: IslandRegistry = {
6485
+ post: {
6486
+ fetch: (_request, params) => getPost(params.get('slug') ?? 'hello-world'),
6487
+ component: PostBody,
6488
+ wrapper: { tag: 'article' },
6489
+ propsFromData: (data) => ({
6490
+ data: (data as QueryResult<PostQuery>).data?.post,
6491
+ }),
6492
+ },
6493
+ };
6494
+ `,
6495
+ "src/pages/tina-island/[name].ts": `import type { APIRoute } from 'astro';
6496
+ import { experimental_createIslandRoute } from '@tinacms/astro/experimental';
6497
+ import { islands } from '../../lib/tina/islands';
6498
+
6499
+ export const prerender = false;
6500
+ export const ALL: APIRoute = experimental_createIslandRoute(islands);
6501
+ `,
6502
+ "src/components/tina/PostBody.astro": `---
6503
+ ${TS_NOCHECK.trim()}
6504
+ import TinaMarkdown from '@tinacms/astro/TinaMarkdown.astro';
6505
+ import { tinaField } from '@tinacms/astro/tina-field';
6506
+ import { sanitizeHref } from '@tinacms/astro/sanitize';
6507
+ import type { PostQuery } from '../../../tina/__generated__/types';
6508
+
6509
+ interface Props {
6510
+ data?: PostQuery['post'] | null;
6511
+ }
6512
+ const { data } = Astro.props;
6513
+ ---
6514
+ {
6515
+ data && (
6516
+ <>
6517
+ {data.eyebrow && (
6518
+ <p class="eyebrow" data-tina-field={tinaField(data, 'eyebrow')}>{data.eyebrow}</p>
6519
+ )}
6520
+ <h1 data-tina-field={tinaField(data, 'title')}>{data.title}</h1>
6521
+ {data.body && (
6522
+ <div class="body" data-tina-field={tinaField(data, 'body')}>
6523
+ <TinaMarkdown content={data.body} />
6524
+ </div>
6525
+ )}
6526
+ <div class="tina-actions">
6527
+ {data.ctaPrimary?.label && (
6528
+ <a class="primary" href={sanitizeHref(data.ctaPrimary.href, '/admin/index.html#/~/tinacms-demo')} data-tina-field={tinaField(data.ctaPrimary, 'label')}>{data.ctaPrimary.label}</a>
6529
+ )}
6530
+ {data.ctaSecondary?.label && (
6531
+ <a class="secondary" href={sanitizeHref(data.ctaSecondary.href, 'https://tina.io/docs/frameworks/astro')} target="_blank" rel="noopener noreferrer" data-tina-field={tinaField(data.ctaSecondary, 'label')}>{data.ctaSecondary.label}</a>
6532
+ )}
6533
+ </div>
6534
+ </>
6535
+ )
6536
+ }
6537
+ `,
6538
+ "src/components/tina/Starfield.astro": `---
6539
+ // Decorative twinkling starfield for the demo hero. Self-contained SVG, no deps.
6540
+ interface Props {
6541
+ count?: number;
6542
+ }
6543
+ const { count = 70 } = Astro.props;
6544
+ const frac = (n: number) => {
6545
+ const x = Math.sin(n) * 43758.5453;
6546
+ return Math.abs(x - Math.floor(x));
6547
+ };
6548
+ const STAR = 'M0-1C.2-.2.2-.2 1 0 .2.2.2.2 0 1-.2.2-.2.2-1 0-.2-.2-.2-.2 0-1Z';
6549
+ const stars = Array.from({ length: count }, (_, i) => {
6550
+ const cx = (frac(i + 1) * 100).toFixed(2);
6551
+ const cy = (frac((i + 1) * 2.7) * 100).toFixed(2);
6552
+ const scale = (frac((i + 1) * 5.3) * 1.3 + 0.5).toFixed(2);
6553
+ const delay = (frac((i + 1) * 9.1) * 3).toFixed(2);
6554
+ return {
6555
+ transform: 'translate(' + cx + ' ' + cy + ') scale(' + scale + ')',
6556
+ delay: delay + 's',
6557
+ };
6558
+ });
6559
+ ---
6560
+ <svg class="tina-stars" aria-hidden="true" viewBox="0 0 100 100" preserveAspectRatio="xMidYMid slice">
6561
+ {stars.map((star) => (
6562
+ <path d={STAR} fill="currentColor" transform={star.transform} style={'animation-delay:' + star.delay} />
6563
+ ))}
6564
+ </svg>
6565
+ `,
6566
+ "src/pages/tinacms-demo.astro": `---
6567
+ // Tina visual-editing demo hero. Open this page in the CMS to click-and-edit
6568
+ // the title (headline) and body (tagline), then copy the pattern into your own
6569
+ // pages. Self-contained: scoped styles, no CSS framework, no image assets. Safe
6570
+ // to delete (with src/lib/tina/, src/components/tina/, src/pages/tina-island/)
6571
+ // once you're done.
6572
+ import TinaIsland from '@tinacms/astro/TinaIsland.astro';
6573
+ import PostBody from '../components/tina/PostBody.astro';
6574
+ import Starfield from '../components/tina/Starfield.astro';
6575
+ import { getPost } from '../lib/tina/data';
6576
+ import { islands } from '../lib/tina/islands';
6577
+
6578
+ const slug = 'hello-world';
6579
+ const post = await getPost(slug);
6580
+ const data = post.data?.post;
6581
+ if (!data) return new Response('Not Found', { status: 404 });
6582
+ ---
6583
+ <!doctype html>
6584
+ <html lang="en">
6585
+ <head>
6586
+ <meta charset="utf-8" />
6587
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
6588
+ <title>{data.title}</title>
6589
+ <style is:global>
6590
+ :root {
6591
+ color-scheme: dark;
6592
+ --tina-bg: oklch(0.16 0.02 285);
6593
+ --tina-fg: oklch(0.95 0.012 285);
6594
+ --tina-muted: oklch(0.72 0.03 285);
6595
+ --tina-primary: oklch(0.7 0.18 40);
6596
+ --tina-primary-fg: oklch(0.16 0.02 285);
6597
+ --tina-border: oklch(0.95 0.01 285 / 12%);
6598
+ }
6599
+ body {
6600
+ margin: 0;
6601
+ min-height: 100vh;
6602
+ overflow: hidden;
6603
+ background: radial-gradient(120% 120% at 50% 0%, oklch(0.22 0.03 285) 0%, var(--tina-bg) 55%);
6604
+ color: var(--tina-fg);
6605
+ display: flex;
6606
+ align-items: center;
6607
+ justify-content: center;
6608
+ font-family: ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
6609
+ }
6610
+ .tina-hero {
6611
+ position: relative;
6612
+ z-index: 1;
6613
+ width: 100%;
6614
+ max-width: 64rem;
6615
+ margin: 0 auto;
6616
+ padding: 5rem 1.5rem;
6617
+ text-align: center;
6618
+ }
6619
+ .tina-hero .eyebrow {
6620
+ margin: 0 0 1.5rem;
6621
+ font-size: 0.8rem;
6622
+ letter-spacing: 0.22em;
6623
+ text-transform: uppercase;
6624
+ color: var(--tina-primary);
6625
+ }
6626
+ .tina-hero h1 {
6627
+ margin: 0;
6628
+ font-size: clamp(2.75rem, 6vw, 5.25rem);
6629
+ line-height: 1.05;
6630
+ font-weight: 600;
6631
+ letter-spacing: -0.025em;
6632
+ text-wrap: balance;
6633
+ color: var(--tina-fg);
6634
+ }
6635
+ .tina-hero .body {
6636
+ max-width: 42rem;
6637
+ margin: 2rem auto 0;
6638
+ font-size: 1.125rem;
6639
+ line-height: 1.6;
6640
+ color: var(--tina-muted);
6641
+ }
6642
+ .tina-hero .body :where(p) { margin: 0; }
6643
+ .tina-actions {
6644
+ margin-top: 3rem;
6645
+ display: flex;
6646
+ flex-wrap: wrap;
6647
+ gap: 0.75rem;
6648
+ justify-content: center;
6649
+ }
6650
+ .tina-actions a {
6651
+ display: inline-flex;
6652
+ align-items: center;
6653
+ height: 2.5rem;
6654
+ padding: 0 1.25rem;
6655
+ border-radius: 0.75rem;
6656
+ font-size: 1rem;
6657
+ font-weight: 500;
6658
+ text-decoration: none;
6659
+ }
6660
+ .tina-actions .primary { background: var(--tina-primary); color: var(--tina-primary-fg); }
6661
+ .tina-actions .secondary { border: 1px solid var(--tina-border); color: var(--tina-fg); }
6662
+ .tina-stars {
6663
+ position: fixed;
6664
+ inset: 0;
6665
+ z-index: 0;
6666
+ width: 100%;
6667
+ height: 100%;
6668
+ color: oklch(0.95 0.012 285 / 0.2);
6669
+ pointer-events: none;
6670
+ }
6671
+ @media (prefers-reduced-motion: no-preference) {
6672
+ .tina-stars path { animation: tina-twinkle 4s ease-in-out infinite; }
6673
+ }
6674
+ @keyframes tina-twinkle {
6675
+ 0%, 100% { opacity: 0.25; }
6676
+ 50% { opacity: 0.9; }
6677
+ }
6678
+ </style>
6679
+ </head>
6680
+ <body>
6681
+ <Starfield count={70} />
6682
+ <main class="tina-hero">
6683
+ <TinaIsland name="post" wrapper={islands.post.wrapper} params={{ slug }} primary>
6684
+ <PostBody data={data} />
6685
+ </TinaIsland>
6686
+ </main>
6687
+ </body>
6688
+ </html>
6689
+ `
6690
+ };
6691
+ var VISUAL_EDITING_SNIPPET = ` import node from '@astrojs/node';
6692
+ import tina from '@tinacms/astro/integration';
6693
+ import { tinaAdminDevRedirect } from '@tinacms/astro/vite';
6694
+
6695
+ export default defineConfig({
6696
+ output: 'server',
6697
+ adapter: node({ mode: 'standalone' }),
6698
+ integrations: [tina() /* , ...your integrations */],
6699
+ vite: {
6700
+ plugins: [tinaAdminDevRedirect()],
6701
+ ssr: { noExternal: ['@tinacms/astro', '@tinacms/bridge'] },
6702
+ },
6703
+ });
6704
+
6705
+ Full guide: https://tina.io/docs/frameworks/astro#enabling-visual-editing`;
6706
+ var CONFIG_GUIDANCE = `Your astro.config already has content, so it was left unchanged.
6707
+ The /tinacms-demo page was created and works in astro dev right now.
6708
+
6709
+ To make it build (astro build) and deploy, add an SSR adapter + the Tina integration to your config:
6710
+
6711
+ ${VISUAL_EDITING_SNIPPET}
6712
+
6713
+ Once /tinacms-demo works, reuse its tina/config.ts collection and the src/components/tina pattern in your own pages.`;
6714
+ var ASTRO_CONFIG = `import { defineConfig } from 'astro/config';
6715
+ import node from '@astrojs/node';
6716
+ import tina from '@tinacms/astro/integration';
6717
+ import { tinaAdminDevRedirect } from '@tinacms/astro/vite';
6718
+
6719
+ // https://astro.build/config
6720
+ export default defineConfig({
6721
+ output: 'server',
6722
+ adapter: node({ mode: 'standalone' }),
6723
+ integrations: [tina()],
6724
+ vite: {
6725
+ plugins: [tinaAdminDevRedirect()],
6726
+ ssr: { noExternal: ['@tinacms/astro', '@tinacms/bridge'] },
6727
+ },
6728
+ });
6729
+ `;
6730
+ var ASTRO_CONFIG_FILES = [
6731
+ "astro.config.mjs",
6732
+ "astro.config.ts",
6733
+ "astro.config.mts",
6734
+ "astro.config.js",
6735
+ "astro.config.cjs",
6736
+ "astro.config.cts"
6737
+ ];
6738
+ var findAstroConfig = (baseDir) => ASTRO_CONFIG_FILES.map((file) => path17.join(baseDir, file)).find(
6739
+ (p) => fs16.existsSync(p)
6740
+ );
6741
+ var setupAstroVisualEditing = ({
6742
+ baseDir
6743
+ }) => {
6744
+ const relPaths = Object.keys(DEMO_FILES);
6745
+ const existing = findExistingPaths(baseDir, relPaths);
6746
+ if (existing.length > 0) {
6747
+ logger.warn(
6748
+ logText(
6749
+ `Skipping the visual-editing demo \u2014 these files already exist: ${existing.join(
6750
+ ", "
6751
+ )}. See https://tina.io/docs/frameworks/astro to wire it up manually.`
6752
+ )
6753
+ );
6754
+ return { configHandled: true, demoScaffolded: false };
6755
+ }
6756
+ for (const rel of relPaths) {
6757
+ fs16.outputFileSync(path17.join(baseDir, rel), DEMO_FILES[rel]);
6758
+ }
6759
+ logger.info("Adding a visual-editing demo at /tinacms-demo... \u2705");
6760
+ const configPath = findAstroConfig(baseDir);
6761
+ if (!configPath) {
6762
+ fs16.writeFileSync(path17.join(baseDir, "astro.config.mjs"), ASTRO_CONFIG);
6763
+ logger.info("Creating astro.config for visual editing... \u2705");
6764
+ return { configHandled: true, demoScaffolded: true };
6765
+ }
6766
+ if (isDefaultAstroConfig(fs16.readFileSync(configPath).toString())) {
6767
+ fs16.writeFileSync(configPath, ASTRO_CONFIG);
6768
+ logger.info("Wiring astro.config for visual editing... \u2705");
6769
+ return { configHandled: true, demoScaffolded: true };
6770
+ }
6771
+ return { configHandled: false, demoScaffolded: true };
6772
+ };
6773
+ var logAstroConfigGuidance = () => {
6774
+ logger.info(logText(CONFIG_GUIDANCE));
6775
+ };
6340
6776
 
6341
6777
  // src/cmds/init/codegen/index.ts
6342
6778
  import ts2 from "typescript";
6343
- import fs15 from "fs-extra";
6779
+ import fs17 from "fs-extra";
6344
6780
 
6345
6781
  // src/cmds/init/codegen/util.ts
6346
6782
  import ts from "typescript";
@@ -6577,7 +7013,7 @@ var addSelfHostedTinaAuthToConfig = async (config2, configFile) => {
6577
7013
  const pathToConfig = configFile.resolve(config2.typescript).path;
6578
7014
  const sourceFile = ts2.createSourceFile(
6579
7015
  pathToConfig,
6580
- fs15.readFileSync(pathToConfig, "utf8"),
7016
+ fs17.readFileSync(pathToConfig, "utf8"),
6581
7017
  config2.typescript ? ts2.ScriptTarget.Latest : ts2.ScriptTarget.ESNext
6582
7018
  );
6583
7019
  const { configImports, configAuthProviderClass, extraTinaCollections } = config2.authProvider;
@@ -6627,7 +7063,7 @@ var addSelfHostedTinaAuthToConfig = async (config2, configFile) => {
6627
7063
  )
6628
7064
  ].map((visitor) => makeTransformer(visitor))
6629
7065
  );
6630
- return fs15.writeFile(
7066
+ return fs17.writeFile(
6631
7067
  pathToConfig,
6632
7068
  ts2.createPrinter({ omitTrailingSemicolon: true }).printFile(transformedSourceFileResult.transformed[0])
6633
7069
  );
@@ -6640,7 +7076,7 @@ async function apply({
6640
7076
  params,
6641
7077
  config: config2
6642
7078
  }) {
6643
- if (config2.framework.name === "other" && config2.hosting === "self-host") {
7079
+ if ((config2.framework.name === "other" || config2.framework.name === "astro") && config2.hosting === "self-host") {
6644
7080
  logger.error(
6645
7081
  logText(
6646
7082
  "Self-hosted Tina requires init setup only works with next.js right now. Please check out the docs for info on how to setup Tina on another framework: https://tina.io/docs/r/self-hosting-nextjs"
@@ -6743,13 +7179,18 @@ async function apply({
6743
7179
  generatedFile: env.generatedFiles["reactive-example"]
6744
7180
  });
6745
7181
  }
7182
+ let astroSetup = null;
7183
+ if (config2.framework.name === "astro" && !env.tinaConfigExists && !env.forestryConfigExists) {
7184
+ await updateAstroPackageJson({ baseDir });
7185
+ astroSetup = setupAstroVisualEditing({ baseDir });
7186
+ }
6746
7187
  await addDependencies(config2, env, params);
6747
7188
  if (!env.tinaConfigExists) {
6748
7189
  await addConfigFile({
6749
7190
  configArgs: {
6750
7191
  config: config2,
6751
- publicFolder: path16.join(
6752
- path16.relative(process.cwd(), pathToForestryConfig),
7192
+ publicFolder: path18.join(
7193
+ path18.relative(process.cwd(), pathToForestryConfig),
6753
7194
  config2.publicFolder
6754
7195
  ),
6755
7196
  collections,
@@ -6777,8 +7218,12 @@ async function apply({
6777
7218
  isBackend: params.isBackendInit,
6778
7219
  dataLayer: usingDataLayer,
6779
7220
  packageManager: config2.packageManager,
6780
- framework: config2.framework
7221
+ framework: config2.framework,
7222
+ demoScaffolded: !!astroSetup?.demoScaffolded
6781
7223
  });
7224
+ if (astroSetup && !astroSetup.configHandled) {
7225
+ logAstroConfigGuidance();
7226
+ }
6782
7227
  }
6783
7228
  var forestryMigrate = async ({
6784
7229
  pathToForestryConfig,
@@ -6822,8 +7267,8 @@ var createPackageJSON = async () => {
6822
7267
  };
6823
7268
  var createGitignore = async ({ baseDir }) => {
6824
7269
  logger.info(logText("No .gitignore found, creating one"));
6825
- fs16.outputFileSync(
6826
- path16.join(baseDir, ".gitignore"),
7270
+ fs18.outputFileSync(
7271
+ path18.join(baseDir, ".gitignore"),
6827
7272
  "node_modules\ntina/__generated__\n"
6828
7273
  );
6829
7274
  };
@@ -6832,11 +7277,11 @@ var updateGitIgnore = async ({
6832
7277
  items
6833
7278
  }) => {
6834
7279
  logger.info(logText(`Adding ${items.join(",")} to .gitignore`));
6835
- const gitignoreContent = fs16.readFileSync(path16.join(baseDir, ".gitignore")).toString();
7280
+ const gitignoreContent = fs18.readFileSync(path18.join(baseDir, ".gitignore")).toString();
6836
7281
  const newGitignoreContent = [...gitignoreContent.split("\n"), ...items].join(
6837
7282
  "\n"
6838
7283
  );
6839
- await fs16.writeFile(path16.join(baseDir, ".gitignore"), newGitignoreContent);
7284
+ await fs18.writeFile(path18.join(baseDir, ".gitignore"), newGitignoreContent);
6840
7285
  };
6841
7286
  var addDependencies = async (config2, env, params) => {
6842
7287
  const { packageManager } = config2;
@@ -6853,6 +7298,12 @@ var addDependencies = async (config2, env, params) => {
6853
7298
  if (config2.hosting === "self-host") {
6854
7299
  deps.push("@tinacms/datalayer");
6855
7300
  }
7301
+ if (config2.framework.name === "astro") {
7302
+ deps.push("@tinacms/astro", astroNodeAdapterDep(env.astroMajor));
7303
+ if (!env.hasReactDep) {
7304
+ devDeps.push("react@^18.3.1", "react-dom@^18.3.1");
7305
+ }
7306
+ }
6856
7307
  deps.push(
6857
7308
  ...config2.databaseAdapter?.imports?.map((x) => x.packageName) || []
6858
7309
  );
@@ -6907,22 +7358,22 @@ var writeGeneratedFile = async ({
6907
7358
  content,
6908
7359
  typescript
6909
7360
  }) => {
6910
- const { exists, path: path18, parentPath } = generatedFile.resolve(typescript);
7361
+ const { exists, path: path20, parentPath } = generatedFile.resolve(typescript);
6911
7362
  if (exists) {
6912
7363
  if (overwrite) {
6913
- logger.info(`Overwriting file at ${path18}... \u2705`);
6914
- fs16.outputFileSync(path18, content);
7364
+ logger.info(`Overwriting file at ${path20}... \u2705`);
7365
+ fs18.outputFileSync(path20, content);
6915
7366
  } else {
6916
- logger.info(`Not overwriting file at ${path18}.`);
7367
+ logger.info(`Not overwriting file at ${path20}.`);
6917
7368
  logger.info(
6918
- logText(`Please add the following to ${path18}:
7369
+ logText(`Please add the following to ${path20}:
6919
7370
  ${indentText(content)}}`)
6920
7371
  );
6921
7372
  }
6922
7373
  } else {
6923
- logger.info(`Adding file at ${path18}... \u2705`);
6924
- await fs16.ensureDir(parentPath);
6925
- fs16.outputFileSync(path18, content);
7374
+ logger.info(`Adding file at ${path20}... \u2705`);
7375
+ await fs18.ensureDir(parentPath);
7376
+ fs18.outputFileSync(path20, content);
6926
7377
  }
6927
7378
  };
6928
7379
  var addConfigFile = async ({
@@ -7000,12 +7451,12 @@ var addContentFile = async ({
7000
7451
  return () => ({
7001
7452
  exists: env.sampleContentExists,
7002
7453
  path: env.sampleContentPath,
7003
- parentPath: path16.dirname(env.sampleContentPath)
7454
+ parentPath: path18.dirname(env.sampleContentPath)
7004
7455
  });
7005
7456
  }
7006
7457
  },
7007
7458
  overwrite: config2.overwriteList?.includes("sample-content"),
7008
- content: helloWorldPost,
7459
+ content: config2.framework?.name === "astro" ? astroHelloWorldPost : helloWorldPost,
7009
7460
  typescript: false
7010
7461
  });
7011
7462
  };
@@ -7014,7 +7465,8 @@ var logNextSteps = ({
7014
7465
  dataLayer: _datalayer,
7015
7466
  framework,
7016
7467
  packageManager,
7017
- isBackend
7468
+ isBackend,
7469
+ demoScaffolded
7018
7470
  }) => {
7019
7471
  if (isBackend) {
7020
7472
  logger.info(focusText(`
@@ -7023,10 +7475,10 @@ ${titleText(" TinaCMS ")} backend initialized!`));
7023
7475
  return `${x.key}=${x.value || "***"}`;
7024
7476
  }).join("\n") + `
7025
7477
  TINA_PUBLIC_IS_LOCAL=true`;
7026
- const envFile = path16.join(process.cwd(), ".env");
7027
- if (!fs16.existsSync(envFile)) {
7478
+ const envFile = path18.join(process.cwd(), ".env");
7479
+ if (!fs18.existsSync(envFile)) {
7028
7480
  logger.info(`Adding .env file to your project... \u2705`);
7029
- fs16.writeFileSync(envFile, envFileText);
7481
+ fs18.writeFileSync(envFile, envFileText);
7030
7482
  } else {
7031
7483
  logger.info(
7032
7484
  "Please add the following environment variables to your .env file"
@@ -7060,6 +7512,13 @@ Once your site is running, access the CMS at ${linkText(
7060
7512
  "<YourDevURL>/admin/index.html"
7061
7513
  )}`
7062
7514
  );
7515
+ if (framework.name === "astro" && demoScaffolded) {
7516
+ logger.info(
7517
+ `Try the visual-editing demo at ${linkText(
7518
+ "<YourDevURL>/tinacms-demo"
7519
+ )} (open the post in the CMS to click-and-edit).`
7520
+ );
7521
+ }
7063
7522
  }
7064
7523
  };
7065
7524
  var other = ({ packageManager }) => {
@@ -7072,20 +7531,21 @@ var other = ({ packageManager }) => {
7072
7531
  };
7073
7532
  return `${packageManagers[packageManager]} tinacms dev -c "<your dev command>"`;
7074
7533
  };
7534
+ var runDevScript = ({ packageManager }) => {
7535
+ const packageManagers = {
7536
+ pnpm: `pnpm`,
7537
+ npm: `npm run`,
7538
+ yarn: `yarn`,
7539
+ bun: `bun run`
7540
+ };
7541
+ return `${packageManagers[packageManager]} dev`;
7542
+ };
7075
7543
  var frameworkDevCmds = {
7076
7544
  other,
7077
7545
  hugo: other,
7078
7546
  jekyll: other,
7079
- next: ({ packageManager }) => {
7080
- const packageManagers = {
7081
- pnpm: `pnpm`,
7082
- npm: `npm run`,
7083
- // npx is the way to run executables that aren't in your "scripts"
7084
- yarn: `yarn`,
7085
- bun: `bun run`
7086
- };
7087
- return `${packageManagers[packageManager]} dev`;
7088
- }
7547
+ astro: runDevScript,
7548
+ next: runDevScript
7089
7549
  };
7090
7550
  var addReactiveFile = {
7091
7551
  next: async ({
@@ -7095,7 +7555,7 @@ var addReactiveFile = {
7095
7555
  baseDir,
7096
7556
  dataLayer
7097
7557
  }) => {
7098
- const packageJsonPath = path16.join(baseDir, "package.json");
7558
+ const packageJsonPath = path18.join(baseDir, "package.json");
7099
7559
  await writeGeneratedFile({
7100
7560
  generatedFile,
7101
7561
  typescript: config2.typescript,
@@ -7108,7 +7568,7 @@ var addReactiveFile = {
7108
7568
  })
7109
7569
  });
7110
7570
  logger.info("Adding a nextjs example... \u2705");
7111
- const packageJson = JSON.parse(fs16.readFileSync(packageJsonPath).toString());
7571
+ const packageJson = JSON.parse(fs18.readFileSync(packageJsonPath).toString());
7112
7572
  const scripts = packageJson.scripts || {};
7113
7573
  const updatedPackageJson = JSON.stringify(
7114
7574
  {
@@ -7121,8 +7581,26 @@ var addReactiveFile = {
7121
7581
  null,
7122
7582
  2
7123
7583
  );
7124
- fs16.writeFileSync(packageJsonPath, updatedPackageJson);
7584
+ fs18.writeFileSync(packageJsonPath, updatedPackageJson);
7585
+ }
7586
+ };
7587
+ var updateAstroPackageJson = async ({ baseDir }) => {
7588
+ const packageJsonPath = path18.join(baseDir, "package.json");
7589
+ if (!fs18.existsSync(packageJsonPath)) {
7590
+ return;
7125
7591
  }
7592
+ const packageJson = JSON.parse(fs18.readFileSync(packageJsonPath).toString());
7593
+ const scripts = packageJson.scripts || {};
7594
+ const updatedPackageJson = JSON.stringify(
7595
+ {
7596
+ ...packageJson,
7597
+ scripts: extendAstroScripts(scripts)
7598
+ },
7599
+ null,
7600
+ 2
7601
+ );
7602
+ fs18.writeFileSync(packageJsonPath, updatedPackageJson);
7603
+ logger.info("Updating package.json scripts for Astro... \u2705");
7126
7604
  };
7127
7605
  function execShellCommand(cmd) {
7128
7606
  return new Promise((resolve2, reject) => {
@@ -7321,8 +7799,8 @@ var SearchIndexCommand = class extends Command7 {
7321
7799
  import { Command as Command8, Option as Option8 } from "clipanion";
7322
7800
 
7323
7801
  // src/next/commands/doctor-command/doctor.ts
7324
- import path17 from "path";
7325
- import fs17 from "fs-extra";
7802
+ import path19 from "path";
7803
+ import fs19 from "fs-extra";
7326
7804
  import yaml3 from "js-yaml";
7327
7805
  var DEPENDENCY_TYPES = [
7328
7806
  "dependencies",
@@ -7355,11 +7833,11 @@ function getTinaDependencies(packageJson) {
7355
7833
  );
7356
7834
  }
7357
7835
  async function readProjectPackageJson(rootPath) {
7358
- const packageJsonPath = path17.join(rootPath, "package.json");
7359
- if (!await fs17.pathExists(packageJsonPath)) {
7836
+ const packageJsonPath = path19.join(rootPath, "package.json");
7837
+ if (!await fs19.pathExists(packageJsonPath)) {
7360
7838
  throw new Error(`No package.json found at ${packageJsonPath}`);
7361
7839
  }
7362
- return fs17.readJSON(packageJsonPath);
7840
+ return fs19.readJSON(packageJsonPath);
7363
7841
  }
7364
7842
  async function resolveInstalledVersions({
7365
7843
  rootPath,
@@ -7482,14 +7960,14 @@ function isLocalReference(version2) {
7482
7960
  );
7483
7961
  }
7484
7962
  async function readNodeModulesVersion(rootPath, packageName) {
7485
- const packageJsonPath = path17.join(
7963
+ const packageJsonPath = path19.join(
7486
7964
  rootPath,
7487
7965
  "node_modules",
7488
7966
  ...packageName.split("/"),
7489
7967
  "package.json"
7490
7968
  );
7491
- if (!await fs17.pathExists(packageJsonPath)) return void 0;
7492
- const packageJson = await fs17.readJSON(packageJsonPath);
7969
+ if (!await fs19.pathExists(packageJsonPath)) return void 0;
7970
+ const packageJson = await fs19.readJSON(packageJsonPath);
7493
7971
  return typeof packageJson.version === "string" ? packageJson.version : void 0;
7494
7972
  }
7495
7973
  async function readLockfileVersions(rootPath) {
@@ -7505,10 +7983,10 @@ async function readLockfileVersions(rootPath) {
7505
7983
  return /* @__PURE__ */ new Map();
7506
7984
  }
7507
7985
  async function readPackageLockVersions(rootPath) {
7508
- const lockfilePath = path17.join(rootPath, "package-lock.json");
7986
+ const lockfilePath = path19.join(rootPath, "package-lock.json");
7509
7987
  const versions = /* @__PURE__ */ new Map();
7510
- if (!await fs17.pathExists(lockfilePath)) return versions;
7511
- const lockfile = await fs17.readJSON(lockfilePath);
7988
+ if (!await fs19.pathExists(lockfilePath)) return versions;
7989
+ const lockfile = await fs19.readJSON(lockfilePath);
7512
7990
  for (const [key, value] of Object.entries(lockfile.packages || {})) {
7513
7991
  if (!key.startsWith("node_modules/")) continue;
7514
7992
  const name2 = key.replace(/^node_modules\//, "");
@@ -7526,10 +8004,10 @@ async function readPackageLockVersions(rootPath) {
7526
8004
  return versions;
7527
8005
  }
7528
8006
  async function readPnpmLockVersions(rootPath) {
7529
- const lockfilePath = path17.join(rootPath, "pnpm-lock.yaml");
8007
+ const lockfilePath = path19.join(rootPath, "pnpm-lock.yaml");
7530
8008
  const versions = /* @__PURE__ */ new Map();
7531
- if (!await fs17.pathExists(lockfilePath)) return versions;
7532
- const lockfile = yaml3.load(await fs17.readFile(lockfilePath, "utf8"));
8009
+ if (!await fs19.pathExists(lockfilePath)) return versions;
8010
+ const lockfile = yaml3.load(await fs19.readFile(lockfilePath, "utf8"));
7533
8011
  const rootImporter = lockfile?.importers?.["."];
7534
8012
  for (const dependencyType of DEPENDENCY_TYPES) {
7535
8013
  for (const [name2, value] of Object.entries(
@@ -7549,10 +8027,10 @@ async function readPnpmLockVersions(rootPath) {
7549
8027
  return versions;
7550
8028
  }
7551
8029
  async function readYarnLockVersions(rootPath) {
7552
- const lockfilePath = path17.join(rootPath, "yarn.lock");
8030
+ const lockfilePath = path19.join(rootPath, "yarn.lock");
7553
8031
  const versions = /* @__PURE__ */ new Map();
7554
- if (!await fs17.pathExists(lockfilePath)) return versions;
7555
- const contents = await fs17.readFile(lockfilePath, "utf8");
8032
+ if (!await fs19.pathExists(lockfilePath)) return versions;
8033
+ const contents = await fs19.readFile(lockfilePath, "utf8");
7556
8034
  if (contents.includes("__metadata:")) {
7557
8035
  return readYarnBerryLockVersions(contents);
7558
8036
  }
@@ -10,3 +10,4 @@ export declare function extendNextScripts(scripts: any, opts?: {
10
10
  isLocalEnvVarName?: string;
11
11
  addSetupUsers?: boolean;
12
12
  }): any;
13
+ export declare function extendAstroScripts(scripts: any): any;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@tinacms/cli",
3
3
  "type": "module",
4
- "version": "2.4.4",
4
+ "version": "2.5.1",
5
5
  "main": "dist/index.js",
6
6
  "typings": "dist/index.d.ts",
7
7
  "files": [
@@ -92,12 +92,12 @@
92
92
  "vite": "^4.5.9",
93
93
  "yup": "^1.6.1",
94
94
  "zod": "^3.24.2",
95
- "@tinacms/app": "2.5.4",
96
- "@tinacms/graphql": "2.4.3",
95
+ "@tinacms/app": "2.5.6",
96
+ "@tinacms/graphql": "2.4.5",
97
97
  "@tinacms/metrics": "2.1.0",
98
+ "tinacms": "3.9.3",
98
99
  "@tinacms/schema-tools": "2.8.1",
99
- "tinacms": "3.9.1",
100
- "@tinacms/search": "1.2.17"
100
+ "@tinacms/search": "1.2.19"
101
101
  },
102
102
  "publishConfig": {
103
103
  "registry": "https://registry.npmjs.org"